diff --git a/Documentation/content/docs/gallery/RegularPolygonSource.jpg b/Documentation/content/docs/gallery/RegularPolygonSource.jpg new file mode 100644 index 00000000000..ded657bbe02 Binary files /dev/null and b/Documentation/content/docs/gallery/RegularPolygonSource.jpg differ diff --git a/Documentation/content/examples/index.md b/Documentation/content/examples/index.md index b92e8a2ad50..f63f6759b62 100644 --- a/Documentation/content/examples/index.md +++ b/Documentation/content/examples/index.md @@ -151,6 +151,7 @@ This will allow you to see the some live code running in your browser. Just pick [![PlaneSource Example][PlaneSource]](./PlaneSource.html "PlaneSource") [![PointSource Example][PointSource]](./PointSource.html "PointSource") [![PlatonicSolidSource Example][PlatonicSolidSource]](./PlatonicSolidSource.html "PlatonicSolidSource") +[![RegularPolygonSource Example][RegularPolygonSource]](./RegularPolygonSource.html "RegularPolygonSource") [![SLICSource Example][SLICSource]](./SLICSource.html "SLICSource") [![SphereSource Example][SphereSource]](./SphereSource.html "SphereSource") [![WarpScalar Example][WarpScalargif]](./WarpScalar.html "WarpScalar") @@ -173,6 +174,7 @@ This will allow you to see the some live code running in your browser. Just pick [PlaneSource]: ../docs/gallery/PlaneSource.jpg [PointSource]: ../docs/gallery/PointSource.jpg [PlatonicSolidSource]: ../docs/gallery/PlatonicSolidSource.jpg +[RegularPolygonSource]: ../docs/gallery/RegularPolygonSource.jpg [SLICSource]: ../docs/gallery/SLICSource.jpg [SphereSource]: ../docs/gallery/SphereSource.gif [WarpScalargif]: ../docs/gallery/WarpScalar.gif diff --git a/Sources/Filters/Sources/PlatonicSolidSource/index.d.ts b/Sources/Filters/Sources/PlatonicSolidSource/index.d.ts index 659e4de6e84..393ae38e379 100644 --- a/Sources/Filters/Sources/PlatonicSolidSource/index.d.ts +++ b/Sources/Filters/Sources/PlatonicSolidSource/index.d.ts @@ -96,10 +96,10 @@ export function newInstance( * * @example * ```js - * import vtkPlatonicSolidSource from '@kitware/vtk.js/Filters/Sources/RegularPolygonSource'; + * import vtkPlatonicSolidSource from '@kitware/vtk.js/Filters/Sources/PlatonicSolidSource'; * - * const regularPolygonSource = vtkPlatonicSolidSource.newInstance(); - * const polydata = regularPolygonSource.getOutputData(); + * const platonicSolidSource = vtkPlatonicSolidSource.newInstance(); + * const polydata = platonicSolidSource.getOutputData(); * ``` */ export declare const vtkPlatonicSolidSource: { diff --git a/Sources/Filters/Sources/RegularPolygonSource/example/controlPanel.html b/Sources/Filters/Sources/RegularPolygonSource/example/controlPanel.html new file mode 100644 index 00000000000..014e7d29234 --- /dev/null +++ b/Sources/Filters/Sources/RegularPolygonSource/example/controlPanel.html @@ -0,0 +1,8 @@ + + + + + +
Number Of Sides + +
diff --git a/Sources/Filters/Sources/RegularPolygonSource/example/index.js b/Sources/Filters/Sources/RegularPolygonSource/example/index.js new file mode 100644 index 00000000000..85048b326e3 --- /dev/null +++ b/Sources/Filters/Sources/RegularPolygonSource/example/index.js @@ -0,0 +1,62 @@ +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 vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; +import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; +import vtkRegularPolygonSource from '@kitware/vtk.js/Filters/Sources/RegularPolygonSource'; +import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; +import { Representation } from '@kitware/vtk.js/Rendering/Core/Property/Constants'; + +import controlPanel from './controlPanel.html'; + +// ---------------------------------------------------------------------------- +// Standard rendering code setup +// ---------------------------------------------------------------------------- + +const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance(); +const renderer = fullScreenRenderer.getRenderer(); +const renderWindow = fullScreenRenderer.getRenderWindow(); + +// ---------------------------------------------------------------------------- +// Example code +// ---------------------------------------------------------------------------- +const regularPolygonSource = vtkRegularPolygonSource.newInstance(); + +const mapper = vtkMapper.newInstance(); +const actor = vtkActor.newInstance(); + +actor.getProperty().setRepresentation(Representation.WIREFRAME); + +mapper.setInputConnection(regularPolygonSource.getOutputPort()); +actor.setMapper(mapper); + +renderer.addActor(actor); +renderer.resetCamera(); +renderWindow.render(); + +// ----------------------------------------------------------- +// UI control handling +// ----------------------------------------------------------- + +fullScreenRenderer.addController(controlPanel); + +['numberOfSides'].forEach((propertyName) => { + document.querySelector(`.${propertyName}`).addEventListener('input', (e) => { + const value = Number(e.target.value); + regularPolygonSource.set({ [propertyName]: value }); + renderWindow.render(); + }); +}); + +// ----------------------------------------------------------- +// Make some variables global so that you can inspect and +// modify objects in your browser's developer console: +// ----------------------------------------------------------- + +global.regularPolygonSource = regularPolygonSource; +global.mapper = mapper; +global.actor = actor; +global.renderer = renderer; +global.renderWindow = renderWindow; diff --git a/Sources/Filters/Sources/RegularPolygonSource/index.d.ts b/Sources/Filters/Sources/RegularPolygonSource/index.d.ts new file mode 100644 index 00000000000..7970fe0e8e2 --- /dev/null +++ b/Sources/Filters/Sources/RegularPolygonSource/index.d.ts @@ -0,0 +1,183 @@ +import { vtkAlgorithm, vtkObject } from '../../../interfaces'; +import { Vector3 } from '../../../types'; +import { DesiredOutputPrecision } from '../../../Common/DataModel/DataSetAttributes'; + +/** + * + */ +export interface IPlaneSourceInitialValues { + numberOfSides?: number; + center?: Vector3; + normal?: Vector3; + radius?: number; + generatePolygon?: boolean; + generatePolyline?: boolean; + outputPointsPrecision?: DesiredOutputPrecision; +} + +type vtkRegularPolygonSourceBase = vtkObject & + Omit< + vtkAlgorithm, + | 'getInputData' + | 'setInputData' + | 'setInputConnection' + | 'getInputConnection' + | 'addInputConnection' + | 'addInputData' + >; + +export interface vtkRegularPolygonSource extends vtkRegularPolygonSourceBase { + /** + * Get the center of the regular polygon. + * @returns {Vector3} center of the polygon + */ + getCenter(): Vector3; + + /** + * Get a reference to the center of the regular polygon. + * @returns {Vector3} reference to the center of the polygon + */ + getCenterByReference(): Vector3; + + /** + * Get whether to generate polygon points. + * @returns {Boolean} true if polygon points are generated, false otherwise + */ + getGeneratePolygon(): boolean; + + /** + * Get whether to generate polyline points. + * @returns {Boolean} true if polyline points are generated, false otherwise + */ + getGeneratePolyline(): boolean; + + /** + * Get the normal of the regular polygon. + * @returns {Vector3} normal of the polygon + */ + getNormal(): Vector3; + + /** + * Get a reference to the normal of the regular polygon. + * @returns {Vector3} reference to the normal of the polygon + */ + getNormalByReference(): Vector3; + + /** + * Get the number of sides for the regular polygon. + * @returns {Number} number of sides + */ + getNumberOfSides(): number; + + /** + * Get the output points precision. + * @returns {DesiredOutputPrecision} the output points precision + */ + getOutputPointsPrecision(): DesiredOutputPrecision; + + /** + * Get the radius of the regular polygon. + * @returns {Number} radius of the polygon + */ + getRadius(): number; + + /** + * + * @param inData + * @param outData + */ + requestData(inData: any, outData: any): void; + + /** + * Set the center of the regular polygon. + * @param {Vector3} center + * @returns {Boolean} true if the value was changed, false otherwise + */ + setCenter(center: Vector3): boolean; + + /** + * Set whether to generate polygon points. + * @param generatePolygon + * @returns {Boolean} true if the value was changed, false otherwise + */ + setGeneratePolygon(generatePolygon: boolean): boolean; + + /** + * Set whether to generate polyline points. + * @param generatePolyline + * @returns {Boolean} true if the value was changed, false otherwise + */ + setGeneratePolyline(generatePolyline: boolean): boolean; + + /** + * Set the normal of the regular polygon. + * @param {Vector3} normal + * @returns {Boolean} true if the value was changed, false otherwise + */ + setNormal(normal: Vector3): boolean; + + /** + * Set the number of sides for the regular polygon. + * @param numberOfSides + * @returns {Boolean} true if the value was changed, false otherwise + */ + setNumberOfSides(numberOfSides: number): boolean; + + /** + * Set the output points precision. + * @param outputPointsPrecision + * @returns {Boolean} true if the value was changed, false otherwise + */ + setOutputPointsPrecision( + outputPointsPrecision: DesiredOutputPrecision + ): boolean; + + /** + * Set the radius of the regular polygon. + * @param radius + * @returns {Boolean} true if the value was changed, false otherwise + */ + setRadius(radius: number): boolean; +} + +/** + * Method used to decorate a given object (publicAPI+model) with vtkRegularPolygonSource characteristics. + * + * @param publicAPI object on which methods will be bounds (public) + * @param model object on which data structure will be bounds (protected) + * @param {IPlaneSourceInitialValues} [initialValues] (default: {}) + */ +export function extend( + publicAPI: object, + model: object, + initialValues?: IPlaneSourceInitialValues +): void; + +/** + * Method used to create a new instance of vtkRegularPolygonSource. + * @param {IPlaneSourceInitialValues} [initialValues] for pre-setting some of its content + */ +export function newInstance( + initialValues?: IPlaneSourceInitialValues +): vtkRegularPolygonSource; + +/** + * vtkRegularPolygonSource is a source object that creates a single n-sided + * polygon and/or polyline. The polygon is centered at a specified point, + * orthogonal to a specified normal, and with a circumscribing radius set by the + * user. The user can also specify the number of sides of the polygon ranging + * from [3,N]. + * + * @example + * ```js + * import vtkRegularPolygonSource from '@kitware/vtk.js/Filters/Sources/RegularPolygonSource'; + * + * const regularPolygonSource = vtkRegularPolygonSource.newInstance(); + * const polydata = regularPolygonSource.getOutputData(); + * ``` + */ +export declare const vtkRegularPolygonSource: { + newInstance: typeof newInstance; + extend: typeof extend; +}; +export default vtkRegularPolygonSource; diff --git a/Sources/Filters/Sources/RegularPolygonSource/index.js b/Sources/Filters/Sources/RegularPolygonSource/index.js new file mode 100644 index 00000000000..7aa883fd7b3 --- /dev/null +++ b/Sources/Filters/Sources/RegularPolygonSource/index.js @@ -0,0 +1,166 @@ +import macro from 'vtk.js/Sources/macros'; +import vtkMath from 'vtk.js/Sources/Common/Core/Math'; +import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; +import vtkPoints from 'vtk.js/Sources/Common/Core/Points'; +import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; +import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants'; +import { DesiredOutputPrecision } from 'vtk.js/Sources/Common/DataModel/DataSetAttributes/Constants'; + +// ---------------------------------------------------------------------------- +// vtkRegularPolygonSource methods +// ---------------------------------------------------------------------------- + +function vtkRegularPolygonSource(publicAPI, model) { + // Set our className + model.classHierarchy.push('vtkRegularPolygonSource'); + + publicAPI.requestData = (inData, outData) => { + const output = outData[0]?.initialize() || vtkPolyData.newInstance(); + const numPts = model.numberOfSides; + + const newPoints = vtkPoints.newInstance({ + dataType: + model.outputPointsPrecision === DesiredOutputPrecision.DOUBLE + ? VtkDataTypes.DOUBLE + : VtkDataTypes.FLOAT, + }); + + // Generate polyline if requested + if (model.generatePolyline) { + const newLine = vtkCellArray.newInstance(); + const linePoints = []; + for (let i = 0; i < numPts; i++) { + linePoints.push(i); + } + linePoints.push(0); // close the polyline + newLine.insertNextCell(linePoints); + output.setLines(newLine); + } + + // Generate polygon if requested + if (model.generatePolygon) { + const newPoly = vtkCellArray.newInstance(); + const polyPoints = []; + for (let i = 0; i < numPts; i++) { + polyPoints.push(i); + } + newPoly.insertNextCell(polyPoints); + output.setPolys(newPoly); + } + + // Make sure the polygon normal is a unit vector + const n = [...model.normal]; + const nLength = vtkMath.normalize(n); + if (nLength === 0.0) { + n[0] = 0.0; + n[1] = 0.0; + n[2] = 1.0; + } + + // Find a vector in the polygon plane (perpendicular to normal) + const px = [0, 0, 0]; + const py = [0, 0, 0]; + let foundPlaneVector = false; + + // Cross with unit axis vectors and eventually find vector in the polygon plane + const axis = [1.0, 0.0, 0.0]; + vtkMath.cross(n, axis, px); + const pxLength = vtkMath.normalize(px); + if (pxLength > 1.0e-3) { + foundPlaneVector = true; + } + + if (!foundPlaneVector) { + axis[0] = 0.0; + axis[1] = 1.0; + axis[2] = 0.0; + vtkMath.cross(n, axis, px); + const pxLength2 = vtkMath.normalize(px); + if (pxLength2 > 1.0e-3) { + foundPlaneVector = true; + } + } + + if (!foundPlaneVector) { + axis[0] = 0.0; + axis[1] = 0.0; + axis[2] = 1.0; + vtkMath.cross(n, axis, px); + vtkMath.normalize(px); + } + + // Create second orthogonal axis in polygon plane + vtkMath.cross(px, n, py); + + // Generate polygon points + const theta = (2.0 * Math.PI) / numPts; + const points = []; + const r = [0, 0, 0]; + const x = [0, 0, 0]; + + for (let j = 0; j < numPts; j++) { + const cosTheta = Math.cos(j * theta); + const sinTheta = Math.sin(j * theta); + + r[0] = px[0] * cosTheta + py[0] * sinTheta; + r[1] = px[1] * cosTheta + py[1] * sinTheta; + r[2] = px[2] * cosTheta + py[2] * sinTheta; + + x[0] = model.center[0] + model.radius * r[0]; + x[1] = model.center[1] + model.radius * r[1]; + x[2] = model.center[2] + model.radius * r[2]; + + points.push(x[0], x[1], x[2]); + } + + newPoints.setData(points); + output.setPoints(newPoints); + + outData[0] = output; + }; +} + +// ---------------------------------------------------------------------------- +// Object factory +// ---------------------------------------------------------------------------- + +const DEFAULT_VALUES = { + numberOfSides: 6, + center: [0.0, 0.0, 0.0], + normal: [0.0, 0.0, 1.0], + radius: 0.5, + generatePolygon: true, + generatePolyline: true, + outputPointsPrecision: DesiredOutputPrecision.FLOAT, +}; + +// ---------------------------------------------------------------------------- + +export function extend(publicAPI, model, initialValues = {}) { + Object.assign(model, DEFAULT_VALUES, initialValues); + + // Build VTK API + macro.obj(publicAPI, model); + macro.algo(publicAPI, model, 0, 1); + + // Build VTK API + macro.setGet(publicAPI, model, [ + 'numberOfSides', + 'radius', + 'generatePolygon', + 'generatePolyline', + 'outputPointsPrecision', + ]); + + macro.setGetArray(publicAPI, model, ['center', 'normal'], 3); + + vtkRegularPolygonSource(publicAPI, model); +} + +// ---------------------------------------------------------------------------- + +export const newInstance = macro.newInstance(extend, 'vtkRegularPolygonSource'); + +// ---------------------------------------------------------------------------- + +export default { newInstance, extend }; diff --git a/Sources/Filters/Sources/RegularPolygonSource/test/testRegularPolygon.js b/Sources/Filters/Sources/RegularPolygonSource/test/testRegularPolygon.js new file mode 100644 index 00000000000..aeafa9ff9c3 --- /dev/null +++ b/Sources/Filters/Sources/RegularPolygonSource/test/testRegularPolygon.js @@ -0,0 +1,64 @@ +import test from 'tape'; +import testUtils from 'vtk.js/Sources/Testing/testUtils'; + +import 'vtk.js/Sources/Rendering/Misc/RenderingAPIs'; +import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow'; +import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer'; +import vtkRegularPolygonSource from 'vtk.js/Sources/Filters/Sources/RegularPolygonSource'; +import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'; +import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper'; +import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants'; + +import baseline from './testRegularPolygon.png'; + +test.onlyIfWebGL('Test vtkRegularPolygonSource Rendering', (t) => { + const gc = testUtils.createGarbageCollector(); + t.ok('rendering', 'vtkRegularPolygonSource Rendering'); + + // Create some control UI + const container = document.querySelector('body'); + const renderWindowContainer = gc.registerDOMElement( + document.createElement('div') + ); + container.appendChild(renderWindowContainer); + + // create what we will view + const renderWindow = gc.registerResource(vtkRenderWindow.newInstance()); + const renderer = gc.registerResource(vtkRenderer.newInstance()); + renderWindow.addRenderer(renderer); + renderer.setBackground(0.32, 0.34, 0.43); + + const actor = gc.registerResource(vtkActor.newInstance()); + actor.getProperty().setRepresentation(Representation.WIREFRAME); + + renderer.addActor(actor); + + const mapper = gc.registerResource(vtkMapper.newInstance()); + actor.setMapper(mapper); + + const regularPolygonSource = gc.registerResource( + vtkRegularPolygonSource.newInstance() + ); + mapper.setInputConnection(regularPolygonSource.getOutputPort()); + + // now create something to view it, in this case webgl + const glwindow = gc.registerResource(renderWindow.newAPISpecificView()); + glwindow.setContainer(renderWindowContainer); + renderWindow.addView(glwindow); + glwindow.setSize(400, 400); + + const promise = glwindow + .captureNextImage() + .then((image) => + testUtils.compareImages( + image, + [baseline], + 'Filters/Sources/regularPolygonSource/testRegularPolygon', + t, + 2.5 + ) + ) + .finally(gc.releaseResources); + renderWindow.render(); + return promise; +}); diff --git a/Sources/Filters/Sources/RegularPolygonSource/test/testRegularPolygon.png b/Sources/Filters/Sources/RegularPolygonSource/test/testRegularPolygon.png new file mode 100644 index 00000000000..2ecb6237063 Binary files /dev/null and b/Sources/Filters/Sources/RegularPolygonSource/test/testRegularPolygon.png differ diff --git a/Sources/Filters/Sources/index.js b/Sources/Filters/Sources/index.js index eeb901b7667..9624826dfca 100644 --- a/Sources/Filters/Sources/index.js +++ b/Sources/Filters/Sources/index.js @@ -13,6 +13,7 @@ import vtkLineSource from './LineSource'; import vtkPlaneSource from './PlaneSource'; import vtkPlatonicSolidSource from './PlatonicSolidSource'; import vtkPointSource from './PointSource'; +import vtkRegularPolygonSource from './RegularPolygonSource'; import vtkRTAnalyticSource from './RTAnalyticSource'; import vtkSLICSource from './SLICSource'; import vtkSphereSource from './SphereSource'; @@ -34,6 +35,7 @@ export default { vtkPlaneSource, vtkPlatonicSolidSource, vtkPointSource, + vtkRegularPolygonSource, vtkRTAnalyticSource, vtkSLICSource, vtkSphereSource,