From a952c88b3b9bb2c893b5efdc65e74a07778b0af4 Mon Sep 17 00:00:00 2001 From: Adnane Belmadiaf Date: Thu, 2 Apr 2026 12:25:36 +0200 Subject: [PATCH] feat(ReverseSense): add vktReverseSense --- Documentation/public/gallery/ReverseSense.jpg | Bin 0 -> 17940 bytes .../Core/ReverseSense/example/index.js | 100 ++++++++++++ Sources/Filters/Core/ReverseSense/index.d.ts | 72 +++++++++ Sources/Filters/Core/ReverseSense/index.js | 142 ++++++++++++++++++ .../ReverseSense/test/testReverseSense.js | 117 +++++++++++++++ Sources/Filters/Core/index.js | 2 + 6 files changed, 433 insertions(+) create mode 100644 Documentation/public/gallery/ReverseSense.jpg create mode 100644 Sources/Filters/Core/ReverseSense/example/index.js create mode 100644 Sources/Filters/Core/ReverseSense/index.d.ts create mode 100644 Sources/Filters/Core/ReverseSense/index.js create mode 100644 Sources/Filters/Core/ReverseSense/test/testReverseSense.js diff --git a/Documentation/public/gallery/ReverseSense.jpg b/Documentation/public/gallery/ReverseSense.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c7d8c4a2a4e491734219ca058a2f0cfccc65110 GIT binary patch literal 17940 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 CdGJX9 literal 0 HcmV?d00001 diff --git a/Sources/Filters/Core/ReverseSense/example/index.js b/Sources/Filters/Core/ReverseSense/example/index.js new file mode 100644 index 00000000000..f046927e736 --- /dev/null +++ b/Sources/Filters/Core/ReverseSense/example/index.js @@ -0,0 +1,100 @@ +import '@kitware/vtk.js/favicon'; + +import '@kitware/vtk.js/Rendering/Profiles/Geometry'; +import '@kitware/vtk.js/Rendering/Profiles/Glyph'; + +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 vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; +import vtkGlyph3DMapper from '@kitware/vtk.js/Rendering/Core/Glyph3DMapper'; +import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; +import vtkReverseSense from '@kitware/vtk.js/Filters/Core/ReverseSense'; + +import GUI from 'lil-gui'; + +const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance(); +const renderer = fullScreenRenderer.getRenderer(); +const renderWindow = fullScreenRenderer.getRenderWindow(); + +const COLORS = { + source: [0.2, 0.55, 0.86], + reversed: [0.94, 0.52, 0.25], +}; + +function rgb(color) { + return `rgb(${color.map((value) => Math.round(value * 255)).join(', ')})`; +} + +function styleCube(actor, color) { + actor.getProperty().setColor(...color); + actor.getProperty().setDiffuse(0.75); + actor.getProperty().setAmbient(0.2); + actor.getProperty().setSpecular(0.18); + actor.getProperty().setSpecularPower(22); +} + +function addLegend() { + const gui = new GUI({ title: 'Controls' }); + const details = { + source: rgb(COLORS.source), + reversed: rgb(COLORS.reversed), + }; + + gui.addColor(details, 'source').name('Source').disable(); + gui.addColor(details, 'reversed').name('Reversed').disable(); +} + +const cubeSource1 = vtkCubeSource.newInstance(); +const cubeActor1 = vtkActor.newInstance(); +const cubeMapper1 = vtkMapper.newInstance(); +cubeActor1.setMapper(cubeMapper1); +cubeMapper1.setInputConnection(cubeSource1.getOutputPort()); +styleCube(cubeActor1, COLORS.source); +renderer.addActor(cubeActor1); + +const arrowSource1 = vtkArrowSource.newInstance(); +const glyphMapper1 = vtkGlyph3DMapper.newInstance(); +glyphMapper1.setInputConnection(cubeSource1.getOutputPort()); +glyphMapper1.setSourceConnection(arrowSource1.getOutputPort()); +glyphMapper1.setOrientationModeToDirection(); +glyphMapper1.setOrientationArray('Normals'); +glyphMapper1.setScaleModeToScaleByMagnitude(); +glyphMapper1.setScaleArray('Normals'); +glyphMapper1.setScaleFactor(0.1); + +const glyphActor1 = vtkActor.newInstance(); +glyphActor1.setMapper(glyphMapper1); +renderer.addActor(glyphActor1); + +const cubeSource2 = vtkCubeSource.newInstance(); +const cubeActor2 = vtkActor.newInstance(); +const cubeMapper2 = vtkMapper.newInstance(); +cubeActor2.setMapper(cubeMapper2); +cubeMapper2.setInputConnection(cubeSource2.getOutputPort()); +cubeActor2.setPosition(2, 0, 0); +styleCube(cubeActor2, COLORS.reversed); +renderer.addActor(cubeActor2); + +const reverseSense = vtkReverseSense.newInstance({ reverseNormals: true }); +reverseSense.setInputConnection(cubeSource2.getOutputPort()); + +const arrowSource2 = vtkArrowSource.newInstance(); +const glyphMapper2 = vtkGlyph3DMapper.newInstance(); +glyphMapper2.setInputConnection(reverseSense.getOutputPort()); +glyphMapper2.setSourceConnection(arrowSource2.getOutputPort()); +glyphMapper2.setOrientationModeToDirection(); +glyphMapper2.setOrientationArray('Normals'); +glyphMapper2.setScaleModeToScaleByMagnitude(); +glyphMapper2.setScaleArray('Normals'); +glyphMapper2.setScaleFactor(0.1); + +const glyphActor2 = vtkActor.newInstance(); +glyphActor2.setMapper(glyphMapper2); +glyphActor2.setPosition(2, 0, 0); +renderer.addActor(glyphActor2); + +addLegend(); + +renderer.resetCamera(); +renderWindow.render(); diff --git a/Sources/Filters/Core/ReverseSense/index.d.ts b/Sources/Filters/Core/ReverseSense/index.d.ts new file mode 100644 index 00000000000..1060c9d3bde --- /dev/null +++ b/Sources/Filters/Core/ReverseSense/index.d.ts @@ -0,0 +1,72 @@ +import { vtkAlgorithm, vtkObject } from '../../../interfaces'; + +export interface IReverseSenseInitialValues { + reverseCells?: boolean; + reverseNormals?: boolean; +} + +type vtkReverseSenseBase = vtkObject & vtkAlgorithm; + +export interface vtkReverseSense extends vtkReverseSenseBase { + /** + * Get whether the order of polygonal cells is reversed. + */ + getReverseCells(): boolean; + + /** + * Get whether the direction of point and cell normals is reversed. + */ + getReverseNormals(): boolean; + + /** + * Request data from the input and produce output. + * @param inData The input data. + * @param outData The output data. + */ + requestData(inData: any, outData: any): void; + + /** + * Controls whether the order of polygonal cells is reversed. + * @param {Boolean} reverseCells The new state of the `reverseCells` flag. + */ + setReverseCells(reverseCells: boolean): boolean; + + /** + * Controls whether the direction of point and cell normals is reversed. + * @param {Boolean} reverseNormals The new state of the `reverseNormals` flag. + */ + setReverseNormals(reverseNormals: boolean): boolean; +} + +/** + * Method used to decorate a given object (publicAPI+model) with vtkReverseSense characteristics. + * + * @param publicAPI object on which methods will be bounds (public) + * @param model object on which data structure will be bounds (protected) + * @param {IReverseSenseInitialValues} [initialValues] (default: {}) + */ +export function extend( + publicAPI: object, + model: object, + initialValues?: IReverseSenseInitialValues +): void; + +/** + * Method used to create a new instance of vtkReverseSense. + * @param {IReverseSenseInitialValues} [initialValues] for pre-setting some of its content + */ +export function newInstance( + initialValues?: IReverseSenseInitialValues +): vtkReverseSense; + +/** + * vtkReverseSense is a filter that reverses the order of polygonal cells and/or reverses the direction of point and cell normals. + * Two flags are used to control these operations: `reverseCells` and `reverseNormals`. + * Cell reversal means reversing the order of indices in the cell connectivity list. + * Normal reversal means multiplying the normal vector by -1 (both point and cell normals, if present). + */ +export declare const vtkReverseSense: { + newInstance: typeof newInstance; + extend: typeof extend; +}; +export default vtkReverseSense; diff --git a/Sources/Filters/Core/ReverseSense/index.js b/Sources/Filters/Core/ReverseSense/index.js new file mode 100644 index 00000000000..73ea455e9d7 --- /dev/null +++ b/Sources/Filters/Core/ReverseSense/index.js @@ -0,0 +1,142 @@ +import macro from 'vtk.js/Sources/macros'; + +import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; +import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; +import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; + +const { vtkErrorMacro } = macro; + +function reverseCellArray(inputCells) { + if (!inputCells) { + return null; + } + + const inputData = inputCells.getData(); + const outputData = inputData.slice(); + + for (let offset = 0, len = inputData.length; offset < len; ) { + const cellSize = inputData[offset]; + let left = offset + 1; + let right = offset + cellSize; + // reverse segment + while (left < right) { + const l = inputData[left]; + const r = inputData[right]; + + outputData[left++] = r; + outputData[right--] = l; + } + offset += cellSize + 1; + } + + return vtkCellArray.newInstance({ + values: outputData, + numberOfComponents: 1, + }); +} + +function reverseNormals(inputNormals) { + if (!inputNormals) { + return null; + } + + const values = inputNormals.getData().slice(); + for (let i = 0; i < values.length; i++) { + const v = -values[i]; + values[i] = v === 0 ? 0 : v; // avoids -0 + } + + return vtkDataArray.newInstance({ + name: inputNormals.getName(), + values, + numberOfComponents: inputNormals.getNumberOfComponents(), + }); +} + +// ---------------------------------------------------------------------------- +// vtkReverseSense methods +// ---------------------------------------------------------------------------- + +function vtkReverseSense(publicAPI, model) { + model.classHierarchy.push('vtkReverseSense'); + + publicAPI.requestData = (inData, outData) => { + const input = inData[0]; + + if (!input) { + vtkErrorMacro('No input!'); + return; + } + + const output = outData[0]?.initialize() || vtkPolyData.newInstance(); + + output.setPoints(input.getPoints()); + + output.setVerts( + model.reverseCells ? reverseCellArray(input.getVerts()) : input.getVerts() + ); + output.setLines( + model.reverseCells ? reverseCellArray(input.getLines()) : input.getLines() + ); + output.setPolys( + model.reverseCells ? reverseCellArray(input.getPolys()) : input.getPolys() + ); + output.setStrips( + model.reverseCells + ? reverseCellArray(input.getStrips()) + : input.getStrips() + ); + + const outPointData = output.getPointData(); + const outCellData = output.getCellData(); + + outPointData.passData(input.getPointData()); + outCellData.passData(input.getCellData()); + output.getFieldData().passData(input.getFieldData()); + + if (model.reverseNormals) { + const pointNormals = reverseNormals(input.getPointData().getNormals()); + const cellNormals = reverseNormals(input.getCellData().getNormals()); + + if (pointNormals) { + outPointData.setNormals(pointNormals); + } + + if (cellNormals) { + outCellData.setNormals(cellNormals); + } + } + + outData[0] = output; + }; +} + +// ---------------------------------------------------------------------------- +// Object factory +// ---------------------------------------------------------------------------- + +const DEFAULT_VALUES = { + reverseCells: true, + reverseNormals: false, +}; + +// ---------------------------------------------------------------------------- + +export function extend(publicAPI, model, initialValues = {}) { + Object.assign(model, DEFAULT_VALUES, initialValues); + + macro.obj(publicAPI, model); + macro.algo(publicAPI, model, 1, 1); + + macro.setGet(publicAPI, model, ['reverseCells', 'reverseNormals']); + + vtkReverseSense(publicAPI, model); +} + +// ---------------------------------------------------------------------------- + +export const newInstance = macro.newInstance(extend, 'vtkReverseSense'); + +// ---------------------------------------------------------------------------- + +export default { newInstance, extend }; diff --git a/Sources/Filters/Core/ReverseSense/test/testReverseSense.js b/Sources/Filters/Core/ReverseSense/test/testReverseSense.js new file mode 100644 index 00000000000..1fa4ed799b5 --- /dev/null +++ b/Sources/Filters/Core/ReverseSense/test/testReverseSense.js @@ -0,0 +1,117 @@ +import test from 'tape'; + +import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; +import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; +import vtkPoints from 'vtk.js/Sources/Common/Core/Points'; +import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; +import vtkReverseSense from 'vtk.js/Sources/Filters/Core/ReverseSense'; + +function createPolyData() { + const points = vtkPoints.newInstance(); + points.setData( + new Float32Array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1]), + 3 + ); + + const verts = vtkCellArray.newInstance(); + verts.insertNextCell([0, 4]); + + const lines = vtkCellArray.newInstance(); + lines.insertNextCell([0, 1, 2]); + + const polys = vtkCellArray.newInstance(); + polys.insertNextCell([0, 1, 2, 3]); + + const strips = vtkCellArray.newInstance(); + strips.insertNextCell([0, 1, 2, 3, 4]); + + const pointNormals = vtkDataArray.newInstance({ + name: 'Normals', + numberOfComponents: 3, + values: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1]), + }); + + const cellNormals = vtkDataArray.newInstance({ + name: 'Normals', + numberOfComponents: 3, + values: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1]), + }); + + const polyData = vtkPolyData.newInstance(); + polyData.setPoints(points); + polyData.setVerts(verts); + polyData.setLines(lines); + polyData.setPolys(polys); + polyData.setStrips(strips); + polyData.getPointData().setNormals(pointNormals); + polyData.getCellData().setNormals(cellNormals); + + return polyData; +} + +test('vtkReverseSense reverses cell connectivity and normals', (t) => { + const reverseSense = vtkReverseSense.newInstance({ + reverseCells: true, + reverseNormals: true, + }); + reverseSense.setInputData(createPolyData()); + + const output = reverseSense.getOutputData(); + + t.deepEqual( + Array.from(output.getVerts().getData()), + [2, 4, 0], + 'reversed verts' + ); + t.deepEqual( + Array.from(output.getLines().getData()), + [3, 2, 1, 0], + 'reversed lines' + ); + t.deepEqual( + Array.from(output.getPolys().getData()), + [4, 3, 2, 1, 0], + 'reversed polys' + ); + t.deepEqual( + Array.from(output.getStrips().getData()), + [5, 4, 3, 2, 1, 0], + 'reversed strips' + ); + t.deepEqual( + Array.from(output.getPointData().getNormals().getData()), + [-1, 0, 0, 0, -1, 0, 0, 0, -1, -1, -1, 0, 0, -1, -1], + 'reversed point normals' + ); + t.deepEqual( + Array.from(output.getCellData().getNormals().getData()), + [-1, 0, 0, 0, -1, 0, 0, 0, -1, -1, -1, -1], + 'reversed cell normals' + ); + + t.end(); +}); + +test('vtkReverseSense leaves normals unchanged when disabled', (t) => { + const reverseSense = vtkReverseSense.newInstance({ + reverseCells: true, + reverseNormals: false, + }); + const input = createPolyData(); + reverseSense.setInputData(input); + + const output = reverseSense.getOutputData(); + + t.deepEqual( + Array.from(output.getPointData().getNormals().getData()), + Array.from(input.getPointData().getNormals().getData()), + 'point normals are unchanged' + ); + t.deepEqual( + Array.from(output.getCellData().getNormals().getData()), + Array.from(input.getCellData().getNormals().getData()), + 'cell normals are unchanged' + ); + + t.end(); +}); diff --git a/Sources/Filters/Core/index.js b/Sources/Filters/Core/index.js index fad4539e06c..e02b9c4d757 100644 --- a/Sources/Filters/Core/index.js +++ b/Sources/Filters/Core/index.js @@ -2,6 +2,7 @@ import vtkCleanPolyData from './CleanPolyData'; import vtkClipPolyData from './ClipPolyData'; import vtkCutter from './Cutter'; import vtkPolyDataNormals from './PolyDataNormals'; +import vktReverseSense from './ReverseSense'; import vtkThresholdPoints from './ThresholdPoints'; export default { @@ -9,5 +10,6 @@ export default { vtkClipPolyData, vtkCutter, vtkPolyDataNormals, + vktReverseSense, vtkThresholdPoints, };