@pmndrs/xr
Version:
VR/AR for threejs
90 lines (89 loc) • 3.96 kB
JavaScript
import { Mesh, MeshBasicMaterial, WebGLRenderTarget } from 'three';
import { createXRLayer, createXRLayerGeometry, getXRLayerSrcTexture, setupXRImageLayer, updateXRLayerProperties, updateXRLayerTransform, waitForXRLayerSrcSize, } from '../layer.js';
export class XRLayer extends Mesh {
store;
options;
properties;
layerRenderOrder;
layerEntry;
cleanup;
cleanupSubscription;
constructor(store, renderer, options, properties = {}, layerRenderOrder = 0) {
super(createXRLayerGeometry(options.shape ?? 'quad', properties), new MeshBasicMaterial({ toneMapped: false }));
this.store = store;
this.options = options;
this.properties = properties;
this.layerRenderOrder = layerRenderOrder;
this.frustumCulled = false; //to prevent onBeforeRender from not beeing called
let aborted = false;
this.cleanup = () => (aborted = true);
waitForXRLayerSrcSize(options.src).then(() => {
if (aborted) {
return;
}
const update = ({ session, originReferenceSpace }, prevState) => {
if (originReferenceSpace == null) {
return;
}
if (prevState != null && session === prevState.session) {
return;
}
this.cleanup?.();
const layersEnabled = session?.enabledFeatures?.includes('layers') === true;
this.material.colorWrite = !layersEnabled;
this.renderOrder = layersEnabled ? -Infinity : 0;
if (!layersEnabled) {
this.material.colorWrite = true;
const texture = getXRLayerSrcTexture(options.src);
this.material.map = texture;
this.material.needsUpdate = true;
this.cleanup = options.src instanceof WebGLRenderTarget ? () => { } : () => texture.dispose();
return;
}
this.material.map = null;
const layer = createXRLayer(options.src, store.getState(), originReferenceSpace, renderer.xr, this, options, properties);
if (layer == null) {
this.cleanup = () => { };
return;
}
const layerEntry = (this.layerEntry = { layer, renderOrder: this.layerRenderOrder, object3D: this });
store.addLayerEntry(this.layerEntry);
if (options.src instanceof HTMLVideoElement || options.src instanceof WebGLRenderTarget) {
this.cleanup = () => this.store.removeLayerEntry(layerEntry);
return;
}
const cleanupXRImageLayer = setupXRImageLayer(renderer, store, layer, options.src);
this.cleanup = () => {
this.store.removeLayerEntry(layerEntry);
cleanupXRImageLayer();
};
};
update(store.getState());
this.cleanupSubscription = store.subscribe(update);
});
}
setLayerRenderOrder(layerRenderOrder) {
this.layerRenderOrder = layerRenderOrder;
if (this.layerEntry != null) {
this.layerEntry.renderOrder = layerRenderOrder;
}
}
setProperties(properties = {}) {
this.properties = properties;
this.geometry.dispose();
this.geometry = createXRLayerGeometry(this.options.shape ?? 'quad', properties);
if (this.layerEntry != null) {
updateXRLayerProperties(this.layerEntry.layer, properties);
}
}
destroy() {
this.cleanupSubscription?.();
this.cleanup?.();
}
onBeforeRender() {
if (this.layerEntry == null) {
return;
}
updateXRLayerTransform(this.store.getState(), this.layerEntry?.layer, this.properties.centralAngle, this);
}
}