Skip to content

Commit b182613

Browse files
authored
Merge pull request #2593 from bruyeret/fix/hardware-selector-edges
Fix hardware selector on edges
2 parents c6f2e12 + 978f0b5 commit b182613

File tree

3 files changed

+210
-61
lines changed

3 files changed

+210
-61
lines changed

Sources/Rendering/Core/HardwareSelector/example/index.js

Lines changed: 141 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'vtk.js/Sources/favicon';
55

66
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
77
import 'vtk.js/Sources/Rendering/Profiles/Geometry';
8+
import 'vtk.js/Sources/Rendering/OpenGL/Glyph3DMapper';
89

910
import { throttle } from 'vtk.js/Sources/macros';
1011
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
@@ -15,6 +16,10 @@ import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenR
1516
import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper';
1617
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
1718
import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource';
19+
import vtkCubeSource from 'vtk.js/Sources/Filters/Sources/CubeSource';
20+
import vtkPolydata from 'vtk.js/Sources/Common/DataModel/PolyData';
21+
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
22+
import { mat4 } from 'gl-matrix';
1823
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
1924
import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
2025
import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants';
@@ -30,17 +35,25 @@ const GREEN = [0.1, 0.8, 0.1];
3035
// Create DOM tooltip
3136
// ----------------------------------------------------------------------------
3237

33-
const tooltipElem = document.createElement('div');
34-
tooltipElem.style.position = 'absolute';
35-
tooltipElem.style.top = 0;
36-
tooltipElem.style.left = 0;
37-
tooltipElem.style.width = '150px';
38-
tooltipElem.style.padding = '10px';
39-
tooltipElem.style.zIndex = 1;
40-
tooltipElem.style.background = 'white';
41-
tooltipElem.style.textAlign = 'center';
42-
43-
document.querySelector('body').appendChild(tooltipElem);
38+
const tooltipsElem = document.createElement('div');
39+
tooltipsElem.style.position = 'absolute';
40+
tooltipsElem.style.top = 0;
41+
tooltipsElem.style.left = 0;
42+
tooltipsElem.style.padding = '10px';
43+
tooltipsElem.style.zIndex = 1;
44+
tooltipsElem.style.background = 'white';
45+
tooltipsElem.style.textAlign = 'center';
46+
47+
const positionTooltipElem = document.createElement('div');
48+
const fieldIdTooltipElem = document.createElement('div');
49+
const compositeIdTooltipElem = document.createElement('div');
50+
const propIdTooltipElem = document.createElement('div');
51+
tooltipsElem.appendChild(positionTooltipElem);
52+
tooltipsElem.appendChild(propIdTooltipElem);
53+
tooltipsElem.appendChild(fieldIdTooltipElem);
54+
tooltipsElem.appendChild(compositeIdTooltipElem);
55+
56+
document.querySelector('body').appendChild(tooltipsElem);
4457

4558
// ----------------------------------------------------------------------------
4659
// Create 4 objects
@@ -60,8 +73,23 @@ const sphereSource = vtkSphereSource.newInstance({
6073
const sphereMapper = vtkMapper.newInstance();
6174
const sphereActor = vtkActor.newInstance();
6275
sphereActor.setMapper(sphereMapper);
76+
sphereActor.getProperty().setEdgeVisibility(true);
6377
sphereMapper.setInputConnection(sphereSource.getOutputPort());
6478

79+
// Cube -------------------------------------------------
80+
81+
const cubeSource = vtkCubeSource.newInstance({
82+
xLength: 1,
83+
yLength: 1,
84+
zLength: 1,
85+
});
86+
87+
const cubeMapper = vtkMapper.newInstance();
88+
const cubeActor = vtkActor.newInstance({ position: [-1, 0, 0] });
89+
cubeActor.setMapper(cubeMapper);
90+
cubeActor.getProperty().setEdgeVisibility(true);
91+
cubeMapper.setInputConnection(cubeSource.getOutputPort());
92+
6593
// Sphere with point representation -----------------------
6694

6795
const spherePointsSource = vtkSphereSource.newInstance({
@@ -70,7 +98,7 @@ const spherePointsSource = vtkSphereSource.newInstance({
7098
radius: 0.6,
7199
});
72100
const spherePointsMapper = vtkMapper.newInstance();
73-
const spherePointsActor = vtkActor.newInstance();
101+
const spherePointsActor = vtkActor.newInstance({ position: [0, -1, 0] });
74102
spherePointsActor.setMapper(spherePointsMapper);
75103
spherePointsMapper.setInputConnection(spherePointsSource.getOutputPort());
76104

@@ -82,7 +110,7 @@ spherePointsActor.getProperty().setPointSize(20);
82110

83111
const coneSource = vtkConeSource.newInstance({ resolution: 20 });
84112
const coneMapper = vtkMapper.newInstance();
85-
const coneActor = vtkActor.newInstance();
113+
const coneActor = vtkActor.newInstance({ position: [1, 0, 0] });
86114
coneActor.setMapper(coneMapper);
87115
coneMapper.setInputConnection(coneSource.getOutputPort());
88116

@@ -100,7 +128,7 @@ const cylinderMapper = vtkGlyph3DMapper.newInstance({
100128
scaleMode: vtkGlyph3DMapper.ScaleModes.SCALE_BY_MAGNITUDE,
101129
scaleArray: 'scale',
102130
});
103-
const cylinderActor = vtkActor.newInstance();
131+
const cylinderActor = vtkActor.newInstance({ position: [0, 1, 0] });
104132
const cylinderGlyph = sphereSource.getOutputData();
105133
const cylinderPointSet = cylinderSource.getOutputData();
106134
cylinderActor.setMapper(cylinderMapper);
@@ -117,6 +145,23 @@ cylinderPointSet.getPointData().addArray(
117145
})
118146
);
119147

148+
// PolyLines -------------------------------------------------
149+
150+
const polyLinesMapper = vtkMapper.newInstance();
151+
const polyLinesData = vtkPolydata.newInstance();
152+
const squarePoints = [-1, 2, 0, 0, 2, 0, 0, 1, 0, -1, 1, 0];
153+
const trianglePoints = [1, 2, 0, 1, 1, 0, 2, 1.5, 0];
154+
polyLinesData
155+
.getPoints()
156+
.setData(Float32Array.from([...squarePoints, ...trianglePoints]), 3);
157+
polyLinesData
158+
.getLines()
159+
.setData(Uint16Array.from([5, 0, 1, 2, 3, 0, 4, 4, 5, 6, 4]));
160+
polyLinesMapper.setInputData(polyLinesData);
161+
162+
const polyLines = vtkActor.newInstance();
163+
polyLines.setMapper(polyLinesMapper);
164+
120165
// ----------------------------------------------------------------------------
121166
// Create Picking pointer
122167
// ----------------------------------------------------------------------------
@@ -142,10 +187,12 @@ const interactor = renderWindow.getInteractor();
142187
const apiSpecificRenderWindow = interactor.getView();
143188

144189
renderer.addActor(sphereActor);
190+
renderer.addActor(cubeActor);
145191
renderer.addActor(spherePointsActor);
146192
renderer.addActor(coneActor);
147193
renderer.addActor(cylinderActor);
148194
renderer.addActor(pointerActor);
195+
renderer.addActor(polyLines);
149196

150197
renderer.resetCamera();
151198
renderWindow.render();
@@ -156,6 +203,10 @@ renderWindow.render();
156203

157204
const hardwareSelector = apiSpecificRenderWindow.getSelector();
158205
hardwareSelector.setCaptureZValues(true);
206+
// TODO: bug in FIELD_ASSOCIATION_POINTS mode
207+
// hardwareSelector.setFieldAssociation(
208+
// FieldAssociations.FIELD_ASSOCIATION_POINTS
209+
// );
159210
hardwareSelector.setFieldAssociation(FieldAssociations.FIELD_ASSOCIATION_CELLS);
160211

161212
// ----------------------------------------------------------------------------
@@ -177,64 +228,119 @@ function eventToWindowXY(event) {
177228
let needGlyphCleanup = false;
178229
let lastProcessedActor = null;
179230

180-
const updateWorldPosition = (worldPosition) => {
231+
const updatePositionTooltip = (worldPosition) => {
232+
if (lastProcessedActor) {
233+
positionTooltipElem.innerHTML = `Position: ${worldPosition
234+
.map((v) => v.toFixed(3))
235+
.join(' , ')}`;
236+
} else {
237+
positionTooltipElem.innerHTML = '';
238+
}
239+
};
240+
241+
const updateAssociationTooltip = (type, id) => {
242+
if (type !== undefined && id !== undefined) {
243+
fieldIdTooltipElem.innerHTML = `${type} ID: ${id}`;
244+
} else {
245+
fieldIdTooltipElem.innerHTML = '';
246+
}
247+
};
248+
249+
const updateCompositeAndPropIdTooltip = (compositeID, propID) => {
250+
if (compositeID !== undefined) {
251+
compositeIdTooltipElem.innerHTML = `Composite ID: ${compositeID}`;
252+
} else {
253+
compositeIdTooltipElem.innerHTML = '';
254+
}
255+
if (propID !== undefined) {
256+
propIdTooltipElem.innerHTML = `Prop ID: ${propID}`;
257+
} else {
258+
propIdTooltipElem.innerHTML = '';
259+
}
260+
};
261+
262+
const updateCursor = (worldPosition) => {
181263
if (lastProcessedActor) {
182264
pointerActor.setVisibility(true);
183-
tooltipElem.innerHTML = worldPosition.map((v) => v.toFixed(3)).join(' , ');
184265
pointerActor.setPosition(worldPosition);
185266
} else {
186267
pointerActor.setVisibility(false);
187-
tooltipElem.innerHTML = '';
188268
}
189269
renderWindow.render();
270+
updatePositionTooltip(worldPosition);
190271
};
191272

192273
function processSelections(selections) {
274+
renderer.getActors().forEach((a) => a.getProperty().setColor(...WHITE));
193275
if (!selections || selections.length === 0) {
194-
renderer.getActors().forEach((a) => a.getProperty().setColor(...WHITE));
195-
pointerActor.setVisibility(false);
196-
renderWindow.render();
197276
lastProcessedActor = null;
277+
updateAssociationTooltip();
278+
updateCursor();
279+
updateCompositeAndPropIdTooltip();
198280
return;
199281
}
200282

201-
const { worldPosition, compositeID, prop, attributeID } =
202-
selections[0].getProperties();
203-
let cursorPosition = [...worldPosition];
283+
const {
284+
worldPosition: rayHitWorldPosition,
285+
compositeID,
286+
prop,
287+
propID,
288+
attributeID,
289+
} = selections[0].getProperties();
290+
291+
updateCompositeAndPropIdTooltip(compositeID, propID);
292+
293+
let closestCellPointWorldPosition = [...rayHitWorldPosition];
204294
if (attributeID || attributeID === 0) {
205295
const input = prop.getMapper().getInputData();
206296
if (!input.getCells()) {
207297
input.buildCells();
208298
}
299+
300+
// Get matrices to convert coordinates: (prop coordinates) <-> (world coordinates)
301+
const glTempMat = mat4.fromValues(...prop.getMatrix());
302+
mat4.transpose(glTempMat, glTempMat);
303+
const propToWorld = vtkMatrixBuilder.buildFromDegree().setMatrix(glTempMat);
304+
mat4.invert(glTempMat, glTempMat);
305+
const worldToProp = vtkMatrixBuilder.buildFromDegree().setMatrix(glTempMat);
306+
// Compute the position of the cursor in prop coordinates
307+
const propPosition = [...rayHitWorldPosition];
308+
worldToProp.apply(propPosition);
309+
209310
if (
210311
hardwareSelector.getFieldAssociation() ===
211312
FieldAssociations.FIELD_ASSOCIATION_POINTS
212313
) {
213-
cursorPosition = input.getPoints().getTuple(attributeID);
314+
// Selecting points
315+
closestCellPointWorldPosition = [
316+
...input.getPoints().getTuple(attributeID),
317+
];
318+
propToWorld.apply(closestCellPointWorldPosition);
319+
updateAssociationTooltip('Point', attributeID);
214320
} else {
321+
// Selecting cells
215322
const cellPoints = input.getCellPoints(attributeID);
323+
updateAssociationTooltip('Cell', attributeID);
216324
if (cellPoints) {
217325
const pointIds = cellPoints.cellPointIds;
218326
// Find the closest cell point, and use that as cursor position
219-
const points = pointIds.map((pointId) =>
327+
const points = Array.from(pointIds).map((pointId) =>
220328
input.getPoints().getPoint(pointId)
221329
);
222330
const distance = (pA, pB) =>
223-
vtkMath.distance2BetweenPoints(pA, worldPosition) -
224-
vtkMath.distance2BetweenPoints(pB, worldPosition);
331+
vtkMath.distance2BetweenPoints(pA, propPosition) -
332+
vtkMath.distance2BetweenPoints(pB, propPosition);
225333
const sorted = points.sort(distance);
226-
cursorPosition = [...sorted[0]];
334+
closestCellPointWorldPosition = [...sorted[0]];
335+
propToWorld.apply(closestCellPointWorldPosition);
227336
}
228337
}
229-
} else if (lastProcessedActor === prop) {
230-
// Skip render call when nothing change
231-
return;
232338
}
233-
updateWorldPosition(cursorPosition);
234339
lastProcessedActor = prop;
340+
// Use closestCellPointWorldPosition or rayHitWorldPosition
341+
updateCursor(closestCellPointWorldPosition);
235342

236343
// Make the picked actor green
237-
renderer.getActors().forEach((a) => a.getProperty().setColor(...WHITE));
238344
prop.getProperty().setColor(...GREEN);
239345

240346
// We hit the glyph, let's scale the picked glyph
@@ -248,9 +354,7 @@ function processSelections(selections) {
248354
scaleArray.fill(0.5);
249355
cylinderPointSet.modified();
250356
}
251-
252-
// Update picture for the user so we can see the green one
253-
updateWorldPosition(worldPosition);
357+
renderWindow.render();
254358
}
255359

256360
// ----------------------------------------------------------------------------
@@ -271,6 +375,6 @@ function pickOnMouseEvent(event) {
271375
}
272376
});
273377
}
274-
const throttleMouseHandler = throttle(pickOnMouseEvent, 100);
378+
const throttleMouseHandler = throttle(pickOnMouseEvent, 20);
275379

276380
document.addEventListener('mousemove', throttleMouseHandler);

0 commit comments

Comments
 (0)