@@ -5,6 +5,7 @@ import 'vtk.js/Sources/favicon';
5
5
6
6
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
7
7
import 'vtk.js/Sources/Rendering/Profiles/Geometry' ;
8
+ import 'vtk.js/Sources/Rendering/OpenGL/Glyph3DMapper' ;
8
9
9
10
import { throttle } from 'vtk.js/Sources/macros' ;
10
11
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor' ;
@@ -15,6 +16,10 @@ import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenR
15
16
import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper' ;
16
17
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper' ;
17
18
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' ;
18
23
import vtkMath from 'vtk.js/Sources/Common/Core/Math' ;
19
24
import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants' ;
20
25
import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants' ;
@@ -30,17 +35,25 @@ const GREEN = [0.1, 0.8, 0.1];
30
35
// Create DOM tooltip
31
36
// ----------------------------------------------------------------------------
32
37
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 ) ;
44
57
45
58
// ----------------------------------------------------------------------------
46
59
// Create 4 objects
@@ -60,8 +73,23 @@ const sphereSource = vtkSphereSource.newInstance({
60
73
const sphereMapper = vtkMapper . newInstance ( ) ;
61
74
const sphereActor = vtkActor . newInstance ( ) ;
62
75
sphereActor . setMapper ( sphereMapper ) ;
76
+ sphereActor . getProperty ( ) . setEdgeVisibility ( true ) ;
63
77
sphereMapper . setInputConnection ( sphereSource . getOutputPort ( ) ) ;
64
78
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
+
65
93
// Sphere with point representation -----------------------
66
94
67
95
const spherePointsSource = vtkSphereSource . newInstance ( {
@@ -70,7 +98,7 @@ const spherePointsSource = vtkSphereSource.newInstance({
70
98
radius : 0.6 ,
71
99
} ) ;
72
100
const spherePointsMapper = vtkMapper . newInstance ( ) ;
73
- const spherePointsActor = vtkActor . newInstance ( ) ;
101
+ const spherePointsActor = vtkActor . newInstance ( { position : [ 0 , - 1 , 0 ] } ) ;
74
102
spherePointsActor . setMapper ( spherePointsMapper ) ;
75
103
spherePointsMapper . setInputConnection ( spherePointsSource . getOutputPort ( ) ) ;
76
104
@@ -82,7 +110,7 @@ spherePointsActor.getProperty().setPointSize(20);
82
110
83
111
const coneSource = vtkConeSource . newInstance ( { resolution : 20 } ) ;
84
112
const coneMapper = vtkMapper . newInstance ( ) ;
85
- const coneActor = vtkActor . newInstance ( ) ;
113
+ const coneActor = vtkActor . newInstance ( { position : [ 1 , 0 , 0 ] } ) ;
86
114
coneActor . setMapper ( coneMapper ) ;
87
115
coneMapper . setInputConnection ( coneSource . getOutputPort ( ) ) ;
88
116
@@ -100,7 +128,7 @@ const cylinderMapper = vtkGlyph3DMapper.newInstance({
100
128
scaleMode : vtkGlyph3DMapper . ScaleModes . SCALE_BY_MAGNITUDE ,
101
129
scaleArray : 'scale' ,
102
130
} ) ;
103
- const cylinderActor = vtkActor . newInstance ( ) ;
131
+ const cylinderActor = vtkActor . newInstance ( { position : [ 0 , 1 , 0 ] } ) ;
104
132
const cylinderGlyph = sphereSource . getOutputData ( ) ;
105
133
const cylinderPointSet = cylinderSource . getOutputData ( ) ;
106
134
cylinderActor . setMapper ( cylinderMapper ) ;
@@ -117,6 +145,23 @@ cylinderPointSet.getPointData().addArray(
117
145
} )
118
146
) ;
119
147
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
+
120
165
// ----------------------------------------------------------------------------
121
166
// Create Picking pointer
122
167
// ----------------------------------------------------------------------------
@@ -142,10 +187,12 @@ const interactor = renderWindow.getInteractor();
142
187
const apiSpecificRenderWindow = interactor . getView ( ) ;
143
188
144
189
renderer . addActor ( sphereActor ) ;
190
+ renderer . addActor ( cubeActor ) ;
145
191
renderer . addActor ( spherePointsActor ) ;
146
192
renderer . addActor ( coneActor ) ;
147
193
renderer . addActor ( cylinderActor ) ;
148
194
renderer . addActor ( pointerActor ) ;
195
+ renderer . addActor ( polyLines ) ;
149
196
150
197
renderer . resetCamera ( ) ;
151
198
renderWindow . render ( ) ;
@@ -156,6 +203,10 @@ renderWindow.render();
156
203
157
204
const hardwareSelector = apiSpecificRenderWindow . getSelector ( ) ;
158
205
hardwareSelector . setCaptureZValues ( true ) ;
206
+ // TODO: bug in FIELD_ASSOCIATION_POINTS mode
207
+ // hardwareSelector.setFieldAssociation(
208
+ // FieldAssociations.FIELD_ASSOCIATION_POINTS
209
+ // );
159
210
hardwareSelector . setFieldAssociation ( FieldAssociations . FIELD_ASSOCIATION_CELLS ) ;
160
211
161
212
// ----------------------------------------------------------------------------
@@ -177,64 +228,119 @@ function eventToWindowXY(event) {
177
228
let needGlyphCleanup = false ;
178
229
let lastProcessedActor = null ;
179
230
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 ) => {
181
263
if ( lastProcessedActor ) {
182
264
pointerActor . setVisibility ( true ) ;
183
- tooltipElem . innerHTML = worldPosition . map ( ( v ) => v . toFixed ( 3 ) ) . join ( ' , ' ) ;
184
265
pointerActor . setPosition ( worldPosition ) ;
185
266
} else {
186
267
pointerActor . setVisibility ( false ) ;
187
- tooltipElem . innerHTML = '' ;
188
268
}
189
269
renderWindow . render ( ) ;
270
+ updatePositionTooltip ( worldPosition ) ;
190
271
} ;
191
272
192
273
function processSelections ( selections ) {
274
+ renderer . getActors ( ) . forEach ( ( a ) => a . getProperty ( ) . setColor ( ...WHITE ) ) ;
193
275
if ( ! selections || selections . length === 0 ) {
194
- renderer . getActors ( ) . forEach ( ( a ) => a . getProperty ( ) . setColor ( ...WHITE ) ) ;
195
- pointerActor . setVisibility ( false ) ;
196
- renderWindow . render ( ) ;
197
276
lastProcessedActor = null ;
277
+ updateAssociationTooltip ( ) ;
278
+ updateCursor ( ) ;
279
+ updateCompositeAndPropIdTooltip ( ) ;
198
280
return ;
199
281
}
200
282
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 ] ;
204
294
if ( attributeID || attributeID === 0 ) {
205
295
const input = prop . getMapper ( ) . getInputData ( ) ;
206
296
if ( ! input . getCells ( ) ) {
207
297
input . buildCells ( ) ;
208
298
}
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
+
209
310
if (
210
311
hardwareSelector . getFieldAssociation ( ) ===
211
312
FieldAssociations . FIELD_ASSOCIATION_POINTS
212
313
) {
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 ) ;
214
320
} else {
321
+ // Selecting cells
215
322
const cellPoints = input . getCellPoints ( attributeID ) ;
323
+ updateAssociationTooltip ( 'Cell' , attributeID ) ;
216
324
if ( cellPoints ) {
217
325
const pointIds = cellPoints . cellPointIds ;
218
326
// 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 ) =>
220
328
input . getPoints ( ) . getPoint ( pointId )
221
329
) ;
222
330
const distance = ( pA , pB ) =>
223
- vtkMath . distance2BetweenPoints ( pA , worldPosition ) -
224
- vtkMath . distance2BetweenPoints ( pB , worldPosition ) ;
331
+ vtkMath . distance2BetweenPoints ( pA , propPosition ) -
332
+ vtkMath . distance2BetweenPoints ( pB , propPosition ) ;
225
333
const sorted = points . sort ( distance ) ;
226
- cursorPosition = [ ...sorted [ 0 ] ] ;
334
+ closestCellPointWorldPosition = [ ...sorted [ 0 ] ] ;
335
+ propToWorld . apply ( closestCellPointWorldPosition ) ;
227
336
}
228
337
}
229
- } else if ( lastProcessedActor === prop ) {
230
- // Skip render call when nothing change
231
- return ;
232
338
}
233
- updateWorldPosition ( cursorPosition ) ;
234
339
lastProcessedActor = prop ;
340
+ // Use closestCellPointWorldPosition or rayHitWorldPosition
341
+ updateCursor ( closestCellPointWorldPosition ) ;
235
342
236
343
// Make the picked actor green
237
- renderer . getActors ( ) . forEach ( ( a ) => a . getProperty ( ) . setColor ( ...WHITE ) ) ;
238
344
prop . getProperty ( ) . setColor ( ...GREEN ) ;
239
345
240
346
// We hit the glyph, let's scale the picked glyph
@@ -248,9 +354,7 @@ function processSelections(selections) {
248
354
scaleArray . fill ( 0.5 ) ;
249
355
cylinderPointSet . modified ( ) ;
250
356
}
251
-
252
- // Update picture for the user so we can see the green one
253
- updateWorldPosition ( worldPosition ) ;
357
+ renderWindow . render ( ) ;
254
358
}
255
359
256
360
// ----------------------------------------------------------------------------
@@ -271,6 +375,6 @@ function pickOnMouseEvent(event) {
271
375
}
272
376
} ) ;
273
377
}
274
- const throttleMouseHandler = throttle ( pickOnMouseEvent , 100 ) ;
378
+ const throttleMouseHandler = throttle ( pickOnMouseEvent , 20 ) ;
275
379
276
380
document . addEventListener ( 'mousemove' , throttleMouseHandler ) ;
0 commit comments