@kitware/vtk.js 29.11.2 → 30.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BREAKING_CHANGES.md +5 -0
- package/Interaction/Manipulators/3DControllerModelSelectorManipulator/index.js +182 -0
- package/Interaction/Manipulators/3DControllerModelSelectorManipulator.d.ts +19 -0
- package/Interaction/Manipulators/CompositeVRManipulator.d.ts +9 -9
- package/Interaction/Manipulators/CompositeVRManipulator.js +2 -2
- package/Interaction/Manipulators/VRButtonPanManipulator.js +5 -5
- package/Interaction/Style/InteractorStyleHMDXR.d.ts +22 -0
- package/Interaction/Style/InteractorStyleHMDXR.js +50 -0
- package/Interaction/Style/InteractorStyleManipulator.js +20 -10
- package/Rendering/Core/CellPicker.d.ts +2 -20
- package/Rendering/Core/CellPicker.js +8 -8
- package/Rendering/Core/Picker.d.ts +12 -10
- package/Rendering/Core/Picker.js +143 -134
- package/Rendering/Core/PointPicker.d.ts +2 -18
- package/Rendering/Core/PointPicker.js +5 -5
- package/Rendering/Core/Prop3D.d.ts +16 -1
- package/Rendering/Core/Prop3D.js +14 -0
- package/Rendering/Core/RenderWindowInteractor.d.ts +21 -5
- package/Rendering/Core/RenderWindowInteractor.js +43 -37
- package/Rendering/Core/VolumeMapper.d.ts +3 -3
- package/Rendering/WebXR/RenderWindowHelper.js +66 -11
- package/Widgets/Widgets3D/ResliceCursorWidget/behavior.d.ts +0 -1
- package/Widgets/Widgets3D/ResliceCursorWidget/behavior.js +3 -12
- package/Widgets/Widgets3D/ResliceCursorWidget.d.ts +0 -1
- package/Widgets/Widgets3D/ResliceCursorWidget.js +2 -2
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/Rendering/Core/Picker.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { m as macro } from '../../macros2.js';
|
|
2
2
|
import vtkAbstractPicker from './AbstractPicker.js';
|
|
3
3
|
import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js';
|
|
4
|
-
import { d as dot, l as normalize,
|
|
5
|
-
import { mat4, vec4 } from 'gl-matrix';
|
|
4
|
+
import { d as dot, l as normalize, s as subtract, e as distance2BetweenPoints } from '../../Common/Core/Math/index.js';
|
|
5
|
+
import { vec3, mat4, vec4 } from 'gl-matrix';
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
vtkErrorMacro
|
|
@@ -62,17 +62,114 @@ function vtkPicker(publicAPI, model) {
|
|
|
62
62
|
return Math.sqrt(tolerance);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Perform picking on the given renderer, given a ray defined in world coordinates.
|
|
67
|
+
* @param {*} renderer
|
|
68
|
+
* @param {*} tolerance
|
|
69
|
+
* @param {*} p1World
|
|
70
|
+
* @param {*} p2World
|
|
71
|
+
* @returns true if we picked something else false
|
|
72
|
+
*/
|
|
73
|
+
function pick3DInternal(renderer, tolerance, p1World, p2World) {
|
|
74
|
+
const p1Mapper = new Float64Array(4);
|
|
75
|
+
const p2Mapper = new Float64Array(4);
|
|
76
|
+
const ray = [];
|
|
77
|
+
const hitPosition = [];
|
|
78
|
+
const props = model.pickFromList ? model.pickList : renderer.getActors();
|
|
79
|
+
|
|
80
|
+
// pre-allocate some arrays.
|
|
81
|
+
const transformScale = new Float64Array(3);
|
|
82
|
+
const pickedPosition = new Float64Array(3);
|
|
83
|
+
|
|
84
|
+
// Loop over props.
|
|
85
|
+
// Transform ray (defined from position of camera to selection point) into coordinates of mapper (not
|
|
86
|
+
// transformed to actors coordinates! Reduces overall computation!!!).
|
|
87
|
+
// Note that only vtkProp3D's can be picked by vtkPicker.
|
|
88
|
+
props.forEach(prop => {
|
|
89
|
+
const mapper = prop.getMapper();
|
|
90
|
+
const propIsNotFullyTranslucent = prop.getProperty?.()?.getOpacity() > 0.0;
|
|
91
|
+
const pickable = prop.getNestedPickable() && prop.getNestedVisibility() && propIsNotFullyTranslucent;
|
|
92
|
+
if (!pickable) {
|
|
93
|
+
// prop cannot be picked
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The prop is candidate for picking:
|
|
98
|
+
// - get its composite matrix and invert it
|
|
99
|
+
// - use the inverted matrix to transform the ray points into mapper coordinates
|
|
100
|
+
model.transformMatrix = prop.getMatrix().slice(0);
|
|
101
|
+
mat4.transpose(model.transformMatrix, model.transformMatrix);
|
|
102
|
+
mat4.invert(model.transformMatrix, model.transformMatrix);
|
|
103
|
+
vec4.transformMat4(p1Mapper, p1World, model.transformMatrix);
|
|
104
|
+
vec4.transformMat4(p2Mapper, p2World, model.transformMatrix);
|
|
105
|
+
vec3.scale(p1Mapper, p1Mapper, 1 / p1Mapper[3]);
|
|
106
|
+
vec3.scale(p2Mapper, p2Mapper, 1 / p2Mapper[3]);
|
|
107
|
+
subtract(p2Mapper, p1Mapper, ray);
|
|
108
|
+
|
|
109
|
+
// We now have the ray endpoints in mapper coordinates.
|
|
110
|
+
// Compare it with the mapper bounds to check if intersection is possible.
|
|
111
|
+
|
|
112
|
+
// Get the bounding box of the mapper.
|
|
113
|
+
// Note that the tolerance is added to the bounding box to make sure things on the edge of the
|
|
114
|
+
// bounding box are picked correctly.
|
|
115
|
+
const bounds = mapper ? vtkBoundingBox.inflate(mapper.getBounds(), tolerance) : [...vtkBoundingBox.INIT_BOUNDS];
|
|
116
|
+
if (vtkBoundingBox.intersectBox(bounds, p1Mapper, ray, hitPosition, [])) {
|
|
117
|
+
mat4.getScaling(transformScale, model.transformMatrix);
|
|
118
|
+
const t = model.intersectWithLine(p1Mapper, p2Mapper, tolerance * 0.333 * (transformScale[0] + transformScale[1] + transformScale[2]), prop, mapper);
|
|
119
|
+
if (t < Number.MAX_VALUE) {
|
|
120
|
+
pickedPosition[0] = (1.0 - t) * p1World[0] + t * p2World[0];
|
|
121
|
+
pickedPosition[1] = (1.0 - t) * p1World[1] + t * p2World[1];
|
|
122
|
+
pickedPosition[2] = (1.0 - t) * p1World[2] + t * p2World[2];
|
|
123
|
+
const actorIndex = model.actors.indexOf(prop);
|
|
124
|
+
if (actorIndex !== -1) {
|
|
125
|
+
// If already in list, compare the previous picked position with the new one.
|
|
126
|
+
// Store the new one if it is closer from the ray endpoint.
|
|
127
|
+
const previousPickedPosition = model.pickedPositions[actorIndex];
|
|
128
|
+
if (distance2BetweenPoints(p1World, pickedPosition) < distance2BetweenPoints(p1World, previousPickedPosition)) {
|
|
129
|
+
model.pickedPositions[actorIndex] = pickedPosition.slice(0);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
model.actors.push(prop);
|
|
133
|
+
model.pickedPositions.push(pickedPosition.slice(0));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// sort array by distance
|
|
140
|
+
const tempArray = [];
|
|
141
|
+
for (let i = 0; i < model.pickedPositions.length; i++) {
|
|
142
|
+
tempArray.push({
|
|
143
|
+
actor: model.actors[i],
|
|
144
|
+
pickedPosition: model.pickedPositions[i],
|
|
145
|
+
distance2: distance2BetweenPoints(p1World, model.pickedPositions[i])
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
tempArray.sort((a, b) => {
|
|
149
|
+
const keyA = a.distance2;
|
|
150
|
+
const keyB = b.distance2;
|
|
151
|
+
// order the actors based on the distance2 attribute, so the near actors comes
|
|
152
|
+
// first in the list
|
|
153
|
+
if (keyA < keyB) return -1;
|
|
154
|
+
if (keyA > keyB) return 1;
|
|
155
|
+
return 0;
|
|
156
|
+
});
|
|
157
|
+
model.pickedPositions = [];
|
|
158
|
+
model.actors = [];
|
|
159
|
+
tempArray.forEach(obj => {
|
|
160
|
+
model.pickedPositions.push(obj.pickedPosition);
|
|
161
|
+
model.actors.push(obj.actor);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
65
165
|
// Intersect data with specified ray.
|
|
66
166
|
// Project the center point of the mapper onto the ray and determine its parametric value
|
|
67
|
-
|
|
167
|
+
model.intersectWithLine = (p1, p2, tolerance, prop, mapper) => {
|
|
68
168
|
if (!mapper) {
|
|
69
169
|
return Number.MAX_VALUE;
|
|
70
170
|
}
|
|
71
171
|
const center = mapper.getCenter();
|
|
72
|
-
const ray =
|
|
73
|
-
for (let i = 0; i < 3; i++) {
|
|
74
|
-
ray[i] = p2[i] - p1[i];
|
|
75
|
-
}
|
|
172
|
+
const ray = vec3.subtract(new Float64Array(3), p2, p1);
|
|
76
173
|
const rayFactor = dot(ray, ray);
|
|
77
174
|
if (rayFactor === 0.0) {
|
|
78
175
|
return 2.0;
|
|
@@ -86,55 +183,43 @@ function vtkPicker(publicAPI, model) {
|
|
|
86
183
|
// To be overridden in subclasses
|
|
87
184
|
publicAPI.pick = (selection, renderer) => {
|
|
88
185
|
if (selection.length !== 3) {
|
|
89
|
-
vtkWarningMacro('vtkPicker
|
|
186
|
+
vtkWarningMacro('vtkPicker.pick - selection needs three components');
|
|
90
187
|
}
|
|
188
|
+
if (!renderer) {
|
|
189
|
+
vtkErrorMacro('vtkPicker.pick - renderer cannot be null');
|
|
190
|
+
throw new Error('renderer cannot be null');
|
|
191
|
+
}
|
|
192
|
+
initialize();
|
|
91
193
|
const selectionX = selection[0];
|
|
92
194
|
const selectionY = selection[1];
|
|
93
195
|
let selectionZ = selection[2];
|
|
94
|
-
let cameraPos = [];
|
|
95
|
-
let cameraFP = [];
|
|
96
|
-
let displayCoords = [];
|
|
97
|
-
let worldCoords = [];
|
|
98
|
-
const ray = [];
|
|
99
|
-
const cameraDOP = [];
|
|
100
|
-
let clipRange = [];
|
|
101
|
-
let tF;
|
|
102
|
-
let tB;
|
|
103
|
-
const p1World = [];
|
|
104
|
-
const p2World = [];
|
|
105
|
-
let props = [];
|
|
106
|
-
let pickable = false;
|
|
107
|
-
const p1Mapper = new Float64Array(4);
|
|
108
|
-
const p2Mapper = new Float64Array(4);
|
|
109
|
-
const bbox = vtkBoundingBox.newInstance();
|
|
110
|
-
const t = [];
|
|
111
|
-
const hitPosition = [];
|
|
112
|
-
const view = renderer.getRenderWindow().getViews()[0];
|
|
113
|
-
initialize();
|
|
114
196
|
model.renderer = renderer;
|
|
115
197
|
model.selectionPoint[0] = selectionX;
|
|
116
198
|
model.selectionPoint[1] = selectionY;
|
|
117
199
|
model.selectionPoint[2] = selectionZ;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
200
|
+
const p1World = new Float64Array(4);
|
|
201
|
+
const p2World = new Float64Array(4);
|
|
122
202
|
|
|
123
203
|
// Get camera focal point and position. Convert to display (screen)
|
|
124
204
|
// coordinates. We need a depth value for z-buffer.
|
|
125
205
|
const camera = renderer.getActiveCamera();
|
|
126
|
-
cameraPos = camera.getPosition();
|
|
127
|
-
cameraFP = camera.getFocalPoint();
|
|
206
|
+
const cameraPos = camera.getPosition();
|
|
207
|
+
const cameraFP = camera.getFocalPoint();
|
|
208
|
+
const view = renderer.getRenderWindow().getViews()[0];
|
|
128
209
|
const dims = view.getViewportSize(renderer);
|
|
210
|
+
if (dims[1] === 0) {
|
|
211
|
+
vtkWarningMacro('vtkPicker.pick - viewport area is 0');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
129
214
|
const aspect = dims[0] / dims[1];
|
|
215
|
+
let displayCoords = [];
|
|
130
216
|
displayCoords = renderer.worldToNormalizedDisplay(cameraFP[0], cameraFP[1], cameraFP[2], aspect);
|
|
131
217
|
displayCoords = view.normalizedDisplayToDisplay(displayCoords[0], displayCoords[1], displayCoords[2]);
|
|
132
218
|
selectionZ = displayCoords[2];
|
|
133
|
-
const tolerance = computeTolerance(selectionZ, aspect, renderer) * model.tolerance;
|
|
134
219
|
|
|
135
220
|
// Convert the selection point into world coordinates.
|
|
136
221
|
const normalizedDisplay = view.displayToNormalizedDisplay(selectionX, selectionY, selectionZ);
|
|
137
|
-
worldCoords = renderer.normalizedDisplayToWorld(normalizedDisplay[0], normalizedDisplay[1], normalizedDisplay[2], aspect);
|
|
222
|
+
const worldCoords = renderer.normalizedDisplayToWorld(normalizedDisplay[0], normalizedDisplay[1], normalizedDisplay[2], aspect);
|
|
138
223
|
for (let i = 0; i < 3; i++) {
|
|
139
224
|
model.pickPosition[i] = worldCoords[i];
|
|
140
225
|
}
|
|
@@ -143,9 +228,11 @@ function vtkPicker(publicAPI, model) {
|
|
|
143
228
|
// the camera position to the selection point, starting where this line
|
|
144
229
|
// intersects the front clipping plane, and terminating where this
|
|
145
230
|
// line intersects the back clipping plane.
|
|
231
|
+
const ray = [];
|
|
146
232
|
for (let i = 0; i < 3; i++) {
|
|
147
233
|
ray[i] = model.pickPosition[i] - cameraPos[i];
|
|
148
234
|
}
|
|
235
|
+
const cameraDOP = [];
|
|
149
236
|
for (let i = 0; i < 3; i++) {
|
|
150
237
|
cameraDOP[i] = cameraFP[i] - cameraPos[i];
|
|
151
238
|
}
|
|
@@ -155,7 +242,9 @@ function vtkPicker(publicAPI, model) {
|
|
|
155
242
|
vtkWarningMacro('Picker::Pick Cannot process points');
|
|
156
243
|
return;
|
|
157
244
|
}
|
|
158
|
-
clipRange = camera.getClippingRange();
|
|
245
|
+
const clipRange = camera.getClippingRange();
|
|
246
|
+
let tF;
|
|
247
|
+
let tB;
|
|
159
248
|
if (camera.getParallelProjection()) {
|
|
160
249
|
tF = clipRange[0] - rayLength;
|
|
161
250
|
tB = clipRange[1] - rayLength;
|
|
@@ -173,105 +262,25 @@ function vtkPicker(publicAPI, model) {
|
|
|
173
262
|
}
|
|
174
263
|
p1World[3] = 1.0;
|
|
175
264
|
p2World[3] = 1.0;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
265
|
+
const tolerance = computeTolerance(selectionZ, aspect, renderer) * model.tolerance;
|
|
266
|
+
pick3DInternal(model.renderer, tolerance, p1World, p2World);
|
|
267
|
+
};
|
|
268
|
+
publicAPI.pick3DPoint = (selectionPoint, focalPoint, renderer) => {
|
|
269
|
+
if (!renderer) {
|
|
270
|
+
throw new Error('renderer cannot be null');
|
|
180
271
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
model.transformMatrix = prop.getMatrix().slice(0);
|
|
190
|
-
// Webgl need a transpose matrix but we need the untransposed one to project world points
|
|
191
|
-
// into the right referential
|
|
192
|
-
mat4.transpose(model.transformMatrix, model.transformMatrix);
|
|
193
|
-
mat4.invert(model.transformMatrix, model.transformMatrix);
|
|
194
|
-
// Extract scale
|
|
195
|
-
const col1 = [model.transformMatrix[0], model.transformMatrix[1], model.transformMatrix[2]];
|
|
196
|
-
const col2 = [model.transformMatrix[4], model.transformMatrix[5], model.transformMatrix[6]];
|
|
197
|
-
const col3 = [model.transformMatrix[8], model.transformMatrix[9], model.transformMatrix[10]];
|
|
198
|
-
scale[0] = norm(col1);
|
|
199
|
-
scale[1] = norm(col2);
|
|
200
|
-
scale[2] = norm(col3);
|
|
201
|
-
vec4.transformMat4(p1Mapper, p1World, model.transformMatrix);
|
|
202
|
-
vec4.transformMat4(p2Mapper, p2World, model.transformMatrix);
|
|
203
|
-
p1Mapper[0] /= p1Mapper[3];
|
|
204
|
-
p1Mapper[1] /= p1Mapper[3];
|
|
205
|
-
p1Mapper[2] /= p1Mapper[3];
|
|
206
|
-
p2Mapper[0] /= p2Mapper[3];
|
|
207
|
-
p2Mapper[1] /= p2Mapper[3];
|
|
208
|
-
p2Mapper[2] /= p2Mapper[3];
|
|
209
|
-
for (let i = 0; i < 3; i++) {
|
|
210
|
-
ray[i] = p2Mapper[i] - p1Mapper[i];
|
|
211
|
-
}
|
|
212
|
-
if (mapper) {
|
|
213
|
-
bbox.setBounds(mapper.getBounds());
|
|
214
|
-
bbox.inflate(tolerance);
|
|
215
|
-
} else {
|
|
216
|
-
bbox.reset();
|
|
217
|
-
}
|
|
218
|
-
if (bbox.intersectBox(p1Mapper, ray, hitPosition, t)) {
|
|
219
|
-
t[0] = publicAPI.intersectWithLine(p1Mapper, p2Mapper, tolerance * 0.333 * (scale[0] + scale[1] + scale[2]), prop, mapper);
|
|
220
|
-
if (t[0] < Number.MAX_VALUE) {
|
|
221
|
-
const p = [];
|
|
222
|
-
p[0] = (1.0 - t[0]) * p1World[0] + t[0] * p2World[0];
|
|
223
|
-
p[1] = (1.0 - t[0]) * p1World[1] + t[0] * p2World[1];
|
|
224
|
-
p[2] = (1.0 - t[0]) * p1World[2] + t[0] * p2World[2];
|
|
225
|
-
|
|
226
|
-
// Check if the current actor is already in the list
|
|
227
|
-
let actorID = -1;
|
|
228
|
-
for (let i = 0; i < model.actors.length; i++) {
|
|
229
|
-
if (model.actors[i] === prop) {
|
|
230
|
-
actorID = i;
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (actorID === -1) {
|
|
235
|
-
model.actors.push(prop);
|
|
236
|
-
model.pickedPositions.push(p);
|
|
237
|
-
} else {
|
|
238
|
-
const oldPoint = model.pickedPositions[actorID];
|
|
239
|
-
const distOld = distance2BetweenPoints(p1World, oldPoint);
|
|
240
|
-
const distCurrent = distance2BetweenPoints(p1World, p);
|
|
241
|
-
if (distCurrent < distOld) {
|
|
242
|
-
model.pickedPositions[actorID] = p;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
publicAPI.invokePickChange(model.pickedPositions);
|
|
249
|
-
return 1;
|
|
250
|
-
});
|
|
251
|
-
// sort array by distance
|
|
252
|
-
const tempArray = [];
|
|
253
|
-
for (let i = 0; i < model.pickedPositions.length; i++) {
|
|
254
|
-
tempArray.push({
|
|
255
|
-
actor: model.actors[i],
|
|
256
|
-
pickedPosition: model.pickedPositions[i],
|
|
257
|
-
distance2: distance2BetweenPoints(p1World, model.pickedPositions[i])
|
|
258
|
-
});
|
|
272
|
+
initialize();
|
|
273
|
+
model.renderer = renderer;
|
|
274
|
+
vec3.copy(model.selectionPoint, selectionPoint);
|
|
275
|
+
const view = renderer.getRenderWindow().getViews()[0];
|
|
276
|
+
const dims = view.getViewportSize(renderer);
|
|
277
|
+
if (dims[1] === 0) {
|
|
278
|
+
vtkWarningMacro('vtkPicker.pick3DPoint - viewport area is 0');
|
|
279
|
+
return;
|
|
259
280
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// order the actors based on the distance2 attribute, so the near actors comes
|
|
264
|
-
// first in the list
|
|
265
|
-
if (keyA < keyB) return -1;
|
|
266
|
-
if (keyA > keyB) return 1;
|
|
267
|
-
return 0;
|
|
268
|
-
});
|
|
269
|
-
model.pickedPositions = [];
|
|
270
|
-
model.actors = [];
|
|
271
|
-
tempArray.forEach(obj => {
|
|
272
|
-
model.pickedPositions.push(obj.pickedPosition);
|
|
273
|
-
model.actors.push(obj.actor);
|
|
274
|
-
});
|
|
281
|
+
const aspect = dims[0] / dims[1];
|
|
282
|
+
const tolerance = computeTolerance(model.selectionPoint[2], aspect, renderer) * model.tolerance;
|
|
283
|
+
pick3DInternal(renderer, tolerance, selectionPoint, focalPoint);
|
|
275
284
|
};
|
|
276
285
|
}
|
|
277
286
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import vtkPicker, { IPickerInitialValues } from './Picker';
|
|
2
2
|
import vtkMapper from './Mapper';
|
|
3
3
|
import { Vector3 } from './../../types';
|
|
4
|
+
import vtkProp3D from './Prop3D';
|
|
5
|
+
import { Nullable } from './../../types';
|
|
4
6
|
|
|
5
7
|
interface IPointPickerInitialValues extends IPickerInitialValues {
|
|
6
8
|
pointId?: number;
|
|
@@ -31,24 +33,6 @@ export interface vtkPointPicker extends vtkPicker {
|
|
|
31
33
|
*/
|
|
32
34
|
getUseCells(): boolean;
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
* @param {Vector3} p1
|
|
37
|
-
* @param {Vector3} p2
|
|
38
|
-
* @param {Number} tol
|
|
39
|
-
* @param {vtkMapper} mapper
|
|
40
|
-
*/
|
|
41
|
-
intersectWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
* @param {Vector3} p1
|
|
46
|
-
* @param {Vector3} p2
|
|
47
|
-
* @param {Number} tol
|
|
48
|
-
* @param {vtkMapper} mapper
|
|
49
|
-
*/
|
|
50
|
-
intersectActorWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;
|
|
51
|
-
|
|
52
36
|
/**
|
|
53
37
|
* Specify whether the point search should be based on cell points or directly on the point list.
|
|
54
38
|
* @param useCells
|
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
function vtkPointPicker(publicAPI, model) {
|
|
14
14
|
// Set our className
|
|
15
15
|
model.classHierarchy.push('vtkPointPicker');
|
|
16
|
-
|
|
16
|
+
model.intersectWithLine = (p1, p2, tolerance, prop, mapper) => {
|
|
17
17
|
let tMin = Number.MAX_VALUE;
|
|
18
18
|
if (mapper.isA('vtkImageMapper') || mapper.isA('vtkImageArrayMapper')) {
|
|
19
19
|
const pickData = mapper.intersectWithLineForPointPicking(p1, p2);
|
|
@@ -22,11 +22,11 @@ function vtkPointPicker(publicAPI, model) {
|
|
|
22
22
|
model.pointIJK = pickData.ijk;
|
|
23
23
|
}
|
|
24
24
|
} else if (mapper.isA('vtkMapper')) {
|
|
25
|
-
tMin =
|
|
25
|
+
tMin = model.intersectActorWithLine(p1, p2, tolerance, mapper);
|
|
26
26
|
}
|
|
27
27
|
return tMin;
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
model.intersectActorWithLine = (p1, p2, tolerance, mapper) => {
|
|
30
30
|
// Get dataset
|
|
31
31
|
const input = mapper.getInputData();
|
|
32
32
|
|
|
@@ -77,7 +77,7 @@ function vtkPointPicker(publicAPI, model) {
|
|
|
77
77
|
maxDist = dist;
|
|
78
78
|
}
|
|
79
79
|
} // end for i
|
|
80
|
-
if (maxDist <=
|
|
80
|
+
if (maxDist <= tolerance && maxDist < minPtDist) {
|
|
81
81
|
// within tolerance
|
|
82
82
|
minPtId = ptId;
|
|
83
83
|
minPtDist = maxDist;
|
|
@@ -105,7 +105,7 @@ function vtkPointPicker(publicAPI, model) {
|
|
|
105
105
|
maxDist = dist;
|
|
106
106
|
}
|
|
107
107
|
} // end for i
|
|
108
|
-
if (maxDist <=
|
|
108
|
+
if (maxDist <= tolerance && maxDist < minPtDist) {
|
|
109
109
|
// within tolerance
|
|
110
110
|
minPtId = ptId;
|
|
111
111
|
minPtDist = maxDist;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mat4 } from "gl-matrix";
|
|
1
|
+
import { mat4, quat } from "gl-matrix";
|
|
2
2
|
import { Bounds, Vector3, Range } from './../../types';
|
|
3
3
|
import vtkProp, { IPropInitialValues } from './Prop';
|
|
4
4
|
|
|
@@ -88,6 +88,13 @@ export interface vtkProp3D extends vtkProp {
|
|
|
88
88
|
*/
|
|
89
89
|
getOrientationWXYZ(): number[];
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Get the orientation quaternion of the Prop3D.
|
|
93
|
+
* out is optional and will be created if not supplied.
|
|
94
|
+
* @param {quat | undefined} out
|
|
95
|
+
*/
|
|
96
|
+
getOrientationQuaternion(out?: quat): quat;
|
|
97
|
+
|
|
91
98
|
/**
|
|
92
99
|
* Get a reference to the Prop3D’s 4x4 composite matrix.
|
|
93
100
|
* Get the matrix from the position, origin, scale and orientation This
|
|
@@ -167,6 +174,14 @@ export interface vtkProp3D extends vtkProp {
|
|
|
167
174
|
*/
|
|
168
175
|
rotateWXYZ(degrees: number, x: number, y: number, z: number): void;
|
|
169
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Rotate the Prop3D by the provided orientation quaternion.
|
|
179
|
+
* If the provided quaternion is identity (~epsilon), this function does nothing.
|
|
180
|
+
* The quaternion should follow the gl-matrix convention: [x,y,z,w]
|
|
181
|
+
* @param {quat} orientationQuaternion The quaternion to rotate the prop by.
|
|
182
|
+
*/
|
|
183
|
+
rotateQuaternion(orientationQuaternion: quat): void;
|
|
184
|
+
|
|
170
185
|
/**
|
|
171
186
|
* Orientation is specified as X, Y and Z rotations in that order,
|
|
172
187
|
* but they are performed as RotateZ, RotateX, and finally RotateY.
|
package/Rendering/Core/Prop3D.js
CHANGED
|
@@ -4,6 +4,8 @@ import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js';
|
|
|
4
4
|
import { A as degreesFromRadians, r as radiansFromDegrees, a as areMatricesEqual } from '../../Common/Core/Math/index.js';
|
|
5
5
|
import vtkProp from './Prop.js';
|
|
6
6
|
|
|
7
|
+
const VTK_EPSILON = 1e-6;
|
|
8
|
+
|
|
7
9
|
// ----------------------------------------------------------------------------
|
|
8
10
|
// vtkProp3D methods
|
|
9
11
|
// ----------------------------------------------------------------------------
|
|
@@ -22,6 +24,10 @@ function vtkProp3D(publicAPI, model) {
|
|
|
22
24
|
const w = quat.getAxisAngle(oaxis, q);
|
|
23
25
|
return [degreesFromRadians(w), oaxis[0], oaxis[1], oaxis[2]];
|
|
24
26
|
};
|
|
27
|
+
publicAPI.getOrientationQuaternion = function () {
|
|
28
|
+
let out = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
29
|
+
return mat4.getRotation(out, model.rotation);
|
|
30
|
+
};
|
|
25
31
|
publicAPI.rotateX = val => {
|
|
26
32
|
if (val === 0.0) {
|
|
27
33
|
return;
|
|
@@ -57,6 +63,14 @@ function vtkProp3D(publicAPI, model) {
|
|
|
57
63
|
mat4.multiply(model.rotation, model.rotation, quatMat);
|
|
58
64
|
publicAPI.modified();
|
|
59
65
|
};
|
|
66
|
+
publicAPI.rotateQuaternion = orientationQuaternion => {
|
|
67
|
+
if (Math.abs(orientationQuaternion[3]) >= 1 - VTK_EPSILON) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const oriQuatMat = mat4.fromQuat(new Float64Array(16), orientationQuaternion);
|
|
71
|
+
mat4.multiply(model.rotation, model.rotation, oriQuatMat);
|
|
72
|
+
publicAPI.modified();
|
|
73
|
+
};
|
|
60
74
|
publicAPI.setOrientation = (x, y, z) => {
|
|
61
75
|
if (x === model.orientation[0] && y === model.orientation[1] && z === model.orientation[2]) {
|
|
62
76
|
return false;
|
|
@@ -81,6 +81,20 @@ export interface IRenderWindowInteractorEvent {
|
|
|
81
81
|
type: InteractorEventType;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
export interface I3DEvent {
|
|
85
|
+
gamepad: Gamepad;
|
|
86
|
+
position: DOMPointReadOnly;
|
|
87
|
+
orientation: DOMPointReadOnly;
|
|
88
|
+
targetPosition: DOMPointReadOnly;
|
|
89
|
+
targetOrientation: DOMPointReadOnly;
|
|
90
|
+
device: Device;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface IButton3DEvent extends I3DEvent {
|
|
94
|
+
pressed: boolean;
|
|
95
|
+
input: Input;
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
export interface vtkRenderWindowInteractor extends vtkObject {
|
|
85
99
|
|
|
86
100
|
/**
|
|
@@ -678,10 +692,11 @@ export interface vtkRenderWindowInteractor extends vtkObject {
|
|
|
678
692
|
animationEvent(args: any): any;
|
|
679
693
|
|
|
680
694
|
/**
|
|
681
|
-
*
|
|
695
|
+
* Triggers the 'Button3D' event.
|
|
696
|
+
*
|
|
682
697
|
* @param args
|
|
683
698
|
*/
|
|
684
|
-
button3DEvent(
|
|
699
|
+
button3DEvent(eventPayload: IButton3DEvent): void;
|
|
685
700
|
|
|
686
701
|
/**
|
|
687
702
|
*
|
|
@@ -804,10 +819,11 @@ export interface vtkRenderWindowInteractor extends vtkObject {
|
|
|
804
819
|
mouseWheelEvent(args: any): any;
|
|
805
820
|
|
|
806
821
|
/**
|
|
807
|
-
*
|
|
808
|
-
*
|
|
822
|
+
* Triggers the 'Move3D' event.
|
|
823
|
+
*
|
|
824
|
+
* @param eventPayload
|
|
809
825
|
*/
|
|
810
|
-
move3DEvent(
|
|
826
|
+
move3DEvent(eventPayload: I3DEvent): void;
|
|
811
827
|
|
|
812
828
|
/**
|
|
813
829
|
*
|
|
@@ -481,45 +481,51 @@ function vtkRenderWindowInteractor(publicAPI, model) {
|
|
|
481
481
|
// watch for when buttons change state and fire events
|
|
482
482
|
xrSession.inputSources.forEach(inputSource => {
|
|
483
483
|
const gripPose = inputSource.gripSpace == null ? null : xrFrame.getPose(inputSource.gripSpace, xrRefSpace);
|
|
484
|
-
const
|
|
484
|
+
const targetRayPose = inputSource.gripSpace == null ? null : xrFrame.getPose(inputSource.targetRaySpace, xrRefSpace);
|
|
485
|
+
const gamepad = inputSource.gamepad;
|
|
485
486
|
const hand = inputSource.handedness;
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
for (let b = 0; b < gp.buttons.length; ++b) {
|
|
501
|
-
if (!(b in model.lastGamepadValues[gp.index][hand].buttons)) {
|
|
502
|
-
model.lastGamepadValues[gp.index][hand].buttons[b] = false;
|
|
503
|
-
}
|
|
504
|
-
if (model.lastGamepadValues[gp.index][hand].buttons[b] !== gp.buttons[b].pressed && gripPose != null) {
|
|
505
|
-
publicAPI.button3DEvent({
|
|
506
|
-
gamepad: gp,
|
|
507
|
-
position: gripPose.transform.position,
|
|
508
|
-
orientation: gripPose.transform.orientation,
|
|
509
|
-
pressed: gp.buttons[b].pressed,
|
|
510
|
-
device: inputSource.handedness === 'left' ? Device.LeftController : Device.RightController,
|
|
511
|
-
input: deviceInputMap[gp.mapping] && deviceInputMap[gp.mapping][b] ? deviceInputMap[gp.mapping][b] : Input.Trigger
|
|
512
|
-
});
|
|
513
|
-
model.lastGamepadValues[gp.index][hand].buttons[b] = gp.buttons[b].pressed;
|
|
514
|
-
}
|
|
515
|
-
if (model.lastGamepadValues[gp.index][hand].buttons[b] && gripPose != null) {
|
|
516
|
-
publicAPI.move3DEvent({
|
|
517
|
-
gamepad: gp,
|
|
518
|
-
position: gripPose.transform.position,
|
|
519
|
-
orientation: gripPose.transform.orientation,
|
|
520
|
-
device: inputSource.handedness === 'left' ? Device.LeftController : Device.RightController
|
|
521
|
-
});
|
|
487
|
+
if (!gamepad) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!(gamepad.index in model.lastGamepadValues)) {
|
|
491
|
+
model.lastGamepadValues[gamepad.index] = {
|
|
492
|
+
left: {
|
|
493
|
+
buttons: {}
|
|
494
|
+
},
|
|
495
|
+
right: {
|
|
496
|
+
buttons: {}
|
|
497
|
+
},
|
|
498
|
+
none: {
|
|
499
|
+
buttons: {}
|
|
522
500
|
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
for (let buttonIdx = 0; buttonIdx < gamepad.buttons.length; ++buttonIdx) {
|
|
504
|
+
if (!(buttonIdx in model.lastGamepadValues[gamepad.index][hand].buttons)) {
|
|
505
|
+
model.lastGamepadValues[gamepad.index][hand].buttons[buttonIdx] = false;
|
|
506
|
+
}
|
|
507
|
+
if (model.lastGamepadValues[gamepad.index][hand].buttons[buttonIdx] !== gamepad.buttons[buttonIdx].pressed && gripPose != null) {
|
|
508
|
+
publicAPI.button3DEvent({
|
|
509
|
+
gamepad,
|
|
510
|
+
position: gripPose.transform.position,
|
|
511
|
+
orientation: gripPose.transform.orientation,
|
|
512
|
+
targetPosition: targetRayPose.transform.position,
|
|
513
|
+
targetOrientation: targetRayPose.transform.orientation,
|
|
514
|
+
pressed: gamepad.buttons[buttonIdx].pressed,
|
|
515
|
+
device: inputSource.handedness === 'left' ? Device.LeftController : Device.RightController,
|
|
516
|
+
input: deviceInputMap[gamepad.mapping] && deviceInputMap[gamepad.mapping][buttonIdx] ? deviceInputMap[gamepad.mapping][buttonIdx] : Input.Trigger
|
|
517
|
+
});
|
|
518
|
+
model.lastGamepadValues[gamepad.index][hand].buttons[buttonIdx] = gamepad.buttons[buttonIdx].pressed;
|
|
519
|
+
}
|
|
520
|
+
if (model.lastGamepadValues[gamepad.index][hand].buttons[buttonIdx] && gripPose != null) {
|
|
521
|
+
publicAPI.move3DEvent({
|
|
522
|
+
gamepad,
|
|
523
|
+
position: gripPose.transform.position,
|
|
524
|
+
orientation: gripPose.transform.orientation,
|
|
525
|
+
targetPosition: targetRayPose.transform.position,
|
|
526
|
+
targetOrientation: targetRayPose.transform.orientation,
|
|
527
|
+
device: inputSource.handedness === 'left' ? Device.LeftController : Device.RightController
|
|
528
|
+
});
|
|
523
529
|
}
|
|
524
530
|
}
|
|
525
531
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import vtkPiecewiseFunction from './../../Common/DataModel/PiecewiseFunction';
|
|
2
2
|
import { Bounds, Range } from './../../types';
|
|
3
|
-
import
|
|
3
|
+
import vtkAbstractMapper3D, { IAbstractMapper3DInitialValues } from './AbstractMapper3D';
|
|
4
4
|
import { BlendMode, FilterMode } from './VolumeMapper/Constants';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
|
-
export interface IVolumeMapperInitialValues extends
|
|
9
|
+
export interface IVolumeMapperInitialValues extends IAbstractMapper3DInitialValues {
|
|
10
10
|
anisotropy?: number;
|
|
11
11
|
autoAdjustSampleDistances?: boolean;
|
|
12
12
|
averageIPScalarRange?: Range;
|
|
@@ -23,7 +23,7 @@ export interface IVolumeMapperInitialValues extends IAbstractMapperInitialValues
|
|
|
23
23
|
LAOKernelSize?: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export interface vtkVolumeMapper extends
|
|
26
|
+
export interface vtkVolumeMapper extends vtkAbstractMapper3D {
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Get the bounds for this mapper as [xmin, xmax, ymin, ymax,zmin, zmax].
|