Skip to content

Commit

Permalink
Merge pull request Kitware#1931 from thewtex/image-mapper-cropping-pl…
Browse files Browse the repository at this point in the history
…anes
  • Loading branch information
thewtex authored Jul 19, 2021
2 parents 7d25773 + 75146d2 commit 3b816b9
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 40 deletions.
9 changes: 9 additions & 0 deletions Sources/Rendering/Core/AbstractMapper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export interface vtkAbstractMapper extends vtkAbstractMapperBase {
*/
setClippingPlanes(planes: vtkPlane[]): void;

/**
* Get the ith clipping plane as a homogeneous plane equation.
* Use getNumberOfClippingPlanes() to get the number of planes.
* @param {mat4} propMatrix
* @param {Number} i
* @param {Number[]} hnormal
*/
getClippingPlaneInDataCoords(propMatrix : mat4, i : number, hnormal : number[]): void;

/**
*
*/
Expand Down
30 changes: 30 additions & 0 deletions Sources/Rendering/Core/AbstractMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,36 @@ function vtkAbstractMapper(publicAPI, model) {
}
}
};

publicAPI.getClippingPlaneInDataCoords = (propMatrix, i, hnormal) => {
const clipPlanes = model.clippingPlanes;
const mat = propMatrix;

if (clipPlanes) {
const n = clipPlanes.length;
if (i >= 0 && i < n) {
// Get the plane
const plane = clipPlanes[i];
const normal = plane.getNormal();
const origin = plane.getOrigin();

// Compute the plane equation
const v1 = normal[0];
const v2 = normal[1];
const v3 = normal[2];
const v4 = -(v1 * origin[0] + v2 * origin[1] + v3 * origin[2]);

// Transform normal from world to data coords
hnormal[0] = v1 * mat[0] + v2 * mat[4] + v3 * mat[8] + v4 * mat[12];
hnormal[1] = v1 * mat[1] + v2 * mat[5] + v3 * mat[9] + v4 * mat[13];
hnormal[2] = v1 * mat[2] + v2 * mat[6] + v3 * mat[10] + v4 * mat[14];
hnormal[3] = v1 * mat[3] + v2 * mat[7] + v3 * mat[11] + v4 * mat[15];

return;
}
}
macro.vtkErrorMacro(`Clipping plane index ${i} is out of range.`);
};
}

// ----------------------------------------------------------------------------
Expand Down
8 changes: 0 additions & 8 deletions Sources/Rendering/Core/AbstractMapper3D/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ export interface vtkAbstractMapper3D extends vtkAbstractMapper {
*/
getLength(): number;

/**
* Get the ith clipping plane as a homogeneous plane equation.
* Use getNumberOfClippingPlanes() to get the number of planes.
* @param {mat4} propMatrix
* @param {Number} i
* @param {Number[]} hnormal
*/
getClippingPlaneInDataCoords(propMatrix : mat4, i : number, hnormal : number[]): void;
}

/**
Expand Down
32 changes: 0 additions & 32 deletions Sources/Rendering/Core/AbstractMapper3D/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import macro from 'vtk.js/Sources/macro';

import vtkAbstractMapper from 'vtk.js/Sources/Rendering/Core/AbstractMapper';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -35,36 +33,6 @@ function vtkAbstractMapper3D(publicAPI, model) {

return Math.sqrt(l);
};

publicAPI.getClippingPlaneInDataCoords = (propMatrix, i, hnormal) => {
const clipPlanes = model.clippingPlanes;
const mat = propMatrix;

if (clipPlanes) {
const n = clipPlanes.length;
if (i >= 0 && i < n) {
// Get the plane
const plane = clipPlanes[i];
const normal = plane.getNormal();
const origin = plane.getOrigin();

// Compute the plane equation
const v1 = normal[0];
const v2 = normal[1];
const v3 = normal[2];
const v4 = -(v1 * origin[0] + v2 * origin[1] + v3 * origin[2]);

// Transform normal from world to data coords
hnormal[0] = v1 * mat[0] + v2 * mat[4] + v3 * mat[8] + v4 * mat[12];
hnormal[1] = v1 * mat[1] + v2 * mat[5] + v3 * mat[9] + v4 * mat[13];
hnormal[2] = v1 * mat[2] + v2 * mat[6] + v3 * mat[10] + v4 * mat[14];
hnormal[3] = v1 * mat[3] + v2 * mat[7] + v3 * mat[11] + v4 * mat[15];

return;
}
}
macro.vtkErrorMacro(`Clipping plane index ${i} is out of range.`);
};
}

// ----------------------------------------------------------------------------
Expand Down
76 changes: 76 additions & 0 deletions Sources/Rendering/OpenGL/ImageMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,56 @@ function vtkOpenGLImageMapper(publicAPI, model) {
shaders.Vertex = VSSource;
shaders.Fragment = FSSource;

publicAPI.replaceShaderClip(shaders, ren, actor);
publicAPI.replaceShaderCoincidentOffset(shaders, ren, actor);
};

publicAPI.replaceShaderClip = (shaders, ren, actor) => {
let VSSource = shaders.Vertex;
let FSSource = shaders.Fragment;

if (model.renderable.getNumberOfClippingPlanes()) {
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
if (numClipPlanes > 6) {
macro.vtkErrorMacro('OpenGL has a limit of 6 clipping planes');
numClipPlanes = 6;
}
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Dec', [
'uniform int numClipPlanes;',
'uniform vec4 clipPlanes[6];',
'varying float clipDistancesVSOutput[6];',
]).result;

VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Impl', [
'for (int planeNum = 0; planeNum < 6; planeNum++)',
' {',
' if (planeNum >= numClipPlanes)',
' {',
' break;',
' }',
' clipDistancesVSOutput[planeNum] = dot(clipPlanes[planeNum], vertexMC);',
' }',
]).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Dec', [
'uniform int numClipPlanes;',
'varying float clipDistancesVSOutput[6];',
]).result;

FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Impl', [
'for (int planeNum = 0; planeNum < 6; planeNum++)',
' {',
' if (planeNum >= numClipPlanes)',
' {',
' break;',
' }',
' if (clipDistancesVSOutput[planeNum] < 0.0) discard;',
' }',
]).result;
}
shaders.Vertex = VSSource;
shaders.Fragment = FSSource;
};

publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => {
// has something changed that would require us to recreate the shader?
// candidates are
Expand Down Expand Up @@ -509,6 +556,33 @@ function vtkOpenGLImageMapper(publicAPI, model) {

const texOpacityUnit = model.pwfTexture.getTextureUnit();
cellBO.getProgram().setUniformi('pwfTexture1', texOpacityUnit);

if (model.renderable.getNumberOfClippingPlanes()) {
// add all the clipping planes
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
if (numClipPlanes > 6) {
macro.vtkErrorMacro('OpenGL has a limit of 6 clipping planes');
numClipPlanes = 6;
}
const image = model.currentInput;
const w2imat4 = image.getWorldToIndex();
mat4.multiply(model.imagematinv, w2imat4, actor.getMatrix());
const planeEquations = [];
for (let i = 0; i < numClipPlanes; i++) {
const planeEquation = [];
model.renderable.getClippingPlaneInDataCoords(
model.imagematinv,
i,
planeEquation
);

for (let j = 0; j < 4; j++) {
planeEquations.push(planeEquation[j]);
}
}
cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
cellBO.getProgram().setUniform4fv('clipPlanes', 6, planeEquations);
}
};

publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
Expand Down Expand Up @@ -963,6 +1037,7 @@ const DEFAULT_VALUES = {
openGLTexture: null,
tris: null,
imagemat: null,
imagematinv: null,
colorTexture: null,
pwfTexture: null,
lastHaveSeenDepthRequest: false,
Expand All @@ -989,6 +1064,7 @@ export function extend(publicAPI, model, initialValues = {}) {
model.pwfTexture = vtkOpenGLTexture.newInstance();

model.imagemat = mat4.identity(new Float64Array(16));
model.imagematinv = mat4.identity(new Float64Array(16));

// Build VTK API
macro.setGet(publicAPI, model, []);
Expand Down
143 changes: 143 additions & 0 deletions Sources/Rendering/OpenGL/ImageMapper/test/testImageCroppingPlanes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import test from 'tape-catch';
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 vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';

import baseline from './testImageCroppingPlanes.png';

test.onlyIfWebGL('Test ImageMapper ClippingPlanes', (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 spacing = 16;
const origin = 8;
gridSource.setDataExtent(0, extent, 0, extent, 0, 0);
gridSource.setGridSpacing(spacing, spacing, 0);
gridSource.setGridOrigin(origin, origin, 0);
const direction = [0.866, 0.5, 0, -0.5, 0.866, 0, 0, 0, 1];
gridSource.setDataDirection(...direction);

const mapper = gc.registerResource(vtkImageMapper.newInstance());
mapper.setInputConnection(gridSource.getOutputPort());

const clipPlane = vtkPlane.newInstance();
clipPlane.setOrigin([0.0, 0.0, 0.0]);
clipPlane.setNormal([0.707, 0.707, 0.0]);
mapper.addClippingPlane(clipPlane);

const actor = gc.registerResource(vtkImageSlice.newInstance());
actor.getProperty().setColorWindow(255);
actor.getProperty().setColorLevel(127);
actor.getProperty().setOpacity(0.3);
actor.setMapper(mapper);

const position = [0.3, 0.0, 0.0];
actor.setPosition(position);

const polyData = vtkPolyData.newInstance();
const polyMapper = vtkMapper.newInstance();
polyMapper.addClippingPlane(clipPlane);
polyMapper.setInputData(polyData);

const polySideCount = parseInt(extent / spacing, 10);
const polyPoints = new Float32Array(polySideCount * polySideCount * 3);
let vertexIndex = 0;
for (let i = 0; i < polySideCount; i++) {
for (let j = 0; j < polySideCount; j++) {
polyPoints[vertexIndex] = origin + i * spacing;
polyPoints[vertexIndex + 1] = origin + j * spacing;
polyPoints[vertexIndex + 2] = origin;
vertexIndex += 3;
}
}

function addLines(linesArray, offset, lineCount) {
for (let i = 0; i < lineCount - 1; i++) {
for (let j = 0; j < lineCount - 1; j++) {
const start = i * lineCount + j + offset;
linesArray.push(
5,
start,
start + 1,
(i + 1) * lineCount + j + 1 + offset,
(i + 1) * lineCount + j + offset,
start
);
}
}
}

const polyActor = vtkActor.newInstance();
polyActor.setMapper(polyMapper);
polyActor.getProperty().setOpacity(1.0);
polyActor.getProperty().setColor(1.0, 0.6, 0.6);

polyActor.setPosition(position);

const polyLines = [];
addLines(polyLines, 0, polySideCount);

const verts = new Uint32Array(polyPoints.length);
verts.fill(1);
for (let i = 0; i < polyPoints.length; i++) {
verts[i * 2 + 1] = i;
}
polyData.getPoints().setData(polyPoints, 3);
polyData.getVerts().setData(verts);
polyData.getLines().setData(new Uint32Array(polyLines));

// Applied with DataDirection on the grid
polyActor.rotateZ(30);

renderer.addActor(actor);
renderer.addActor(polyActor);
renderer.resetCamera();
renderWindow.render();

// 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);

glwindow.captureNextImage().then((image) => {
testUtils.compareImages(
image,
[baseline],
'Rendering/OpenGL/ImageMapper',
t,
1,
gc.releaseResources
);
});

renderWindow.render();
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Sources/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import './Rendering/Core/SphereMapper/test/testSphere';
import './Rendering/Core/StickMapper/test/testStick';
import './Rendering/Misc/GenericRenderWindow/test/testGenericRenderWindowCreateDelete';
import './Rendering/OpenGL/ImageMapper/test/testImage';
import './Rendering/OpenGL/ImageMapper/test/testImageCroppingPlanes';
import './Rendering/OpenGL/ImageMapper/test/testImageNearestNeighbor';
import './Rendering/OpenGL/ImageMapper/test/testImageColorTransferFunction';
import './Rendering/OpenGL/PolyDataMapper/test/testAddShaderReplacements';
Expand Down

0 comments on commit 3b816b9

Please sign in to comment.