Skip to content

Commit 15c0f1e

Browse files
authored
Merge pull request #2152 from ebrahimebrahim/issue_2134
allow more than 6 clipping planes on a polydata
2 parents e11f54d + 9236e51 commit 15c0f1e

File tree

8 files changed

+256
-27
lines changed

8 files changed

+256
-27
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<button type="button" class="addClippingPlane">add clipping plane</button>
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import 'vtk.js/Sources/favicon';
2+
3+
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
4+
import 'vtk.js/Sources/Rendering/Profiles/All';
5+
6+
import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
7+
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
8+
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
9+
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
10+
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
11+
import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource';
12+
import vtkPlaneSource from 'vtk.js/Sources/Filters/Sources/PlaneSource';
13+
import vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane';
14+
import controlPanel from './controlPanel.html';
15+
16+
// ----------------------------------------------------------------------------
17+
// Standard rendering code setup
18+
// ----------------------------------------------------------------------------
19+
20+
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
21+
background: [0.33, 0.4, 0.4],
22+
});
23+
fullScreenRenderer.addController(controlPanel);
24+
const renderer = fullScreenRenderer.getRenderer();
25+
const renderWindow = fullScreenRenderer.getRenderWindow();
26+
27+
const sphereMapper = vtkMapper.newInstance();
28+
const sphereSource = vtkSphereSource.newInstance({
29+
radius: 1.0,
30+
phiResolution: 30,
31+
thetaResolution: 30,
32+
});
33+
sphereMapper.setInputConnection(sphereSource.getOutputPort());
34+
const sphereActor = vtkActor.newInstance();
35+
sphereActor.setMapper(sphereMapper);
36+
sphereActor.getProperty().setColor(1, 0, 1);
37+
38+
// Add a clipping plane to the scene
39+
// The center of the plane will be `origin`
40+
// The normal direction of the plane is `normal`; it's okay if it's not a unit vector
41+
// `scale` is the size of the plane, i.e. the side length of the square that is created
42+
function addClippingPlaneToScene(origin, normal, scale) {
43+
vtkMath.normalize(normal);
44+
45+
const dir1 = [];
46+
const dir2 = [];
47+
vtkMath.perpendiculars(normal, dir1, dir2, 0);
48+
49+
const corner = [];
50+
vtkMath.multiplyAccumulate(origin, dir1, -0.5 * scale, corner);
51+
vtkMath.multiplyAccumulate(corner, dir2, -0.5 * scale, corner);
52+
53+
const point1 = [];
54+
const point2 = [];
55+
vtkMath.multiplyAccumulate(corner, dir1, scale, point1);
56+
vtkMath.multiplyAccumulate(corner, dir2, scale, point2);
57+
58+
const planeSource = vtkPlaneSource.newInstance({
59+
xResolution: 1,
60+
yResolution: 1,
61+
origin: corner,
62+
point1,
63+
point2,
64+
});
65+
66+
const clipPlane = vtkPlane.newInstance({
67+
normal,
68+
origin,
69+
});
70+
71+
const planeMapper = vtkMapper.newInstance();
72+
planeMapper.setInputConnection(planeSource.getOutputPort());
73+
const planeActor = vtkActor.newInstance();
74+
planeActor.setMapper(planeMapper);
75+
planeActor.getProperty().setOpacity(0.2);
76+
renderer.addActor(planeActor);
77+
78+
sphereMapper.addClippingPlane(clipPlane);
79+
}
80+
81+
renderer.addActor(sphereActor);
82+
83+
renderer.resetCamera();
84+
renderWindow.render();
85+
86+
const numPlanes = 8;
87+
88+
const theta = (2 * Math.PI) / numPlanes;
89+
const rotationMatrix = [
90+
[Math.cos(theta), Math.sin(theta), 0],
91+
[-Math.sin(theta), Math.cos(theta), 0],
92+
[0, 0, 1],
93+
];
94+
const normal = [1, 0, 0];
95+
const origin = [0, 0, 0];
96+
97+
document.querySelector('.addClippingPlane').addEventListener('click', (e) => {
98+
vtkMath.multiplyAccumulate([0, 0, 0], normal, -0.8, origin);
99+
addClippingPlaneToScene(
100+
origin, // origin
101+
normal, // normal
102+
3 // scale
103+
);
104+
vtkMath.multiply3x3_vect3(rotationMatrix, normal, normal);
105+
renderWindow.render();
106+
});
107+
108+
// -----------------------------------------------------------
109+
// Make some variables global so that you can inspect and
110+
// modify objects in your browser's developer console:
111+
// -----------------------------------------------------------
112+
113+
global.renderer = renderer;
114+
global.renderWindow = renderWindow;
115+
global.vtkMapper = vtkMapper;
116+
global.vtkRenderWindow = vtkRenderWindow;

Sources/Rendering/Core/AbstractMapper/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function vtkAbstractMapper(publicAPI, model) {
1414
return;
1515
}
1616
model.clippingPlanes.push(plane);
17+
publicAPI.modified();
1718
};
1819

1920
publicAPI.getNumberOfClippingPlanes = () => model.clippingPlanes.length;

Sources/Rendering/OpenGL/ImageMapper/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
580580
}
581581
}
582582
cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
583-
cellBO.getProgram().setUniform4fv('clipPlanes', 6, planeEquations);
583+
cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
584584
}
585585
};
586586

Sources/Rendering/OpenGL/PolyDataMapper/index.js

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -880,19 +880,15 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
880880
let FSSource = shaders.Fragment;
881881

882882
if (model.renderable.getNumberOfClippingPlanes()) {
883-
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
884-
if (numClipPlanes > 6) {
885-
macro.vtkErrorMacro('OpenGL has a limit of 6 clipping planes');
886-
numClipPlanes = 6;
887-
}
883+
const numClipPlanes = model.renderable.getNumberOfClippingPlanes();
888884
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Dec', [
889885
'uniform int numClipPlanes;',
890-
'uniform vec4 clipPlanes[6];',
891-
'varying float clipDistancesVSOutput[6];',
886+
`uniform vec4 clipPlanes[${numClipPlanes}];`,
887+
`varying float clipDistancesVSOutput[${numClipPlanes}];`,
892888
]).result;
893889

894890
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Impl', [
895-
'for (int planeNum = 0; planeNum < 6; planeNum++)',
891+
`for (int planeNum = 0; planeNum < ${numClipPlanes}; planeNum++)`,
896892
' {',
897893
' if (planeNum >= numClipPlanes)',
898894
' {',
@@ -903,11 +899,11 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
903899
]).result;
904900
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Dec', [
905901
'uniform int numClipPlanes;',
906-
'varying float clipDistancesVSOutput[6];',
902+
`varying float clipDistancesVSOutput[${numClipPlanes}];`,
907903
]).result;
908904

909905
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Impl', [
910-
'for (int planeNum = 0; planeNum < 6; planeNum++)',
906+
`for (int planeNum = 0; planeNum < ${numClipPlanes}; planeNum++)`,
911907
' {',
912908
' if (planeNum >= numClipPlanes)',
913909
' {',
@@ -1289,11 +1285,7 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
12891285

12901286
if (model.renderable.getNumberOfClippingPlanes()) {
12911287
// add all the clipping planes
1292-
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
1293-
if (numClipPlanes > 6) {
1294-
macro.vtkErrorMacro('OpenGL has a limit of 6 clipping planes');
1295-
numClipPlanes = 6;
1296-
}
1288+
const numClipPlanes = model.renderable.getNumberOfClippingPlanes();
12971289
const planeEquations = [];
12981290
for (let i = 0; i < numClipPlanes; i++) {
12991291
const planeEquation = [];
@@ -1308,7 +1300,7 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
13081300
}
13091301
}
13101302
cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
1311-
cellBO.getProgram().setUniform4fv('clipPlanes', 6, planeEquations);
1303+
cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
13121304
}
13131305

13141306
if (
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import test from 'tape-catch';
2+
3+
import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow';
4+
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
5+
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
6+
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
7+
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
8+
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
9+
import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource';
10+
import vtkPlaneSource from 'vtk.js/Sources/Filters/Sources/PlaneSource';
11+
import vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane';
12+
import testUtils from 'vtk.js/Sources/Testing/testUtils';
13+
import baseline from './testMoreClippingPlanes.png';
14+
15+
test('Test PolyDataMapper Clipping Planes 2', (t) => {
16+
const gc = testUtils.createGarbageCollector(t);
17+
// TODO switch back to onlyIfWebGL
18+
// Create some control UI
19+
const container = document.querySelector('body');
20+
const renderWindowContainer = document.createElement('div');
21+
container.appendChild(renderWindowContainer);
22+
23+
// Create what we will view
24+
const renderWindow = gc.registerResource(vtkRenderWindow.newInstance());
25+
const renderer = gc.registerResource(vtkRenderer.newInstance());
26+
renderWindow.addRenderer(renderer);
27+
renderer.setBackground(0.32, 0.34, 0.43);
28+
29+
// The rest is pretty much a static test version of the interactive example _MeshClipPlane
30+
31+
const sphereMapper = gc.registerResource(vtkMapper.newInstance());
32+
const sphereSource = gc.registerResource(
33+
vtkSphereSource.newInstance({
34+
radius: 1.0,
35+
phiResolution: 30,
36+
thetaResolution: 30,
37+
})
38+
);
39+
sphereMapper.setInputConnection(sphereSource.getOutputPort());
40+
const sphereActor = gc.registerResource(vtkActor.newInstance());
41+
sphereActor.setMapper(sphereMapper);
42+
sphereActor.getProperty().setColor(1, 0, 1);
43+
44+
// This function adds a clipping plane to the scene
45+
// The center of the plane will be `origin`
46+
// The normal direction of the plane is `normal`; it's okay if it's not a unit vector
47+
// `scale` is the size of the plane, i.e. the side length of the square that is created
48+
function addClippingPlaneToScene(origin, normal, scale) {
49+
vtkMath.normalize(normal);
50+
51+
const dir1 = [];
52+
const dir2 = [];
53+
vtkMath.perpendiculars(normal, dir1, dir2, 0);
54+
55+
const corner = [];
56+
vtkMath.multiplyAccumulate(origin, dir1, -0.5 * scale, corner);
57+
vtkMath.multiplyAccumulate(corner, dir2, -0.5 * scale, corner);
58+
59+
const point1 = [];
60+
const point2 = [];
61+
vtkMath.multiplyAccumulate(corner, dir1, scale, point1);
62+
vtkMath.multiplyAccumulate(corner, dir2, scale, point2);
63+
64+
const planeSource = gc.registerResource(
65+
vtkPlaneSource.newInstance({
66+
xResolution: 1,
67+
yResolution: 1,
68+
origin: corner,
69+
point1,
70+
point2,
71+
})
72+
);
73+
74+
const clipPlane = gc.registerResource(
75+
vtkPlane.newInstance({
76+
normal,
77+
origin,
78+
})
79+
);
80+
81+
const planeMapper = gc.registerResource(vtkMapper.newInstance());
82+
planeMapper.setInputConnection(planeSource.getOutputPort());
83+
const planeActor = gc.registerResource(vtkActor.newInstance());
84+
planeActor.setMapper(planeMapper);
85+
planeActor.getProperty().setOpacity(0.2);
86+
renderer.addActor(planeActor);
87+
88+
sphereMapper.addClippingPlane(clipPlane);
89+
}
90+
91+
renderer.addActor(sphereActor);
92+
93+
const numPlanes = 8;
94+
95+
const theta = (2 * Math.PI) / numPlanes;
96+
const rotationMatrix = [
97+
[Math.cos(theta), Math.sin(theta), 0],
98+
[-Math.sin(theta), Math.cos(theta), 0],
99+
[0, 0, 1],
100+
];
101+
const normal = [1, 0, 0];
102+
const origin = [0, 0, 0];
103+
104+
const glwindow = gc.registerResource(vtkOpenGLRenderWindow.newInstance());
105+
glwindow.setContainer(renderWindowContainer);
106+
renderWindow.addView(glwindow);
107+
glwindow.setSize(400, 400);
108+
109+
// Render once without any clipping planes present
110+
// Hopefully when we render again below, the shader will be
111+
// rebuilt because we added clipping planes.
112+
renderer.resetCamera();
113+
renderWindow.render();
114+
115+
for (let i = 0; i < 7; ++i) {
116+
vtkMath.multiplyAccumulate([0, 0, 0], normal, -0.8, origin);
117+
addClippingPlaneToScene(
118+
origin, // origin
119+
normal, // normal
120+
3 // scale
121+
);
122+
vtkMath.multiply3x3_vect3(rotationMatrix, normal, normal);
123+
}
124+
125+
glwindow.captureNextImage().then((image) => {
126+
testUtils.compareImages(image, [baseline], 'TestMoreClippingPlanes', t, 2);
127+
});
128+
renderWindow.render();
129+
});
23.5 KB
Loading

Sources/Rendering/OpenGL/ShaderProgram/index.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -362,16 +362,6 @@ function vtkShaderProgram(publicAPI, model) {
362362
return true;
363363
};
364364

365-
publicAPI.setUniform4fv = (name, count, v) => {
366-
const location = publicAPI.findUniform(name);
367-
if (location === -1) {
368-
model.error = `Could not set uniform ${name} . No such uniform.`;
369-
return false;
370-
}
371-
model.context.uniform4fv(location, v);
372-
return true;
373-
};
374-
375365
publicAPI.findUniform = (name) => {
376366
if (!name || !model.linked) {
377367
return -1;

0 commit comments

Comments
 (0)