@kitware/vtk.js 28.13.1 → 29.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 +7 -0
- package/Common/Core/Math/index.js +1 -1
- package/Common/Core/Math.js +1 -1
- package/Proxy/Core/ViewProxy.d.ts +2 -1
- package/Rendering/Core/CellPicker.d.ts +11 -0
- package/Rendering/Core/CellPicker.js +112 -4
- package/Rendering/Core/ColorTransferFunction.js +1 -1
- package/Rendering/Core/Coordinate.js +1 -1
- package/Rendering/Core/CubeAxesActor.js +1 -1
- package/Rendering/Core/ImageMapper.js +1 -1
- package/Rendering/Core/Picker.js +26 -2
- package/Rendering/Core/PointPicker.js +1 -1
- package/Rendering/Core/RenderWindow.d.ts +1 -0
- package/Rendering/Core/Renderer.js +1 -1
- package/Rendering/Core/ScalarBarActor.js +1 -1
- package/Rendering/Core/VolumeMapper.js +1 -1
- package/Rendering/Misc/FullScreenRenderWindow.d.ts +4 -1
- package/Rendering/Misc/FullScreenRenderWindow.js +2 -1
- package/Rendering/Misc/GenericRenderWindow.d.ts +3 -2
- package/Rendering/Misc/GenericRenderWindow.js +11 -9
- package/Rendering/OpenGL/PolyDataMapper2D.js +1 -1
- package/Rendering/OpenGL/RenderWindow.d.ts +0 -34
- package/Rendering/OpenGL/RenderWindow.js +2 -177
- package/Rendering/OpenGL/Texture.js +1 -1
- package/Rendering/OpenGL/VolumeMapper.js +4 -4
- package/Rendering/WebGPU/RenderWindow.js +12 -0
- package/Rendering/{OpenGL/RenderWindow → WebXR/RenderWindowHelper}/Constants.d.ts +2 -1
- package/Rendering/WebXR/RenderWindowHelper.d.ts +90 -0
- package/Rendering/WebXR/RenderWindowHelper.js +242 -0
- package/Widgets/Core/WidgetManager.d.ts +0 -24
- package/Widgets/Core/WidgetManager.js +5 -29
- package/Widgets/Widgets3D/AngleWidget.js +1 -1
- package/Widgets/Widgets3D/ResliceCursorWidget/behavior.js +1 -1
- package/Widgets/Widgets3D/ResliceCursorWidget/helpers.js +1 -1
- package/Widgets/Widgets3D.js +0 -2
- package/index.d.ts +2 -1
- package/package.json +2 -1
- package/Widgets/Widgets3D/DistanceWidget/behavior.js +0 -133
- package/Widgets/Widgets3D/DistanceWidget/state.js +0 -22
- package/Widgets/Widgets3D/DistanceWidget.js +0 -109
- /package/Rendering/{OpenGL/RenderWindow → WebXR/RenderWindowHelper}/Constants.js +0 -0
|
@@ -8,16 +8,12 @@ import vtkTextureUnitManager from './TextureUnitManager.js';
|
|
|
8
8
|
import vtkViewNodeFactory from './ViewNodeFactory.js';
|
|
9
9
|
import vtkRenderPass from '../SceneGraph/RenderPass.js';
|
|
10
10
|
import vtkRenderWindowViewNode from '../SceneGraph/RenderWindowViewNode.js';
|
|
11
|
-
import
|
|
12
|
-
import { createContextProxyHandler, GET_UNDERLYING_CONTEXT } from './RenderWindow/ContextProxy.js';
|
|
11
|
+
import { createContextProxyHandler } from './RenderWindow/ContextProxy.js';
|
|
13
12
|
|
|
14
13
|
const {
|
|
15
14
|
vtkDebugMacro,
|
|
16
15
|
vtkErrorMacro
|
|
17
16
|
} = macro;
|
|
18
|
-
const {
|
|
19
|
-
XrSessionTypes
|
|
20
|
-
} = Constants;
|
|
21
17
|
const SCREENSHOT_PLACEHOLDER = {
|
|
22
18
|
position: 'absolute',
|
|
23
19
|
top: 0,
|
|
@@ -25,12 +21,6 @@ const SCREENSHOT_PLACEHOLDER = {
|
|
|
25
21
|
width: '100%',
|
|
26
22
|
height: '100%'
|
|
27
23
|
};
|
|
28
|
-
const DEFAULT_RESET_FACTORS = {
|
|
29
|
-
rescaleFactor: 0.25,
|
|
30
|
-
// isotropic scale factor reduces apparent size of objects
|
|
31
|
-
translateZ: -1.5 // default translation initializes object in front of camera
|
|
32
|
-
};
|
|
33
|
-
|
|
34
24
|
function checkRenderTargetSupport(gl, format, type) {
|
|
35
25
|
// create temporary frame buffer and texture
|
|
36
26
|
const framebuffer = gl.createFramebuffer();
|
|
@@ -227,168 +217,6 @@ function vtkOpenGLRenderWindow(publicAPI, model) {
|
|
|
227
217
|
}
|
|
228
218
|
return new Proxy(result, cachingContextHandler);
|
|
229
219
|
};
|
|
230
|
-
|
|
231
|
-
// Request an XR session on the user device with WebXR,
|
|
232
|
-
// typically in response to a user request such as a button press
|
|
233
|
-
publicAPI.startXR = xrSessionType => {
|
|
234
|
-
if (navigator.xr === undefined) {
|
|
235
|
-
throw new Error('WebXR is not available');
|
|
236
|
-
}
|
|
237
|
-
model.xrSessionType = xrSessionType !== undefined ? xrSessionType : XrSessionTypes.HmdVR;
|
|
238
|
-
const isXrSessionAR = [XrSessionTypes.HmdAR, XrSessionTypes.MobileAR].includes(model.xrSessionType);
|
|
239
|
-
const sessionType = isXrSessionAR ? 'immersive-ar' : 'immersive-vr';
|
|
240
|
-
if (!navigator.xr.isSessionSupported(sessionType)) {
|
|
241
|
-
if (isXrSessionAR) {
|
|
242
|
-
throw new Error('Device does not support AR session');
|
|
243
|
-
} else {
|
|
244
|
-
throw new Error('VR display is not available');
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
if (model.xrSession === null) {
|
|
248
|
-
navigator.xr.requestSession(sessionType).then(publicAPI.enterXR, () => {
|
|
249
|
-
throw new Error('Failed to create XR session!');
|
|
250
|
-
});
|
|
251
|
-
} else {
|
|
252
|
-
throw new Error('XR Session already exists!');
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// When an XR session is available, set up the XRWebGLLayer
|
|
257
|
-
// and request the first animation frame for the device
|
|
258
|
-
publicAPI.enterXR = async xrSession => {
|
|
259
|
-
model.xrSession = xrSession;
|
|
260
|
-
model.oldCanvasSize = model.size.slice();
|
|
261
|
-
if (model.xrSession !== null) {
|
|
262
|
-
const gl = publicAPI.get3DContext();
|
|
263
|
-
await gl.makeXRCompatible();
|
|
264
|
-
const glLayer = new global.XRWebGLLayer(model.xrSession,
|
|
265
|
-
// constructor needs unproxied context
|
|
266
|
-
gl[GET_UNDERLYING_CONTEXT]());
|
|
267
|
-
publicAPI.setSize(glLayer.framebufferWidth, glLayer.framebufferHeight);
|
|
268
|
-
model.xrSession.updateRenderState({
|
|
269
|
-
baseLayer: glLayer
|
|
270
|
-
});
|
|
271
|
-
model.xrSession.requestReferenceSpace('local').then(refSpace => {
|
|
272
|
-
model.xrReferenceSpace = refSpace;
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// Initialize transparent background for augmented reality session
|
|
276
|
-
const isXrSessionAR = [XrSessionTypes.HmdAR, XrSessionTypes.MobileAR].includes(model.xrSessionType);
|
|
277
|
-
if (isXrSessionAR) {
|
|
278
|
-
const ren = model.renderable.getRenderers()[0];
|
|
279
|
-
model.preXrSessionBackground = ren.getBackground();
|
|
280
|
-
ren.setBackground([0, 0, 0, 0]);
|
|
281
|
-
}
|
|
282
|
-
publicAPI.resetXRScene();
|
|
283
|
-
model.renderable.getInteractor().switchToXRAnimation();
|
|
284
|
-
model.xrSceneFrame = model.xrSession.requestAnimationFrame(publicAPI.xrRender);
|
|
285
|
-
} else {
|
|
286
|
-
throw new Error('Failed to enter XR with a null xrSession.');
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
publicAPI.resetXRScene = function () {
|
|
290
|
-
let rescaleFactor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_RESET_FACTORS.rescaleFactor;
|
|
291
|
-
let translateZ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_RESET_FACTORS.translateZ;
|
|
292
|
-
// Adjust world-to-physical parameters for different modalities
|
|
293
|
-
|
|
294
|
-
const ren = model.renderable.getRenderers()[0];
|
|
295
|
-
ren.resetCamera();
|
|
296
|
-
const camera = ren.getActiveCamera();
|
|
297
|
-
let physicalScale = camera.getPhysicalScale();
|
|
298
|
-
const physicalTranslation = camera.getPhysicalTranslation();
|
|
299
|
-
const rescaledTranslateZ = translateZ * physicalScale;
|
|
300
|
-
physicalScale /= rescaleFactor;
|
|
301
|
-
physicalTranslation[2] += rescaledTranslateZ;
|
|
302
|
-
camera.setPhysicalScale(physicalScale);
|
|
303
|
-
camera.setPhysicalTranslation(physicalTranslation);
|
|
304
|
-
// Clip at 0.1m, 100.0m in physical space by default
|
|
305
|
-
camera.setClippingRange(0.1 * physicalScale, 100.0 * physicalScale);
|
|
306
|
-
};
|
|
307
|
-
publicAPI.stopXR = async () => {
|
|
308
|
-
if (navigator.xr === undefined) {
|
|
309
|
-
// WebXR polyfill not available so nothing to do
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
if (model.xrSession !== null) {
|
|
313
|
-
model.xrSession.cancelAnimationFrame(model.xrSceneFrame);
|
|
314
|
-
model.renderable.getInteractor().returnFromXRAnimation();
|
|
315
|
-
const gl = publicAPI.get3DContext();
|
|
316
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
317
|
-
await model.xrSession.end().catch(error => {
|
|
318
|
-
if (!(error instanceof DOMException)) {
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
model.xrSession = null;
|
|
323
|
-
}
|
|
324
|
-
if (model.oldCanvasSize !== undefined) {
|
|
325
|
-
publicAPI.setSize(...model.oldCanvasSize);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Reset to default canvas
|
|
329
|
-
const ren = model.renderable.getRenderers()[0];
|
|
330
|
-
if (model.preXrSessionBackground != null) {
|
|
331
|
-
ren.setBackground(model.preXrSessionBackground);
|
|
332
|
-
model.preXrSessionBackground = null;
|
|
333
|
-
}
|
|
334
|
-
ren.getActiveCamera().setProjectionMatrix(null);
|
|
335
|
-
ren.resetCamera();
|
|
336
|
-
ren.setViewport(0.0, 0, 1.0, 1.0);
|
|
337
|
-
publicAPI.traverseAllPasses();
|
|
338
|
-
};
|
|
339
|
-
publicAPI.xrRender = async (t, frame) => {
|
|
340
|
-
const xrSession = frame.session;
|
|
341
|
-
const isXrSessionHMD = [XrSessionTypes.HmdVR, XrSessionTypes.HmdAR].includes(model.xrSessionType);
|
|
342
|
-
model.renderable.getInteractor().updateXRGamepads(xrSession, frame, model.xrReferenceSpace);
|
|
343
|
-
model.xrSceneFrame = model.xrSession.requestAnimationFrame(publicAPI.xrRender);
|
|
344
|
-
const xrPose = frame.getViewerPose(model.xrReferenceSpace);
|
|
345
|
-
if (xrPose) {
|
|
346
|
-
const gl = publicAPI.get3DContext();
|
|
347
|
-
if (model.xrSessionType === XrSessionTypes.MobileAR && model.oldCanvasSize !== undefined) {
|
|
348
|
-
gl.canvas.width = model.oldCanvasSize[0];
|
|
349
|
-
gl.canvas.height = model.oldCanvasSize[1];
|
|
350
|
-
}
|
|
351
|
-
const glLayer = xrSession.renderState.baseLayer;
|
|
352
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
|
|
353
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
354
|
-
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
355
|
-
publicAPI.setSize(glLayer.framebufferWidth, glLayer.framebufferHeight);
|
|
356
|
-
|
|
357
|
-
// get the first renderer
|
|
358
|
-
const ren = model.renderable.getRenderers()[0];
|
|
359
|
-
|
|
360
|
-
// Do a render pass for each eye
|
|
361
|
-
xrPose.views.forEach((view, index) => {
|
|
362
|
-
const viewport = glLayer.getViewport(view);
|
|
363
|
-
if (isXrSessionHMD) {
|
|
364
|
-
if (view.eye === 'left') {
|
|
365
|
-
ren.setViewport(0, 0, 0.5, 1.0);
|
|
366
|
-
} else if (view.eye === 'right') {
|
|
367
|
-
ren.setViewport(0.5, 0, 1.0, 1.0);
|
|
368
|
-
} else {
|
|
369
|
-
// No handling for non-eye viewport
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
} else if (model.xrSessionType === XrSessionTypes.LookingGlassVR) {
|
|
373
|
-
const startX = viewport.x / glLayer.framebufferWidth;
|
|
374
|
-
const startY = viewport.y / glLayer.framebufferHeight;
|
|
375
|
-
const endX = (viewport.x + viewport.width) / glLayer.framebufferWidth;
|
|
376
|
-
const endY = (viewport.y + viewport.height) / glLayer.framebufferHeight;
|
|
377
|
-
ren.setViewport(startX, startY, endX, endY);
|
|
378
|
-
} else {
|
|
379
|
-
ren.setViewport(0, 0, 1, 1);
|
|
380
|
-
}
|
|
381
|
-
ren.getActiveCamera().computeViewParametersFromPhysicalMatrix(view.transform.inverse.matrix);
|
|
382
|
-
ren.getActiveCamera().setProjectionMatrix(view.projectionMatrix);
|
|
383
|
-
publicAPI.traverseAllPasses();
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
// Reset scissorbox before any subsequent rendering to external displays
|
|
387
|
-
// on frame end, such as rendering to a Looking Glass display.
|
|
388
|
-
gl.scissor(0, 0, glLayer.framebufferWidth, glLayer.framebufferHeight);
|
|
389
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
220
|
publicAPI.restoreContext = () => {
|
|
393
221
|
const rp = vtkRenderPass.newInstance();
|
|
394
222
|
rp.setCurrentOperation('Release');
|
|
@@ -855,9 +683,6 @@ const DEFAULT_VALUES = {
|
|
|
855
683
|
defaultToWebgl2: true,
|
|
856
684
|
// attempt webgl2 on by default
|
|
857
685
|
activeFramebuffer: null,
|
|
858
|
-
xrSession: null,
|
|
859
|
-
xrReferenceSpace: null,
|
|
860
|
-
xrSupported: true,
|
|
861
686
|
imageFormat: 'image/png',
|
|
862
687
|
useOffScreen: false,
|
|
863
688
|
useBackgroundImage: false
|
|
@@ -906,7 +731,7 @@ function extend(publicAPI, model) {
|
|
|
906
731
|
macro.event(publicAPI, model, 'imageReady');
|
|
907
732
|
|
|
908
733
|
// Build VTK API
|
|
909
|
-
macro.get(publicAPI, model, ['shaderCache', 'textureUnitManager', 'webgl2', '
|
|
734
|
+
macro.get(publicAPI, model, ['shaderCache', 'textureUnitManager', 'webgl2', 'useBackgroundImage', 'activeFramebuffer']);
|
|
910
735
|
macro.setGet(publicAPI, model, ['initialized', 'context', 'canvas', 'renderPasses', 'notifyStartCaptureImage', 'defaultToWebgl2', 'cursor', 'useOffScreen']);
|
|
911
736
|
macro.setGetArray(publicAPI, model, ['size'], 2);
|
|
912
737
|
macro.event(publicAPI, model, 'windowResizeEvent');
|
|
@@ -2,7 +2,7 @@ import Constants from './Texture/Constants.js';
|
|
|
2
2
|
import HalfFloat from '../../Common/Core/HalfFloat.js';
|
|
3
3
|
import { n as newInstance$1, o as obj, s as set, e as setGet, g as get, i as moveToProtected, a as newTypedArray, c as macro } from '../../macros2.js';
|
|
4
4
|
import vtkDataArray from '../../Common/Core/DataArray.js';
|
|
5
|
-
import {
|
|
5
|
+
import { S as isPowerOfTwo, O as nearestPowerOfTwo } from '../../Common/Core/Math/index.js';
|
|
6
6
|
import vtkViewNode from '../SceneGraph/ViewNode.js';
|
|
7
7
|
import { registerOverride } from './ViewNodeFactory.js';
|
|
8
8
|
|
|
@@ -175,8 +175,8 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
175
175
|
|
|
176
176
|
// if we have a ztexture then declare it and use it
|
|
177
177
|
if (model.zBufferTexture !== null) {
|
|
178
|
-
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Dec', ['uniform sampler2D zBufferTexture;', 'uniform float
|
|
179
|
-
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Impl', ['vec4 depthVec = texture2D(zBufferTexture, vec2(gl_FragCoord.x /
|
|
178
|
+
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Dec', ['uniform sampler2D zBufferTexture;', 'uniform float vpZWidth;', 'uniform float vpZHeight;']).result;
|
|
179
|
+
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Impl', ['vec4 depthVec = texture2D(zBufferTexture, vec2(gl_FragCoord.x / vpZWidth, gl_FragCoord.y/vpZHeight));', 'float zdepth = (depthVec.r*256.0 + depthVec.g)/257.0;', 'zdepth = zdepth * 2.0 - 1.0;', 'if (cameraParallel == 0) {', 'zdepth = -2.0 * camFar * camNear / (zdepth*(camFar-camNear)-(camFar+camNear)) - camNear;}', 'else {', 'zdepth = (zdepth + 1.0) * 0.5 * (camFar - camNear);}\n', 'zdepth = -zdepth/rayDir.z;', 'dists.y = min(zdepth,dists.y);']).result;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// Set the BlendMode approach
|
|
@@ -362,8 +362,8 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
362
362
|
if (model.zBufferTexture !== null) {
|
|
363
363
|
program.setUniformi('zBufferTexture', model.zBufferTexture.getTextureUnit());
|
|
364
364
|
const size = model._useSmallViewport ? [model._smallViewportWidth, model._smallViewportHeight] : model._openGLRenderWindow.getFramebufferSize();
|
|
365
|
-
program.setUniformf('
|
|
366
|
-
program.setUniformf('
|
|
365
|
+
program.setUniformf('vpZWidth', size[0]);
|
|
366
|
+
program.setUniformf('vpZHeight', size[1]);
|
|
367
367
|
}
|
|
368
368
|
};
|
|
369
369
|
publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
|
|
@@ -490,6 +490,17 @@ function vtkWebGPURenderWindow(publicAPI, model) {
|
|
|
490
490
|
ret.setWebGPURenderWindow(publicAPI);
|
|
491
491
|
return ret;
|
|
492
492
|
};
|
|
493
|
+
const superSetSize = publicAPI.setSize;
|
|
494
|
+
publicAPI.setSize = (width, height) => {
|
|
495
|
+
const modified = superSetSize(width, height);
|
|
496
|
+
if (modified) {
|
|
497
|
+
publicAPI.invokeWindowResizeEvent({
|
|
498
|
+
width,
|
|
499
|
+
height
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return modified;
|
|
503
|
+
};
|
|
493
504
|
publicAPI.delete = macro.chain(publicAPI.delete, publicAPI.setViewStream);
|
|
494
505
|
}
|
|
495
506
|
|
|
@@ -555,6 +566,7 @@ function extend(publicAPI, model) {
|
|
|
555
566
|
macro.get(publicAPI, model, ['commandEncoder', 'device', 'presentationFormat', 'useBackgroundImage', 'xrSupported']);
|
|
556
567
|
macro.setGet(publicAPI, model, ['initialized', 'context', 'canvas', 'device', 'renderPasses', 'notifyStartCaptureImage', 'cursor', 'useOffScreen']);
|
|
557
568
|
macro.setGetArray(publicAPI, model, ['size'], 2);
|
|
569
|
+
macro.event(publicAPI, model, 'windowResizeEvent');
|
|
558
570
|
|
|
559
571
|
// Object methods
|
|
560
572
|
vtkWebGPURenderWindow(publicAPI, model);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { vtkObject } from './../../interfaces';
|
|
2
|
+
import { Nullable } from './../../types';
|
|
3
|
+
import vtkOpenGLRenderWindow from './../OpenGL/RenderWindow';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
export interface IWebXRRenderWindowHelperInitialValues {
|
|
9
|
+
initialized: boolean,
|
|
10
|
+
initCanvasSize?: [number, number],
|
|
11
|
+
initBackground?: [number, number, number, number],
|
|
12
|
+
renderWindow?: Nullable<vtkOpenGLRenderWindow>,
|
|
13
|
+
xrSession?: Nullable<XRSession>,
|
|
14
|
+
xrSessionType: number,
|
|
15
|
+
xrReferenceSpace?: any,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface vtkWebXRRenderWindowHelper extends vtkObject {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the instance.
|
|
22
|
+
*/
|
|
23
|
+
initialize(): void;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Request an XR session on the user device with WebXR,
|
|
27
|
+
* typically in response to a user request such as a button press.
|
|
28
|
+
*/
|
|
29
|
+
startXR(xrSessionType: Number): void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* When an XR session is available, set up the XRWebGLLayer
|
|
33
|
+
* and request the first animation frame for the device
|
|
34
|
+
*/
|
|
35
|
+
enterXR(): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Adjust world-to-physical parameters for different viewing modalities
|
|
39
|
+
*
|
|
40
|
+
* @param {Number} inputRescaleFactor
|
|
41
|
+
* @param {Number} inputTranslateZ
|
|
42
|
+
*/
|
|
43
|
+
resetXRScene(inputRescaleFactor: number, inputTranslateZ: number): void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Request to stop the current XR session
|
|
47
|
+
*/
|
|
48
|
+
stopXR(): void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the underlying render window to drive XR rendering.
|
|
52
|
+
*/
|
|
53
|
+
getRenderWindow(): Nullable<vtkOpenGLRenderWindow>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Set the underlying render window to drive XR rendering.
|
|
57
|
+
*/
|
|
58
|
+
setRenderWindow(renderWindow:Nullable<vtkOpenGLRenderWindow>);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the active WebXR session.
|
|
62
|
+
*/
|
|
63
|
+
getXrSession(): Nullable<XRSession>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Method used to decorate a given object (publicAPI+model) with vtkWebXRRenderWindowHelper characteristics.
|
|
68
|
+
*
|
|
69
|
+
* @param publicAPI object on which methods will be bounds (public)
|
|
70
|
+
* @param model object on which data structure will be bounds (protected)
|
|
71
|
+
* @param {IWebXRRenderWindowHelperInitialValues} [initialValues] (default: {})
|
|
72
|
+
*/
|
|
73
|
+
export function extend(publicAPI: object, model: object, initialValues?: IWebXRRenderWindowHelperInitialValues): void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Method used to create a new instance of vtkWebXRRenderWindowHelper.
|
|
77
|
+
* @param {IWebXRRenderWindowHelperInitialValues} [initialValues] for pre-setting some of its content
|
|
78
|
+
*/
|
|
79
|
+
export function newInstance(initialValues?: IWebXRRenderWindowHelperInitialValues): vtkWebXRRenderWindowHelper;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* WebXR rendering helper
|
|
83
|
+
*
|
|
84
|
+
* vtkWebXRRenderWindowHelper is designed to wrap a vtkRenderWindow for XR rendering.
|
|
85
|
+
*/
|
|
86
|
+
export declare const vtkWebXRRenderWindowHelper: {
|
|
87
|
+
newInstance: typeof newInstance,
|
|
88
|
+
extend: typeof extend,
|
|
89
|
+
};
|
|
90
|
+
export default vtkWebXRRenderWindowHelper;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { m as macro } from '../../macros2.js';
|
|
2
|
+
import Constants from './RenderWindowHelper/Constants.js';
|
|
3
|
+
import { GET_UNDERLYING_CONTEXT } from '../OpenGL/RenderWindow/ContextProxy.js';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
XrSessionTypes
|
|
7
|
+
} = Constants;
|
|
8
|
+
const DEFAULT_RESET_FACTORS = {
|
|
9
|
+
rescaleFactor: 0.25,
|
|
10
|
+
// isotropic scale factor reduces apparent size of objects
|
|
11
|
+
translateZ: -1.5 // default translation initializes object in front of camera
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ----------------------------------------------------------------------------
|
|
15
|
+
// vtkWebXRRenderWindowHelper methods
|
|
16
|
+
// ----------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
function vtkWebXRRenderWindowHelper(publicAPI, model) {
|
|
19
|
+
// Set our className
|
|
20
|
+
model.classHierarchy.push('vtkWebXRRenderWindowHelper');
|
|
21
|
+
publicAPI.initialize = renderWindow => {
|
|
22
|
+
if (!model.initialized) {
|
|
23
|
+
model.renderWindow = renderWindow;
|
|
24
|
+
model.initialized = true;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
publicAPI.getXrSupported = () => navigator.xr !== undefined;
|
|
28
|
+
|
|
29
|
+
// Request an XR session on the user device with WebXR,
|
|
30
|
+
// typically in response to a user request such as a button press
|
|
31
|
+
publicAPI.startXR = xrSessionType => {
|
|
32
|
+
if (navigator.xr === undefined) {
|
|
33
|
+
throw new Error('WebXR is not available');
|
|
34
|
+
}
|
|
35
|
+
model.xrSessionType = xrSessionType !== undefined ? xrSessionType : XrSessionTypes.HmdVR;
|
|
36
|
+
const isXrSessionAR = [XrSessionTypes.HmdAR, XrSessionTypes.MobileAR].includes(model.xrSessionType);
|
|
37
|
+
const sessionType = isXrSessionAR ? 'immersive-ar' : 'immersive-vr';
|
|
38
|
+
if (!navigator.xr.isSessionSupported(sessionType)) {
|
|
39
|
+
if (isXrSessionAR) {
|
|
40
|
+
throw new Error('Device does not support AR session');
|
|
41
|
+
} else {
|
|
42
|
+
throw new Error('VR display is not available');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (model.xrSession === null) {
|
|
46
|
+
navigator.xr.requestSession(sessionType).then(publicAPI.enterXR, () => {
|
|
47
|
+
throw new Error('Failed to create XR session!');
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error('XR Session already exists!');
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// When an XR session is available, set up the XRWebGLLayer
|
|
55
|
+
// and request the first animation frame for the device
|
|
56
|
+
publicAPI.enterXR = async xrSession => {
|
|
57
|
+
model.xrSession = xrSession;
|
|
58
|
+
model.initCanvasSize = model.renderWindow.getSize();
|
|
59
|
+
if (model.xrSession !== null) {
|
|
60
|
+
const gl = model.renderWindow.get3DContext();
|
|
61
|
+
await gl.makeXRCompatible();
|
|
62
|
+
|
|
63
|
+
// XRWebGLLayer definition is deferred to here to give any WebXR polyfill
|
|
64
|
+
// an opportunity to override this definition.
|
|
65
|
+
const {
|
|
66
|
+
XRWebGLLayer
|
|
67
|
+
} = window;
|
|
68
|
+
const glLayer = new XRWebGLLayer(model.xrSession,
|
|
69
|
+
// constructor needs unproxied context
|
|
70
|
+
gl[GET_UNDERLYING_CONTEXT]());
|
|
71
|
+
model.renderWindow.setSize(glLayer.framebufferWidth, glLayer.framebufferHeight);
|
|
72
|
+
model.xrSession.updateRenderState({
|
|
73
|
+
baseLayer: glLayer
|
|
74
|
+
});
|
|
75
|
+
model.xrSession.requestReferenceSpace('local').then(refSpace => {
|
|
76
|
+
model.xrReferenceSpace = refSpace;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Initialize transparent background for augmented reality session
|
|
80
|
+
const isXrSessionAR = [XrSessionTypes.HmdAR, XrSessionTypes.MobileAR].includes(model.xrSessionType);
|
|
81
|
+
if (isXrSessionAR) {
|
|
82
|
+
const ren = model.renderWindow.getRenderable().getRenderers()[0];
|
|
83
|
+
model.initBackground = ren.getBackground();
|
|
84
|
+
ren.setBackground([0, 0, 0, 0]);
|
|
85
|
+
}
|
|
86
|
+
publicAPI.resetXRScene();
|
|
87
|
+
model.renderWindow.getRenderable().getInteractor().switchToXRAnimation();
|
|
88
|
+
model.xrSceneFrame = model.xrSession.requestAnimationFrame(model.xrRender);
|
|
89
|
+
publicAPI.modified();
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error('Failed to enter XR with a null xrSession.');
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
publicAPI.resetXRScene = function () {
|
|
95
|
+
let rescaleFactor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_RESET_FACTORS.rescaleFactor;
|
|
96
|
+
let translateZ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_RESET_FACTORS.translateZ;
|
|
97
|
+
// Adjust world-to-physical parameters for different modalities
|
|
98
|
+
|
|
99
|
+
const ren = model.renderWindow.getRenderable().getRenderers()[0];
|
|
100
|
+
ren.resetCamera();
|
|
101
|
+
const camera = ren.getActiveCamera();
|
|
102
|
+
let physicalScale = camera.getPhysicalScale();
|
|
103
|
+
const physicalTranslation = camera.getPhysicalTranslation();
|
|
104
|
+
const rescaledTranslateZ = translateZ * physicalScale;
|
|
105
|
+
physicalScale /= rescaleFactor;
|
|
106
|
+
physicalTranslation[2] += rescaledTranslateZ;
|
|
107
|
+
camera.setPhysicalScale(physicalScale);
|
|
108
|
+
camera.setPhysicalTranslation(physicalTranslation);
|
|
109
|
+
// Clip at 0.1m, 100.0m in physical space by default
|
|
110
|
+
camera.setClippingRange(0.1 * physicalScale, 100.0 * physicalScale);
|
|
111
|
+
};
|
|
112
|
+
publicAPI.stopXR = async () => {
|
|
113
|
+
if (navigator.xr === undefined) {
|
|
114
|
+
// WebXR polyfill not available so nothing to do
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (model.xrSession !== null) {
|
|
118
|
+
model.xrSession.cancelAnimationFrame(model.xrSceneFrame);
|
|
119
|
+
model.renderWindow.getRenderable().getInteractor().returnFromXRAnimation();
|
|
120
|
+
const gl = model.renderWindow.get3DContext();
|
|
121
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
122
|
+
await model.xrSession.end().catch(error => {
|
|
123
|
+
if (!(error instanceof DOMException)) {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
model.xrSession = null;
|
|
128
|
+
}
|
|
129
|
+
if (model.initCanvasSize !== null) {
|
|
130
|
+
model.renderWindow.setSize(...model.initCanvasSize);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Reset to default canvas
|
|
134
|
+
const ren = model.renderWindow.getRenderable().getRenderers()[0];
|
|
135
|
+
if (model.initBackground != null) {
|
|
136
|
+
ren.setBackground(model.initBackground);
|
|
137
|
+
model.initBackground = null;
|
|
138
|
+
}
|
|
139
|
+
ren.getActiveCamera().setProjectionMatrix(null);
|
|
140
|
+
ren.resetCamera();
|
|
141
|
+
ren.setViewport(0.0, 0, 1.0, 1.0);
|
|
142
|
+
model.renderWindow.traverseAllPasses();
|
|
143
|
+
publicAPI.modified();
|
|
144
|
+
};
|
|
145
|
+
model.xrRender = async (t, frame) => {
|
|
146
|
+
const xrSession = frame.session;
|
|
147
|
+
const isXrSessionHMD = [XrSessionTypes.HmdVR, XrSessionTypes.HmdAR].includes(model.xrSessionType);
|
|
148
|
+
model.renderWindow.getRenderable().getInteractor().updateXRGamepads(xrSession, frame, model.xrReferenceSpace);
|
|
149
|
+
model.xrSceneFrame = model.xrSession.requestAnimationFrame(model.xrRender);
|
|
150
|
+
const xrPose = frame.getViewerPose(model.xrReferenceSpace);
|
|
151
|
+
if (xrPose) {
|
|
152
|
+
const gl = model.renderWindow.get3DContext();
|
|
153
|
+
if (model.xrSessionType === XrSessionTypes.MobileAR && model.initCanvasSize !== null) {
|
|
154
|
+
gl.canvas.width = model.initCanvasSize[0];
|
|
155
|
+
gl.canvas.height = model.initCanvasSize[1];
|
|
156
|
+
}
|
|
157
|
+
const glLayer = xrSession.renderState.baseLayer;
|
|
158
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
|
|
159
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
160
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
161
|
+
model.renderWindow.setSize(glLayer.framebufferWidth, glLayer.framebufferHeight);
|
|
162
|
+
|
|
163
|
+
// get the first renderer
|
|
164
|
+
const ren = model.renderWindow.getRenderable().getRenderers()[0];
|
|
165
|
+
|
|
166
|
+
// Do a render pass for each eye
|
|
167
|
+
xrPose.views.forEach((view, index) => {
|
|
168
|
+
const viewport = glLayer.getViewport(view);
|
|
169
|
+
if (isXrSessionHMD) {
|
|
170
|
+
if (view.eye === 'left') {
|
|
171
|
+
ren.setViewport(0, 0, 0.5, 1.0);
|
|
172
|
+
} else if (view.eye === 'right') {
|
|
173
|
+
ren.setViewport(0.5, 0, 1.0, 1.0);
|
|
174
|
+
} else {
|
|
175
|
+
// No handling for non-eye viewport
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
} else if (model.xrSessionType === XrSessionTypes.LookingGlassVR) {
|
|
179
|
+
const startX = viewport.x / glLayer.framebufferWidth;
|
|
180
|
+
const startY = viewport.y / glLayer.framebufferHeight;
|
|
181
|
+
const endX = (viewport.x + viewport.width) / glLayer.framebufferWidth;
|
|
182
|
+
const endY = (viewport.y + viewport.height) / glLayer.framebufferHeight;
|
|
183
|
+
ren.setViewport(startX, startY, endX, endY);
|
|
184
|
+
} else {
|
|
185
|
+
ren.setViewport(0, 0, 1, 1);
|
|
186
|
+
}
|
|
187
|
+
ren.getActiveCamera().computeViewParametersFromPhysicalMatrix(view.transform.inverse.matrix);
|
|
188
|
+
ren.getActiveCamera().setProjectionMatrix(view.projectionMatrix);
|
|
189
|
+
model.renderWindow.traverseAllPasses();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Reset scissorbox before any subsequent rendering to external displays
|
|
193
|
+
// on frame end, such as rendering to a Looking Glass display.
|
|
194
|
+
gl.scissor(0, 0, glLayer.framebufferWidth, glLayer.framebufferHeight);
|
|
195
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
publicAPI.delete = macro.chain(publicAPI.delete);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ----------------------------------------------------------------------------
|
|
202
|
+
// Object factory
|
|
203
|
+
// ----------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
const DEFAULT_VALUES = {
|
|
206
|
+
initialized: false,
|
|
207
|
+
initCanvasSize: null,
|
|
208
|
+
initBackground: null,
|
|
209
|
+
renderWindow: null,
|
|
210
|
+
xrSession: null,
|
|
211
|
+
xrSessionType: 0,
|
|
212
|
+
xrReferenceSpace: null
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// ----------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
function extend(publicAPI, model) {
|
|
218
|
+
let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
219
|
+
Object.assign(model, DEFAULT_VALUES, initialValues);
|
|
220
|
+
|
|
221
|
+
// Build VTK API
|
|
222
|
+
macro.obj(publicAPI, model);
|
|
223
|
+
macro.event(publicAPI, model, 'event');
|
|
224
|
+
macro.get(publicAPI, model, ['xrSession']);
|
|
225
|
+
macro.setGet(publicAPI, model, ['renderWindow']);
|
|
226
|
+
|
|
227
|
+
// Object methods
|
|
228
|
+
vtkWebXRRenderWindowHelper(publicAPI, model);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ----------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
const newInstance = macro.newInstance(extend, 'vtkWebXRRenderWindowHelper');
|
|
234
|
+
|
|
235
|
+
// ----------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
var index = {
|
|
238
|
+
newInstance,
|
|
239
|
+
extend
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export { index as default, extend, newInstance };
|
|
@@ -99,16 +99,6 @@ export interface vtkWidgetManager extends vtkObject {
|
|
|
99
99
|
*/
|
|
100
100
|
getPickingEnabled(): boolean;
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* @deprecated
|
|
104
|
-
*/
|
|
105
|
-
getUseSvgLayer(): boolean;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @deprecated
|
|
109
|
-
*/
|
|
110
|
-
setUseSvgLayer(use: boolean): boolean;
|
|
111
|
-
|
|
112
102
|
/**
|
|
113
103
|
* Enable the picking.
|
|
114
104
|
*/
|
|
@@ -165,16 +155,6 @@ export interface vtkWidgetManager extends vtkObject {
|
|
|
165
155
|
*/
|
|
166
156
|
getSelectedDataForXY(x: number, y: number): Promise<ISelectedData>;
|
|
167
157
|
|
|
168
|
-
/**
|
|
169
|
-
* @deprecated
|
|
170
|
-
*/
|
|
171
|
-
updateSelectionFromXY(x: number, y: number): void;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* @deprecated
|
|
175
|
-
*/
|
|
176
|
-
updateSelectionFromMouseEvent(event: MouseEvent): void;
|
|
177
|
-
|
|
178
158
|
/**
|
|
179
159
|
* The all currently selected data.
|
|
180
160
|
*/
|
|
@@ -197,10 +177,6 @@ export interface IWidgetManagerInitialValues {
|
|
|
197
177
|
captureOn?: CaptureOn;
|
|
198
178
|
viewType?: ViewTypes;
|
|
199
179
|
pickingEnabled?: boolean;
|
|
200
|
-
/**
|
|
201
|
-
* @deprecated
|
|
202
|
-
*/
|
|
203
|
-
useSvgLayer?: boolean;
|
|
204
180
|
}
|
|
205
181
|
|
|
206
182
|
/**
|