Skip to content

Commit cdd5933

Browse files
committed
feat(SegmentedLineRepresentation): scale cylinders to connect points
1 parent 77e366d commit cdd5933

File tree

2 files changed

+79
-88
lines changed
  • Sources/Widgets/Representations/SegmentedLineRepresentation

2 files changed

+79
-88
lines changed

Sources/Widgets/Representations/SegmentedLineRepresentation/example/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ const compositeState = vtkStateBuilder
3434
const z = -50;
3535
const points = [
3636
[0, 0, z],
37-
[5, 5, z],
38-
[3, 10, z],
39-
[5, 15, z],
37+
[5, -5, z],
38+
[15, 5, z],
39+
[5, 10, z],
4040
];
4141
points.forEach((point) => {
4242
const handle = compositeState.addHandle();
@@ -49,10 +49,11 @@ points.forEach((point) => {
4949

5050
const widgetRep = vtkSegmentedLineRepresentation.newInstance({
5151
scaleInPixels: false,
52-
// closePolyLine: true,
52+
close: true,
5353
});
5454
widgetRep.setInputData(compositeState);
5555
widgetRep.setLabels(['handles']);
5656
widgetRep.getActors().forEach(renderer.addActor);
5757

58+
renderer.resetCamera();
5859
renderWindow.render();

Sources/Widgets/Representations/SegmentedLineRepresentation/index.js

Lines changed: 74 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,46 @@ import { RenderingTypes } from 'vtk.js/Sources/Widgets/Core/WidgetManager/Consta
99
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
1010
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
1111
import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper';
12-
import { OrientationModes } from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper/Constants';
12+
import {
13+
OrientationModes,
14+
ScaleModes,
15+
} from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper/Constants';
1316
import vtkCylinderSource from 'vtk.js/Sources/Filters/Sources/CylinderSource';
17+
import { vec3 } from 'gl-matrix';
1418

15-
// ----------------------------------------------------------------------------
16-
// vtkPolyLineRepresentation methods
17-
// ----------------------------------------------------------------------------
18-
19-
function vtkPolyLineRepresentation(publicAPI, model) {
20-
model.classHierarchy.push('vtkPolyLineRepresentation');
19+
function vtkSegmentedLineRepresentation(publicAPI, model) {
20+
model.classHierarchy.push('vtkSegmentedLineRepresentation');
2121
const superClass = { ...publicAPI };
2222

2323
const internalPolyData = vtkPolyData.newInstance({ mtime: 0 });
2424

25-
function allocateSize(polyData, size, closePolyLine = false) {
26-
if (!polyData.getPoints() || polyData.getPoints().length !== size * 3) {
27-
allocateArray(polyData, 'points', size).getData();
28-
}
29-
30-
const cellSize = size + (closePolyLine ? 1 : 0);
25+
function allocateSize(polyData, pointCount, close = false) {
26+
const glyphCount = pointCount + (close ? 0 : -1);
3127
if (
32-
polyData.getLines().getNumberOfCells() !== 1 ||
33-
polyData.getLines().getCellSizes()[0] !== cellSize
28+
!polyData.getPoints() ||
29+
polyData.getPoints().length !== glyphCount * 3
3430
) {
35-
const lines = allocateArray(polyData, 'lines', cellSize + 1).getData(); // +1 for the number of points
36-
lines[0] = cellSize;
31+
allocateArray(polyData, 'points', glyphCount).getData();
32+
}
33+
34+
const cellSize = glyphCount + 1;
35+
const oldSize = Array.from(polyData.getLines().getCellSizes())[0];
36+
if (oldSize !== cellSize) {
37+
const lines = allocateArray(polyData, 'lines', cellSize + 1); // +1 for to hold number of elements per cell
38+
const cellArray = lines.getData();
39+
cellArray[0] = cellSize;
3740
for (let i = 1; i <= cellSize; i++) {
38-
lines[i] = i - 1;
41+
cellArray[i] = i - 1;
3942
}
40-
if (closePolyLine) {
41-
lines[cellSize] = 0;
43+
if (close) {
44+
cellArray[cellSize] = 0;
4245
}
46+
lines.setData(cellArray);
4347
}
4448
}
4549

4650
/**
47-
* Change the segments thickness.
51+
* Change the segments' thickness.
4852
* @param {number} lineThickness
4953
*/
5054
function applyLineThickness(lineThickness) {
@@ -67,6 +71,8 @@ function vtkPolyLineRepresentation(publicAPI, model) {
6771
mapper: vtkGlyph3DMapper.newInstance({
6872
orientationArray: 'directions',
6973
orientationMode: OrientationModes.DIRECTION,
74+
scaleArray: 'lengths',
75+
scaleMode: ScaleModes.SCALE_BY_COMPONENTS,
7076
}),
7177
actor: vtkActor.newInstance({ parentProp: publicAPI }),
7278
};
@@ -79,62 +85,57 @@ function vtkPolyLineRepresentation(publicAPI, model) {
7985
const state = inData[0];
8086
outData[0] = internalPolyData;
8187

82-
// Remove invalid and coincident points.
83-
const list = publicAPI
84-
.getRepresentationStates(state)
85-
.reduce((subStates, subState) => {
86-
const subStateOrigin =
87-
subState.getOrigin && subState.getOrigin()
88-
? subState.getOrigin()
89-
: null;
90-
const previousSubStateOrigin =
91-
subStates.length && subStates[subStates.length - 1].getOrigin();
92-
if (
93-
!subStateOrigin ||
94-
(previousSubStateOrigin &&
95-
vtkMath.areEquals(subStateOrigin, previousSubStateOrigin))
96-
) {
97-
return subStates;
98-
}
99-
subStates.push(subState);
100-
return subStates;
101-
}, []);
102-
const size = list.length;
103-
104-
allocateSize(internalPolyData, size, model.closePolyLine && size > 2);
105-
106-
const points = internalPolyData.getPoints().getData();
107-
const lines = internalPolyData.getLines().getData();
88+
const originStates = publicAPI.getRepresentationStates(state);
89+
const points = originStates
90+
.map((subState) => subState.getOrigin())
91+
.filter(Boolean); // filter out states that return invalid origins
92+
const pointCount = points.length;
10893

109-
for (let i = 0; i < size; i++) {
110-
const coords = list[i].getOrigin();
111-
points[i * 3] = coords[0];
112-
points[i * 3 + 1] = coords[1];
113-
points[i * 3 + 2] = coords[2];
114-
}
94+
allocateSize(internalPolyData, pointCount, model.close && pointCount > 2);
95+
96+
const glyphPositions = internalPolyData.getPoints().getData();
97+
const lines = internalPolyData.getLines().getData();
11598

116-
// Orient glyphs to next point.
11799
const directions = allocateArray(
118100
internalPolyData,
119101
'directions',
120102
lines.length - 1,
121103
undefined,
122104
3
123105
).getData();
106+
const lengths = allocateArray(
107+
internalPolyData,
108+
'lengths',
109+
lines.length - 1,
110+
undefined,
111+
3
112+
).getData();
113+
114+
const pos = []; // scratch
124115
for (let point = 1; point < lines.length - 1; point++) {
125-
const eye = lines[point] * 3;
126-
const eyePoint = [points[eye], points[eye + 1], points[eye + 2]];
127-
const target = lines[point + 1] * 3;
128-
const targetPoint = [
129-
points[target],
130-
points[target + 1],
131-
points[target + 2],
132-
];
133-
const direction = vtkMath.subtract(targetPoint, eyePoint, []);
116+
// Orient glyphs to next point.
117+
const eye = points[lines[point]];
118+
const target = points[lines[point + 1]];
119+
const direction = vtkMath.subtract(target, eye, pos);
134120
const glyph = (point - 1) * 3;
135-
directions[glyph] = direction[0];
136-
directions[glyph + 1] = direction[1];
137-
directions[glyph + 2] = direction[2];
121+
[directions[glyph], directions[glyph + 1], directions[glyph + 2]] =
122+
direction;
123+
124+
// scale to span between points
125+
const distance = vec3.length(direction);
126+
lengths[glyph] = distance;
127+
lengths[glyph + 1] = 1;
128+
lengths[glyph + 2] = 1;
129+
130+
// Position glyph at center of line segment.
131+
vec3.normalize(pos, direction);
132+
vec3.scale(pos, pos, distance / 2);
133+
vec3.add(pos, eye, direction);
134+
[
135+
glyphPositions[glyph],
136+
glyphPositions[glyph + 1],
137+
glyphPositions[glyph + 2],
138+
] = pos;
138139
}
139140

140141
internalPolyData.getPoints().modified();
@@ -144,14 +145,8 @@ function vtkPolyLineRepresentation(publicAPI, model) {
144145
applyLineThickness(lineThickness);
145146
};
146147

147-
/**
148-
* When mousing over the line, if behavior != CONTEXT,
149-
* returns the parent state.
150-
* @param {object} prop
151-
* @param {number} compositeID
152-
* @returns {object}
153-
*/
154-
publicAPI.getSelectedState = (prop, compositeID) => model.inputData[0];
148+
// return array of all states
149+
publicAPI.getSelectedState = () => model.inputData[0];
155150

156151
publicAPI.updateActorVisibility = (renderingType, ctxVisible, hVisible) => {
157152
const state = model.inputData[0];
@@ -176,9 +171,8 @@ function vtkPolyLineRepresentation(publicAPI, model) {
176171
// ----------------------------------------------------------------------------
177172

178173
const DEFAULT_VALUES = {
179-
threshold: Number.EPSILON,
180-
closePolyLine: false,
181-
lineThickness: 2,
174+
close: false,
175+
lineThickness: 1,
182176
scaleInPixels: true,
183177
};
184178

@@ -187,20 +181,16 @@ const DEFAULT_VALUES = {
187181
export function extend(publicAPI, model, initialValues = {}) {
188182
const newDefault = { ...DEFAULT_VALUES, ...initialValues };
189183
vtkWidgetRepresentation.extend(publicAPI, model, newDefault);
190-
macro.setGet(publicAPI, model, [
191-
'threshold',
192-
'closePolyLine',
193-
'lineThickness',
194-
]);
184+
macro.setGet(publicAPI, model, ['close', 'lineThickness']);
195185

196-
vtkPolyLineRepresentation(publicAPI, model);
186+
vtkSegmentedLineRepresentation(publicAPI, model);
197187
}
198188

199189
// ----------------------------------------------------------------------------
200190

201191
export const newInstance = macro.newInstance(
202192
extend,
203-
'vtkPolyLineRepresentation'
193+
'vtkSegmentedLineRepresentation'
204194
);
205195

206196
// ----------------------------------------------------------------------------

0 commit comments

Comments
 (0)