| @@ -248,7 +248,7 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEvent, IAssetIm | |||
| res = await loader.loadAsync(path + (options.queryString ? (path.includes('?') ? '&' : '?') + options.queryString : ''), (e)=>{ | |||
| if (onDownloadProgress) onDownloadProgress(e) | |||
| this.dispatchEvent({type: 'importFile', path, state:'downloading', progress: e.loaded / e.total}) | |||
| this.dispatchEvent({type: 'importFile', path, state:'downloading', progress: e.total > 0 ? e.loaded / e.total : 1}) | |||
| }) | |||
| if (loader.transform) res = await loader.transform(res, options) | |||
| @@ -6,6 +6,7 @@ import { | |||
| IMaterialParameters, | |||
| IMaterialTemplate, | |||
| ITexture, | |||
| ITextureEvent, | |||
| PhysicalMaterial, | |||
| UnlitMaterial, | |||
| } from '../core' | |||
| @@ -121,24 +122,30 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| this._refreshTextureRefs(mat) | |||
| } | |||
| protected _textureUpdate = function(this: IMaterial, e: ITextureEvent<'update'>) { | |||
| if (!this || this.assetType !== 'material') return | |||
| this.dispatchEvent({texture: e.target, bubbleToParent: true, bubbleToObject: true, ...e, type: 'textureUpdate'}) | |||
| } | |||
| private _refreshTextureRefs(mat: any) { | |||
| if (!mat.__textureUpdate) mat.__textureUpdate = this._textureUpdate.bind(mat) | |||
| const newMaps = this._getMapsForMaterial(mat) | |||
| const oldMaps = this._materialMaps.get(mat.uuid) || new Set<ITexture>() | |||
| newMaps.forEach(map => { | |||
| if (!oldMaps.has(map)) { | |||
| if (!map.userData.__appliedMaterials) map.userData.__appliedMaterials = new Set<IMaterial>() | |||
| map.userData.__appliedMaterials.add(mat) | |||
| } | |||
| }) | |||
| oldMaps.forEach(map => { | |||
| if (!newMaps.has(map)) { | |||
| if (!map.userData.__appliedMaterials) return | |||
| const mats = map.userData.__appliedMaterials | |||
| mats?.delete(mat) | |||
| if (!mats || map.userData.disposeOnIdle === false) return | |||
| if (mats.size === 0) map.dispose() | |||
| } | |||
| }) | |||
| for (const map of newMaps) { | |||
| if (oldMaps.has(map)) continue | |||
| if (!map.userData.__appliedMaterials) map.userData.__appliedMaterials = new Set<IMaterial>() | |||
| map.userData.__appliedMaterials.add(mat) | |||
| map.addEventListener('update', mat.__textureUpdate) | |||
| } | |||
| for (const map of oldMaps) { | |||
| if (newMaps.has(map)) continue | |||
| map.removeEventListener('update', mat.__textureUpdate) | |||
| if (!map.userData.__appliedMaterials) continue | |||
| const mats = map.userData.__appliedMaterials | |||
| mats?.delete(mat) | |||
| if (!mats || map.userData.disposeOnIdle === false) continue | |||
| if (mats.size === 0) map.dispose() | |||
| } | |||
| this._materialMaps.set(mat.uuid, newMaps) | |||
| } | |||
| @@ -289,6 +296,7 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| for (const c of currentMats) { | |||
| // console.log(c) | |||
| if (!c) continue | |||
| if (c === material) continue | |||
| if (c.userData.__isVariation) continue | |||
| const cType = Object.getPrototypeOf(c).constructor.TYPE | |||
| // console.log(cType, mType) | |||
| @@ -8,7 +8,7 @@ import type {ITexture} from './ITexture' | |||
| import type {IImportResultUserData} from '../assetmanager' | |||
| export type IMaterialParameters = MaterialParameters & {customMaterialExtensions?: MaterialExtension[]} | |||
| export type IMaterialEventTypes = 'dispose' | 'materialUpdate' | 'beforeRender' | 'beforeCompile' | 'afterRender' | 'textureChanged' | 'beforeDeserialize' | |||
| export type IMaterialEventTypes = 'dispose' | 'materialUpdate' | 'beforeRender' | 'beforeCompile' | 'afterRender' | 'textureUpdate' | 'beforeDeserialize' | |||
| export type IMaterialEvent<T extends string = IMaterialEventTypes> = Event & { | |||
| type: T | |||
| bubbleToObject?: boolean | |||
| @@ -6,7 +6,7 @@ import {IGeometry, IGeometryEvent} from './IGeometry' | |||
| import {IImportResultUserData} from '../assetmanager' | |||
| import {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js' | |||
| export type IObject3DEventTypes = 'dispose' | 'materialUpdate' | 'objectUpdate' | 'geometryChanged' | | |||
| export type IObject3DEventTypes = 'dispose' | 'materialUpdate' | 'objectUpdate' | 'textureUpdate' | 'geometryChanged' | | |||
| 'materialChanged' | 'geometryUpdate' | 'added' | 'removed' | 'select' | 'beforeDeserialize' | | |||
| 'setView' | 'activateMain' | 'cameraUpdate' // from camera | |||
| // | string | |||
| @@ -1,11 +1,19 @@ | |||
| import {IMaterial} from './IMaterial' | |||
| import {Texture} from 'three' | |||
| import {Event, Texture} from 'three' | |||
| import {ChangeEvent} from 'uiconfig.js' | |||
| export interface ITextureUserData{ | |||
| mimeType?: string | |||
| disposeOnIdle?: boolean // automatically dispose when added to a material and then not used in any material | |||
| __appliedMaterials?: Set<IMaterial> | |||
| } | |||
| export type ITextureEventTypes = 'dispose' | 'update' | |||
| export type ITextureEvent<T extends string = ITextureEventTypes> = Event & { | |||
| type: T | |||
| texture?: ITexture | |||
| uiChangeEvent?: ChangeEvent | |||
| } | |||
| export interface ITexture extends Texture { | |||
| assetType?: 'texture' | |||
| userData: ITextureUserData | |||
| @@ -17,4 +17,4 @@ export type {IMaterial, IMaterialEvent, IMaterialEventTypes, IMaterialParameters | |||
| export type {IObject3D, IObject3DEvent, IObjectSetDirtyOptions, IObjectProcessor, IObject3DEventTypes, IObject3DUserData} from './IObject' | |||
| export type {IRenderManager, IRenderManagerOptions, IWebGLRenderer, IRenderManagerEventTypes, IAnimationLoopEvent, TThreeRendererMode, TThreeRendererModeUserData, IRenderManagerUpdateEvent, IRenderManagerEvent} from './IRenderer' | |||
| export type {IScene, ISceneEvent, ISceneEventTypes, ISceneSetDirtyOptions, AddObjectOptions, ISceneUserData, IWidget} from './IScene' | |||
| export type {ITexture, ITextureUserData} from './ITexture' | |||
| export type {ITexture, ITextureUserData, ITextureEvent, ITextureEventTypes} from './ITexture' | |||
| @@ -128,7 +128,7 @@ export const iMaterialCommons = { | |||
| superDispatchEvent.call(this, event) | |||
| const type = event.type | |||
| if (event.bubbleToObject && ( | |||
| type === 'beforeDeserialize' || type === 'materialUpdate' // todo - add more events | |||
| type === 'beforeDeserialize' || type === 'materialUpdate' || type === 'textureUpdate' // todo - add more events | |||
| )) { | |||
| this.appliedMeshes.forEach(m => m.dispatchEvent({...event, material: this, type})) | |||
| } | |||
| @@ -160,7 +160,7 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| this._composer.setPixelRatio(this._renderScale, false) | |||
| this._composer.setSize(this._renderSize.width, this._renderSize.height) | |||
| this._resizeTracedTargets() | |||
| this.resizeTrackedTargets() | |||
| // console.log('setSize', {...this._renderSize}, this._trackedTargets.length) | |||
| @@ -2,6 +2,7 @@ import {Class} from 'ts-browser-helpers' | |||
| import {createRenderTargetKey, CreateRenderTargetOptions, IRenderTarget} from './RenderTarget' | |||
| import { | |||
| BaseEvent, | |||
| ClampToEdgeWrapping, | |||
| DepthTexture, | |||
| EventDispatcher, | |||
| LinearFilter, | |||
| @@ -187,15 +188,19 @@ export abstract class RenderTargetManager<E extends BaseEvent = BaseEvent, ET ex | |||
| this._trackedTempTargets = [] | |||
| } | |||
| protected _resizeTracedTargets() { | |||
| this._trackedTargets.forEach(v=>{ | |||
| const target = v as any as WebGLRenderTarget | |||
| const multiplier = (target as any).sizeMultiplier | |||
| if (multiplier) { | |||
| const s = this.renderSize.clone().multiplyScalar(this.renderScale * multiplier) | |||
| target.setSize(Math.floor(s.width), Math.floor(s.height)) | |||
| } | |||
| }) | |||
| /** | |||
| * Resizes all tracked targets with a sizeMultiplier based on the current renderSize and renderScale. | |||
| * This must be automatically called by the renderer on resize, and manually when sizeMultiplier of a target changes. | |||
| */ | |||
| resizeTrackedTargets() { | |||
| for (const v of this._trackedTargets) this.resizeTrackedTarget(v) | |||
| } | |||
| resizeTrackedTarget(target: IRenderTarget): void { | |||
| const multiplier = target.sizeMultiplier | |||
| if (multiplier) { | |||
| const s = this.renderSize.clone().multiplyScalar(this.renderScale * multiplier) | |||
| target.setSize(Math.floor(s.width), Math.floor(s.height)) | |||
| } | |||
| } | |||
| private _processNewTempTarget(target: IRenderTarget, key: string): IRenderTarget { | |||
| @@ -215,8 +220,10 @@ export abstract class RenderTargetManager<E extends BaseEvent = BaseEvent, ET ex | |||
| private _setTargetTextureOptions(texture: Texture, op: CreateRenderTargetOptions) { | |||
| texture.minFilter = op.minFilter ?? LinearFilter | |||
| texture.magFilter = op.magFilter ?? LinearFilter | |||
| texture.wrapS = op.wrapS ?? ClampToEdgeWrapping | |||
| texture.wrapT = op.wrapT ?? ClampToEdgeWrapping | |||
| texture.generateMipmaps = op.generateMipmaps ?? false | |||
| if (texture.generateMipmaps && texture.minFilter === LinearFilter) // todo: check if this is needed for magFilter | |||
| if (texture.generateMipmaps && texture.minFilter === LinearFilter) | |||
| texture.minFilter = LinearMipMapLinearFilter | |||
| if (!texture.generateMipmaps && texture.minFilter === LinearMipMapLinearFilter) | |||
| texture.minFilter = LinearFilter | |||
| @@ -286,6 +286,7 @@ export class ThreeSerialization { | |||
| if (!obj?.isWebGLRenderTarget || !obj.uuid) throw new Error('Expected a IRenderTarget') | |||
| if (meta?.extras[obj.uuid]) return {uuid: obj.uuid, resource: 'extras'} | |||
| // This is for the class implementing IRenderTarget, check {@link RenderTargetManager} for class implementation | |||
| const tex = Array.isArray(obj.texture) ? obj.texture[0] : obj.texture | |||
| let res: any = { | |||
| metadata: {type: 'RenderTarget'}, | |||
| @@ -714,7 +715,7 @@ export function metaToResources(meta?: SerializationMetaType): Partial<Serializa | |||
| if (!meta) return {} | |||
| const res: Partial<SerializationResourcesType> = {...meta} | |||
| if (res._context) delete res._context | |||
| return meta | |||
| return res | |||
| } | |||
| export function metaFromResources(resources?: Partial<SerializationResourcesType>, viewer?: ThreeViewer): SerializationMetaType { | |||
| return { | |||
| @@ -255,6 +255,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| this._scene.addEventListener('materialUpdate', (e) => this.setDirty(this._scene, e)) | |||
| this._scene.addEventListener('materialChanged', (e) => this.setDirty(this._scene, e)) | |||
| this._scene.addEventListener('objectUpdate', (e) => this.setDirty(this._scene, e)) | |||
| this._scene.addEventListener('textureUpdate', (e) => this.setDirty(this._scene, e)) | |||
| this._scene.addEventListener('sceneUpdate', (e) => { | |||
| this.setDirty(this._scene, e) | |||
| if (e.geometryChanged === false) return | |||