diff --git a/Sources/Rendering/Core/ImageSlice/index.d.ts b/Sources/Rendering/Core/ImageSlice/index.d.ts index 914dc3f4a83..52f5962bd9f 100755 --- a/Sources/Rendering/Core/ImageSlice/index.d.ts +++ b/Sources/Rendering/Core/ImageSlice/index.d.ts @@ -135,6 +135,18 @@ export interface vtkImageSlice extends vtkProp3D { * @param {vtkImageProperty} property The vtkImageProperty instance. */ setProperty(property: vtkImageProperty): boolean; + + /** + * + * @param {boolean} forceOpaque If true, render during opaque pass even if opacity value is below 1.0. + */ + setForceOpaque(forceOpaque: boolean): boolean; + + /** + * + * @param {boolean} forceTranslucent If true, render during translucent pass even if opacity value is 1.0. + */ + setForceTranslucent(forceTranslucent: boolean): boolean; } /** diff --git a/Sources/Rendering/Core/ImageSlice/index.js b/Sources/Rendering/Core/ImageSlice/index.js index 1e83b8256f2..3cdc5ca3136 100644 --- a/Sources/Rendering/Core/ImageSlice/index.js +++ b/Sources/Rendering/Core/ImageSlice/index.js @@ -179,6 +179,9 @@ const DEFAULT_VALUES = { mapper: null, property: null, + forceOpaque: false, + forceTranslucent: false, + bounds: [...vtkBoundingBox.INIT_BOUNDS], }; @@ -196,7 +199,8 @@ export function extend(publicAPI, model, initialValues = {}) { // Build VTK API macro.set(publicAPI, model, ['property']); - macro.setGet(publicAPI, model, ['mapper']); + macro.setGet(publicAPI, model, ['mapper', 'forceOpaque', 'forceTranslucent']); + macro.getArray(publicAPI, model, ['bounds'], 6); // Object methods diff --git a/Sources/Rendering/OpenGL/ImageSlice/test/testForceOpaque.png b/Sources/Rendering/OpenGL/ImageSlice/test/testForceOpaque.png new file mode 100644 index 00000000000..6c7067d07c2 Binary files /dev/null and b/Sources/Rendering/OpenGL/ImageSlice/test/testForceOpaque.png differ diff --git a/Sources/Rendering/OpenGL/ImageSlice/test/testForceRenderPass.js b/Sources/Rendering/OpenGL/ImageSlice/test/testForceRenderPass.js new file mode 100644 index 00000000000..22cde57a8b4 --- /dev/null +++ b/Sources/Rendering/OpenGL/ImageSlice/test/testForceRenderPass.js @@ -0,0 +1,128 @@ +import test from 'tape'; +import testUtils from 'vtk.js/Sources/Testing/testUtils'; + +import vtkImageGridSource from 'vtk.js/Sources/Filters/Sources/ImageGridSource'; +import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper'; +import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice'; +import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow'; +import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer'; +import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow'; +import vtkColorTransferFunction from 'vtk.js/Sources/Rendering/Core/ColorTransferFunction'; + +import forceOpaqueBaseline from './testForceOpaque.png'; +import forceTranslucentBaseline from './testForceTranslucent.png'; +import { SlicingMode } from '../../../Core/ImageMapper/Constants'; + +const setupSlices = (t) => { + const gc = testUtils.createGarbageCollector(t); + t.ok('rendering', 'vtkOpenGLImageMapper testImage'); + + // 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); + + // ---------------------------------------------------------------------------- + // Test code + // ---------------------------------------------------------------------------- + + const gridSource = gc.registerResource(vtkImageGridSource.newInstance()); + const extent = 200; + const gridSpacing = 32; + const dataSpacing = 4; + const origin = 16; + gridSource.setDataExtent(0, extent, 0, extent, 0, 4); + gridSource.setDataSpacing(dataSpacing, dataSpacing, dataSpacing); + gridSource.setGridSpacing(gridSpacing, gridSpacing, gridSpacing); + gridSource.setGridOrigin(origin, origin, 1); + const direction = [0.866, 0.5, 0, -0.5, 0.866, 0, 0, 0, 1]; + gridSource.setDataDirection(...direction); + + const slice = 0; + + // mapperAbove should show above mapperBelow + // scalars, however, should be correct + const mapperBelow = gc.registerResource(vtkImageMapper.newInstance()); + mapperBelow.setInputConnection(gridSource.getOutputPort()); + mapperBelow.setSlicingMode(SlicingMode.Z); + mapperBelow.setSlice(slice * dataSpacing); + + const mapperAbove = gc.registerResource(vtkImageMapper.newInstance()); + mapperAbove.setInputConnection(gridSource.getOutputPort()); + mapperAbove.setSlicingMode(SlicingMode.Z); + mapperAbove.setSlice(slice * dataSpacing); + + const actorBelow = gc.registerResource(vtkImageSlice.newInstance()); + const rgb = vtkColorTransferFunction.newInstance(); + rgb.addRGBPoint(0, 0, 1, 0); + rgb.addRGBPoint(255, 0, 1, 0); + actorBelow.getProperty().setRGBTransferFunction(rgb); + actorBelow.setMapper(mapperBelow); + actorBelow.setPosition(100, 100, 0); + + const actorAbove = gc.registerResource(vtkImageSlice.newInstance()); + actorAbove.setMapper(mapperAbove); + actorAbove.setPosition(-100, 0, 0); + + renderer.addActor(actorBelow); + renderer.addActor(actorAbove); + renderer.resetCamera(); + + // create something to view it, in this case webgl + const glWindow = gc.registerResource(vtkOpenGLRenderWindow.newInstance()); + glWindow.setContainer(renderWindowContainer); + renderWindow.addView(glWindow); + glWindow.setSize(400, 400); + + return { glWindow, renderWindow, actorAbove, actorBelow, gc }; +}; + +test.onlyIfWebGL('Test ImageMapper forceOpaque', (t) => { + const { glWindow, renderWindow, actorBelow, gc } = setupSlices(t); + + // If this actor is simply just made transparent, then it will show above the actorAbove + actorBelow.getProperty().setOpacity(0.5); + // Make actorBelow get rendered in same pass as actorAbove + actorBelow.setForceOpaque(true); + + glWindow.captureNextImage().then((image) => { + testUtils.compareImages( + image, + [forceOpaqueBaseline], + 'Rendering/OpenGL/ImageSlice', + t, + 0.5, + gc.releaseResources + ); + }); + renderWindow.render(); +}); + +test.onlyIfWebGL.only('Test ImageMapper forceTranslucent', (t) => { + const { glWindow, renderWindow, actorAbove, actorBelow, gc } = setupSlices(t); + + actorBelow.getProperty().setOpacity(0.9); + actorAbove.getProperty().setOpacity(1); + // keep translucent blending + actorAbove.setForceTranslucent(true); + + glWindow.captureNextImage().then((image) => { + testUtils.compareImages( + image, + [forceTranslucentBaseline], + 'Rendering/OpenGL/ImageSlice', + t, + 0.5, + gc.releaseResources + ); + }); + renderWindow.render(); +}); diff --git a/Sources/Rendering/OpenGL/ImageSlice/test/testForceTranslucent.png b/Sources/Rendering/OpenGL/ImageSlice/test/testForceTranslucent.png new file mode 100644 index 00000000000..6ce4b08c36d Binary files /dev/null and b/Sources/Rendering/OpenGL/ImageSlice/test/testForceTranslucent.png differ