Home Reference Source

src/display/WebXRViewerDisplay.js

import SceneDisplay from './SceneDisplay.js'
import * as displayConstants from './Constants.js'

class WebXRViewerDisplay extends SceneDisplay {
	constructor(xrDevice, domElement, camera, scene, tickCallback = null) {
		super(displayConstants.PORTAL, camera, scene, tickCallback)
		this._render = this._render.bind(this)
		this._xrDevice = xrDevice
		this._dom = domElement

		this._isStarted = false

		this._xrSession = null
		this._eyeLevelFrameOfReference = null

		this._outputCanvas = document.createElement('canvas')
		this._outputCanvas.setAttribute('class', 'xr-canvas')
		this._outputContext = this._outputCanvas.getContext('xrpresent')
		if (!this._outputContext) {
			console.error('No output context', xrCanvas)
		}

		this._glCanvas = document.createElement('canvas')
		this._glCanvas.setAttribute('class', 'xr-canvas')
		this._glContext = this._glCanvas.getContext('webgl', {
			compatibleXRDevice: this._xrDevice
		})
		if (!this._glContext) {
			console.error('No XR context', this._glContext)
		}

		this._camera.fov = 70
		this._camera.aspect = document.documentElement.offsetWidth / document.documentElement.offsetHeight
		this._camera.near = 0.1
		this._camera.far = 1000
		this._camera.matrixAutoUpdate = false
		this._camera.updateProjectionMatrix()
		this._scene.add(this._camera)

		this._renderer = new THREE.WebGLRenderer({
			canvas: this._glCanvas,
			context: this._glContext,
			antialias: false,
			alpha: false
		})
		this._renderer.autoClear = false
		this._renderer.setPixelRatio(1)
		this._resize()
	}

	get blendMode() {
		return displayConstants.ALPHA_BLEND
	}

	get renderer() {
		return this._renderer
	}

	get isStarted() {
		return this._isStarted
	}

	start() {
		if (this._isStarted) return Promise.reject('Already started')
		this._isStarted = true
		return new Promise((resolve, reject) => {
			this._bgColor = document.body.style['background-color']
			document.body.style['background-color'] = 'rgba(0, 0, 0, 0)'

			this._dom.appendChild(this._outputCanvas)

			this._outputCanvas.width = document.documentElement.offsetWidth
			this._outputCanvas.height = document.documentElement.offsetHeight
			this._glCanvas.width = document.documentElement.offsetWidth
			this._glCanvas.height = document.documentElement.offsetHeight

			this._xrDevice
				.requestSession({ outputContext: this._outputContext })
				.then(session => {
					this._xrSession = session
					this._xrSession.baseLayer = new XRWebGLLayer(this._xrSession, this._glContext)

					this._xrSession
						.requestFrameOfReference('eye-level')
						.then(frameOfReference => {
							this._eyeLevelFrameOfReference = frameOfReference
							// Kick off rendering
							this._xrSession.requestAnimationFrame(this._render)
							resolve()
						})
						.catch(err => {
							document.body.style['background-color'] = this._bgColor
							this._isStarted = false
							reject(err)
						})
				})
				.catch(err => {
					this._dom.removeChild(this._outputCanvas)
					document.body.style['background-color'] = this._bgColor
					this._isStarted = false
					reject(err)
				})
		})
	}

	stop() {
		if (this._isStarted === false) return Promise.resolve()
		this._isStarted = false
		if (this._xrSession === null || this._xrSession.ended) return Promise.resolve()
		document.body.style['background-color'] = this._bgColor
		this._xrSession.end()
		this._xrSession = null
		return Promise.resolve()
	}

	_resize() {
		const w = document.body.clientWidth
		const h = document.body.clientHeight
		this._renderer.setSize(w, h, true)
		this._outputCanvas.width = w
		this._outputCanvas.height = h
		this._glCanvas.width = w
		this._glCanvas.height = h
	}

	_render(t, frame) {
		if (this._isStarted === false || this._xrSession === null || this._xrSession.ended) return
		this._xrSession.requestAnimationFrame(this._render)

		if (this._tickCallback) {
			this._tickCallback()
		}

		if (
			document.documentElement.offsetHeight != this._glCanvas.height ||
			document.documentElement.offsetWidth != this._glCanvas.width
		) {
			this._resize()
		}

		this._renderer.clear()

		_workingPose = frame.getDevicePose(this._eyeLevelFrameOfReference)
		if (!_workingPose) {
			console.log('No pose')
			return
		}
		for (_workingView of frame.views) {
			_workingViewport = this._xrSession.baseLayer.getViewport(_workingView)

			this._camera.matrix.fromArray(_workingPose.getViewMatrix(_workingView))
			this._camera.projectionMatrix.fromArray(_workingView.projectionMatrix)
			this._camera.updateMatrixWorld()

			this._renderer.setViewport(
				_workingViewport.x,
				_workingViewport.y,
				_workingViewport.width,
				_workingViewport.height
			)
			this._renderer.clearDepth()
			this._renderer.render(this._scene, this._camera)
			break
		}
	}
}

let _workingView = null
let _workingViewport = null
let _workingPose = null

export default WebXRViewerDisplay