-
-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2669 from tbirdso/looking-glass-webxr
feat(WebXR): Support rendering to Looking Glass holographic displays
- Loading branch information
Showing
19 changed files
with
525 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; | |
import vtkURLExtract from '@kitware/vtk.js/Common/Core/URLExtract'; | ||
import vtkXMLPolyDataReader from '@kitware/vtk.js/IO/XML/XMLPolyDataReader'; | ||
import vtkFPSMonitor from '@kitware/vtk.js/Interaction/UI/FPSMonitor'; | ||
import { XrSessionTypes } from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow/Constants'; | ||
|
||
// Force DataAccessHelper to have access to various data source | ||
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; | ||
|
@@ -75,6 +76,44 @@ function preventDefaults(e) { | |
e.stopPropagation(); | ||
} | ||
|
||
// WebXR | ||
let requestedXrSessionType = | ||
userParams.xrSessionType !== undefined ? userParams.xrSessionType : null; | ||
if ( | ||
requestedXrSessionType !== null && | ||
!Object.values(XrSessionTypes).includes(requestedXrSessionType) | ||
) { | ||
console.warn( | ||
'Could not parse requested XR session type: ', | ||
requestedXrSessionType | ||
); | ||
requestedXrSessionType = null; | ||
} | ||
|
||
if (requestedXrSessionType === XrSessionTypes.LookingGlassVR) { | ||
// Import the Looking Glass WebXR Polyfill override | ||
// Assumes that the Looking Glass Bridge native application is already running. | ||
// See https://docs.lookingglassfactory.com/developer-tools/webxr | ||
import( | ||
// eslint-disable-next-line import/no-unresolved, import/extensions | ||
/* webpackIgnore: true */ 'https://unpkg.com/@lookingglass/[email protected]/dist/@lookingglass/bundle/webxr.js' | ||
).then((obj) => { | ||
// eslint-disable-next-line no-new | ||
new obj.LookingGlassWebXRPolyfill(); | ||
}); | ||
} else if (requestedXrSessionType === null && navigator.xr !== undefined) { | ||
// Determine supported session type | ||
navigator.xr.isSessionSupported('immersive-ar').then((arSupported) => { | ||
if (arSupported) { | ||
requestedXrSessionType = XrSessionTypes.MobileAR; | ||
} else { | ||
navigator.xr.isSessionSupported('immersive-vr').then((vrSupported) => { | ||
requestedXrSessionType = vrSupported ? XrSessionTypes.HmdVR : null; | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// DOM containers for UI control | ||
// ---------------------------------------------------------------------------- | ||
|
@@ -197,7 +236,10 @@ function createPipeline(fileName, fileContents) { | |
|
||
const immersionSelector = document.createElement('button'); | ||
immersionSelector.setAttribute('class', selectorClass); | ||
immersionSelector.innerHTML = 'Start AR'; | ||
immersionSelector.innerHTML = | ||
requestedXrSessionType === XrSessionTypes.MobileAR | ||
? 'Start AR' | ||
: 'Start VR'; | ||
|
||
const controlContainer = document.createElement('div'); | ||
controlContainer.setAttribute('class', style.control); | ||
|
@@ -210,8 +252,8 @@ function createPipeline(fileName, fileContents) { | |
|
||
if ( | ||
navigator.xr !== undefined && | ||
navigator.xr.isSessionSupported('immersive-ar') && | ||
fullScreenRenderWindow.getApiSpecificRenderWindow().getXrSupported() | ||
fullScreenRenderWindow.getApiSpecificRenderWindow().getXrSupported() && | ||
requestedXrSessionType !== null | ||
) { | ||
controlContainer.appendChild(immersionSelector); | ||
} | ||
|
@@ -387,21 +429,29 @@ function createPipeline(fileName, fileContents) { | |
// Immersion handling | ||
// -------------------------------------------------------------------- | ||
|
||
function toggleAR() { | ||
const SESSION_IS_AR = true; | ||
if (immersionSelector.textContent === 'Start AR') { | ||
function toggleXR() { | ||
if (requestedXrSessionType === XrSessionTypes.MobileAR) { | ||
fullScreenRenderWindow.setBackground([...background, 0]); | ||
} | ||
|
||
if (immersionSelector.textContent.startsWith('Start')) { | ||
fullScreenRenderWindow | ||
.getApiSpecificRenderWindow() | ||
.startXR(SESSION_IS_AR); | ||
immersionSelector.textContent = 'Exit AR'; | ||
.startXR(requestedXrSessionType); | ||
immersionSelector.textContent = | ||
requestedXrSessionType === XrSessionTypes.MobileAR | ||
? 'Exit AR' | ||
: 'Exit VR'; | ||
} else { | ||
fullScreenRenderWindow.setBackground([...background, 255]); | ||
fullScreenRenderWindow.getApiSpecificRenderWindow().stopXR(SESSION_IS_AR); | ||
immersionSelector.textContent = 'Start AR'; | ||
fullScreenRenderWindow.getApiSpecificRenderWindow().stopXR(); | ||
immersionSelector.textContent = | ||
requestedXrSessionType === XrSessionTypes.MobileAR | ||
? 'Start AR' | ||
: 'Start VR'; | ||
} | ||
} | ||
immersionSelector.addEventListener('click', toggleAR); | ||
immersionSelector.addEventListener('click', toggleXR); | ||
|
||
// -------------------------------------------------------------------- | ||
// Pipeline handling | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 Looking Glass</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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// For streamlined VR development install the WebXR emulator extension | ||
// https://github.com/MozillaReality/WebXR-emulator-extension | ||
|
||
import '@kitware/vtk.js/favicon'; | ||
|
||
// Load the rendering pieces we want to use (for both WebGL and WebGPU) | ||
import '@kitware/vtk.js/Rendering/Profiles/Geometry'; | ||
|
||
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; | ||
import vtkCalculator from '@kitware/vtk.js/Filters/General/Calculator'; | ||
import vtkConeSource from '@kitware/vtk.js/Filters/Sources/ConeSource'; | ||
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; | ||
import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; | ||
import { AttributeTypes } from '@kitware/vtk.js/Common/DataModel/DataSetAttributes/Constants'; | ||
import { FieldDataTypes } from '@kitware/vtk.js/Common/DataModel/DataSet/Constants'; | ||
import { XrSessionTypes } from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow/Constants'; | ||
|
||
// Force DataAccessHelper to have access to various data source | ||
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; | ||
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper'; | ||
import '@kitware/vtk.js/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; | ||
|
||
import controlPanel from './controller.html'; | ||
|
||
// Import the Looking Glass WebXR Polyfill override | ||
// Assumes that the Looking Glass Bridge native application is already running. | ||
// See https://docs.lookingglassfactory.com/developer-tools/webxr | ||
import( | ||
// eslint-disable-next-line import/no-unresolved, import/extensions | ||
/* webpackIgnore: true */ 'https://unpkg.com/@lookingglass/[email protected]/dist/@lookingglass/bundle/webxr.js' | ||
).then((obj) => { | ||
// eslint-disable-next-line no-new | ||
new obj.LookingGlassWebXRPolyfill(); | ||
}); | ||
|
||
// ---------------------------------------------------------------------------- | ||
// 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: 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(0.0, 0.0, -20.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 Looking Glass') { | ||
fullScreenRenderer | ||
.getApiSpecificRenderWindow() | ||
.startXR(XrSessionTypes.LookingGlassVR); | ||
vrbutton.textContent = 'Return From Looking Glass'; | ||
} else { | ||
fullScreenRenderer.getApiSpecificRenderWindow().stopXR(); | ||
vrbutton.textContent = 'Send To Looking Glass'; | ||
} | ||
}); | ||
|
||
// ----------------------------------------------------------- | ||
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
## Holographic Scenes with Looking Glass | ||
|
||
vtk.js supports rendering 3D holograms to [Looking Glass](https://lookingglassfactory.com/) holographic displays. The following are required for getting started: | ||
- A physical Looking Glass display with an HDMI and USB connection to the computer running the vtk.js scene; | ||
- The [Looking Glass Bridge](https://lookingglassfactory.com/software/looking-glass-bridge) native application; and | ||
- The [Looking Glass WebXR Polyfill](https://github.com/Looking-Glass/looking-glass-webxr) (already fetched in this example). | ||
|
||
Clicking "Send to Looking Glass" in the example above will open a new popup window on the connected Looking Glass display. Double-clicking the window will maximize the hologram view to display properly. | ||
|
||
If the Looking Glass display is disconnected or the Looking Glass Bridge application is not running then a non-composited, "swizzled" view will be shown in a popup window. | ||
|
||
The Looking Glass display composites a "quilt" of multiple scene renderings into a hologram with "depth" when viewed from multiple angles. vtk.js generates multiple scene renderings for each frame in order to generate new "quilt". | ||
|
||
More information on holograms and Looking Glass displays is available in the [Looking Glass documentation](https://docs.lookingglassfactory.com/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.