Skip to content

Commit

Permalink
feat(Rendering): Add initial support for WebVR
Browse files Browse the repository at this point in the history
Tested on Firefox with the Vive. Supports the notion
of WorldToPhysical coordinate systems and uses the
gamepad API to poll controller events. Currently the
right controller trackpad is used to control movement.

ResetCamera will compute reasonable values for PhysicalScale
and PhysicalTranslation.

Still needs to be tested against DayDream and other VR
systems. Support for their gamepads needs to be added.
  • Loading branch information
martinken committed Dec 8, 2017
1 parent be3ef81 commit 10f0c5b
Show file tree
Hide file tree
Showing 12 changed files with 473 additions and 30 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = {
// ],
globals: {
__BASE_PATH__: false,
VRFrameData: true,
},
'settings': {
'import/resolver': 'webpack'
Expand Down
21 changes: 21 additions & 0 deletions Examples/Geometry/VR/controller.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<table>
<tr>
<td>
<button class='vrbutton' style="width: 100%">Send To VR</button>
</td>
</tr>
<tr>
<td>
<select class='representations' style="width: 100%">
<option value='0'>Points</option>
<option value='1'>Wireframe</option>
<option value='2' selected>Surface</option>
</select>
</td>
</tr>
<tr>
<td>
<input class='resolution' type='range' min='4' max='80' value='6' />
</td>
</tr>
</table>
101 changes: 101 additions & 0 deletions Examples/Geometry/VR/index.js
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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 */

Expand Down Expand Up @@ -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());
Expand Down
Loading

0 comments on commit 10f0c5b

Please sign in to comment.