Skip to content

Commit d1b08c6

Browse files
feat(sliceHelper): add helper for slicing imageMapper with clip planes
To deprecate vtkInteractorStyleMPRSlice I add a helper using two clip planes instead. The included example is similar to vtkInteractorStyleMPRSlice example. re #1872
1 parent 6e82cca commit d1b08c6

File tree

5 files changed

+161
-1
lines changed

5 files changed

+161
-1
lines changed

Examples/Volume/MPRSlice/index.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'vtk.js/Sources/favicon';
2+
/* eslint-disable */
3+
4+
import 'vtk.js/Sources/Rendering/Profiles/Volume';
5+
6+
// Force DataAccessHelper to have access to various data source
7+
import 'vtk.js/Sources/IO/Core/DataAccessHelper/HtmlDataAccessHelper';
8+
import 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper';
9+
import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper';
10+
11+
import vtkHttpDataSetReader from 'vtk.js/Sources/IO/Core/HttpDataSetReader';
12+
import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
13+
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
14+
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
15+
16+
// ----------------------------------------------------------------------------
17+
// Standard rendering code setup
18+
// ----------------------------------------------------------------------------
19+
20+
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
21+
background: [0, 0, 0],
22+
});
23+
const renderer = fullScreenRenderer.getRenderer();
24+
const renderWindow = fullScreenRenderer.getRenderWindow();
25+
26+
global.fullScreen = fullScreenRenderer;
27+
global.renderWindow = renderWindow;
28+
29+
// ----------------------------------------------------------------------------
30+
// Example code
31+
// ----------------------------------------------------------------------------
32+
33+
const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
34+
const actor = vtkVolume.newInstance();
35+
const mapper = vtkVolumeMapper.newInstance();
36+
mapper.setSampleDistance(1.1);
37+
actor.setMapper(mapper);
38+
mapper.setInputConnection(reader.getOutputPort());
39+
40+
const mprSlice = vtkVolumeMapper.vtkSliceHelper.newInstance({ thickness: 3 });
41+
mprSlice.registerClipPlanesToMapper(mapper);
42+
43+
reader.setUrl(`${__BASE_PATH__}/data/volume/headsq.vti`).then(() => {
44+
reader.loadData().then(() => {
45+
const data = reader.getOutputData();
46+
renderer.addVolume(actor);
47+
const [xMin, xMax, yMin, yMax, zMin, zMax] = data.getBounds();
48+
mprSlice.setOrigin((xMin + xMax) / 2, (yMin + yMax) / 2, (zMin + zMax) / 2);
49+
50+
renderer.resetCamera();
51+
renderWindow.render();
52+
});
53+
});
54+
55+
// Set MPR slice to follow camera orientation
56+
const camera = renderer.getActiveCamera();
57+
camera.onModified(() => {
58+
const direction = camera.getDirectionOfProjection();
59+
mprSlice.setNormal(direction);
60+
});

Sources/Rendering/Core/AbstractMapper/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ function vtkAbstractMapper(publicAPI, model) {
1313
if (plane.getClassName() !== 'vtkPlane') {
1414
return;
1515
}
16+
for (let i = 0; i < model.clippingPlanes.length; i++) {
17+
if (model.clippingPlanes[i] === plane) return;
18+
}
1619
model.clippingPlanes.push(plane);
1720
};
1821

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import macro from 'vtk.js/Sources/macros';
2+
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
3+
import vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane';
4+
5+
function vtkSliceHelper(publicAPI, model) {
6+
model.classHierarchy.push('vtkSliceHelper');
7+
8+
model._clipPlane1 = vtkPlane.newInstance();
9+
model._clipPlane2 = vtkPlane.newInstance();
10+
11+
const superClass = { ...publicAPI };
12+
13+
function update() {
14+
const n1 = model._clipPlane1.getNormalByReference();
15+
n1[0] = model.normal[0];
16+
n1[1] = model.normal[1];
17+
n1[2] = model.normal[2];
18+
19+
const n2 = model._clipPlane2.getNormalByReference();
20+
n2[0] = -model.normal[0];
21+
n2[1] = -model.normal[1];
22+
n2[2] = -model.normal[2];
23+
24+
vtkMath.multiplyAccumulate(
25+
model.origin,
26+
model.normal,
27+
-model.thickness / 2,
28+
model._clipPlane1.getOriginByReference()
29+
);
30+
31+
vtkMath.multiplyAccumulate(
32+
model.origin,
33+
model.normal,
34+
model.thickness / 2,
35+
model._clipPlane2.getOriginByReference()
36+
);
37+
38+
model._clipPlane1.modified();
39+
model._clipPlane2.modified();
40+
}
41+
42+
const subscription = publicAPI.onModified(update);
43+
publicAPI.delete = () => {
44+
superClass.delete();
45+
subscription.unsubscribe();
46+
};
47+
48+
publicAPI.registerClipPlanesToMapper = (mapper) => {
49+
if (!mapper.isA('vtkVolumeMapper')) {
50+
return;
51+
}
52+
model._clipPlane1Index = mapper.getNumberOfClippingPlanes;
53+
mapper.addClippingPlane(model._clipPlane1);
54+
model._clipPlane2Index = mapper.getNumberOfClippingPlanes;
55+
mapper.addClippingPlane(model._clipPlane2);
56+
};
57+
58+
publicAPI.unregisterClipPlanesFromMapper = (mapper) => {
59+
if (!mapper.isA('vtkVolumeMapper')) {
60+
return;
61+
}
62+
mapper.removeClippingPlane(model._clipPlane1Index);
63+
mapper.removeClippingPlane(model._clipPlane2Index);
64+
};
65+
}
66+
67+
// ----------------------------------------------------------------------------
68+
// Object factory
69+
// ----------------------------------------------------------------------------
70+
71+
const DEFAULT_VALUES = {
72+
thickness: 0,
73+
origin: [0, 0, 0],
74+
normal: [1, 0, 0],
75+
};
76+
77+
export function extend(publicAPI, model, initialValues = {}) {
78+
Object.assign(model, DEFAULT_VALUES, initialValues);
79+
80+
// Build VTK API
81+
macro.obj(publicAPI, model);
82+
macro.setGet(publicAPI, model, ['thickness']);
83+
macro.setGetArray(publicAPI, model, ['origin', 'normal'], 3);
84+
85+
// Object methods
86+
vtkSliceHelper(publicAPI, model);
87+
}
88+
89+
// ----------------------------------------------------------------------------
90+
91+
export const newInstance = macro.newInstance(extend, 'vtkSliceHelper');
92+
93+
// ----------------------------------------------------------------------------
94+
95+
export default { newInstance, extend };

Sources/Rendering/Core/VolumeMapper/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import macro from 'vtk.js/Sources/macros';
22
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
33
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants';
44
import vtkAbstractMapper from 'vtk.js/Sources/Rendering/Core/AbstractMapper';
5+
import vtkSliceHelper from 'vtk.js/Sources/Rendering/Core/VolumeMapper/SliceHelper';
56

67
const { BlendMode, FilterMode } = Constants;
78

@@ -126,4 +127,4 @@ export const newInstance = macro.newInstance(extend, 'vtkVolumeMapper');
126127

127128
// ----------------------------------------------------------------------------
128129

129-
export default { newInstance, extend };
130+
export default { newInstance, extend, vtkSliceHelper };

Sources/Rendering/Core/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export default {
7979
vtkRenderWindowInteractor,
8080
vtkScalarBarActor,
8181
vtkSkybox,
82+
vtkSliceHelper: vtkVolumeMapper.vtkSliceHelper,
8283
vtkSphereMapper,
8384
vtkStickMapper,
8485
vtkTexture,

0 commit comments

Comments
 (0)