| "files": [ | "files": [ | ||||
| "dist", | "dist", | ||||
| "src", | "src", | ||||
| "lib", | |||||
| "examples", | "examples", | ||||
| "plugins/*/dist", | "plugins/*/dist", | ||||
| "plugins/*/src", | "plugins/*/src", |
| import {IDisposable, PartialRecord} from 'ts-browser-helpers' | |||||
| import {PartialRecord} from 'ts-browser-helpers' | |||||
| import { | import { | ||||
| Blending, | Blending, | ||||
| Clock, | Clock, | ||||
| } | } | ||||
| export type IRenderManagerEventTypes = 'animationLoop'|'update'|'resize'|'contextLost'|'contextRestored' | export type IRenderManagerEventTypes = 'animationLoop'|'update'|'resize'|'contextLost'|'contextRestored' | ||||
| export interface RendererBlitOptions {source?: Texture, viewport?: Vector4, material?: ShaderMaterial, clear?: boolean, respectColorSpace?: boolean, blending?: Blending, transparent?: boolean} | export interface RendererBlitOptions {source?: Texture, viewport?: Vector4, material?: ShaderMaterial, clear?: boolean, respectColorSpace?: boolean, blending?: Blending, transparent?: boolean} | ||||
| export interface IRenderManager<E extends IRenderManagerEvent = IRenderManagerEvent, ET extends string = IRenderManagerEventTypes> extends RenderTargetManager<E, ET>, IDisposable, IShaderPropertiesUpdater{ | |||||
| export interface IRenderManager<E extends IRenderManagerEvent = IRenderManagerEvent, ET extends string = IRenderManagerEventTypes> extends RenderTargetManager<E, ET>, IShaderPropertiesUpdater{ | |||||
| readonly renderer: IWebGLRenderer | readonly renderer: IWebGLRenderer | ||||
| readonly needsRender: boolean | readonly needsRender: boolean | ||||
| rebuildPipeline(setDirty?: boolean): void | rebuildPipeline(setDirty?: boolean): void |
| this.setDirty({refreshScene: true}) | this.setDirty({refreshScene: true}) | ||||
| } | } | ||||
| @uiButton() | |||||
| @uiButton(undefined, {sendArgs: false}) | |||||
| centerAllGeometries(keepPosition = true, obj?: IObject3D) { | centerAllGeometries(keepPosition = true, obj?: IObject3D) { | ||||
| const geoms = new Set<IGeometry>() | const geoms = new Set<IGeometry>() | ||||
| ;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry)) | ;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry)) | ||||
| setDirty && this.setDirty({refreshScene: true}) | setDirty && this.setDirty({refreshScene: true}) | ||||
| } | } | ||||
| disposeSceneModels(setDirty = true) { | |||||
| [...this.modelRoot.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent()) | |||||
| this.modelRoot.clear() | |||||
| if (setDirty) this.setDirty({refreshScene: true}) | |||||
| disposeSceneModels(setDirty = true, clear = true) { | |||||
| if (clear) { | |||||
| [...this.modelRoot.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent()) | |||||
| this.modelRoot.clear() | |||||
| if (setDirty) this.setDirty({refreshScene: true}) | |||||
| } else { | |||||
| this.modelRoot.children.forEach(child => child.dispose && child.dispose()) | |||||
| } | |||||
| } | } | ||||
| private _onEnvironmentChange() { | private _onEnvironmentChange() { | ||||
| * Dispose the scene and clear all resources. | * Dispose the scene and clear all resources. | ||||
| * @warn Not fully implemented yet, just clears the scene. | * @warn Not fully implemented yet, just clears the scene. | ||||
| */ | */ | ||||
| dispose(): void { | |||||
| this.disposeSceneModels(); | |||||
| [...this.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent()) | |||||
| this.clear() | |||||
| dispose(clear = true): void { | |||||
| this.disposeSceneModels(false, clear) | |||||
| if (clear) { | |||||
| [...this.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent()) | |||||
| this.clear() | |||||
| } | |||||
| // todo: dispose more stuff? | // todo: dispose more stuff? | ||||
| this.environment?.dispose() | this.environment?.dispose() | ||||
| if ((this.background as ITexture)?.isTexture) (this.background as ITexture)?.dispose?.() | if ((this.background as ITexture)?.isTexture) (this.background as ITexture)?.dispose?.() | ||||
| this.environment = null | |||||
| this.background = null | |||||
| if (clear) { | |||||
| this.environment = null | |||||
| this.background = null | |||||
| } | |||||
| return | return | ||||
| } | } | ||||
| return super.onRemove(viewer) | return super.onRemove(viewer) | ||||
| } | } | ||||
| @uiButton('Reset To First View') | |||||
| @uiButton('Reset To First View', {sendArgs: false}) | |||||
| public async resetToFirstView(duration = 100) { | public async resetToFirstView(duration = 100) { | ||||
| if (this.isDisabled()) return | if (this.isDisabled()) return | ||||
| this._currentView = undefined | this._currentView = undefined | ||||
| } | } | ||||
| addView(view: CameraView) { | addView(view: CameraView) { | ||||
| this._cameraViews.push(view) | |||||
| if (!this._cameraViews.includes(view)) this._cameraViews.push(view) | |||||
| view.addEventListener('setView', this._viewSetView as any) | view.addEventListener('setView', this._viewSetView as any) | ||||
| view.addEventListener('updateView', this._viewUpdateView as any) | view.addEventListener('updateView', this._viewUpdateView as any) | ||||
| view.addEventListener('deleteView', this._viewDeleteView as any) | view.addEventListener('deleteView', this._viewDeleteView as any) | ||||
| const i = this._cameraViews.indexOf(view) | const i = this._cameraViews.indexOf(view) | ||||
| if (i >= 0) | if (i >= 0) | ||||
| this._cameraViews.splice(i, 1) | this._cameraViews.splice(i, 1) | ||||
| view.removeEventListener('setView', this._viewSetView as any) | |||||
| view.removeEventListener('updateView', this._viewUpdateView as any) | |||||
| view.removeEventListener('deleteView', this._viewDeleteView as any) | |||||
| view.removeEventListener('animateView', this._viewAnimateView as any) | |||||
| this.uiConfig.uiRefresh?.() | this.uiConfig.uiRefresh?.() | ||||
| this.dispatchEvent({type: 'viewDelete', view}) | this.dispatchEvent({type: 'viewDelete', view}) | ||||
| } | } | ||||
| private _currentView: CameraView | undefined | private _currentView: CameraView | undefined | ||||
| @uiButton('Focus Next') focusNext = (wrap = true)=>{ | |||||
| @uiButton('Focus Next', {sendArgs: false}) focusNext = (wrap = true)=>{ | |||||
| if (this._animating) return | if (this._animating) return | ||||
| if (this._cameraViews.length < 2) return | if (this._cameraViews.length < 2) return | ||||
| let index = this._cameraViews.findIndex(v=>v === this._currentView) | let index = this._cameraViews.findIndex(v=>v === this._currentView) | ||||
| else index = index % this._cameraViews.length | else index = index % this._cameraViews.length | ||||
| this.animateToView(index) | this.animateToView(index) | ||||
| } | } | ||||
| @uiButton('Focus Previous') focusPrevious = (wrap = true)=> { | |||||
| @uiButton('Focus Previous', {sendArgs: false}) focusPrevious = (wrap = true)=> { | |||||
| if (this._animating) return | if (this._animating) return | ||||
| if (this._cameraViews.length < 2 || !this._currentView) return | if (this._cameraViews.length < 2 || !this._currentView) return | ||||
| let index = this._cameraViews.findIndex(v=>v === this._currentView) | let index = this._cameraViews.findIndex(v=>v === this._currentView) | ||||
| private _popAnimations: AnimationResult[] = [] | private _popAnimations: AnimationResult[] = [] | ||||
| async animateToView(_view: CameraView|number, duration?: number, easing?: Easing|EasingFunctionType, camera?: ICamera, throwOnStop = false) { | |||||
| async animateToView(_view: CameraView|number|string, duration?: number, easing?: Easing|EasingFunctionType, camera?: ICamera, throwOnStop = false) { | |||||
| camera = camera || this._viewer?.scene.mainCamera | camera = camera || this._viewer?.scene.mainCamera | ||||
| if (!camera) return | if (!camera) return | ||||
| // if (this._currentView === view) return // todo: also check if the camera is at the correct position and orientation, till then use resetToFirstView to reset current view | // if (this._currentView === view) return // todo: also check if the camera is at the correct position and orientation, till then use resetToFirstView to reset current view | ||||
| return | return | ||||
| } | } | ||||
| } | } | ||||
| const view = typeof _view === 'number' ? this._cameraViews[_view] : _view | |||||
| const view = typeof _view === 'number' ? this._cameraViews[_view] : | |||||
| typeof _view === 'string' ? this._cameraViews.find(v=>v.name === _view) : | |||||
| _view | |||||
| if (!view) { | |||||
| this._viewer?.console.warn('Invalid view', _view) | |||||
| return | |||||
| } | |||||
| this._currentView = view | this._currentView = view | ||||
| this._animating = true | this._animating = true | ||||
| fromJSON(data: any, meta?: any): this | null { | fromJSON(data: any, meta?: any): this | null { | ||||
| this._cameraViews.forEach(v=>this.deleteView(v)) // deserialize pushes to the existing array | this._cameraViews.forEach(v=>this.deleteView(v)) // deserialize pushes to the existing array | ||||
| if (super.fromJSON(data, meta)) { | if (super.fromJSON(data, meta)) { | ||||
| this._cameraViews.forEach(v=>this.addView(v)) | |||||
| this.uiConfig.uiRefresh?.() | this.uiConfig.uiRefresh?.() | ||||
| return this | return this | ||||
| } | } | ||||
| public async animateToObject(selected?: Object3D, distanceMultiplier = 4, duration?: number, ease?: Easing|EasingFunctionType, distanceBounds = {min: 0.5, max: 5.0}) { | public async animateToObject(selected?: Object3D, distanceMultiplier = 4, duration?: number, ease?: Easing|EasingFunctionType, distanceBounds = {min: 0.5, max: 5.0}) { | ||||
| if (!this._viewer) return | if (!this._viewer) return | ||||
| const bbox = new Box3B().expandByObject(selected || this._viewer.scene.modelRoot.modelObject, false, true) | |||||
| const bbox = new Box3B().expandByObject(selected || this._viewer.scene.modelRoot, false, true) | |||||
| const center = bbox.getCenter(new Vector3()) | const center = bbox.getCenter(new Vector3()) | ||||
| const size = bbox.getSize(new Vector3()) | const size = bbox.getSize(new Vector3()) | ||||
| const radius = size.length() / 2 | const radius = size.length() / 2 |
| this._viewer?.setDirty() | this._viewer?.setDirty() | ||||
| } | } | ||||
| @uiButton('Stop') | |||||
| @uiButton('Stop', {sendArgs: false}) | |||||
| stopAnimation(reset = false) { | stopAnimation(reset = false) { | ||||
| this._animationState = 'stopped' | this._animationState = 'stopped' | ||||
| // safeSetProperty(this._viewer?.getPlugin<PickingPlugin>('Picking'), 'enabled', true) | // safeSetProperty(this._viewer?.getPlugin<PickingPlugin>('Picking'), 'enabled', true) | ||||
| } | } | ||||
| @uiButton('Reset') | |||||
| @uiButton('Reset', {sendArgs: false}) | |||||
| resetAnimation() { | resetAnimation() { | ||||
| if (this._animationState !== 'stopped' && this._animationState !== 'none') { | if (this._animationState !== 'stopped' && this._animationState !== 'none') { | ||||
| this.stopAnimation(true) // reset and stop | this.stopAnimation(true) // reset and stop |
| quality: 0.9, | quality: 0.9, | ||||
| } | } | ||||
| @uiButton('Download .png') | |||||
| // @uiButton('Download .png', {sendArgs: false}) | |||||
| async downloadSnapshot(filename?: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {waitForProgressive: true}): Promise<void> { | async downloadSnapshot(filename?: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {waitForProgressive: true}): Promise<void> { | ||||
| if (!this._viewer) return | if (!this._viewer) return | ||||
| if (!options.mimeType && !filename) this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png' | if (!options.mimeType && !filename) this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png' | ||||
| if (file) await this._viewer.exportBlob(file, file.name) | if (file) await this._viewer.exportBlob(file, file.name) | ||||
| } | } | ||||
| @uiButton('Download .png') | |||||
| protected async _downloadPng(): Promise<void> { | |||||
| this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png' | |||||
| return this.downloadSnapshot(undefined, {mimeType: 'image/png'}) | |||||
| } | |||||
| @uiButton('Download .jpeg') | @uiButton('Download .jpeg') | ||||
| protected async _downloadJpeg(): Promise<void> { | protected async _downloadJpeg(): Promise<void> { | ||||
| this.filename = this.filename.split('.').slice(0, -1).join('.') + '.jpeg' | this.filename = this.filename.split('.').slice(0, -1).join('.') + '.jpeg' |
| import {GBufferRenderPass} from '../../postprocessing' | import {GBufferRenderPass} from '../../postprocessing' | ||||
| import {ThreeViewer} from '../../viewer' | import {ThreeViewer} from '../../viewer' | ||||
| import {IRenderTarget} from '../../rendering' | import {IRenderTarget} from '../../rendering' | ||||
| import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js' | |||||
| import {uiPanelContainer, uiSlider, uiToggle} from 'uiconfig.js' | |||||
| import {HVBlurHelper} from '../../three/utils/HVBlurHelper' | import {HVBlurHelper} from '../../three/utils/HVBlurHelper' | ||||
| import {shaderReplaceString} from '../../utils' | import {shaderReplaceString} from '../../utils' | ||||
| @uiFolderContainer('Contact Shadow Ground') | |||||
| @uiPanelContainer('Contact Shadow Ground') | |||||
| export class ContactShadowGroundPlugin extends BaseGroundPlugin { | export class ContactShadowGroundPlugin extends BaseGroundPlugin { | ||||
| static readonly PluginType = 'ContactShadowGroundPlugin' | static readonly PluginType = 'ContactShadowGroundPlugin' | ||||
| })) | })) | ||||
| protected _selectedType = '' | protected _selectedType = '' | ||||
| @uiButton('Generate') | |||||
| @uiButton('Generate', {sendArgs: false}) | |||||
| generate(type?: string, params?: any, addToScene = true) { | generate(type?: string, params?: any, addToScene = true) { | ||||
| if (!this._viewer) throw new Error('No viewer') | if (!this._viewer) throw new Error('No viewer') | ||||
| const obj = this.generators[type ?? this._selectedType]?.(params) | const obj = this.generators[type ?? this._selectedType]?.(params) |
| */ | */ | ||||
| protected abstract _simplify(geometry: IGeometry, count: number): IGeometry | protected abstract _simplify(geometry: IGeometry, count: number): IGeometry | ||||
| @uiButton('Simplify All') | |||||
| @uiButton('Simplify All', {sendArgs: false}) | |||||
| async simplifyAll(root?: IObject3D, options?: SimplifyOptions) { | async simplifyAll(root?: IObject3D, options?: SimplifyOptions) { | ||||
| if (!root && this._viewer) root = this._viewer.scene.modelRoot | if (!root && this._viewer) root = this._viewer.scene.modelRoot | ||||
| if (!root) { | if (!root) { |
| } | } | ||||
| } | } | ||||
| @uiButton('Enter FullScreen') | |||||
| @uiButton('Enter FullScreen', {sendArgs: false}) | |||||
| async enter(element?: HTMLElement): Promise<void> { | async enter(element?: HTMLElement): Promise<void> { | ||||
| if (this.isFullScreen()) return | if (this.isFullScreen()) return | ||||
| return elem.msRequestFullscreen() | return elem.msRequestFullscreen() | ||||
| } | } | ||||
| } | } | ||||
| @uiButton('Exit FullScreen') | |||||
| @uiButton('Exit FullScreen', {sendArgs: false}) | |||||
| async exit(): Promise<void> { | async exit(): Promise<void> { | ||||
| if (document.exitFullscreen) { | if (document.exitFullscreen) { | ||||
| return document.exitFullscreen() | return document.exitFullscreen() | ||||
| return (document as any).msExitFullscreen() | return (document as any).msExitFullscreen() | ||||
| } | } | ||||
| } | } | ||||
| @uiButton('Toggle FullScreen') | |||||
| @uiButton('Toggle FullScreen', {sendArgs: false}) | |||||
| async toggle(element?: HTMLElement): Promise<void> { | async toggle(element?: HTMLElement): Promise<void> { | ||||
| if (this.isFullScreen()) { | if (this.isFullScreen()) { | ||||
| return this.exit() | return this.exit() |
| * This is a port of Relief Parallax Mapping from [Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html) | * This is a port of Relief Parallax Mapping from [Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html) | ||||
| * @category Plugins | * @category Plugins | ||||
| */ | */ | ||||
| @uiFolderContainer('Parallax Mapping') | |||||
| @uiFolderContainer('Parallax Bump Mapping (MatExt)') | |||||
| export class ParallaxMappingPlugin extends AViewerPluginSync<''> { | export class ParallaxMappingPlugin extends AViewerPluginSync<''> { | ||||
| public static PluginType = 'ReliefParallaxMapping' | public static PluginType = 'ReliefParallaxMapping' | ||||
| preprocessMaterial = (material: IMaterial, renderToGBuffer?: boolean) => { | preprocessMaterial = (material: IMaterial, renderToGBuffer?: boolean) => { | ||||
| renderToGBuffer = renderToGBuffer ?? material.userData.renderToGBuffer | renderToGBuffer = renderToGBuffer ?? material.userData.renderToGBuffer | ||||
| if (material.userData.pluginsDisabled) renderToGBuffer = false | |||||
| if ( | if ( | ||||
| material.transparent && renderToGBuffer || // transparent and render to gbuffer | |||||
| material.transparent && (renderToGBuffer || material.opacity > 0.99) || // transparent and render to gbuffer | |||||
| !material.transparent && !material.transmission && renderToGBuffer === false // opaque and dont render to gbuffer | !material.transparent && !material.transmission && renderToGBuffer === false // opaque and dont render to gbuffer | ||||
| ) { | ) { | ||||
| this._transparentMats.add(material) | this._transparentMats.add(material) |
| @onChange2(RenderManager.prototype.rebuildPipeline) | @onChange2(RenderManager.prototype.rebuildPipeline) | ||||
| public autoBuildPipeline = true | public autoBuildPipeline = true | ||||
| @uiButton('Rebuild Pipeline') | |||||
| @uiButton('Rebuild Pipeline', {sendArgs: false}) | |||||
| rebuildPipeline(setDirty = true): void { | rebuildPipeline(setDirty = true): void { | ||||
| this._passesNeedsUpdate = true | this._passesNeedsUpdate = true | ||||
| if (setDirty) this._updated({change: 'rebuild'}) | if (setDirty) this._updated({change: 'rebuild'}) | ||||
| this._updated({change: 'passRefresh'}) | this._updated({change: 'passRefresh'}) | ||||
| } | } | ||||
| dispose(): void { | |||||
| super.dispose() | |||||
| dispose(clear = true): void { | |||||
| super.dispose(clear) | |||||
| this._renderer.dispose() | this._renderer.dispose() | ||||
| } | } | ||||
| * @param quality | * @param quality | ||||
| * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) | * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) | ||||
| */ | */ | ||||
| renderTargetToDataUrl(target: WebGLMultipleRenderTargets|WebGLRenderTarget, mimeType = 'image/png', quality = 90, textureIndex = 0): string { | |||||
| renderTargetToDataUrl(target: WebGLMultipleRenderTargets|WebGLRenderTarget|IRenderTarget, mimeType = 'image/png', quality = 90, textureIndex = 0): string { | |||||
| const canvas = document.createElement('canvas') | const canvas = document.createElement('canvas') | ||||
| canvas.width = target.width | canvas.width = target.width | ||||
| canvas.height = target.height | canvas.height = target.height | ||||
| const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture | const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture | ||||
| const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(texture.colorSpace) ? <PredefinedColorSpace>texture.colorSpace : undefined}) | const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(texture.colorSpace) ? <PredefinedColorSpace>texture.colorSpace : undefined}) | ||||
| if (texture.type === HalfFloatType || texture.type === FloatType) { | if (texture.type === HalfFloatType || texture.type === FloatType) { | ||||
| const buffer = this.renderTargetToBuffer(target, textureIndex) | |||||
| const buffer = this.renderTargetToBuffer(target as any, textureIndex) | |||||
| textureDataToImageData({data: buffer, width: target.width, height: target.height}, texture.colorSpace, imageData) // this handles converting to srgb | textureDataToImageData({data: buffer, width: target.width, height: target.height}, texture.colorSpace, imageData) // this handles converting to srgb | ||||
| } else { | } else { | ||||
| // todo: handle rgbm to srgb conversion? | // todo: handle rgbm to srgb conversion? | ||||
| this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, imageData.data, undefined, textureIndex) | |||||
| this._renderer.readRenderTargetPixels(target as any, 0, 0, target.width, target.height, imageData.data, undefined, textureIndex) | |||||
| } | } | ||||
| ctx.putImageData(imageData, 0, 0) | ctx.putImageData(imageData, 0, 0) |
| protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget | protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget | ||||
| dispose() { | |||||
| dispose(clear = true) { | |||||
| this._trackedTargets.forEach(t=>t.dispose()) | this._trackedTargets.forEach(t=>t.dispose()) | ||||
| Object.values(this._trackedTempTargets).forEach(t=>t.dispose()) | Object.values(this._trackedTempTargets).forEach(t=>t.dispose()) | ||||
| this._trackedTargets = [] | |||||
| this._releasedTempTargets = {} | |||||
| this._trackedTempTargets = [] | |||||
| if (clear) { | |||||
| this._trackedTargets = [] | |||||
| this._releasedTempTargets = {} | |||||
| this._trackedTempTargets = [] | |||||
| } | |||||
| } | } | ||||
| /** | /** |
| /** | /** | ||||
| * Disposes the viewer and frees up all resource and events. Do not use the viewer after calling dispose. | * Disposes the viewer and frees up all resource and events. Do not use the viewer after calling dispose. | ||||
| * @note - If you want to reuse the viewer, set viewer.enabled to false instead, then set it to true again when required. To dispose all the objects, materials in the scene use `viewer.scene.disposeSceneModels()` | * @note - If you want to reuse the viewer, set viewer.enabled to false instead, then set it to true again when required. To dispose all the objects, materials in the scene use `viewer.scene.disposeSceneModels()` | ||||
| * This function is not fully implemented yet. There might be some memory leaks. | |||||
| * This function is not fully implemented yet. There might be some leaks. | |||||
| * @todo - return promise? | * @todo - return promise? | ||||
| */ | */ | ||||
| public dispose(): void { | |||||
| public dispose(clear = true): void { | |||||
| // todo: dispose stuff from constructor etc | // todo: dispose stuff from constructor etc | ||||
| for (const plugin of [...Object.values(this.plugins)]) { | |||||
| this.removePlugin(plugin, true) | |||||
| if (clear) { | |||||
| for (const plugin of [...Object.values(this.plugins)]) { | |||||
| this.removePlugin(plugin, true) | |||||
| } | |||||
| } | } | ||||
| this._scene.dispose() | |||||
| this.renderManager.dispose() | |||||
| this._scene.dispose(clear) | |||||
| this.renderManager.dispose(clear) | |||||
| this._canvas.removeEventListener('webglcontextrestored', this._onContextRestore, false) | |||||
| this._canvas.removeEventListener('webglcontextlost', this._onContextLost, false) | |||||
| if (clear) { | |||||
| this._canvas.removeEventListener('webglcontextrestored', this._onContextRestore, false) | |||||
| this._canvas.removeEventListener('webglcontextlost', this._onContextLost, false) | |||||
| ;(window as any).threeViewers?.splice((window as any).threeViewers.indexOf(this), 1) | |||||
| ;(window as any).threeViewers?.splice((window as any).threeViewers.indexOf(this), 1) | |||||
| if (this.resizeObserver) this.resizeObserver.unobserve(this._canvas) | |||||
| window.removeEventListener('resize', this.resize) | |||||
| if (this.resizeObserver) this.resizeObserver.unobserve(this._canvas) | |||||
| window.removeEventListener('resize', this.resize) | |||||
| } | |||||
| this.dispatchEvent({type: 'dispose'}) | |||||
| this.dispatchEvent({type: 'dispose', clear}) | |||||
| } | } | ||||
| /** | /** |
| export const VERSION = '0.0.31' | |||||
| export const VERSION = '0.0.32' |