Skip to content

Commit ac619e4

Browse files
committed
feat(SegmentedLineRepresentation): copy from PolyLineRepresentation
1 parent 7f404e9 commit ac619e4

File tree

2 files changed

+262
-0
lines changed
  • Sources/Widgets/Representations/SegmentedLineRepresentation

2 files changed

+262
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import '@kitware/vtk.js/favicon';
2+
3+
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
4+
import '@kitware/vtk.js/Rendering/Profiles/All';
5+
6+
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
7+
import vtkStateBuilder from '@kitware/vtk.js/Widgets/Core/StateBuilder';
8+
9+
import vtkSegmentedLineRepresentation from '@kitware/vtk.js/Widgets/Representations/SegmentedLineRepresentation';
10+
11+
// ----------------------------------------------------------------------------
12+
// Standard rendering code setup
13+
// ----------------------------------------------------------------------------
14+
15+
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
16+
background: [0, 0, 0],
17+
});
18+
const renderer = fullScreenRenderer.getRenderer();
19+
const renderWindow = fullScreenRenderer.getRenderWindow();
20+
21+
// -----------------------------------------------------------
22+
// State
23+
// -----------------------------------------------------------
24+
25+
const compositeState = vtkStateBuilder
26+
.createBuilder()
27+
.addDynamicMixinState({
28+
labels: ['handles'],
29+
mixins: ['origin'],
30+
name: 'handle',
31+
})
32+
.build();
33+
34+
const z = -50;
35+
const points = [
36+
[0, 0, z],
37+
[5, 0, z],
38+
[5, 10, z],
39+
];
40+
points.forEach((point) => {
41+
const handle = compositeState.addHandle();
42+
handle.setOrigin(point);
43+
});
44+
45+
// -----------------------------------------------------------
46+
// Representation
47+
// -----------------------------------------------------------
48+
49+
const widgetRep = vtkSegmentedLineRepresentation.newInstance({
50+
scaleInPixels: false,
51+
});
52+
widgetRep.setInputData(compositeState);
53+
widgetRep.setLabels(['handles']);
54+
widgetRep.getActors().forEach(renderer.addActor);
55+
56+
renderWindow.render();
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import macro from 'vtk.js/Sources/macros';
2+
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
3+
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
4+
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
5+
import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
6+
import vtkTubeFilter from 'vtk.js/Sources/Filters/General/TubeFilter';
7+
import { getPixelWorldHeightAtCoord } from 'vtk.js/Sources/Widgets/Core/WidgetManager';
8+
import vtkWidgetRepresentation, {
9+
allocateArray,
10+
} from 'vtk.js/Sources/Widgets/Representations/WidgetRepresentation';
11+
import { RenderingTypes } from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants';
12+
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
13+
14+
// ----------------------------------------------------------------------------
15+
// vtkPolyLineRepresentation methods
16+
// ----------------------------------------------------------------------------
17+
18+
function vtkPolyLineRepresentation(publicAPI, model) {
19+
// Set our className
20+
model.classHierarchy.push('vtkPolyLineRepresentation');
21+
const superClass = { ...publicAPI };
22+
23+
// --------------------------------------------------------------------------
24+
// Internal polydata dataset
25+
// --------------------------------------------------------------------------
26+
const internalPolyData = vtkPolyData.newInstance({ mtime: 0 });
27+
28+
function allocateSize(polyData, size, closePolyLine = false) {
29+
let points = null;
30+
if (size < 2) {
31+
// FIXME: Why 1 point and not 0 ?
32+
points = allocateArray(polyData, 'points', 1).getData();
33+
points.set([0, 0, 0]);
34+
allocateArray(polyData, 'lines', 0).getData();
35+
} else if (
36+
!polyData.getPoints() ||
37+
polyData.getPoints().length !== size * 3
38+
) {
39+
points = allocateArray(polyData, 'points', size).getData();
40+
const cellSize = size + (closePolyLine ? 1 : 0);
41+
if (
42+
polyData.getLines().getNumberOfCells() !== 1 ||
43+
polyData.getLines().getCellSizes()[0] !== cellSize
44+
) {
45+
const lines = allocateArray(polyData, 'lines', cellSize + 1); // +1 for the number of points
46+
const cellData = lines.getData();
47+
cellData[0] = cellSize;
48+
for (let i = 1; i <= cellSize; i++) {
49+
cellData[i] = i - 1;
50+
}
51+
if (closePolyLine) {
52+
cellData[cellSize] = 0;
53+
}
54+
lines.setData(cellData);
55+
}
56+
}
57+
return points;
58+
}
59+
60+
/**
61+
* Change the line/tube thickness.
62+
* @param {number} lineThickness
63+
*/
64+
function applyLineThickness(lineThickness) {
65+
let scaledLineThickness = lineThickness;
66+
if (publicAPI.getScaleInPixels() && internalPolyData) {
67+
const center = vtkBoundingBox.getCenter(internalPolyData.getBounds());
68+
scaledLineThickness *= getPixelWorldHeightAtCoord(
69+
center,
70+
model.displayScaleParams
71+
);
72+
}
73+
model._pipelines.tubes.filter.setRadius(scaledLineThickness);
74+
}
75+
76+
// --------------------------------------------------------------------------
77+
// Generic rendering pipeline
78+
// --------------------------------------------------------------------------
79+
80+
model._pipelines = {
81+
tubes: {
82+
source: publicAPI,
83+
filter: vtkTubeFilter.newInstance({
84+
radius: model.lineThickness,
85+
numberOfSides: 12,
86+
capping: false,
87+
}),
88+
mapper: vtkMapper.newInstance(),
89+
actor: vtkActor.newInstance({ parentProp: publicAPI }),
90+
},
91+
};
92+
93+
vtkWidgetRepresentation.connectPipeline(model._pipelines.tubes);
94+
publicAPI.addActor(model._pipelines.tubes.actor);
95+
96+
// --------------------------------------------------------------------------
97+
publicAPI.requestData = (inData, outData) => {
98+
const state = inData[0];
99+
outData[0] = internalPolyData;
100+
101+
// Remove invalid and coincident points for tube filter.
102+
const list = publicAPI
103+
.getRepresentationStates(state)
104+
.reduce((subStates, subState) => {
105+
const subStateOrigin =
106+
subState.getOrigin && subState.getOrigin()
107+
? subState.getOrigin()
108+
: null;
109+
const previousSubStateOrigin =
110+
subStates.length && subStates[subStates.length - 1].getOrigin();
111+
if (
112+
!subStateOrigin ||
113+
(previousSubStateOrigin &&
114+
vtkMath.areEquals(subStateOrigin, previousSubStateOrigin))
115+
) {
116+
return subStates;
117+
}
118+
subStates.push(subState);
119+
return subStates;
120+
}, []);
121+
const size = list.length;
122+
123+
const points = allocateSize(
124+
outData[0],
125+
size,
126+
model.closePolyLine && size > 2
127+
);
128+
129+
if (points) {
130+
for (let i = 0; i < size; i++) {
131+
const coords = list[i].getOrigin();
132+
points[i * 3] = coords[0];
133+
points[i * 3 + 1] = coords[1];
134+
points[i * 3 + 2] = coords[2];
135+
}
136+
}
137+
138+
outData[0].getPoints().modified();
139+
outData[0].modified();
140+
141+
const lineThickness = state.getLineThickness?.() ?? model.lineThickness;
142+
applyLineThickness(lineThickness);
143+
};
144+
145+
/**
146+
* When mousing over the line, if behavior != CONTEXT,
147+
* returns the parent state.
148+
* @param {object} prop
149+
* @param {number} compositeID
150+
* @returns {object}
151+
*/
152+
publicAPI.getSelectedState = (prop, compositeID) => model.inputData[0];
153+
154+
publicAPI.updateActorVisibility = (renderingType, ctxVisible, hVisible) => {
155+
const state = model.inputData[0];
156+
157+
// Make lines/tubes thicker for picking
158+
let lineThickness = state.getLineThickness?.() ?? model.lineThickness;
159+
if (renderingType === RenderingTypes.PICKING_BUFFER) {
160+
lineThickness = Math.max(4, lineThickness);
161+
}
162+
applyLineThickness(lineThickness);
163+
164+
return superClass.updateActorVisibility(
165+
renderingType,
166+
ctxVisible,
167+
hVisible
168+
);
169+
};
170+
}
171+
172+
// ----------------------------------------------------------------------------
173+
// Object factory
174+
// ----------------------------------------------------------------------------
175+
176+
const DEFAULT_VALUES = {
177+
threshold: Number.EPSILON,
178+
closePolyLine: false,
179+
lineThickness: 2,
180+
scaleInPixels: true,
181+
};
182+
183+
// ----------------------------------------------------------------------------
184+
185+
export function extend(publicAPI, model, initialValues = {}) {
186+
const newDefault = { ...DEFAULT_VALUES, ...initialValues };
187+
vtkWidgetRepresentation.extend(publicAPI, model, newDefault);
188+
macro.setGet(publicAPI, model, [
189+
'threshold',
190+
'closePolyLine',
191+
'lineThickness',
192+
]);
193+
194+
vtkPolyLineRepresentation(publicAPI, model);
195+
}
196+
197+
// ----------------------------------------------------------------------------
198+
199+
export const newInstance = macro.newInstance(
200+
extend,
201+
'vtkPolyLineRepresentation'
202+
);
203+
204+
// ----------------------------------------------------------------------------
205+
206+
export default { newInstance, extend };

0 commit comments

Comments
 (0)