From 79cd985cc7b258989adbb4ed0387a5172673d9f3 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Thu, 4 Apr 2024 15:16:32 +0300 Subject: [PATCH] Textures of all types can now only be loaded by providing an Asset type. Asset now has a mandatory string id. Assets no longer caches the IBL PMREM texture, but the HDRI textures themselves because the PMREMs were only stored in video memory and when read from cache with a different viewer instance they would fail resulting in null IBL (#2192) --- packages/viewer/src/IViewer.ts | 4 +- packages/viewer/src/modules/Assets.ts | 79 ++++++++----------- .../viewer/src/modules/materials/Materials.ts | 10 ++- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index a192c86b67..13689d9909 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -13,7 +13,7 @@ import { Loader } from './modules/loaders/Loader' export interface ViewerParams { showStats: boolean - environmentSrc: Asset | string + environmentSrc: Asset verbose: boolean } export enum AssetType { @@ -24,6 +24,7 @@ export enum AssetType { } export interface Asset { + id: string src: string type: AssetType } @@ -43,6 +44,7 @@ export const DefaultViewerParams: ViewerParams = { showStats: false, verbose: false, environmentSrc: { + id: 'defaultHDRI', src: sampleHdri, type: AssetType.TEXTURE_EXR } diff --git a/packages/viewer/src/modules/Assets.ts b/packages/viewer/src/modules/Assets.ts index 31c3fb79d4..d2ad4605e1 100644 --- a/packages/viewer/src/modules/Assets.ts +++ b/packages/viewer/src/modules/Assets.ts @@ -31,92 +31,79 @@ export class Assets { } } + private static hdriToPMREM(renderer: WebGLRenderer, hdriTex: Texture): Texture { + const generator = new PMREMGenerator(renderer) + generator.compileEquirectangularShader() + const pmremRT = generator.fromEquirectangular(hdriTex) + generator.dispose() + return pmremRT.texture + } + public static getEnvironment( - asset: Asset | string, + asset: Asset, renderer: WebGLRenderer ): Promise { - let srcUrl: string = null - let assetType: AssetType = undefined - if ((asset).src) { - srcUrl = (asset as Asset).src - assetType = (asset as Asset).type - } else { - srcUrl = asset as string - } - if (this._cache[srcUrl]) { - return Promise.resolve(this._cache[srcUrl] as Texture) + if (this._cache[asset.id]) { + return Promise.resolve( + Assets.hdriToPMREM(renderer, this._cache[asset.id] as Texture) + ) } return new Promise((resolve, reject) => { - const loader = Assets.getLoader(srcUrl, assetType) + const loader = Assets.getLoader(asset.src, asset.type) if (loader) { loader.load( - srcUrl, + asset.src, (texture) => { - const generator = new PMREMGenerator(renderer) - generator.compileEquirectangularShader() - const pmremRT = generator.fromEquirectangular(texture) - this._cache[srcUrl] = pmremRT.texture - texture.dispose() - generator.dispose() - resolve(this._cache[srcUrl] as Texture) + this._cache[asset.id] = texture + resolve(Assets.hdriToPMREM(renderer, texture)) }, undefined, (error: ErrorEvent) => { - reject(`Loading asset ${srcUrl} failed ${error.message}`) + reject(`Loading asset ${asset.id} failed ${error.message}`) } ) } else { - reject(`Loading asset ${srcUrl} failed`) + reject(`Loading asset ${asset.id} failed`) } }) } - /** Will unify with environment fetching soon */ - public static getTexture(asset: Asset | string): Promise { - let srcUrl: string = null - let assetType: AssetType = undefined - if ((asset).src) { - srcUrl = (asset as Asset).src - assetType = (asset as Asset).type - } else { - srcUrl = asset as string - } - - if (this._cache[srcUrl]) { - return Promise.resolve(this._cache[srcUrl] as Texture) + public static getTexture(asset: Asset): Promise { + if (this._cache[asset.id]) { + return Promise.resolve(this._cache[asset.id] as Texture) } return new Promise((resolve, reject) => { // Hack to load 'data:image's - for some reason, the frontend receives the default // gradient map as a data image url, rather than a file (?). - if (srcUrl.includes('data:image')) { + if (asset.src.includes('data:image')) { const image = new Image() - image.src = srcUrl + image.src = asset.src image.onload = () => { const texture = new Texture(image) texture.needsUpdate = true - this._cache[srcUrl] = texture + this._cache[asset.id] = texture resolve(texture) } image.onerror = (ev) => { - reject(`Loading asset ${srcUrl} failed with ${ev.toString()}`) + reject(`Loading asset ${asset.id} failed with ${ev.toString()}`) } } else { - const loader = Assets.getLoader(srcUrl, assetType) + const loader = Assets.getLoader(asset.src, asset.type) if (loader) { loader.load( - srcUrl, + asset.src, (texture) => { - this._cache[srcUrl] = texture - resolve(this._cache[srcUrl] as Texture) + this._cache[asset.id] = texture + resolve(this._cache[asset.id] as Texture) }, undefined, (error: ErrorEvent) => { - reject(`Loading asset ${srcUrl} failed ${error.message}`) + reject(`Loading asset ${asset.id} failed ${error.message}`) } ) } else { - reject(`Loading asset ${srcUrl} failed`) + reject(`Loading asset ${asset.id} failed`) } } }) @@ -149,7 +136,7 @@ export class Assets { } /** To be used wisely */ - public static async getTextureData(asset: Asset | string): Promise { + public static async getTextureData(asset: Asset): Promise { const texture = await Assets.getTexture(asset) const canvas = document.createElement('canvas') canvas.width = texture.image.width diff --git a/packages/viewer/src/modules/materials/Materials.ts b/packages/viewer/src/modules/materials/Materials.ts index b022dd3c48..7529fe25fa 100644 --- a/packages/viewer/src/modules/materials/Materials.ts +++ b/packages/viewer/src/modules/materials/Materials.ts @@ -6,14 +6,20 @@ import SpeckleLineMaterial from './SpeckleLineMaterial' import SpeckleStandardMaterial from './SpeckleStandardMaterial' import SpecklePointMaterial from './SpecklePointMaterial' import SpeckleStandardColoredMaterial from './SpeckleStandardColoredMaterial' -import defaultGradient from '../../assets/gradient.png' +import defaultGradientTexture from '../../assets/gradient.png' import { Assets } from '../Assets' import { getConversionFactor } from '../converter/Units' import SpeckleGhostMaterial from './SpeckleGhostMaterial' import SpeckleTextMaterial from './SpeckleTextMaterial' import { SpeckleMaterial } from './SpeckleMaterial' import SpecklePointColouredMaterial from './SpecklePointColouredMaterial' -import { MaterialOptions } from '../../IViewer' +import { Asset, AssetType, MaterialOptions } from '../../IViewer' + +const defaultGradient: Asset = { + id: 'defaultGradient', + src: defaultGradientTexture, + type: AssetType.TEXTURE_8BPP +} export interface RenderMaterial { id: string