diff --git a/.eslintrc.js b/.eslintrc.js
index 834df991194..71fdc9a6ae6 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -37,6 +37,7 @@ module.exports = {
// ],
globals: {
__BASE_PATH__: false,
+ VRFrameData: true,
},
'settings': {
'import/resolver': 'webpack'
diff --git a/Examples/Geometry/VR/controller.html b/Examples/Geometry/VR/controller.html
new file mode 100644
index 00000000000..00e847cb3f8
--- /dev/null
+++ b/Examples/Geometry/VR/controller.html
@@ -0,0 +1,21 @@
+
diff --git a/Examples/Geometry/VR/index.js b/Examples/Geometry/VR/index.js
new file mode 100644
index 00000000000..bb67b372d72
--- /dev/null
+++ b/Examples/Geometry/VR/index.js
@@ -0,0 +1,101 @@
+import 'vtk.js/Sources/favicon';
+
+import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
+import vtkCalculator from 'vtk.js/Sources/Filters/General/Calculator';
+import vtkConeSource from 'vtk.js/Sources/Filters/Sources/ConeSource';
+import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
+import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
+import { AttributeTypes } from 'vtk.js/Sources/Common/DataModel/DataSetAttributes/Constants';
+import { FieldDataTypes } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
+
+import controlPanel from './controller.html';
+
+// ----------------------------------------------------------------------------
+// Standard rendering code setup
+// ----------------------------------------------------------------------------
+
+const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ background: [0, 0, 0] });
+const renderer = fullScreenRenderer.getRenderer();
+const renderWindow = fullScreenRenderer.getRenderWindow();
+
+// ----------------------------------------------------------------------------
+// Example code
+// ----------------------------------------------------------------------------
+// create a filter on the fly, sort of cool, this is a random scalars
+// filter we create inline, for a simple cone you would not need
+// this
+// ----------------------------------------------------------------------------
+
+const coneSource = vtkConeSource.newInstance({ height: 100.0, radius: 50.0 });
+// const coneSource = vtkConeSource.newInstance({ height: 1.0, radius: 0.5 });
+const filter = vtkCalculator.newInstance();
+
+filter.setInputConnection(coneSource.getOutputPort());
+// filter.setFormulaSimple(FieldDataTypes.CELL, [], 'random', () => Math.random());
+filter.setFormula({
+ getArrays: inputDataSets => ({
+ input: [],
+ output: [
+ { location: FieldDataTypes.CELL, name: 'Random', dataType: 'Float32Array', attribute: AttributeTypes.SCALARS },
+ ],
+ }),
+ evaluate: (arraysIn, arraysOut) => {
+ const [scalars] = arraysOut.map(d => d.getData());
+ for (let i = 0; i < scalars.length; i++) {
+ scalars[i] = Math.random();
+ }
+ },
+});
+
+const mapper = vtkMapper.newInstance();
+mapper.setInputConnection(filter.getOutputPort());
+
+const actor = vtkActor.newInstance();
+actor.setMapper(mapper);
+actor.setPosition(20.0, 0.0, 0.0);
+
+renderer.addActor(actor);
+renderer.resetCamera();
+renderWindow.render();
+
+// -----------------------------------------------------------
+// UI control handling
+// -----------------------------------------------------------
+
+fullScreenRenderer.addController(controlPanel);
+const representationSelector = document.querySelector('.representations');
+const resolutionChange = document.querySelector('.resolution');
+const vrbutton = document.querySelector('.vrbutton');
+
+representationSelector.addEventListener('change', (e) => {
+ const newRepValue = Number(e.target.value);
+ actor.getProperty().setRepresentation(newRepValue);
+ renderWindow.render();
+});
+
+resolutionChange.addEventListener('input', (e) => {
+ const resolution = Number(e.target.value);
+ coneSource.setResolution(resolution);
+ renderWindow.render();
+});
+
+vrbutton.addEventListener('click', (e) => {
+ if (vrbutton.textContent === 'Send To VR') {
+ fullScreenRenderer.getOpenGLRenderWindow().startVR();
+ vrbutton.textContent = 'Return From VR';
+ } else {
+ fullScreenRenderer.getOpenGLRenderWindow().stopVR();
+ vrbutton.textContent = 'Send To VR';
+ }
+});
+
+// -----------------------------------------------------------
+// Make some variables global so that you can inspect and
+// modify objects in your browser's developer console:
+// -----------------------------------------------------------
+
+global.source = coneSource;
+global.mapper = mapper;
+global.actor = actor;
+global.renderer = renderer;
+global.renderWindow = renderWindow;
diff --git a/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js b/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js
index a0a039dee07..95f44194695 100644
--- a/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js
+++ b/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js
@@ -1,7 +1,9 @@
import macro from 'vtk.js/Sources/macro';
import vtkInteractorStyle from 'vtk.js/Sources/Rendering/Core/InteractorStyle';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
-import { States } from 'vtk.js/Sources/Rendering/Core/InteractorStyle/Constants';
+import { Device, Input } from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor/Constants';
+
+const { States } = vtkInteractorStyle;
/* eslint-disable no-lonely-if */
@@ -47,6 +49,61 @@ function vtkInteractorStyleTrackballCamera(publicAPI, model) {
}
};
+ publicAPI.handleButton3D = (arg) => {
+ const ed = arg.calldata;
+ publicAPI.findPokedRenderer(0, 0);
+ if (model.currentRenderer === null) {
+ return;
+ }
+
+ if (ed && ed.pressed &&
+ ed.device === Device.RightController &&
+ ed.input === Input.TrackPad) {
+ publicAPI.startCameraPose();
+ publicAPI.setAnimationStateOn();
+ return;
+ }
+ if (ed && !ed.pressed &&
+ ed.device === Device.RightController &&
+ ed.input === Input.TrackPad &&
+ model.state === States.IS_CAMERA_POSE) {
+ publicAPI.endCameraPose();
+ publicAPI.setAnimationStateOff();
+ // return;
+ }
+ };
+
+ publicAPI.handleMove3D = (arg) => {
+ const ed = arg.calldata;
+ switch (model.state) {
+ case States.IS_CAMERA_POSE:
+ publicAPI.updateCameraPose(ed);
+ break;
+ default:
+ }
+ };
+
+ publicAPI.updateCameraPose = (ed) => {
+ // move the world in the direction of the
+ // controller
+ const camera = model.currentRenderer.getActiveCamera();
+ const oldTrans = camera.getPhysicalTranslation();
+
+ // look at the y axis to determine how fast / what direction to move
+ const speed = ed.gamepad.axes[1];
+
+ // 0.05 meters / frame movement
+ const pscale = speed * 0.05 / camera.getPhysicalScale();
+
+ // convert orientation to world coordinate direction
+ const dir = camera.physicalOrientationToWorldDirection(ed.orientation);
+
+ camera.setPhysicalTranslation(
+ oldTrans[0] + (dir[0] * pscale),
+ oldTrans[1] + (dir[1] * pscale),
+ oldTrans[2] + (dir[2] * pscale));
+ };
+
//----------------------------------------------------------------------------
publicAPI.handleLeftButtonPress = () => {
const pos = model.interactor.getEventPosition(model.interactor.getPointerIndex());
diff --git a/Sources/Rendering/Core/Camera/index.js b/Sources/Rendering/Core/Camera/index.js
index 83313566d1c..4dc38f5c9ee 100644
--- a/Sources/Rendering/Core/Camera/index.js
+++ b/Sources/Rendering/Core/Camera/index.js
@@ -28,6 +28,13 @@ function vtkCamera(publicAPI, model) {
// Set up private variables and methods
const viewMatrix = mat4.create();
const projectionMatrix = mat4.create();
+ const w2pMatrix = mat4.create();
+ const origin = vec3.create();
+ const dopbasis = vec3.fromValues(0.0, 0.0, -1.0);
+ const upbasis = vec3.fromValues(0.0, 1.0, 0.0);
+ const tmpvec1 = vec3.create();
+ const tmpvec2 = vec3.create();
+ const tmpvec3 = vec3.create();
publicAPI.orthogonalizeViewUp = () => {
const vt = publicAPI.getViewTransformMatrix();
@@ -125,7 +132,7 @@ function vtkCamera(publicAPI, model) {
publicAPI.computeViewPlaneNormal();
};
-//----------------------------------------------------------------------------
+ //----------------------------------------------------------------------------
publicAPI.computeViewPlaneNormal = () => {
// VPN is -DOP
model.viewPlaneNormal[0] = -model.directionOfProjection[0];
@@ -279,6 +286,83 @@ function vtkCamera(publicAPI, model) {
};
+ publicAPI.physicalOrientationToWorldDirection = (ori) => {
+ // get the PhysicalToWorldMatrix
+ publicAPI.getPhysicalToWorldMatrix(w2pMatrix);
+
+ // push the x axis through the orientation quat
+ const oriq = quat.fromValues(ori[0], ori[1], ori[2], ori[3]);
+ const coriq = quat.create();
+ const qdir = quat.fromValues(0.0, 0.0, 1.0, 0.0);
+ quat.conjugate(coriq, oriq);
+
+ // rotate the z axis by the quat
+ quat.multiply(qdir, oriq, qdir);
+ quat.multiply(qdir, qdir, coriq);
+
+ // return the z axis in world coords
+ return [qdir[0], qdir[1], qdir[2]];
+ };
+
+ publicAPI.getPhysicalToWorldMatrix = (result) => {
+ publicAPI.getWorldToPhysicalMatrix(result);
+ mat4.invert(result, result);
+ };
+
+ publicAPI.getWorldToPhysicalMatrix = (result) => {
+ mat4.identity(w2pMatrix);
+ vec3.set(tmpvec1,
+ model.physicalScale, model.physicalScale, model.physicalScale);
+ mat4.scale(w2pMatrix, w2pMatrix, tmpvec1);
+ mat4.translate(w2pMatrix, w2pMatrix, model.physicalTranslation);
+
+ // now the physical to vtk world rotation tform
+ const physVRight = [3];
+ vtkMath.cross(model.physicalViewNorth, model.physicalViewUp, physVRight);
+ const phystoworld = mat4.create();
+ phystoworld[0] = physVRight[0];
+ phystoworld[1] = physVRight[1];
+ phystoworld[2] = physVRight[2];
+ phystoworld[4] = model.physicalViewUp[0];
+ phystoworld[5] = model.physicalViewUp[1];
+ phystoworld[6] = model.physicalViewUp[2];
+ phystoworld[8] = -model.physicalViewNorth[0];
+ phystoworld[9] = -model.physicalViewNorth[1];
+ phystoworld[10] = -model.physicalViewNorth[2];
+ mat4.transpose(phystoworld, phystoworld);
+ mat4.multiply(result, w2pMatrix, phystoworld);
+ };
+
+ // the provided matrix should include
+ // translation and orientation only
+ publicAPI.computeViewParametersFromPhysicalMatrix = (mat) => {
+ // get the WorldToPhysicalMatrix
+ publicAPI.getWorldToPhysicalMatrix(w2pMatrix);
+
+ // first convert the physical -> hmd matrix to be world -> hmd
+ mat4.multiply(viewMatrix, mat, w2pMatrix);
+ // invert to get hmd -> world
+ mat4.invert(viewMatrix, viewMatrix);
+
+ // then extract the params position, orientation
+ // push 0,0,0 through to get a translation
+ vec3.transformMat4(tmpvec1, origin, viewMatrix);
+ publicAPI.computeDistance();
+ const oldDist = model.distance;
+ publicAPI.setPosition(tmpvec1[0], tmpvec1[1], tmpvec1[2]);
+
+ // push basis vectors to get orientation
+ vec3.transformMat4(tmpvec2, dopbasis, viewMatrix);
+ vec3.subtract(tmpvec2, tmpvec2, tmpvec1);
+ vec3.normalize(tmpvec2, tmpvec2);
+ publicAPI.setDirectionOfProjection(tmpvec2[0], tmpvec2[1], tmpvec2[2]);
+ vec3.transformMat4(tmpvec3, upbasis, viewMatrix);
+ vec3.subtract(tmpvec3, tmpvec3, tmpvec1);
+ publicAPI.setViewUp(tmpvec3[0], tmpvec3[1], tmpvec3[2]);
+
+ publicAPI.setDistance(oldDist);
+ };
+
publicAPI.getViewTransformMatrix = () => {
const eye = model.position;
const at = model.focalPoint;
@@ -291,11 +375,29 @@ function vtkCamera(publicAPI, model) {
vec3.fromValues(up[0], up[1], up[2])); // up
mat4.transpose(viewMatrix, viewMatrix);
+
mat4.copy(result, viewMatrix);
return result;
};
+ publicAPI.setProjectionMatrix = (mat) => {
+ model.projectionMatrix = mat;
+ };
+
publicAPI.getProjectionTransformMatrix = (aspect, nearz, farz) => {
+ const result = mat4.create();
+
+ if (model.projectionMatrix) {
+ vec3.set(tmpvec1,
+ model.physicalScale, model.physicalScale, model.physicalScale);
+
+
+ mat4.copy(result, model.projectionMatrix);
+ mat4.scale(result, result, tmpvec1);
+ mat4.transpose(result, result);
+ return result;
+ }
+
mat4.identity(projectionMatrix);
// FIXME: Not sure what to do about adjust z buffer here
@@ -349,7 +451,6 @@ function vtkCamera(publicAPI, model) {
projectionMatrix[15] = 0.0;
}
- const result = mat4.create();
mat4.copy(result, projectionMatrix);
return result;
@@ -502,17 +603,19 @@ export const DEFAULT_VALUES = {
thickness: 1000,
windowCenter: [0, 0],
viewPlaneNormal: [0, 0, 1],
- focalDisk: 1,
useOffAxisProjection: false,
screenBottomLeft: [-0.5, -0.5, -0.5],
screenBottomRight: [0.5, -0.5, -0.5],
screenTopRight: [0.5, 0.5, -0.5],
- userViewTransform: null,
- userTransform: null,
freezeFocalPoint: false,
useScissor: false,
- physicalViewUp: [0.0, 1.0, 0.0],
- physicalViewNorth: [0.0, 0.0, -1.0],
+ projectionMatrix: null,
+
+ // used for world to physical transformations
+ physicalTranslation: [0, 0, 0],
+ physicalScale: 1.0,
+ physicalViewUp: [0, 1, 0],
+ physicalViewNorth: [0, 0, -1],
};
// ----------------------------------------------------------------------------
@@ -523,11 +626,10 @@ export function extend(publicAPI, model, initialValues = {}) {
// Build VTK API
macro.obj(publicAPI, model);
+ model.viewMatrix =
macro.get(publicAPI, model, [
'distance',
'thickness',
- 'userViewTransform',
- 'userTransform',
]);
macro.setGet(publicAPI, model, [
@@ -535,10 +637,10 @@ export function extend(publicAPI, model, initialValues = {}) {
'useHorizontalViewAngle',
'viewAngle',
'parallelScale',
- 'focalDisk',
'useOffAxisProjection',
'freezeFocalPoint',
'useScissor',
+ 'physicalScale',
]);
macro.getArray(publicAPI, model, [
@@ -558,6 +660,7 @@ export function extend(publicAPI, model, initialValues = {}) {
'screenBottomLeft',
'screenBottomRight',
'screenTopRight',
+ 'physicalTranslation',
'physicalViewUp',
'physicalViewNorth',
], 3);
diff --git a/Sources/Rendering/Core/InteractorStyle/Constants.js b/Sources/Rendering/Core/InteractorStyle/Constants.js
index 45bb1c53696..55aa71f9dbb 100644
--- a/Sources/Rendering/Core/InteractorStyle/Constants.js
+++ b/Sources/Rendering/Core/InteractorStyle/Constants.js
@@ -12,6 +12,7 @@ export const States = {
IS_FORWARDFLY: 8,
IS_REVERSEFLY: 9,
IS_TWO_POINTER: 10,
+ IS_CAMERA_POSE: 11,
IS_ANIM_OFF: 0,
IS_ANIM_ON: 1,
diff --git a/Sources/Rendering/Core/InteractorStyle/index.js b/Sources/Rendering/Core/InteractorStyle/index.js
index 19dc947b6be..324b0fd287a 100644
--- a/Sources/Rendering/Core/InteractorStyle/index.js
+++ b/Sources/Rendering/Core/InteractorStyle/index.js
@@ -21,6 +21,7 @@ const stateNames = {
Timer: States.IS_TIMER,
TwoPointer: States.IS_TWO_POINTER,
UniformScale: States.IS_USCALE,
+ CameraPose: States.IS_CAMERA_POSE,
};
const events = [
@@ -49,6 +50,8 @@ const events = [
'Tap',
'LongTap',
'Swipe',
+ 'Button3D',
+ 'Move3D',
];
// ----------------------------------------------------------------------------
@@ -77,9 +80,9 @@ function vtkInteractorStyle(publicAPI, model) {
if (i) {
events.forEach((eventName) => {
model.unsubscribes.push(
- i[`on${eventName}`](() => {
+ i[`on${eventName}`]((data) => {
if (publicAPI[`handle${eventName}`]) {
- publicAPI[`handle${eventName}`]();
+ publicAPI[`handle${eventName}`](data);
}
}));
});
diff --git a/Sources/Rendering/Core/RenderWindowInteractor/Constants.js b/Sources/Rendering/Core/RenderWindowInteractor/Constants.js
new file mode 100644
index 00000000000..79a4de2fd8a
--- /dev/null
+++ b/Sources/Rendering/Core/RenderWindowInteractor/Constants.js
@@ -0,0 +1,18 @@
+export const Device = {
+ Unknown: 0,
+ LeftController: 1,
+ RightController: 2,
+};
+
+export const Input = {
+ Unknown: 0,
+ Trigger: 1,
+ TrackPad: 2,
+ Grip: 3,
+ ApplicationMenu: 4,
+};
+
+export default {
+ Device,
+ Input,
+};
diff --git a/Sources/Rendering/Core/RenderWindowInteractor/index.js b/Sources/Rendering/Core/RenderWindowInteractor/index.js
index aa326d1c3d6..d2c51cde643 100644
--- a/Sources/Rendering/Core/RenderWindowInteractor/index.js
+++ b/Sources/Rendering/Core/RenderWindowInteractor/index.js
@@ -1,13 +1,24 @@
import macro from 'vtk.js/Sources/macro';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkInteractorStyleTrackballCamera from 'vtk.js/Sources/Interaction/Style/InteractorStyleTrackballCamera';
+import Constants from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor/Constants';
+const { Device, Input } = Constants;
const { vtkWarningMacro, vtkErrorMacro } = macro;
// ----------------------------------------------------------------------------
// Global methods
// ----------------------------------------------------------------------------
+const deviceInputMap = {
+ 'OpenVR Gamepad': [
+ Input.TrackPad,
+ Input.Trigger,
+ Input.Grip,
+ Input.ApplicationMenu,
+ ],
+};
+
const eventsWeHandle = [
'Animation',
'Enter',
@@ -40,6 +51,8 @@ const eventsWeHandle = [
'Tap',
'LongTap',
'Swipe',
+ 'Button3D',
+ 'Move3D',
];
function preventDefault(event) {
@@ -229,7 +242,7 @@ function vtkRenderWindowInteractor(publicAPI, model) {
}
};
- publicAPI.isAnimating = () => (model.animationRequest !== null);
+ publicAPI.isAnimating = () => (model.vrAnimation || model.animationRequest !== null);
publicAPI.cancelAnimation = (requestor) => {
model.requestAnimationCount -= 1;
@@ -241,6 +254,57 @@ function vtkRenderWindowInteractor(publicAPI, model) {
}
};
+ publicAPI.switchToVRAnimation = () => {
+ // cancel existing animation if any
+ if (model.animationRequest) {
+ cancelAnimationFrame(model.animationRequest);
+ model.animationRequest = null;
+ }
+ model.vrAnimation = true;
+ };
+
+ publicAPI.returnFromVRAnimation = () => {
+ model.vrAnimation = false;
+ };
+
+ publicAPI.updateGamepads = (displayId) => {
+ const gamepads = navigator.getGamepads();
+
+ // watch for when buttons change state and fire events
+ for (let i = 0; i < gamepads.length; ++i) {
+ const gp = gamepads[i];
+ if (gp && gp.displayId === displayId) {
+ if (!(gp.index in model.lastGamepadValues)) {
+ model.lastGamepadValues[gp.index] = { buttons: {} };
+ }
+ for (let b = 0; b < gp.buttons.length; ++b) {
+ if (!(b in model.lastGamepadValues[gp.index].buttons)) {
+ model.lastGamepadValues[gp.index].buttons[b] = false;
+ }
+ if (model.lastGamepadValues[gp.index].buttons[b] !== gp.buttons[b].pressed) {
+ publicAPI.button3DEvent({
+ gamepad: gp,
+ position: gp.pose.position,
+ orientation: gp.pose.orientation,
+ pressed: gp.buttons[b].pressed,
+ device: (gp.hand === 'left' ? Device.LeftController : Device.RightController),
+ input: (deviceInputMap[gp.id] && deviceInputMap[gp.id][b] ? deviceInputMap[gp.id][b] : Input.Trigger),
+ });
+ model.lastGamepadValues[gp.index].buttons[b] = gp.buttons[b].pressed;
+ }
+ if (model.lastGamepadValues[gp.index].buttons[b]) {
+ publicAPI.move3DEvent({
+ gamepad: gp,
+ position: gp.pose.position,
+ orientation: gp.pose.orientation,
+ device: (gp.hand === 'left' ? Device.LeftController : Device.RightController),
+ });
+ }
+ }
+ }
+ }
+ };
+
publicAPI.handleMouseMove = (event) => {
publicAPI.setEventPosition(event.clientX, model.canvas.clientHeight - event.clientY + 1, 0, 0);
// Do not consume event for move
@@ -416,9 +480,6 @@ function vtkRenderWindowInteractor(publicAPI, model) {
//----------------------------------------------------------------------
publicAPI.forceRender = () => {
- // if (model.renderWindow && model.enabled && model.enableRender) {
- // model.renderWindow.render();
- // }
if (model.view && model.enabled && model.enableRender) {
model.view.traverseAllPasses();
}
@@ -440,11 +501,11 @@ function vtkRenderWindowInteractor(publicAPI, model) {
// create the generic Event methods
eventsWeHandle.forEach((eventName) => {
const lowerFirst = eventName.charAt(0).toLowerCase() + eventName.slice(1);
- publicAPI[`${lowerFirst}Event`] = () => {
+ publicAPI[`${lowerFirst}Event`] = (arg) => {
if (!model.enabled) {
return;
}
- publicAPI[`invoke${eventName}`]({ type: eventName });
+ publicAPI[`invoke${eventName}`]({ type: eventName, calldata: arg });
};
});
@@ -747,6 +808,7 @@ const DEFAULT_VALUES = {
requestAnimationCount: 0,
lastFrameTime: 0.1,
wheelTimeoutID: 0,
+ lastGamepadValues: {},
};
// ----------------------------------------------------------------------------
@@ -819,4 +881,4 @@ export const newInstance = macro.newInstance(extend, 'vtkRenderWindowInteractor'
// ----------------------------------------------------------------------------
-export default Object.assign({ newInstance, extend });
+export default Object.assign({ newInstance, extend }, Constants);
diff --git a/Sources/Rendering/Core/Renderer/index.js b/Sources/Rendering/Core/Renderer/index.js
index d4272a86e8d..4f484f22691 100644
--- a/Sources/Rendering/Core/Renderer/index.js
+++ b/Sources/Rendering/Core/Renderer/index.js
@@ -45,10 +45,6 @@ function vtkRenderer(publicAPI, model) {
vtkDebugMacro('No cameras are on, creating one.');
// the get method will automagically create a camera
// and reset it since one hasn't been specified yet.
- // If is very unlikely that this can occur - if this
- // renderer is part of a vtkRenderWindow, the camera
- // will already have been created as part of the
- // DoStereoRender() method.
publicAPI.getActiveCameraAndResetIfCreated();
}
@@ -394,6 +390,10 @@ function vtkRenderer(publicAPI, model) {
// setup default parallel scale
model.activeCamera.setParallelScale(parallelScale);
+ // update reasonable world to physical values
+ model.activeCamera.setPhysicalScale(1.0 / radius);
+ model.activeCamera.setPhysicalTranslation(-center[0], -center[1], -center[2]);
+
// Here to let parallel/distributed compositing intercept
// and do the right thing.
publicAPI.invokeEvent(RESET_CAMERA_EVENT);
diff --git a/Sources/Rendering/Misc/FullScreenRenderWindow/index.js b/Sources/Rendering/Misc/FullScreenRenderWindow/index.js
index d301c032f24..47276191b4c 100644
--- a/Sources/Rendering/Misc/FullScreenRenderWindow/index.js
+++ b/Sources/Rendering/Misc/FullScreenRenderWindow/index.js
@@ -62,13 +62,13 @@ function vtkFullScreenRenderWindow(publicAPI, model) {
model.renderWindow.addRenderer(model.renderer);
// OpenGlRenderWindow
- model.openGlRenderWindow = vtkOpenGLRenderWindow.newInstance();
- model.openGlRenderWindow.setContainer(model.container);
- model.renderWindow.addView(model.openGlRenderWindow);
+ model.openGLRenderWindow = vtkOpenGLRenderWindow.newInstance();
+ model.openGLRenderWindow.setContainer(model.container);
+ model.renderWindow.addView(model.openGLRenderWindow);
// Interactor
model.interactor = vtkRenderWindowInteractor.newInstance();
- model.interactor.setView(model.openGlRenderWindow);
+ model.interactor.setView(model.openGLRenderWindow);
model.interactor.initialize();
model.interactor.bindEvents(model.container);
@@ -115,7 +115,7 @@ function vtkFullScreenRenderWindow(publicAPI, model) {
// Handle window resize
publicAPI.resize = () => {
const dims = model.container.getBoundingClientRect();
- model.openGlRenderWindow.setSize(Math.floor(dims.width), Math.floor(dims.height));
+ model.openGLRenderWindow.setSize(Math.floor(dims.width), Math.floor(dims.height));
if (model.resizeCallback) {
model.resizeCallback(dims);
}
@@ -155,7 +155,7 @@ export function extend(publicAPI, model, initialValues = {}) {
macro.get(publicAPI, model, [
'renderWindow',
'renderer',
- 'openGlRenderWindow',
+ 'openGLRenderWindow',
'interactor',
'container',
'controlContainer',
diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.js b/Sources/Rendering/OpenGL/RenderWindow/index.js
index 89aaa579dcc..0debfd18bce 100644
--- a/Sources/Rendering/OpenGL/RenderWindow/index.js
+++ b/Sources/Rendering/OpenGL/RenderWindow/index.js
@@ -201,6 +201,19 @@ function vtkOpenGLRenderWindow(publicAPI, model) {
|| model.canvas.getContext('experimental-webgl', options);
}
+ // Do we have webvr support
+ if (navigator.getVRDisplays) {
+ navigator.getVRDisplays().then((displays) => {
+ if (displays.length > 0) {
+ // take the first display for now
+ model.vrDisplay = displays[0];
+ // set the clipping ranges
+ model.vrDisplay.depthNear = 0.01; // meters
+ model.vrDisplay.depthFar = 100.0; // meters
+ }
+ });
+ }
+
// prevent default context lost handler
model.canvas.addEventListener('webglcontextlost', (event) => {
event.preventDefault();
@@ -212,6 +225,69 @@ function vtkOpenGLRenderWindow(publicAPI, model) {
return result;
};
+ publicAPI.startVR = () => {
+ if (model.vrDisplay.isConnected) {
+ model.vrDisplay.requestPresent([{ source: model.canvas }]).then(() => {
+ model.oldCanvasSize = [model.canvas.width, model.canvas.height];
+
+ // const leftEye = model.vrDisplay.getEyeParameters('left');
+ // const rightEye = model.vrDisplay.getEyeParameters('right');
+ // model.canvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
+ // model.canvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
+ const ren = model.renderable.getRenderers()[0];
+ ren.resetCamera();
+ model.vrFrameData = new VRFrameData();
+ model.renderable.getInteractor().switchToVRAnimation();
+
+ publicAPI.vrRender();
+ });
+ } else {
+ vtkErrorMacro('vrDisplay is not connected');
+ }
+ };
+
+ publicAPI.stopVR = () => {
+ model.renderable.getInteractor().returnFromVRAnimation();
+ model.vrDisplay.exitPresent();
+ model.vrDisplay.cancelAnimationFrame(model.vrSceneFrame);
+
+ model.canvas.width = model.oldCanvasSize[0];
+ model.canvas.height = model.oldCanvasSize[1];
+
+
+ const ren = model.renderable.getRenderers()[0];
+ ren.getActiveCamera().setProjectionMatrix(null);
+
+ ren.setViewport(0.0, 0, 1.0, 1.0);
+ publicAPI.traverseAllPasses();
+ };
+
+ publicAPI.vrRender = () => {
+ model.renderable.getInteractor().updateGamepads(model.vrDisplay.displayId);
+ model.vrSceneFrame = model.vrDisplay.requestAnimationFrame(publicAPI.vrRender);
+ model.vrDisplay.getFrameData(model.vrFrameData);
+
+ // get the first renderer
+ const ren = model.renderable.getRenderers()[0];
+
+ // do the left eye
+ ren.setViewport(0, 0, 0.5, 1.0);
+ ren.getActiveCamera().computeViewParametersFromPhysicalMatrix(
+ model.vrFrameData.leftViewMatrix);
+ ren.getActiveCamera().setProjectionMatrix(
+ model.vrFrameData.leftProjectionMatrix);
+ publicAPI.traverseAllPasses();
+
+ ren.setViewport(0.5, 0, 1.0, 1.0);
+ ren.getActiveCamera().computeViewParametersFromPhysicalMatrix(
+ model.vrFrameData.rightViewMatrix);
+ ren.getActiveCamera().setProjectionMatrix(
+ model.vrFrameData.rightProjectionMatrix);
+ publicAPI.traverseAllPasses();
+
+ model.vrDisplay.submitFrame();
+ };
+
publicAPI.restoreContext = () => {
const rp = vtkRenderPass.newInstance();
rp.setCurrentOperation('Release');