| GLTFMeshOptDecodePlugin, | GLTFMeshOptDecodePlugin, | ||||
| HalfFloatType, | HalfFloatType, | ||||
| HDRiGroundPlugin, | HDRiGroundPlugin, | ||||
| HemisphereLight, | |||||
| InteractionPromptPlugin, | InteractionPromptPlugin, | ||||
| KTX2LoadPlugin, | KTX2LoadPlugin, | ||||
| KTXLoadPlugin, | KTXLoadPlugin, | ||||
| TransfrSharePlugin, | TransfrSharePlugin, | ||||
| ]) | ]) | ||||
| KTX2LoadPlugin.SAVE_SOURCE_BLOBS = true // so that ktx files can be exported. todo - add this to blueprint editor init as well | |||||
| // to show more details in the UI and allow to edit changes in title etc. | // to show more details in the UI and allow to edit changes in title etc. | ||||
| viewer.getPlugin(MaterialConfiguratorPlugin)!.enableEditContextMenus = true | viewer.getPlugin(MaterialConfiguratorPlugin)!.enableEditContextMenus = true | ||||
| viewer.getPlugin(SwitchNodePlugin)!.enableEditContextMenus = true | viewer.getPlugin(SwitchNodePlugin)!.enableEditContextMenus = true |
| geometry: BufferGeometry | geometry: BufferGeometry | ||||
| object: Object3D | object: Object3D | ||||
| } | } | ||||
| /** | |||||
| * Fires when the material is set/added to a mesh | |||||
| * This is applicable of all types of Object3D, like Line etc, not just Mesh | |||||
| */ | |||||
| addToMesh: { | |||||
| object: Object3D | |||||
| } | |||||
| /** | |||||
| * Fires when the material is changed/removed to a mesh | |||||
| * This is applicable of all types of Object3D, like Line etc, not just Mesh | |||||
| */ | |||||
| removeFromMesh: { | |||||
| object: Object3D | |||||
| } | |||||
| /** | /** | ||||
| * For internal use | * For internal use | ||||
| */ | */ | ||||
| declare module 'three'{ | declare module 'three'{ | ||||
| export interface MaterialEventMap{ | export interface MaterialEventMap{ | ||||
| materialUpdate: { | materialUpdate: { | ||||
| // These are handled in dispatchEvent override in iMaterialCommons | |||||
| bubbleToObject?: boolean | bubbleToObject?: boolean | ||||
| bubbleToParent?: boolean | bubbleToParent?: boolean | ||||
| uiChangeEvent?: ChangeEvent | uiChangeEvent?: ChangeEvent |
| source: Source & { | source: Source & { | ||||
| _sourceImgBuffer?: ArrayBuffer|Uint8Array // see KTX2LoadPlugin and serializeTextureInExtras | _sourceImgBuffer?: ArrayBuffer|Uint8Array // see KTX2LoadPlugin and serializeTextureInExtras | ||||
| _canSerialize?: boolean // see KTX2LoadPlugin and GLTFExporter.js. Disables auto decompress for glTF export | |||||
| } | } | ||||
| _appliedMaterials?: Set<IMaterial> // for internal use only. refers to the materials that this texture is applied to | _appliedMaterials?: Set<IMaterial> // for internal use only. refers to the materials that this texture is applied to |
| // Remove old material listeners | // Remove old material listeners | ||||
| const oldMats = this.material | const oldMats = this.material | ||||
| const mats = Array.isArray(oldMats) ? [...oldMats] : [oldMats!] | const mats = Array.isArray(oldMats) ? [...oldMats] : [oldMats!] | ||||
| let removed = [] | |||||
| const added = [] | |||||
| for (const mat of mats) { | for (const mat of mats) { | ||||
| if (!mat) continue | if (!mat) continue | ||||
| if (mat.appliedMeshes) { | |||||
| mat.appliedMeshes.delete(this) | |||||
| // 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 | |||||
| } | |||||
| removed.push(mat) | |||||
| // if (mat.appliedMeshes) { | |||||
| // mat.appliedMeshes.delete(this) | |||||
| // // 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 | |||||
| // } | |||||
| } | } | ||||
| const materials = [] | const materials = [] | ||||
| console.warn('Upgrading Material', mat) | console.warn('Upgrading Material', mat) | ||||
| iMaterialCommons.upgradeMaterial.call(mat) | iMaterialCommons.upgradeMaterial.call(mat) | ||||
| } | } | ||||
| if (removed.includes(mat)) removed = removed.filter(m=>m !== mat) | |||||
| else added.push(mat) | |||||
| materials.push(mat) | materials.push(mat) | ||||
| if (mat && mat.appliedMeshes) { | |||||
| // if (mat && mat.appliedMeshes) { | |||||
| // mat.appliedMeshes.add(this) | |||||
| // } | |||||
| } | |||||
| // todo should these be before or after `materialChanged` event? right now its before, also .material will return the old one since _currentMaterial is old | |||||
| for (const mat of removed) { | |||||
| if (mat.appliedMeshes) { | |||||
| mat.appliedMeshes.delete(this) | |||||
| // 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 | |||||
| } | |||||
| mat.dispatchEvent({type: 'removeFromMesh', object: this}) | |||||
| } | |||||
| for (const mat of added) { | |||||
| if (mat.appliedMeshes) { | |||||
| mat.appliedMeshes.add(this) | mat.appliedMeshes.add(this) | ||||
| } | } | ||||
| mat.dispatchEvent({type: 'addToMesh', object: this}) | |||||
| // note - material bubbleToObject is handled in dispatchEvent override in iMaterialCommons | |||||
| } | } | ||||
| this._currentMaterial = !materials.length ? null : materials.length !== 1 ? materials : materials[0] || null | this._currentMaterial = !materials.length ? null : materials.length !== 1 ? materials : materials[0] || null | ||||
| this.dispatchEvent({type: 'materialChanged', material: this._currentMaterial ?? null, oldMaterial: oldMats ?? null, object: this, bubbleToParent: true}) | this.dispatchEvent({type: 'materialChanged', material: this._currentMaterial ?? null, oldMaterial: oldMats ?? null, object: this, bubbleToParent: true}) |
| updateMaterialDefines(materialExtension.extraDefines, material) | updateMaterialDefines(materialExtension.extraDefines, material) | ||||
| // Call shaderExtender if defined | // Call shaderExtender if defined | ||||
| materialExtension.shaderExtender?.(shader as any, material, renderer) | |||||
| materialExtension.shaderExtender && materialExtension.shaderExtender(shader as any, material, renderer) | |||||
| // Save last shader so that it can be used to check if shader has changed in extensions | // Save last shader so that it can be used to check if shader has changed in extensions | ||||
| material.lastShader = shader | material.lastShader = shader | ||||
| } | } | ||||
| (material as any).__ext_afterRenderListen = true | (material as any).__ext_afterRenderListen = true | ||||
| material.addEventListener('afterRender', materialAfterRender) | material.addEventListener('afterRender', materialAfterRender) | ||||
| } | } | ||||
| if (!(material as any).__ext_addToMeshListen) { | |||||
| (material as any).__ext_addToMeshListen = true | |||||
| material.addEventListener('addToMesh', materialAddToMesh) | |||||
| } | |||||
| if (!(material as any).__ext_removeFromMeshListen) { | |||||
| (material as any).__ext_removeFromMeshListen = true | |||||
| material.addEventListener('removeFromMesh', materialRemovedFromMesh) | |||||
| } | |||||
| if (!(material as any).__ext_materialUpdateListen) { | |||||
| (material as any).__ext_materialUpdateListen = true | |||||
| material.addEventListener('materialUpdate', materialUpdate) | |||||
| } | |||||
| for (const ext of exts) { | |||||
| ext.onRegister && ext.onRegister(material) | |||||
| } | |||||
| material.needsUpdate = true | material.needsUpdate = true | ||||
| return exts | return exts | ||||
| } | } | ||||
| static UnregisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]) { | static UnregisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]) { | ||||
| if (customMaterialExtensions) { | if (customMaterialExtensions) { | ||||
| material.materialExtensions = material.materialExtensions?.filter((v)=>!customMaterialExtensions.includes(v)) || [] | material.materialExtensions = material.materialExtensions?.filter((v)=>!customMaterialExtensions.includes(v)) || [] | ||||
| for (const ext of customMaterialExtensions) { | |||||
| ext.onUnregister && ext.onUnregister(material) | |||||
| } | |||||
| } | } | ||||
| if (!material.materialExtensions?.length) { | if (!material.materialExtensions?.length) { | ||||
| material.removeEventListener('beforeRender', materialBeforeRender) | material.removeEventListener('beforeRender', materialBeforeRender) | ||||
| material.removeEventListener('afterRender', materialAfterRender) | material.removeEventListener('afterRender', materialAfterRender) | ||||
| material.removeEventListener('addToMesh', materialAddToMesh) | |||||
| material.removeEventListener('removeFromMesh', materialRemovedFromMesh) | |||||
| material.removeEventListener('materialUpdate', materialUpdate) | |||||
| ;(material as any).__ext_beforeRenderListen = false | ;(material as any).__ext_beforeRenderListen = false | ||||
| ;(material as any).__ext_afterRenderListen = false | ;(material as any).__ext_afterRenderListen = false | ||||
| ;(material as any).__ext_addToMeshListen = false | |||||
| ;(material as any).__ext_removeFromMeshListen = false | |||||
| ;(material as any).__ext_materialUpdateListen = false | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer') | if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer') | ||||
| if (!material.materialExtensions) return | if (!material.materialExtensions) return | ||||
| for (const value of material.materialExtensions) { | for (const value of material.materialExtensions) { | ||||
| value.onObjectRender?.(object, material, renderer) | |||||
| value.onObjectRender && value.onObjectRender(object, material, renderer) | |||||
| if ((material as any).lastShader) { | if ((material as any).lastShader) { | ||||
| const updater = getOrCall(value.updaters) || [] | const updater = getOrCall(value.updaters) || [] | ||||
| if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer') | if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer') | ||||
| if (!material.materialExtensions) return | if (!material.materialExtensions) return | ||||
| for (const value of material.materialExtensions) { | for (const value of material.materialExtensions) { | ||||
| value.onAfterRender?.(object, material, renderer) | |||||
| value.onAfterRender && value.onAfterRender(object, material, renderer) | |||||
| } | |||||
| } | |||||
| function materialAddToMesh({target, object}:{object?: Object3D, target: IMaterial}) { | |||||
| const material = target | |||||
| if (!material || !object) throw new Error('Invalid material or object') | |||||
| if (!material.materialExtensions) return | |||||
| for (const value of material.materialExtensions) { | |||||
| value.onAddToMesh && value.onAddToMesh(object, material) | |||||
| } | |||||
| } | |||||
| function materialRemovedFromMesh({target, object}:{object?: Object3D, target: IMaterial}) { | |||||
| const material = target | |||||
| if (!material || !object) throw new Error('Invalid material or object') | |||||
| if (!material.materialExtensions) return | |||||
| for (const value of material.materialExtensions) { | |||||
| value.onRemoveFromMesh && value.onRemoveFromMesh(object, material) | |||||
| } | |||||
| } | |||||
| function materialUpdate({target}:{target: IMaterial}) { | |||||
| const material = target | |||||
| if (!material) throw new Error('Invalid material') | |||||
| if (!material.materialExtensions) return | |||||
| for (const value of material.materialExtensions) { | |||||
| value.onMaterialUpdate && value.onMaterialUpdate(material) | |||||
| } | } | ||||
| } | } | ||||
| public static TRANSCODER_LIBRARY_PATH = 'https://cdn.jsdelivr.net/gh/BinomialLLC/basis_universal@1.16.4/webgl/transcoder/build/' | public static TRANSCODER_LIBRARY_PATH = 'https://cdn.jsdelivr.net/gh/BinomialLLC/basis_universal@1.16.4/webgl/transcoder/build/' | ||||
| /** | |||||
| * Flag to save the source buffer data in the texture object, it can be used later when downloading/serializing | |||||
| * the texture like when downloading glb with embedded textures. | |||||
| */ | |||||
| public static SAVE_SOURCE_BLOBS = false | |||||
| onAdded(viewer: ThreeViewer) { | onAdded(viewer: ThreeViewer) { | ||||
| this._importer.onCtor = (l: KTX2Loader2) => l | this._importer.onCtor = (l: KTX2Loader2) => l | ||||
| .setTranscoderPath(KTX2LoadPlugin.TRANSCODER_LIBRARY_PATH) | .setTranscoderPath(KTX2LoadPlugin.TRANSCODER_LIBRARY_PATH) | ||||
| } | } | ||||
| export class KTX2Loader2 extends KTX2Loader implements ILoader { | export class KTX2Loader2 extends KTX2Loader implements ILoader { | ||||
| private _initTexture(t: CompressedTexture & ITexture) { | private _initTexture(t: CompressedTexture & ITexture) { | ||||
| upgradeTexture.call(t) | upgradeTexture.call(t) | ||||
| t.userData.mimeType = 'image/ktx2' | t.userData.mimeType = 'image/ktx2' | ||||
| return t | return t | ||||
| } | } | ||||
| async createTexture(buffer: ArrayBuffer, config: any): Promise<CompressedTexture> { | async createTexture(buffer: ArrayBuffer, config: any): Promise<CompressedTexture> { | ||||
| const buffer2 = new Uint8Array(buffer.slice(0)) // clones the buffer | |||||
| const buffer2 = KTX2LoadPlugin.SAVE_SOURCE_BLOBS ? new Uint8Array(buffer.slice(0)) : undefined // clones the buffer | |||||
| const texture = (await super.createTexture(buffer, config)) as CompressedTexture & ITexture | const texture = (await super.createTexture(buffer, config)) as CompressedTexture & ITexture | ||||
| texture.source._sourceImgBuffer = buffer2 // keep the same buffer when cloned and all, used in serializeTextureInExtras | |||||
| // todo check if rootPath is set? | |||||
| if (KTX2LoadPlugin.SAVE_SOURCE_BLOBS && buffer2) { | |||||
| texture.source._sourceImgBuffer = buffer2 // keep the same buffer when cloned and all, used in serializeTextureInExtras | |||||
| texture.source._canSerialize = true | |||||
| } | |||||
| this._initTexture(texture) | this._initTexture(texture) | ||||
| return texture | return texture | ||||
| } | } | ||||
| } | } | ||||
| export const KHR_TEXTURE_BASISU = 'KHR_texture_basisu' | export const KHR_TEXTURE_BASISU = 'KHR_texture_basisu' | ||||
| const glTFTextureBasisUExtensionExport = (w: GLTFWriter2)=> ({ | const glTFTextureBasisUExtensionExport = (w: GLTFWriter2)=> ({ |