Sfoglia il codice sorgente

Refactor processStates to AssetManager.

master
Palash Bansal 2 anni fa
parent
commit
3ca2a4f00e
Nessun account collegato all'indirizzo email del committer

+ 186
- 127
src/assetmanager/AssetManager.ts Vedi File

@@ -82,13 +82,15 @@ export type AddRawOptions = ProcessRawOptions & AddAssetOptions
* Utility class to manage import, export, and material management.
* @category Asset Manager
*/
export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}, 'loadAsset'> {
export class AssetManager extends EventDispatcher<BaseEvent&{data?: ImportResult}, 'loadAsset'|'processStateUpdate'> {
readonly viewer: ThreeViewer
readonly importer: AssetImporter
readonly exporter: AssetExporter
readonly materials: MaterialManager
private _storage?: Cache | Storage
get storage() {return this._storage}
get storage() {
return this._storage
}

constructor(viewer: ThreeViewer, {simpleCache = false, storage}: AssetManagerOptions = {}) {
super()
@@ -106,131 +108,33 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
this.viewer.scene.addEventListener('beforeDeserialize', this._sceneUpdated)
this._initCacheStorage(simpleCache, storage ?? true)

this.importer.addEventListener('processRaw', (event)=>{
// console.log('preprocess mat', mat)
const mat = event.data as IMaterial
if (!mat || !mat.isMaterial || !mat.uuid) return
if (this.materials?.findMaterial(mat.uuid)) {
console.warn('imported material uuid already exists, creating new uuid')
mat.uuid = generateUUID()
if (mat.userData.uuid) mat.userData.uuid = mat.uuid
}
// todo: check for name exists also
this.materials.registerMaterial(mat)
})

this.importer.addEventListener('processRawStart', (event)=>{
// console.log('preprocess mat', mat)
const res = event.data!
const options = event.options! as ProcessRawOptions
// if (!res.assetType) {
// if (res.isBufferGeometry) { // for eg stl todo
// res = new Mesh(res, new MeshStandardMaterial())
// }
// if (res.isObject3D) {
// }
// }
if (res.isObject3D) {
const cameras: Camera[] = []
const lights: Light[] = []
res.traverse((obj: any) => {
if (obj.material) {
const materials = Array.isArray(obj.material) ? obj.material : [obj.material]
const newMaterials = []
for (const material of materials) {
const mat = this.materials.convertToIMaterial(material, {createFromTemplate: options.replaceMaterials !== false}) || material
mat.uuid = material.uuid
mat.userData.uuid = material.uuid
newMaterials.push(mat)
}
if (Array.isArray(obj.material)) obj.material = newMaterials
else obj.material = newMaterials[0]
}
if (obj.isCamera) cameras.push(obj)
if (obj.isLight) lights.push(obj)
})
for (const camera of cameras) {
if ((camera as PerspectiveCamera2).assetType === 'camera') continue
// todo: OrthographicCamera
if (!(camera as PerspectiveCamera).isPerspectiveCamera || !camera.parent || options.replaceCameras === false) {
iCameraCommons.upgradeCamera.call(camera)
} else {
const newCamera: ICamera = (camera as any).iCamera ??
new PerspectiveCamera2('', this.viewer.canvas)
if (camera === newCamera) continue
camera.parent.children.splice(camera.parent.children.indexOf(camera), 1, newCamera)
newCamera.parent = camera.parent as any
newCamera.copy(camera as any)
camera.parent = null
;(newCamera as any).uuid = camera.uuid
newCamera.userData.uuid = camera.uuid
;(camera as any).iCamera = newCamera
// console.log('replacing camera', camera, newCamera)
}
}
for (const light of lights) {
if ((light as ILight).assetType === 'light') continue
if (!light.parent || options.replaceLights === false) {
iLightCommons.upgradeLight.call(light)
} else {
const newLight: ILight|undefined = (light as any).iLight ??
(light as any).isDirectionalLight ? new DirectionalLight2() :
(light as any).isPointLight ? new PointLight2() :
(light as any).isSpotLight ? new SpotLight2() :
(light as any).isAmbientLight ? new AmbientLight2() :
(light as any).isHemisphereLight ? new HemisphereLight2() :
(light as any).isRectAreaLight ? new RectAreaLight2() :
undefined
if (light === newLight || !newLight) continue
light.parent.children.splice(light.parent.children.indexOf(light), 1, newLight)
newLight.parent = light.parent as any
newLight.copy(light as any)
light.parent = null
;(newLight as any).uuid = light.uuid
newLight.userData.uuid = light.uuid
;(light as any).iLight = newLight
}
}

iObjectCommons.upgradeObject3D.call(res)
} else if (res.isMaterial) {
iMaterialCommons.upgradeMaterial.call(res)
// todo update res by generating new material?
} else if (res.isTexture) {
upgradeTexture.call(res)

if (event?.options?.generateMipmaps !== undefined)
res.generateMipmaps = event?.options.generateMipmaps
if (!res.generateMipmaps && !res.isRenderTargetTexture) { // todo: do we need to check more?
res.minFilter = res.minFilter === LinearMipmapLinearFilter ? LinearFilter : res.minFilter
res.magFilter = res.magFilter === LinearMipmapLinearFilter ? LinearFilter : res.magFilter
}

}
// todo other asset/object types?
})

this._setupObjectProcess()
this._setupProcessState()
this._addImporters()
this._addExporters()

}

async addAsset<T extends ImportResult = ImportResult>(assetOrPath?: string | IAsset | IAsset[] | File | File[], options?: ImportAddOptions): Promise<(T|undefined)[]> {
async addAsset<T extends ImportResult = ImportResult>(assetOrPath?: string | IAsset | IAsset[] | File | File[], options?: ImportAddOptions): Promise<(T | undefined)[]> {
if (!this.importer || !this.viewer) return []
const imported = await this.importer.import<T>(assetOrPath, options)
if (!imported) {
console.warn('Unable to import', assetOrPath, imported)
return []
}
return this.loadImported<(T|undefined)[]>(imported, options)
return this.loadImported<(T | undefined)[]>(imported, options)
}

// materials: IMaterial[] = []
// textures: ITexture[] = []

// todo move this function to viewer
async loadImported<T extends ValOrArr<ImportResult|undefined> = ImportResult>(imported: T, {autoSetEnvironment = true, autoSetBackground = false, ...options}: AddAssetOptions = {}): Promise<T | never[]> {
const arr: (ImportResult|undefined)[] = Array.isArray(imported) ? imported : [imported]
async loadImported<T extends ValOrArr<ImportResult | undefined> = ImportResult>(imported: T, {
autoSetEnvironment = true,
autoSetBackground = false,
...options
}: AddAssetOptions = {}): Promise<T | never[]> {
const arr: (ImportResult | undefined)[] = Array.isArray(imported) ? imported : [imported]
let ret: T = Array.isArray(imported) ? [] : undefined as any

for (const obj of arr) {
@@ -254,7 +158,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
case 'model':
case 'light':
case 'camera':
r = await this.viewer.addSceneObject(<IObject3D|RootSceneImportResult>obj, options) // todo update references in scene update event
r = await this.viewer.addSceneObject(<IObject3D | RootSceneImportResult>obj, options) // todo update references in scene update event
break
case 'config':
if (options?.importConfig !== false) await this.viewer.importConfig(<ISerializedConfig>obj)
@@ -263,12 +167,12 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}

// legacy
if (obj.type && typeof obj.type === 'string' && (Array.isArray((obj as any).plugins) ||
(obj as any).type === 'ThreeViewer' || this.viewer.getPlugin((obj as any).type))) {
(obj as any).type === 'ThreeViewer' || this.viewer.getPlugin((obj as any).type))) {
await this.viewer.importConfig(<ISerializedConfig>obj)
}
break
}
this.dispatchEvent({type: 'loadAsset', data: obj})
this.dispatchEvent({type: 'loadAsset', data: obj})
if (Array.isArray(ret)) ret.push(r)
else ret = r as T
}
@@ -281,20 +185,21 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
* @param imported
* @param options
*/
async addProcessedAssets<T extends ImportResult|undefined = ImportResult>(imported: (T|undefined)[], options?: AddAssetOptions): Promise<(T | undefined)[]> {
async addProcessedAssets<T extends ImportResult | undefined = ImportResult>(imported: (T | undefined)[], options?: AddAssetOptions): Promise<(T | undefined)[]> {
return this.loadImported(imported, options)
}

async addAssetSingle<T extends ImportResult = ImportResult>(asset?: string | IAsset | File, options?: ImportAssetOptions): Promise<T|undefined> {
async addAssetSingle<T extends ImportResult = ImportResult>(asset?: string | IAsset | File, options?: ImportAssetOptions): Promise<T | undefined> {
return !asset ? undefined : (await this.addAsset<T>(asset, options))?.[0]
}

// processAndAddObjects
async addRaw<T extends (ImportResult|undefined) = ImportResult>(res: T|T[], options: AddRawOptions = {}): Promise<(T|undefined)[]> {
async addRaw<T extends (ImportResult | undefined) = ImportResult>(res: T | T[], options: AddRawOptions = {}): Promise<(T | undefined)[]> {
const r = await this.importer.processRaw<T>(res, options)
return this.loadImported<T[]>(r, options)
}
async addRawSingle<T extends ImportResult|undefined = ImportResult|undefined>(res: T, options: AddRawOptions = {}): Promise<T|undefined> {

async addRawSingle<T extends ImportResult | undefined = ImportResult | undefined>(res: T, options: AddRawOptions = {}): Promise<T | undefined> {
return (await this.addRaw<T>(res, options))?.[0]
}

@@ -331,7 +236,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
if (data.metadata?.type !== 'Material') {
console.warn('Invalid material data', data)
}
JSONMaterialLoader.DeserializeMaterialJSON(data, this.viewer, meta, event.material).then(()=>{
JSONMaterialLoader.DeserializeMaterialJSON(data, this.viewer, meta, event.material).then(() => {
//
})
}
@@ -344,6 +249,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
dispose() {
this.importer.dispose()
this.materials.dispose()
this.processState.clear()
this.viewer.scene.removeEventListener('addSceneObject', this._sceneUpdated)
this.viewer.scene.removeEventListener('materialChanged', this._sceneUpdated)
this.exporter.dispose()
@@ -359,8 +265,8 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
], false), // todo: use ImageBitmapLoader if supported (better performance)

new Importer<JSONMaterialLoader>(JSONMaterialLoader,
['mat', ...this.materials.templates.map(t=>t.typeSlug!).filter(v=>v)], // todo add others
[], false, (loader)=>{
['mat', ...this.materials.templates.map(t => t.typeSlug!).filter(v => v)], // todo add others
[], false, (loader) => {
if (loader) loader.viewer = this.viewer
return loader
}),
@@ -396,12 +302,14 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}

protected _addExporters() {
const exporters: IExporter[] = [
{ext: ['gltf', 'glb'], extensions: [], ctor: (_, exporter)=>{
const ex = new GLTFExporter2()
// This should be added at the end.
ex.setup(this.viewer, exporter.extensions)
return ex
}},
{
ext: ['gltf', 'glb'], extensions: [], ctor: (_, exporter) => {
const ex = new GLTFExporter2()
// This should be added at the end.
ex.setup(this.viewer, exporter.extensions)
return ex
},
},
]

this.exporter.addExporter(...exporters)
@@ -428,6 +336,156 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
}


protected _setupObjectProcess() {
this.importer.addEventListener('processRaw', (event) => {
// console.log('preprocess mat', mat)
const mat = event.data as IMaterial
if (!mat || !mat.isMaterial || !mat.uuid) return
if (this.materials?.findMaterial(mat.uuid)) {
console.warn('imported material uuid already exists, creating new uuid')
mat.uuid = generateUUID()
if (mat.userData.uuid) mat.userData.uuid = mat.uuid
}
// todo: check for name exists also
this.materials.registerMaterial(mat)
})

this.importer.addEventListener('processRawStart', (event) => {
// console.log('preprocess mat', mat)
const res = event.data!
const options = event.options! as ProcessRawOptions
// if (!res.assetType) {
// if (res.isBufferGeometry) { // for eg stl todo
// res = new Mesh(res, new MeshStandardMaterial())
// }
// if (res.isObject3D) {
// }
// }
if (res.isObject3D) {
const cameras: Camera[] = []
const lights: Light[] = []
res.traverse((obj: any) => {
if (obj.material) {
const materials = Array.isArray(obj.material) ? obj.material : [obj.material]
const newMaterials = []
for (const material of materials) {
const mat = this.materials.convertToIMaterial(material, {createFromTemplate: options.replaceMaterials !== false}) || material
mat.uuid = material.uuid
mat.userData.uuid = material.uuid
newMaterials.push(mat)
}
if (Array.isArray(obj.material)) obj.material = newMaterials
else obj.material = newMaterials[0]
}
if (obj.isCamera) cameras.push(obj)
if (obj.isLight) lights.push(obj)
})
for (const camera of cameras) {
if ((camera as PerspectiveCamera2).assetType === 'camera') continue
// todo: OrthographicCamera
if (!(camera as PerspectiveCamera).isPerspectiveCamera || !camera.parent || options.replaceCameras === false) {
iCameraCommons.upgradeCamera.call(camera)
} else {
const newCamera: ICamera = (camera as any).iCamera ??
new PerspectiveCamera2('', this.viewer.canvas)
if (camera === newCamera) continue
camera.parent.children.splice(camera.parent.children.indexOf(camera), 1, newCamera)
newCamera.parent = camera.parent as any
newCamera.copy(camera as any)
camera.parent = null
;(newCamera as any).uuid = camera.uuid
newCamera.userData.uuid = camera.uuid
;(camera as any).iCamera = newCamera
// console.log('replacing camera', camera, newCamera)
}
}
for (const light of lights) {
if ((light as ILight).assetType === 'light') continue
if (!light.parent || options.replaceLights === false) {
iLightCommons.upgradeLight.call(light)
} else {
const newLight: ILight | undefined = (light as any).iLight ??
(light as any).isDirectionalLight ? new DirectionalLight2() :
(light as any).isPointLight ? new PointLight2() :
(light as any).isSpotLight ? new SpotLight2() :
(light as any).isAmbientLight ? new AmbientLight2() :
(light as any).isHemisphereLight ? new HemisphereLight2() :
(light as any).isRectAreaLight ? new RectAreaLight2() :
undefined
if (light === newLight || !newLight) continue
light.parent.children.splice(light.parent.children.indexOf(light), 1, newLight)
newLight.parent = light.parent as any
newLight.copy(light as any)
light.parent = null
;(newLight as any).uuid = light.uuid
newLight.userData.uuid = light.uuid
;(light as any).iLight = newLight
}
}

iObjectCommons.upgradeObject3D.call(res)
} else if (res.isMaterial) {
iMaterialCommons.upgradeMaterial.call(res)
// todo update res by generating new material?
} else if (res.isTexture) {
upgradeTexture.call(res)

if (event?.options?.generateMipmaps !== undefined)
res.generateMipmaps = event?.options.generateMipmaps
if (!res.generateMipmaps && !res.isRenderTargetTexture) { // todo: do we need to check more?
res.minFilter = res.minFilter === LinearMipmapLinearFilter ? LinearFilter : res.minFilter
res.magFilter = res.magFilter === LinearMipmapLinearFilter ? LinearFilter : res.magFilter
}

}
// todo other asset/object types?
})
}

/**
* State of download/upload/process/other processes in the viewer.
* Subscribes to importer and exporter by default, more can be added by plugins like {@link FileTransferPlugin}
*/
processState: Map<string, {state: string, progress: number | undefined}> = new Map()

/**
* Set process state for a path
* Progress should be a number between 0 and 100
* Pass undefined in value to remove the state
* @param path
* @param value
*/
setProcessState(path: string, value: {state: string, progress: number | undefined} | undefined) {
if (value === undefined) this.processState.delete(path)
else this.processState.set(path, value)
this.dispatchEvent({type: 'processStateUpdate'})
}

protected _setupProcessState() {
this.importer.addEventListener('importFile', (data: any) => {
this.setProcessState(data.path, data.state !== 'done' ? {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
} : undefined)
})
this.importer.addEventListener('processRawStart', (data: any) => {
this.setProcessState(data.path, {
state: 'processing',
progress: undefined,
})
})
this.importer.addEventListener('processRaw', (data: any) => {
this.setProcessState(data.path, undefined)
})
this.exporter.addEventListener('exportFile', (data: any) => {
this.setProcessState(data.obj.name, data.state !== 'done' ? {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
} : undefined)
})
}


// region deprecated

/**
@@ -435,7 +493,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
* @param res
* @param options
*/
async addImported<T extends (ImportResult|undefined) = ImportResult>(res: T|T[], options: AddRawOptions = {}): Promise<(T|undefined)[]> {
async addImported<T extends (ImportResult | undefined) = ImportResult>(res: T | T[], options: AddRawOptions = {}): Promise<(T | undefined)[]> {
console.error('addImported is deprecated, use addRaw instead')
return this.addRaw(res, options)
}
@@ -524,4 +582,5 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
*/
static readonly PluginType = 'AssetManager'
// endregion

}

+ 9
- 87
src/plugins/base/AAssetManagerProcessStatePlugin.ts Vedi File

@@ -1,7 +1,6 @@
import {createDiv, onChange, serialize} from 'ts-browser-helpers'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {uiToggle} from 'uiconfig.js'
import {FileTransferPlugin} from '../export/FileTransferPlugin'

export abstract class AAssetManagerProcessStatePlugin<T extends string = ''> extends AViewerPluginSync<T> {
@uiToggle('Enabled')
@@ -31,105 +30,28 @@ export abstract class AAssetManagerProcessStatePlugin<T extends string = ''> ext
this._mainDiv.style.display = 'none'
}
this._mainDiv.appendChild(this._contentDiv)
this._onProcessStateUpdate = this._onProcessStateUpdate.bind(this)
}

protected abstract _updateMainDiv(processState: Map<string, {state: string, progress?: number|undefined}>): void

processState: Map<string, {state: string, progress: number|undefined}> = new Map()
onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)

;(this.container ?? viewer.container).appendChild(this._mainDiv)
this._updateMainDiv(this.processState)

// todo remove all these listeners onRemove
viewer.assetManager.importer.addEventListener('importFile', (data: any) => {
if (data.state !== 'done') {
this.processState.set(data.path, {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
})
} else {
this.processState.delete(data.path)
}
// console.log('importFile', data)
this._updateMainDiv(this.processState)
})
viewer.assetManager.importer.addEventListener('processRawStart', (data: any) => {
this.processState.set(data.path, {
state: 'processing',
progress: undefined,
})
this._updateMainDiv(this.processState)
})
viewer.assetManager.importer.addEventListener('processRaw', (data: any) => {
this.processState.delete(data.path)
this._updateMainDiv(this.processState)
})
viewer.assetManager.exporter.addEventListener('exportFile', (data: any) => {
if (data.state !== 'done') {
this.processState.set(data.obj.name, {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
})
} else {
this.processState.delete(data.obj.name)
}
this._updateMainDiv(this.processState)
})
viewer.getPlugin<FileTransferPlugin>('FileTransferPlugin')?.addEventListener('transferFile', (data: any) => {
if (data.state !== 'done') {
this.processState.set(data.path, {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
})
} else {
this.processState.delete(data.path)
}
this._updateMainDiv(this.processState)
})

// todo; remove or move to plugin
viewer.getPlugin/* <MaterialConfiguratorPlugin>*/('MaterialConfiguratorPlugin')?.addEventListener('progress' as any, (data: any) => {
if (data.state !== 'done') {
this.processState.set('MatpreviewGeneration', {
state: data.state,
progress: 0,
})
} else {
this.processState.delete('MatpreviewGeneration')
}
this._updateMainDiv(this.processState)
})
viewer.getPlugin/* <SwitchNodePlugin>*/('SwitchNodePlugin')?.addEventListener('progress' as any, (data: any) => {
if (data.state !== 'done') {
this.processState.set('SwitchNodeGeneration', {
state: data.state,
progress: 0,
})
} else {
this.processState.delete('SwitchNodeGeneration')
}
this._updateMainDiv(this.processState)
})
viewer.getPlugin('ThemePlugin')?.addEventListener('progress' as any, (data: any) => {
if (data.state !== 'done') {
this.processState.set('ThemeInit', {
state: data.state,
progress: 0,
})
} else {
this.processState.delete('ThemeInit')
}
this._updateMainDiv(this.processState)
})
this._updateMainDiv(viewer.assetManager.processState)
viewer.assetManager.addEventListener('processStateUpdate', this._onProcessStateUpdate)
}

protected _onProcessStateUpdate() {
if (!this._viewer) return
this._updateMainDiv(this._viewer.assetManager.processState)
}

onRemove(viewer: ThreeViewer) {
this._mainDiv.remove()
this._contentDiv?.remove()
this.processState.clear()
// this._contentDiv?.remove()
viewer.assetManager.removeEventListener('processStateUpdate', this._onProcessStateUpdate)
return super.onRemove(viewer)
}
}

+ 25
- 2
src/plugins/export/FileTransferPlugin.ts Vedi File

@@ -1,4 +1,4 @@
import {AViewerPluginSync} from '../../viewer'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {downloadBlob} from 'ts-browser-helpers'

export class FileTransferPlugin extends AViewerPluginSync<'transferFile'> {
@@ -10,7 +10,7 @@ export class FileTransferPlugin extends AViewerPluginSync<'transferFile'> {

async exportFile(file: File|Blob, name?: string) {
name = name || (file as File).name || 'file_export'
this.dispatchEvent({type: 'transferFile', path: name, state: 'exporting'})
this.dispatchEvent({type: 'transferFile', path: name, state: 'exporting', progress: 0})
await this.actions.exportFile(file, name, ({state, progress})=>{
this.dispatchEvent({type: 'transferFile', path: name, state: state ?? 'exporting', progress})
})
@@ -23,5 +23,28 @@ export class FileTransferPlugin extends AViewerPluginSync<'transferFile'> {
},
}


constructor() {
super()
this._updateProcessState = this._updateProcessState.bind(this)
}

onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)
this.addEventListener('transferFile', this._updateProcessState as any)
}
onRemove(viewer: ThreeViewer) {
this.removeEventListener('transferFile', this._updateProcessState as any)
super.onRemove(viewer)
}

protected _updateProcessState(data: {path: string, state: string, progress?: number}) {
if (!this._viewer) return
this._viewer.assetManager.setProcessState(data.path, data.state !== 'done' ? {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
} : undefined)
}

actions = {...this.defaultActions}
}

+ 3
- 2
src/plugins/interaction/LoadingScreenPlugin.ts Vedi File

@@ -23,7 +23,8 @@ export class LoadingScreenPlugin extends AAssetManagerProcessStatePlugin {
html: '<span class="loader"></span>',
}]
refresh() {
this._updateMainDiv(this._isPreviewing ? this._previewState : this.processState, false)
if (!this._viewer) return
this._updateMainDiv(this._isPreviewing ? this._previewState : this._viewer.assetManager.processState, false)
}

@uiDropdown('Loader', ['Spinner 1'].map((v, i) => ({value: i, label: v})))
@@ -228,7 +229,7 @@ export class LoadingScreenPlugin extends AAssetManagerProcessStatePlugin {
stylesheet?: HTMLStyleElement
stylesheetLoader?: HTMLStyleElement[]
onAdded(viewer: ThreeViewer) {
this.stylesheet = createStyles(styles, viewer.container)
this.stylesheet = createStyles(this.styles, viewer.container)
this.stylesheetLoader = this.spinners.map(s => createStyles(s.styles, viewer.container))

viewer.scene.addEventListener('sceneUpdate', this._sceneUpdate)

Loading…
Annulla
Salva