Skip to content

Commit bd2cbde

Browse files
committed
feat(WebGPU): add clipping plane support for polydata, volume and image
Implement WebGPU clipping planes in CellArrayMapper, VolumePassFSQ and ImageMapper.
1 parent ee124ce commit bd2cbde

File tree

6 files changed

+343
-53
lines changed

6 files changed

+343
-53
lines changed

Sources/Rendering/Core/AbstractMapper/index.d.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { mat4 } from 'gl-matrix';
12
import { vtkAlgorithm, vtkObject } from '../../../interfaces';
23
import vtkPlane from '../../../Common/DataModel/Plane';
3-
import { mat4 } from 'gl-matrix';
4+
import { Vector4 } from '../../../types';
45

56
/**
67
*
@@ -31,6 +32,12 @@ export interface vtkAbstractMapper extends vtkAbstractMapperBase {
3132
*/
3233
getClippingPlanes(): vtkPlane[];
3334

35+
/**
36+
* Get the modified time of the clipping planes list.
37+
* @return {Number} The modified time.
38+
*/
39+
getClippingPlanesMTime(): number;
40+
3441
/**
3542
* Remove all clipping planes.
3643
* @return true if there were planes, false otherwise.
@@ -50,9 +57,25 @@ export interface vtkAbstractMapper extends vtkAbstractMapperBase {
5057
*/
5158
setClippingPlanes(planes: vtkPlane[]): void;
5259

60+
/**
61+
* Get the ith clipping plane transformed from world coordinates into the
62+
* target coordinate system defined by the provided world-to-coordinates
63+
* matrix.
64+
* @param {mat4} worldToCoords
65+
* @param {Number} i
66+
* @param {Number[]} [hnormal]
67+
*/
68+
getClippingPlaneInCoords(
69+
worldToCoords: mat4,
70+
i: number,
71+
hnormal?: Vector4 | Float64Array
72+
): Vector4 | Float64Array | undefined;
73+
5374
/**
5475
* Get the ith clipping plane as a homogeneous plane equation.
5576
* Use getNumberOfClippingPlanes() to get the number of planes.
77+
* This API expects a coordinates-to-world matrix and preserves the legacy
78+
* behavior used by existing data-coordinate callers.
5679
* @param {mat4} propMatrix
5780
* @param {Number} i
5881
* @param {Number[]} hnormal

Sources/Rendering/Core/AbstractMapper/index.js

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
1+
import { mat4, vec4 } from 'gl-matrix';
12
import macro from 'vtk.js/Sources/macros';
23

4+
const { vtkErrorMacro } = macro;
5+
36
// ----------------------------------------------------------------------------
47
// vtkAbstractMapper methods
58
// ----------------------------------------------------------------------------
69

10+
const tmpClipMatrix = new Float64Array(16);
11+
const tmpClipWorldPlane = new Float64Array(4);
12+
13+
function getClipPlaneEquation(plane, out) {
14+
const normal = plane.getNormalByReference();
15+
const origin = plane.getOriginByReference();
16+
17+
out[0] = normal[0];
18+
out[1] = normal[1];
19+
out[2] = normal[2];
20+
out[3] = -(
21+
normal[0] * origin[0] +
22+
normal[1] * origin[1] +
23+
normal[2] * origin[2]
24+
);
25+
}
26+
727
function vtkAbstractMapper(publicAPI, model) {
828
model.classHierarchy.push('vtkAbstractMapper');
929
publicAPI.update = () => {
@@ -45,6 +65,14 @@ function vtkAbstractMapper(publicAPI, model) {
4565

4666
publicAPI.getClippingPlanes = () => model.clippingPlanes;
4767

68+
publicAPI.getClippingPlanesMTime = () => {
69+
let mtime = 0;
70+
for (let i = 0; i < model.clippingPlanes.length; i++) {
71+
mtime = Math.max(mtime, model.clippingPlanes[i].getMTime());
72+
}
73+
return mtime;
74+
};
75+
4876
publicAPI.setClippingPlanes = (planes) => {
4977
if (!planes) {
5078
return;
@@ -59,34 +87,32 @@ function vtkAbstractMapper(publicAPI, model) {
5987
}
6088
};
6189

62-
publicAPI.getClippingPlaneInDataCoords = (propMatrix, i, hnormal) => {
90+
publicAPI.getClippingPlaneInCoords = (worldToCoords, i, hnormal) => {
91+
if (i < 0 || i >= model.clippingPlanes?.length) {
92+
vtkErrorMacro(`Clipping plane index ${i} is out of range.`);
93+
return undefined;
94+
}
95+
const outHNormal = hnormal || new Float64Array(4);
96+
getClipPlaneEquation(model.clippingPlanes[i], tmpClipWorldPlane);
97+
mat4.invert(tmpClipMatrix, worldToCoords);
98+
mat4.transpose(tmpClipMatrix, tmpClipMatrix);
99+
vec4.transformMat4(outHNormal, tmpClipWorldPlane, tmpClipMatrix);
100+
return outHNormal;
101+
};
102+
103+
publicAPI.getClippingPlaneInDataCoords = (coordsToWorld, i, hnormal) => {
63104
const clipPlanes = model.clippingPlanes;
64-
const mat = propMatrix;
65105

66106
if (clipPlanes) {
67107
const n = clipPlanes.length;
68108
if (i >= 0 && i < n) {
69-
// Get the plane
70-
const plane = clipPlanes[i];
71-
const normal = plane.getNormal();
72-
const origin = plane.getOrigin();
73-
74-
// Compute the plane equation
75-
const v1 = normal[0];
76-
const v2 = normal[1];
77-
const v3 = normal[2];
78-
const v4 = -(v1 * origin[0] + v2 * origin[1] + v3 * origin[2]);
79-
80-
// Transform normal from world to data coords
81-
hnormal[0] = v1 * mat[0] + v2 * mat[4] + v3 * mat[8] + v4 * mat[12];
82-
hnormal[1] = v1 * mat[1] + v2 * mat[5] + v3 * mat[9] + v4 * mat[13];
83-
hnormal[2] = v1 * mat[2] + v2 * mat[6] + v3 * mat[10] + v4 * mat[14];
84-
hnormal[3] = v1 * mat[3] + v2 * mat[7] + v3 * mat[11] + v4 * mat[15];
109+
getClipPlaneEquation(clipPlanes[i], tmpClipWorldPlane);
110+
vec4.transformMat4(hnormal, tmpClipWorldPlane, coordsToWorld);
85111

86112
return;
87113
}
88114
}
89-
macro.vtkErrorMacro(`Clipping plane index ${i} is out of range.`);
115+
vtkErrorMacro(`Clipping plane index ${i} is out of range.`);
90116
};
91117
}
92118

Sources/Rendering/WebGPU/CellArrayMapper/index.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mat3, mat4 } from 'gl-matrix';
1+
import { mat3 } from 'gl-matrix';
22

33
import * as macro from 'vtk.js/Sources/macros';
44
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
@@ -11,13 +11,18 @@ import vtkWebGPUShaderCache from 'vtk.js/Sources/Rendering/WebGPU/ShaderCache';
1111
import vtkWebGPUUniformBuffer from 'vtk.js/Sources/Rendering/WebGPU/UniformBuffer';
1212
import vtkWebGPUSimpleMapper from 'vtk.js/Sources/Rendering/WebGPU/SimpleMapper';
1313
import vtkWebGPUTypes from 'vtk.js/Sources/Rendering/WebGPU/Types';
14+
import {
15+
addClipPlaneEntries,
16+
getClippingPlaneEquationsInCoords,
17+
getClipPlaneShaderChecks,
18+
MAX_CLIPPING_PLANES,
19+
} from 'vtk.js/Sources/Rendering/WebGPU/Helpers/ClippingPlanes';
1420

1521
const { BufferUsage, PrimitiveTypes } = vtkWebGPUBufferManager;
1622
const { Representation } = vtkProperty;
1723
const { ScalarMode } = vtkMapper;
1824
const { CoordinateSystem } = vtkProp;
1925
const { DisplayLocation } = vtkProperty2D;
20-
2126
const vtkWebGPUPolyDataVS = `
2227
//VTK::Renderer::Dec
2328
@@ -409,11 +414,15 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
409414
publicAPI.updateUBO = () => {
410415
const actor = model.WebGPUActor.getRenderable();
411416
const ppty = actor.getProperty();
417+
const clippingPlanesMTime = model.renderable.getClippingPlanesMTime();
418+
const activeCamera = model.WebGPURenderer.getRenderable().getActiveCamera();
412419
const utime = model.UBO.getSendTime();
413420
if (
414421
publicAPI.getMTime() <= utime &&
415422
ppty.getMTime() <= utime &&
416-
model.renderable.getMTime() <= utime
423+
model.renderable.getMTime() <= utime &&
424+
clippingPlanesMTime <= utime &&
425+
activeCamera.getMTime() <= utime
417426
) {
418427
return;
419428
}
@@ -474,6 +483,24 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
474483
model.UBO.setValue('LineWidth', ppty.getLineWidth());
475484
model.UBO.setValue('Opacity', ppty.getOpacity());
476485
model.UBO.setValue('PropID', model.WebGPUActor.getPropID());
486+
model.UBO.setValue('NumClipPlanes', 0);
487+
488+
if (!model.is2D && model.useRendererMatrix) {
489+
const webGPUCamera = model.WebGPURenderer.getViewNodeFor(activeCamera);
490+
const cameraKeyMats = webGPUCamera.getKeyMatrices(model.WebGPURenderer);
491+
const numClipPlanes = getClippingPlaneEquationsInCoords(
492+
model.renderable,
493+
cameraKeyMats.wcvc,
494+
model.clipPlanes
495+
);
496+
model.UBO.setValue('NumClipPlanes', numClipPlanes);
497+
498+
if (numClipPlanes > 0) {
499+
for (let i = 0; i < numClipPlanes; i++) {
500+
model.UBO.setArray(`ClipPlane${i}`, model.clipPlanes[i]);
501+
}
502+
}
503+
}
477504

478505
// Only send if needed
479506
model.UBO.sendIfNeeded(model.WebGPURenderWindow.getDevice());
@@ -550,6 +577,19 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
550577
]).result;
551578

552579
vDesc.setCode(code);
580+
581+
const fDesc = pipeline.getShaderDescription('fragment');
582+
code = fDesc.getCode();
583+
const clipPlaneChecks = getClipPlaneShaderChecks({
584+
countName: 'mapperUBO.NumClipPlanes',
585+
planePrefix: 'mapperUBO.ClipPlane',
586+
positionName: 'input.vertexVC',
587+
});
588+
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [
589+
...clipPlaneChecks,
590+
'//VTK::Position::Impl',
591+
]).result;
592+
fDesc.setCode(code);
553593
};
554594
model.shaderReplacements.set(
555595
'replaceShaderPosition',
@@ -1129,7 +1169,8 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
11291169
// --- Texture Coordinates ---
11301170
let tcoords = null;
11311171
if (
1132-
model.renderable.getInterpolateScalarsBeforeMapping?.() &&
1172+
(model.renderable.getAreScalarsMappedFromCells() ||
1173+
model.renderable.getInterpolateScalarsBeforeMapping?.()) &&
11331174
model.renderable.getColorCoordinates()
11341175
) {
11351176
tcoords = model.renderable.getColorCoordinates();
@@ -1363,7 +1404,6 @@ export function extend(publicAPI, model, initiaLalues = {}) {
13631404
model.vertexShaderTemplate = vtkWebGPUPolyDataVS;
13641405

13651406
model._tmpMat3 = mat3.identity(new Float64Array(9));
1366-
model._tmpMat4 = mat4.identity(new Float64Array(16));
13671407

13681408
// UBO
13691409
model.UBO = vtkWebGPUUniformBuffer.newInstance({ label: 'mapperUBO' });
@@ -1391,6 +1431,8 @@ export function extend(publicAPI, model, initiaLalues = {}) {
13911431
model.UBO.addEntry('ClipNear', 'f32');
13921432
model.UBO.addEntry('ClipFar', 'f32');
13931433
model.UBO.addEntry('Time', 'u32');
1434+
addClipPlaneEntries(model.UBO, 'ClipPlane');
1435+
model.UBO.addEntry('NumClipPlanes', 'u32');
13941436

13951437
// Build VTK API
13961438
macro.setGet(publicAPI, model, [
@@ -1403,6 +1445,9 @@ export function extend(publicAPI, model, initiaLalues = {}) {
14031445
]);
14041446

14051447
model.textures = [];
1448+
model.clipPlanes = Array.from({ length: MAX_CLIPPING_PLANES }, () => [
1449+
0.0, 0.0, 0.0, 0.0,
1450+
]);
14061451

14071452
// Object methods
14081453
vtkWebGPUCellArrayMapper(publicAPI, model);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export const MAX_CLIPPING_PLANES = 6;
2+
3+
export function addClipPlaneEntries(buffer, prefix) {
4+
for (let i = 0; i < MAX_CLIPPING_PLANES; i++) {
5+
buffer.addEntry(`${prefix}${i}`, 'vec4<f32>');
6+
}
7+
}
8+
9+
export function getClipPlaneShaderChecks({
10+
countName,
11+
planePrefix,
12+
positionName,
13+
returnValue = 'discard',
14+
}) {
15+
const checks = [];
16+
for (let i = 0; i < MAX_CLIPPING_PLANES; i++) {
17+
checks.push(
18+
` if (${countName} > ${i}u && dot(${planePrefix}${i}, ${positionName}) < 0.0) { ${returnValue}; }`
19+
);
20+
}
21+
return checks;
22+
}
23+
24+
export function getClippingPlaneEquationsInCoords(
25+
mapper,
26+
worldToCoords,
27+
outPlanes
28+
) {
29+
const count = mapper.getClippingPlanes().length;
30+
for (let i = 0; i < count; i++) {
31+
mapper.getClippingPlaneInCoords(worldToCoords, i, outPlanes[i]);
32+
}
33+
return count;
34+
}

0 commit comments

Comments
 (0)