| @@ -23,7 +23,6 @@ import { | |||
| GLTFMeshOptDecodePlugin, | |||
| HalfFloatType, | |||
| HDRiGroundPlugin, | |||
| HemisphereLight, | |||
| InteractionPromptPlugin, | |||
| KTX2LoadPlugin, | |||
| KTXLoadPlugin, | |||
| @@ -168,6 +167,8 @@ async function init() { | |||
| 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. | |||
| viewer.getPlugin(MaterialConfiguratorPlugin)!.enableEditContextMenus = true | |||
| viewer.getPlugin(SwitchNodePlugin)!.enableEditContextMenus = true | |||
| @@ -53,6 +53,20 @@ export interface IMaterialEventMap extends MaterialEventMap{ | |||
| geometry: BufferGeometry | |||
| 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 | |||
| */ | |||
| @@ -67,6 +81,7 @@ export interface IMaterialEventMap extends MaterialEventMap{ | |||
| declare module 'three'{ | |||
| export interface MaterialEventMap{ | |||
| materialUpdate: { | |||
| // These are handled in dispatchEvent override in iMaterialCommons | |||
| bubbleToObject?: boolean | |||
| bubbleToParent?: boolean | |||
| uiChangeEvent?: ChangeEvent | |||
| @@ -36,6 +36,7 @@ export interface ITexture<TE extends ITextureEventMap = ITextureEventMap> extend | |||
| source: Source & { | |||
| _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 | |||
| @@ -232,13 +232,18 @@ export const iObjectCommons = { | |||
| // Remove old material listeners | |||
| const oldMats = this.material | |||
| const mats = Array.isArray(oldMats) ? [...oldMats] : [oldMats!] | |||
| let removed = [] | |||
| const added = [] | |||
| for (const mat of mats) { | |||
| 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 = [] | |||
| @@ -249,11 +254,31 @@ export const iObjectCommons = { | |||
| console.warn('Upgrading Material', mat) | |||
| iMaterialCommons.upgradeMaterial.call(mat) | |||
| } | |||
| if (removed.includes(mat)) removed = removed.filter(m=>m !== mat) | |||
| else added.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.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.dispatchEvent({type: 'materialChanged', material: this._currentMaterial ?? null, oldMaterial: oldMats ?? null, object: this, bubbleToParent: true}) | |||
| @@ -39,7 +39,7 @@ export class MaterialExtender { | |||
| updateMaterialDefines(materialExtension.extraDefines, material) | |||
| // 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 | |||
| material.lastShader = shader | |||
| } | |||
| @@ -89,7 +89,22 @@ export class MaterialExtender { | |||
| (material as any).__ext_afterRenderListen = true | |||
| 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 | |||
| return exts | |||
| } | |||
| @@ -97,12 +112,23 @@ export class MaterialExtender { | |||
| static UnregisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]) { | |||
| if (customMaterialExtensions) { | |||
| material.materialExtensions = material.materialExtensions?.filter((v)=>!customMaterialExtensions.includes(v)) || [] | |||
| for (const ext of customMaterialExtensions) { | |||
| ext.onUnregister && ext.onUnregister(material) | |||
| } | |||
| } | |||
| if (!material.materialExtensions?.length) { | |||
| material.removeEventListener('beforeRender', materialBeforeRender) | |||
| 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_afterRenderListen = false | |||
| ;(material as any).__ext_addToMeshListen = false | |||
| ;(material as any).__ext_removeFromMeshListen = false | |||
| ;(material as any).__ext_materialUpdateListen = false | |||
| } | |||
| } | |||
| } | |||
| @@ -134,7 +160,7 @@ function materialBeforeRender({target, object, renderer}:{object?: Object3D, ren | |||
| if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer') | |||
| if (!material.materialExtensions) return | |||
| for (const value of material.materialExtensions) { | |||
| value.onObjectRender?.(object, material, renderer) | |||
| value.onObjectRender && value.onObjectRender(object, material, renderer) | |||
| if ((material as any).lastShader) { | |||
| const updater = getOrCall(value.updaters) || [] | |||
| @@ -153,7 +179,34 @@ function materialAfterRender({target, object, renderer}:{object?: Object3D, rend | |||
| if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer') | |||
| if (!material.materialExtensions) return | |||
| 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) | |||
| } | |||
| } | |||
| @@ -16,6 +16,12 @@ export class KTX2LoadPlugin extends BaseImporterPlugin { | |||
| 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) { | |||
| this._importer.onCtor = (l: KTX2Loader2) => l | |||
| .setTranscoderPath(KTX2LoadPlugin.TRANSCODER_LIBRARY_PATH) | |||
| @@ -35,6 +41,7 @@ export class KTX2LoadPlugin extends BaseImporterPlugin { | |||
| } | |||
| export class KTX2Loader2 extends KTX2Loader implements ILoader { | |||
| private _initTexture(t: CompressedTexture & ITexture) { | |||
| upgradeTexture.call(t) | |||
| t.userData.mimeType = 'image/ktx2' | |||
| @@ -51,15 +58,20 @@ export class KTX2Loader2 extends KTX2Loader implements ILoader { | |||
| return t | |||
| } | |||
| 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 | |||
| 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) | |||
| return texture | |||
| } | |||
| } | |||
| export const KHR_TEXTURE_BASISU = 'KHR_texture_basisu' | |||
| const glTFTextureBasisUExtensionExport = (w: GLTFWriter2)=> ({ | |||