| ImportResult, | ImportResult, | ||||
| LoadFileOptions, | LoadFileOptions, | ||||
| ProcessRawOptions, | ProcessRawOptions, | ||||
| RootSceneImportResult, | |||||
| } from './IAssetImporter' | } from './IAssetImporter' | ||||
| import {IAsset, IFile} from './IAsset' | import {IAsset, IFile} from './IAsset' | ||||
| import {IImporter, ILoader} from './IImporter' | import {IImporter, ILoader} from './IImporter' | ||||
| // console.log(result) | // console.log(result) | ||||
| if (!options.forceImport && result) { | if (!options.forceImport && result) { | ||||
| const results = await this.processRaw<T>(result, options) // just in case its not processed. Internal check is done to ensure it's not processed twice | const results = await this.processRaw<T>(result, options) // just in case its not processed. Internal check is done to ensure it's not processed twice | ||||
| let isDisposed = false // if any of the objects is disposed | |||||
| for (const r of results) { | |||||
| // todo: check if this is still required. | |||||
| if ((r as RootSceneImportResult)?.userData?.rootSceneModelRoot) { // in case processImported is false we need a special case check here | |||||
| if (r?.children?.find((c: any) => c.__disposed)) { | |||||
| isDisposed = true | |||||
| break | |||||
| } | |||||
| } | |||||
| if (r && !r.__disposed) continue // todo add __disposed to object, material, texture, etc | |||||
| isDisposed = true | |||||
| break | |||||
| } | |||||
| // let isDisposed = false // if any of the objects is disposed | |||||
| // for (const r of results) { | |||||
| // // todo: check if this is still required. | |||||
| // if ((r as RootSceneImportResult)?.userData?.rootSceneModelRoot) { // in case processImported is false we need a special case check here | |||||
| // if (r?.children?.find((c: any) => c.__disposed)) { | |||||
| // isDisposed = true | |||||
| // break | |||||
| // } | |||||
| // } | |||||
| // if (r && !r.__disposed) continue // todo add __disposed to object, material, texture, etc | |||||
| // isDisposed = true | |||||
| // break | |||||
| // } | |||||
| // todo: should we check if any of it's children is disposed ? | // todo: should we check if any of it's children is disposed ? | ||||
| if (!isDisposed || options.reimportDisposed === false) return results | |||||
| // if (!isDisposed || options.reimportDisposed === false) | |||||
| return results | |||||
| } | } | ||||
| // todo: add support to get cloned asset? if we want to import multiple times and everytime return a cloned asset | // todo: add support to get cloned asset? if we want to import multiple times and everytime return a cloned asset | ||||
| else arrs.push(result) | else arrs.push(result) | ||||
| } | } | ||||
| // remove preImportedRaw when any of the assets is disposed. This is to prevent memory leaks | // remove preImportedRaw when any of the assets is disposed. This is to prevent memory leaks | ||||
| arrs.forEach(r=>r.addEventListener?.('dispose', () => { | |||||
| arrs.forEach(r=>r.addEventListener?.('dispose', () => { // todo: recheck after dispose logic change | |||||
| if (asset?.preImportedRaw) asset.preImportedRaw = undefined | if (asset?.preImportedRaw) asset.preImportedRaw = undefined | ||||
| if (asset?.preImported) asset.preImported = undefined | if (asset?.preImported) asset.preImported = undefined | ||||
| })) | })) | ||||
| if (file) { | if (file) { | ||||
| file.__loadedAsset = res | file.__loadedAsset = res | ||||
| // todo: recheck below code after dispose logic change | |||||
| // Clear the reference __loadedAsset when any one asset is disposed. | // Clear the reference __loadedAsset when any one asset is disposed. | ||||
| // it's a bit hacky to do this here, but it works for now. todo: move to a better place | // it's a bit hacky to do this here, but it works for now. todo: move to a better place | ||||
| let ress: any[] = [] | let ress: any[] = [] |
| userData?: IImportResultUserData | userData?: IImportResultUserData | ||||
| // eslin t-disable-next-line @typescript-eslint/naming-convention | |||||
| __rootPath?: string | __rootPath?: string | ||||
| // eslin t-disable-next-line @typescript-eslint/naming-convention | |||||
| __rootBlob?: IFile | __rootBlob?: IFile | ||||
| // eslin t-disable-next-line @typescript-eslint/naming-convention | |||||
| __disposed?: boolean | |||||
| // __disposed?: boolean | |||||
| [key: string]: any | [key: string]: any | ||||
| } | } |
| } from '../core' | } from '../core' | ||||
| import {downloadFile} from 'ts-browser-helpers' | import {downloadFile} from 'ts-browser-helpers' | ||||
| import {MaterialExtension} from '../materials' | import {MaterialExtension} from '../materials' | ||||
| import {generateUUID} from '../three/utils/misc' | |||||
| import {generateUUID, isInScene} from '../three' | |||||
| export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | ||||
| const mat = e.target | const mat = e.target | ||||
| if (!mat || mat.assetType !== 'material') return | if (!mat || mat.assetType !== 'material') return | ||||
| mat.setDirty() | mat.setDirty() | ||||
| const maps = this._getMapsForMaterial(mat) | |||||
| maps.forEach(map=>{ | |||||
| const mats = map.userData.__appliedMaterials! | |||||
| mats?.delete(mat) | |||||
| if (!mats || map.userData.disposeOnIdle === false) return | |||||
| if (mats.size === 0) map.dispose() | |||||
| }) | |||||
| this.unregisterMaterial(mat) | |||||
| this._getMapsForMaterial(mat) | |||||
| .forEach(map=> | |||||
| !map.isRenderTargetTexture && map.userData.disposeOnIdle !== false && | |||||
| map.dispose && !isInScene(map) && map.dispose()) | |||||
| // this.unregisterMaterial(mat) // todo | |||||
| } | } | ||||
| private _materialMaps = new Map<string, Set<ITexture>>() | private _materialMaps = new Map<string, Set<ITexture>>() |
| import {IImportResultUserData} from '../assetmanager' | import {IImportResultUserData} from '../assetmanager' | ||||
| export interface IGeometryUserData extends IImportResultUserData{ | export interface IGeometryUserData extends IImportResultUserData{ | ||||
| disposeOnIdle?: boolean // default: true | |||||
| /** | |||||
| * Automatically dispose geometry when not used by any object in the scene | |||||
| * @default true | |||||
| */ | |||||
| disposeOnIdle?: boolean | |||||
| // [key: string]: any // commented for noe | // [key: string]: any // commented for noe | ||||
| } | } | ||||
| export interface IGeometry<Attributes extends NormalOrGLBufferAttributes = NormalBufferAttributes> extends BufferGeometry<Attributes, IGeometryEvent, IGeometryEventTypes>, IUiConfigContainer { | export interface IGeometry<Attributes extends NormalOrGLBufferAttributes = NormalBufferAttributes> extends BufferGeometry<Attributes, IGeometryEvent, IGeometryEventTypes>, IUiConfigContainer { | ||||
| // Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation | // Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation | ||||
| userData: IGeometryUserData | userData: IGeometryUserData | ||||
| /** | |||||
| * Disposes the geometry from the GPU. | |||||
| * Set force to false if not sure the geometry is used by any object in the scene. | |||||
| * // todo add check for visible in scene also? or is that overkill | |||||
| * @param force - when true, same as three.js dispose. when false, only disposes if disposeOnIdle not false and not used by any object in the scene. default: true | |||||
| */ | |||||
| dispose(force?: boolean): void | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | // eslint-disable-next-line @typescript-eslint/naming-convention | ||||
| _uiConfig?: UiObjectConfig | _uiConfig?: UiObjectConfig |
| } | } | ||||
| export interface IMaterialUserData extends IImportResultUserData{ | export interface IMaterialUserData extends IImportResultUserData{ | ||||
| uuid?: string // adding to userdata also, so that its saved in gltf | uuid?: string // adding to userdata also, so that its saved in gltf | ||||
| disposeOnIdle?: boolean // default: true | |||||
| /** | |||||
| * Automatically dispose material when not used by any object in the scene | |||||
| * @default true | |||||
| */ | |||||
| disposeOnIdle?: boolean | |||||
| renderToGBuffer?: boolean | renderToGBuffer?: boolean | ||||
| /** | /** | ||||
| inverseAlphaMap?: boolean // only for physical material right now | inverseAlphaMap?: boolean // only for physical material right now | ||||
| // [key: string]: any // commented for noe | |||||
| [key: string]: any // commented for noe | |||||
| // legacy, to be removed | // legacy, to be removed | ||||
| // Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation | // Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation | ||||
| userData: IMaterialUserData | userData: IMaterialUserData | ||||
| /** | |||||
| * Disposes the material from the GPU. | |||||
| * Set force to false if not sure the material is used by any object in the scene. | |||||
| * // todo add check for visible in scene also? or is that overkill | |||||
| * @param force - when true, same as three.js dispose. when false, only disposes if disposeOnIdle not false and not used by any object in the scene. default: true | |||||
| */ | |||||
| dispose(force?: boolean): void | |||||
| // optional from subclasses, added here for autocomplete | // optional from subclasses, added here for autocomplete | ||||
| flatShading?: boolean | flatShading?: boolean | ||||
| map?: ITexture | null | map?: ITexture | null |
| * @deprecated | * @deprecated | ||||
| */ | */ | ||||
| dispose?: any | dispose?: any | ||||
| /** | |||||
| * @deprecated | |||||
| */ | |||||
| setMaterial?: any | setMaterial?: any | ||||
| /** | |||||
| * @deprecated | |||||
| */ | |||||
| setGeometry?: any | setGeometry?: any | ||||
| /** | |||||
| * @deprecated | |||||
| */ | |||||
| setDirty?: any | setDirty?: any | ||||
| /** | /** | ||||
| objectProcessor?: IObjectProcessor | objectProcessor?: IObjectProcessor | ||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| __disposed?: boolean | |||||
| // __disposed?: boolean | |||||
| /** | |||||
| * | |||||
| * @param removeFromParent - remove from parent. Default true | |||||
| */ | |||||
| dispose(removeFromParent?: boolean): void; | |||||
| // region inherited type fixes | // region inherited type fixes | ||||
| import {IMaterial} from './IMaterial' | import {IMaterial} from './IMaterial' | ||||
| import {Event, Texture} from 'three' | import {Event, Texture} from 'three' | ||||
| import {ChangeEvent} from 'uiconfig.js' | import {ChangeEvent} from 'uiconfig.js' | ||||
| import {IRenderTarget} from '../rendering' | |||||
| export interface ITextureUserData{ | export interface ITextureUserData{ | ||||
| mimeType?: string | mimeType?: string | ||||
| embedUrlImagePreviews?: boolean | embedUrlImagePreviews?: boolean | ||||
| disposeOnIdle?: boolean // automatically dispose when added to a material and then not used in any material | |||||
| /** | |||||
| * Automatically dispose texture when not used by any material that's applied to some object in the scene. | |||||
| * Works only after it's applied to a material once. | |||||
| */ | |||||
| disposeOnIdle?: boolean | |||||
| __appliedMaterials?: Set<IMaterial> | __appliedMaterials?: Set<IMaterial> | ||||
| } | } | ||||
| export type ITextureEventTypes = 'dispose' | 'update' | export type ITextureEventTypes = 'dispose' | 'update' | ||||
| setDirty?(): void | setDirty?(): void | ||||
| _target?: IRenderTarget // for internal use only. refers to the render target that this texture is attached to | |||||
| } | } | ||||
| export function upgradeTexture(this: ITexture) { | export function upgradeTexture(this: ITexture) { |
| import {UiObjectConfig} from 'uiconfig.js' | import {UiObjectConfig} from 'uiconfig.js' | ||||
| import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | ||||
| import {toIndexedGeometry} from '../../three' | |||||
| import {isInScene, toIndexedGeometry} from '../../three' | |||||
| import {BufferGeometry} from 'three' | |||||
| export const iGeometryCommons = { | export const iGeometryCommons = { | ||||
| setDirty: function(this: IGeometry, options?: IGeometrySetDirtyOptions): void { | setDirty: function(this: IGeometry, options?: IGeometrySetDirtyOptions): void { | ||||
| refreshUi: function(this: IGeometry) { | refreshUi: function(this: IGeometry) { | ||||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | ||||
| }, | }, | ||||
| dispose: (superDispose: BufferGeometry['dispose']): IGeometry['dispose'] => | |||||
| function(this: IGeometry, force = true): void { | |||||
| if (!force && (this.userData.disposeOnIdle === false || isInScene(this))) return | |||||
| superDispose.call(this) | |||||
| }, | |||||
| upgradeGeometry: upgradeGeometry, | upgradeGeometry: upgradeGeometry, | ||||
| makeUiConfig: function(this: IGeometry): UiObjectConfig { | makeUiConfig: function(this: IGeometry): UiObjectConfig { | ||||
| if (this.uiConfig) return this.uiConfig | if (this.uiConfig) return this.uiConfig | ||||
| }, | }, | ||||
| } | } | ||||
| export function upgradeGeometry(this: IGeometry) { | |||||
| function upgradeGeometry(this: IGeometry) { | |||||
| if (this.assetType === 'geometry') return // already upgraded | if (this.assetType === 'geometry') return // already upgraded | ||||
| if (!this.isBufferGeometry) { | if (!this.isBufferGeometry) { | ||||
| console.error('Material is not a this', this) | |||||
| console.error('Geometry is not a this', this) | |||||
| return | return | ||||
| } | } | ||||
| this.assetType = 'geometry' | this.assetType = 'geometry' | ||||
| this.dispose = iGeometryCommons.dispose(this.dispose) | |||||
| if (!this.setDirty) this.setDirty = iGeometryCommons.setDirty | if (!this.setDirty) this.setDirty = iGeometryCommons.setDirty | ||||
| if (!this.refreshUi) this.refreshUi = iGeometryCommons.refreshUi | if (!this.refreshUi) this.refreshUi = iGeometryCommons.refreshUi | ||||
| if (!this.appliedMeshes) this.appliedMeshes = new Set() | if (!this.appliedMeshes) this.appliedMeshes = new Set() | ||||
| if (!this.userData) this.userData = {} | if (!this.userData) this.userData = {} | ||||
| this.uiConfig = iGeometryCommons.makeUiConfig.call(this) | this.uiConfig = iGeometryCommons.makeUiConfig.call(this) | ||||
| // todo: dispose uiconfig on geometry dispose | // todo: dispose uiconfig on geometry dispose | ||||
| // todo: add serialization? | // todo: add serialization? |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | |||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | ||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| readonly appliedMeshes: Set<any> = new Set() | readonly appliedMeshes: Set<any> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | |||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | |||||
| readonly isRawShaderMaterial: boolean | readonly isRawShaderMaterial: boolean | ||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | |||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | ||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| import {MaterialExtender, MaterialExtension} from '../../materials' | import {MaterialExtender, MaterialExtension} from '../../materials' | ||||
| import {IScene} from '../IScene' | import {IScene} from '../IScene' | ||||
| import {IMaterial, IMaterialEvent, IMaterialSetDirtyOptions} from '../IMaterial' | import {IMaterial, IMaterialEvent, IMaterialSetDirtyOptions} from '../IMaterial' | ||||
| import {isInScene} from '../../three' | |||||
| /** | /** | ||||
| * Map of all material properties and their default values in three.js - Material.js | * Map of all material properties and their default values in three.js - Material.js | ||||
| this.setDirty?.() | this.setDirty?.() | ||||
| return this | return this | ||||
| }, | }, | ||||
| dispose: (superDispose: Material<any, any>['dispose']): IMaterial['dispose'] => | |||||
| function(this: IMaterial, force = true): void { | |||||
| if (!force && (this.userData.disposeOnIdle === false || isInScene(this))) return | |||||
| superDispose.call(this) | |||||
| }, | |||||
| clone: (superClone: Material<any, any>['clone']): IMaterial['clone'] => | clone: (superClone: Material<any, any>['clone']): IMaterial['clone'] => | ||||
| function(this: IMaterial): IMaterial { | function(this: IMaterial): IMaterial { | ||||
| if (!this.userData.cloneId) { | if (!this.userData.cloneId) { | ||||
| if (this.assetType === 'material') return this // already upgraded | if (this.assetType === 'material') return this // already upgraded | ||||
| this.assetType = 'material' | this.assetType = 'material' | ||||
| this.setValues = iMaterialCommons.setValues(this.setValues) | this.setValues = iMaterialCommons.setValues(this.setValues) | ||||
| this.dispose = iMaterialCommons.dispose(this.dispose) | |||||
| this.clone = iMaterialCommons.clone(this.clone) | this.clone = iMaterialCommons.clone(this.clone) | ||||
| this.dispatchEvent = iMaterialCommons.dispatchEvent(this.dispatchEvent) | this.dispatchEvent = iMaterialCommons.dispatchEvent(this.dispatchEvent) | ||||
| import {IGeometry, IGeometryEvent} from '../IGeometry' | import {IGeometry, IGeometryEvent} from '../IGeometry' | ||||
| import {Box3B} from '../../three' | import {Box3B} from '../../three' | ||||
| import {makeIObject3DUiConfig} from './IObjectUi' | import {makeIObject3DUiConfig} from './IObjectUi' | ||||
| import {upgradeGeometry} from '../geometry/iGeometryCommons' | |||||
| import {iGeometryCommons} from '../geometry/iGeometryCommons' | |||||
| import {iMaterialCommons} from '../material/iMaterialCommons' | import {iMaterialCommons} from '../material/iMaterialCommons' | ||||
| export const iObjectCommons = { | export const iObjectCommons = { | ||||
| if (!mat) continue | if (!mat) continue | ||||
| if (mat.appliedMeshes) { | if (mat.appliedMeshes) { | ||||
| mat.appliedMeshes.delete(this) | mat.appliedMeshes.delete(this) | ||||
| if (mat.userData && mat.appliedMeshes?.size === 0 && mat.userData.disposeOnIdle !== false) | |||||
| mat.dispose() // this will dispose textures(if they are idle) if the material is registered in the material manager | |||||
| // if (mat.userData && mat.appliedMeshes?.size === 0 && mat.userData.disposeOnIdle !== false) | |||||
| mat.dispose(false) // this will dispose textures(if they are idle) if the material is registered in the material manager | |||||
| } | } | ||||
| } | } | ||||
| this._onGeometryUpdate && geom.removeEventListener('geometryUpdate', this._onGeometryUpdate) | this._onGeometryUpdate && geom.removeEventListener('geometryUpdate', this._onGeometryUpdate) | ||||
| if (geom.appliedMeshes) { | if (geom.appliedMeshes) { | ||||
| geom.appliedMeshes.delete(this) | geom.appliedMeshes.delete(this) | ||||
| if (geom.userData && geom.appliedMeshes.size === 0 && geom.userData.disposeOnIdle !== false) geom.dispose() | |||||
| geom.dispose(false) | |||||
| } | } | ||||
| } | } | ||||
| if (geometry) { | if (geometry) { | ||||
| if (!geometry.assetType) { | if (!geometry.assetType) { | ||||
| // console.error('Geometry not upgraded') | // console.error('Geometry not upgraded') | ||||
| upgradeGeometry.call(geometry) | |||||
| iGeometryCommons.upgradeGeometry.call(geometry) | |||||
| } | } | ||||
| } | } | ||||
| this._currentGeometry = geometry || null | this._currentGeometry = geometry || null | ||||
| return superAdd.call(this, ...args) | return superAdd.call(this, ...args) | ||||
| }, | }, | ||||
| dispose: (superDispose?: IObject3D['dispose']) => | dispose: (superDispose?: IObject3D['dispose']) => | ||||
| function(this: IObject3D): void { | |||||
| this.dispatchEvent({type: 'dispose', bubbleToParent: false}) | |||||
| if (this.__disposed) { | |||||
| console.warn('Object already disposed', this) | |||||
| return | |||||
| function(this: IObject3D, removeFromParent = true): void { | |||||
| if (removeFromParent && this.parent) { | |||||
| this.removeFromParent() | |||||
| delete this.parentRoot | |||||
| } | } | ||||
| this.__disposed = true | |||||
| // this is first so that the leaf children are removed from parent first, removed event will be fired depth first | |||||
| for (const c of [...this.children]) c?.dispose?.() | |||||
| this.children = [] | |||||
| if (this.parent) this.removeFromParent() | |||||
| this.dispatchEvent({type: 'dispose', bubbleToParent: false}) | |||||
| // if (this.__disposed) { | |||||
| // console.warn('Object already disposed', this) | |||||
| // return | |||||
| // } | |||||
| // this.__disposed = true | |||||
| delete this.parentRoot | |||||
| // safeSetProperty(this, 'modelObject', undefined, true) // in-case modelObject is just a getter. | |||||
| this.userData = {} // todo: clear only our userdata and maybe any private variables? | |||||
| for (const c of [...this.children]) c?.dispose && c.dispose(false) // not removing the children from parent to preserve hierarchy | |||||
| // this.children = [] | |||||
| // this.uiConfig?.dispose?.() // todo: make uiConfig.dispose | // this.uiConfig?.dispose?.() // todo: make uiConfig.dispose | ||||
| this.uiConfig = undefined | |||||
| superDispose?.call(this) | superDispose?.call(this) | ||||
| }, | }, | ||||
| function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectProcessor?: IObjectProcessor): void { // parent is the root Object3DModel. | function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectProcessor?: IObjectProcessor): void { // parent is the root Object3DModel. | ||||
| if (!this) return | if (!this) return | ||||
| // console.log('upgradeObject3D', this, parent, objectProcessor) | // console.log('upgradeObject3D', this, parent, objectProcessor) | ||||
| if (this.__disposed) { | |||||
| console.warn('re-init/re-add disposed object, things might not work as intended', this) | |||||
| delete this.__disposed | |||||
| } | |||||
| // if (this.__disposed) { | |||||
| // console.warn('re-init/re-add disposed object, things might not work as intended', this) | |||||
| // delete this.__disposed | |||||
| // } | |||||
| if (!this.userData) this.userData = {} | if (!this.userData) this.userData = {} | ||||
| this.userData.uuid = this.uuid | this.userData.uuid = this.uuid | ||||
| if (parent) this.parentRoot = parent | if (parent) this.parentRoot = parent | ||||
| const oldFunctions = { | |||||
| dispatchEvent: this.dispatchEvent, | |||||
| clone: this.clone, | |||||
| copy: this.copy, | |||||
| add: this.add, | |||||
| dispose: this.dispose, | |||||
| } | |||||
| this.addEventListener('dispose', () => Object.assign(this, oldFunctions)) // todo: is this required? | |||||
| // const oldFunctions = { | |||||
| // dispatchEvent: this.dispatchEvent, | |||||
| // clone: this.clone, | |||||
| // copy: this.copy, | |||||
| // add: this.add, | |||||
| // dispose: this.dispose, | |||||
| // } | |||||
| // this.addEventListener('dispose', () => Object.assign(this, oldFunctions)) // todo: is this required? | |||||
| // typed because of type-checking | // typed because of type-checking | ||||
| this.dispatchEvent = iObjectCommons.dispatchEvent(this.dispatchEvent) | this.dispatchEvent = iObjectCommons.dispatchEvent(this.dispatchEvent) | ||||
| this.addEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent) | this.addEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent) | ||||
| this.addEventListener('removed', iObjectCommons.eventCallbacks.onRemovedFromParent) | this.addEventListener('removed', iObjectCommons.eventCallbacks.onRemovedFromParent) | ||||
| this.addEventListener('dispose', ()=>{ | |||||
| this.removeEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent) | |||||
| this.removeEventListener('removed', iObjectCommons.eventCallbacks.onRemovedFromParent) | |||||
| }) | |||||
| // this.addEventListener('dispose', ()=>{ | |||||
| // this.removeEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent) | |||||
| // this.removeEventListener('removed', iObjectCommons.eventCallbacks.onRemovedFromParent) | |||||
| // }) | |||||
| if ((this.isMesh || this.isLine) && !this.userData.__meshSetup) { | if ((this.isMesh || this.isLine) && !this.userData.__meshSetup) { | ||||
| this.userData.__meshSetup = true | this.userData.__meshSetup = true | ||||
| } | } | ||||
| this.addEventListener('dispose', ()=>{ | this.addEventListener('dispose', ()=>{ | ||||
| if (this.material) { | |||||
| // const oldMats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!] | |||||
| this.material = undefined // this will dispose material if not used by other meshes | |||||
| // delete this.material | |||||
| // for (const oldMat of oldMats) { | |||||
| // if (oldMat && oldMat.userData && oldMat.appliedMeshes?.size === 0 && oldMat.userData.disposeOnIdle !== false) oldMat.dispose() | |||||
| // } | |||||
| } | |||||
| if (this.geometry) { | |||||
| // const oldGeom = this.geometry | |||||
| this.geometry = undefined // this will dispose geometry if not used by other meshes | |||||
| // delete this.geometry | |||||
| // if (oldGeom && oldGeom.userData && oldGeom.appliedMeshes?.size === 0 && oldGeom.userData.disposeOnIdle !== false) oldGeom.dispose() | |||||
| } | |||||
| delete this._onGeometryUpdate | |||||
| (this.materials || [<IMaterial> this.material]).forEach(m => m?.dispose(false)) | |||||
| this.geometry?.dispose(false) | |||||
| // if (this.material) { | |||||
| // // const oldMats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!] | |||||
| // this.material = undefined // this will dispose material if not used by other meshes | |||||
| // // delete this.material | |||||
| // // for (const oldMat of oldMats) { | |||||
| // // if (oldMat && oldMat.userData && oldMat.appliedMeshes?.size === 0 && oldMat.userData.disposeOnIdle !== false) oldMat.dispose() | |||||
| // // } | |||||
| // } | |||||
| // if (this.geometry) { | |||||
| // // const oldGeom = this.geometry | |||||
| // this.geometry = undefined // this will dispose geometry if not used by other meshes | |||||
| // // delete this.geometry | |||||
| // // if (oldGeom && oldGeom.userData && oldGeom.appliedMeshes?.size === 0 && oldGeom.userData.disposeOnIdle !== false) oldGeom.dispose() | |||||
| // } | |||||
| // | |||||
| // delete this._onGeometryUpdate | |||||
| }) | }) | ||||
| } | } | ||||
| for (const c of children) upgradeObject3D.call(c, this) | for (const c of children) upgradeObject3D.call(c, this) | ||||
| // region Legacy | // region Legacy | ||||
| if (this.userData.dispose) console.warn('userData.dispose already defined') | |||||
| this.userData.dispose = () => { | |||||
| // eslint-disable-next-line deprecation/deprecation | |||||
| !this.userData.dispose && (this.userData.dispose = () => { | |||||
| console.warn('userData.dispose is deprecated, use dispose directly') | console.warn('userData.dispose is deprecated, use dispose directly') | ||||
| this.dispose?.() | |||||
| } | |||||
| if (!this.modelObject) { | |||||
| Object.defineProperty(this, 'modelObject', { | |||||
| get: ()=>{ | |||||
| console.error('modelObject is deprecated, use object directly') | |||||
| return this | |||||
| }, | |||||
| }) | |||||
| } | |||||
| if (!this.userData.setDirty) | |||||
| this.userData.setDirty = (e: any)=>{ | |||||
| console.error('object.userData.setDirty is deprecated, use object.setDirty directly') | |||||
| this.setDirty?.(e) | |||||
| } | |||||
| this.dispose && this.dispose() | |||||
| }) | |||||
| // eslint-disable-next-line deprecation/deprecation | |||||
| !this.modelObject && Object.defineProperty(this, 'modelObject', { | |||||
| get: ()=>{ | |||||
| console.error('modelObject is deprecated, use object directly') | |||||
| return this | |||||
| }, | |||||
| }) | |||||
| // eslint-disable-next-line deprecation/deprecation | |||||
| !this.userData.setDirty && (this.userData.setDirty = (e: any)=>{ | |||||
| console.error('object.userData.setDirty is deprecated, use object.setDirty directly') | |||||
| this.setDirty?.(e) | |||||
| }) | |||||
| // endregion | // endregion | ||||
| // if (!this.objectProcessor) console.warn('objectProcessor not set for', this) | |||||
| // else | |||||
| this.objectProcessor?.processObject(this) | this.objectProcessor?.processObject(this) | ||||
| } | } |
| import {BufferGeometry, MathUtils} from 'three' | import {BufferGeometry, MathUtils} from 'three' | ||||
| import {mergeVertices} from 'three/examples/jsm/utils/BufferGeometryUtils.js' | import {mergeVertices} from 'three/examples/jsm/utils/BufferGeometryUtils.js' | ||||
| import {IGeometry, IMaterial, IObject3D, IScene, ITexture} from '../../core' | |||||
| /** | /** | ||||
| * Convert geometry to BufferGeometry with indexed attributes. | * Convert geometry to BufferGeometry with indexed attributes. | ||||
| export function generateUUID() { | export function generateUUID() { | ||||
| return MathUtils.generateUUID() | return MathUtils.generateUUID() | ||||
| } | } | ||||
| /** | |||||
| * Check if a single or multiple object/geometry/material/texture is in the scene. | |||||
| * This is used internally to check if a material is used by any object in the scene, and if not, it can be disposed. | |||||
| * @param sceneObj | |||||
| */ | |||||
| export function isInScene(...sceneObj: (IGeometry|IMaterial|IObject3D|ITexture)[]): boolean { | |||||
| if (sceneObj.length > 1) return sceneObj.some((a)=>isInScene(a)) | |||||
| const o = sceneObj[0] | |||||
| if ((<ITexture>o).isTexture) return Array.from((<ITexture>o).userData.__appliedMaterials || []).some((m) => isInScene(m)) ?? false | |||||
| const objects = | |||||
| (<IObject3D>o).isObject3D ? [<IObject3D>o] : | |||||
| (<IGeometry|IMaterial>o).appliedMeshes | |||||
| for (const obj of objects) { | |||||
| let inScene = false | |||||
| obj.traverseAncestors((ob: IObject3D) => (<IScene>ob).isScene && (inScene = true)) | |||||
| if (inScene) return true | |||||
| } | |||||
| return false | |||||
| } |