import { AssetExporterPlugin, AViewerPluginSync, DRACOLoader2, generateUUID, GLTFLightExtrasExtension, GLTFMaterialExtrasExtension, GLTFMaterialsAlphaMapExtension, GLTFMaterialsBumpMapExtension, GLTFMaterialsDisplacementMapExtension, GLTFMaterialsLightMapExtension, GLTFObject3DExtrasExtension, IExporter, ThreeViewer, clearCoatTintGLTFExtension, customBumpMapGLTFExtension, noiseBumpMaterialGLTFExtension, fragmentClippingGLTFExtension, } from 'threepipe' import {GLTFDracoExporter} from './GLTFDracoExporter' import {UiObjectConfig} from 'uiconfig.js' import {EncoderOptions} from '@gltf-transform/extensions/dist/khr-draco-mesh-compression/encoder' export enum EncoderMethod { EDGEBREAKER = 1, SEQUENTIAL = 0 } /** * GLTF Draco Export Plugin * * Overloads the default gltf exporter in the asset manager with GLTFDracoExporter. When exporting with compress = true, the output will be compressed. * Note - Only `glb` supported right now. * * @category Plugins */ export class GLTFDracoExportPlugin extends AViewerPluginSync<''> { public static readonly PluginType = 'GLTFDracoExportPlugin' enabled = true /** * These are added here, but also added as plugins. Added here by default so that the data is not lost if some plugin is not added in an app. * To explicitly remove the data, use `removeExtension` with the name of the extension */ extraExtensions = [ [GLTFMaterialsBumpMapExtension.WebGiMaterialsBumpMapExtension, GLTFMaterialsBumpMapExtension.Textures], [GLTFMaterialsLightMapExtension.WebGiMaterialsLightMapExtension, GLTFMaterialsLightMapExtension.Textures], [GLTFMaterialsAlphaMapExtension.WebGiMaterialsAlphaMapExtension, GLTFMaterialsAlphaMapExtension.Textures], [GLTFMaterialsDisplacementMapExtension.WebGiMaterialsDisplacementMapExtension, GLTFMaterialsDisplacementMapExtension.Textures], [customBumpMapGLTFExtension.name, customBumpMapGLTFExtension.textures], [GLTFLightExtrasExtension.WebGiLightExtrasExtension, GLTFLightExtrasExtension.Textures], [GLTFObject3DExtrasExtension.WebGiObject3DExtrasExtension, GLTFObject3DExtrasExtension.Textures], [GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension, GLTFMaterialExtrasExtension.Textures], [clearCoatTintGLTFExtension.name, clearCoatTintGLTFExtension.textures], [noiseBumpMaterialGLTFExtension.name, noiseBumpMaterialGLTFExtension.textures], [fragmentClippingGLTFExtension.name, fragmentClippingGLTFExtension.textures], // extenal plugins // AnisotropyMaterialExtension ['WEBGI_materials_anisotropy', { anisotropyDirection: 'RGB', }], // todo port // DiamondMaterialExtension // AnimationMarkersExtension // ThinFilmLayerMaterialExtension // TriplanarMappingMaterialExtension // SSBevelMaterialExtension ] as [string, Record|undefined][] addExtension(name: string, textures?: Record) { const ext = this.extraExtensions.findIndex(e => e[0] === name) if (ext >= 0) this.extraExtensions[ext] = [name, textures] else this.extraExtensions.push([name, textures]) } /** * Note - don't remove an extension when removing a plugin. * * extensions can be removed if you don't want to save the data of some plugin when transforming glb. But since this is not desirable in most cases, it is not recommended. * @param name */ removeExtension(name: string) { const ext = this.extraExtensions.findIndex(e => e[0] === name) if (ext >= 0) this.extraExtensions.splice(ext, 1) } private _lastExporter?: IExporter['ctor'] = undefined protected _ctor: IExporter['ctor'] = (_, _exporter) => { if (!this._viewer) throw new Error('Viewer not set') const tempFile = generateUUID() + '.drc' // dummy const ex = new GLTFDracoExporter({}, // todo unregister on dispose this._viewer.assetManager.importer.registerFile(tempFile) as DRACOLoader2) ex.setup(this._viewer, _exporter.extensions) for (const [ext, config] of this.extraExtensions) { ex.createAndAddExtension(ext, config) } return ex } onAdded(viewer: ThreeViewer): void { super.onAdded(viewer) const exporter = viewer.assetManager.exporter let glbExporter = exporter.getExporter('glb') this._lastExporter = glbExporter?.ctor if (!glbExporter) { console.error('GLTFDracoExportPlugin: GLB exporter not found in AssetManager.exporter, creating a new one.') glbExporter = { ext: ['glb', 'gltf'], extensions: [], ctor: this._ctor, } exporter.addExporter(glbExporter) } else { glbExporter.ctor = this._ctor } // for ui. todo use viewer.forPlugin const exportPlugin = viewer.getPlugin(AssetExporterPlugin) if (exportPlugin) { Object.assign(exportPlugin.exportOptions, { compress: false, dracoOptions: { encodeSpeed: 5, method: EncoderMethod.EDGEBREAKER, quantizationVolume: 'mesh', quantizationBits: { ['POSITION']: 14, ['NORMAL']: 10, ['COLOR']: 8, ['TEX_COORD']: 12, ['GENERIC']: 12, }, } as EncoderOptions, }) const exportOptions = exportPlugin.uiConfig.children?.find(c => (c as UiObjectConfig).label === 'GLB Export') as UiObjectConfig if (exportOptions) { exportOptions.children = [this._makeUi(exportPlugin), ...exportOptions.children || []] } else { console.warn('GLTFDracoExportPlugin: Unable to setup UI') } } } onRemove(viewer: ThreeViewer) { const exporter = viewer.assetManager.exporter const glbExporter = exporter.getExporter('glb') if (glbExporter && this._lastExporter) { glbExporter.ctor = this._lastExporter } super.onRemove(viewer) } protected _makeUi = (exporter: AssetExporterPlugin)=>[ { type: 'checkbox', label: 'DRACO Compress', property: [exporter.exportOptions, 'compress'], onChange: ()=>exporter.uiConfig.uiRefresh?.(true), }, { type: 'folder', hidden: ()=>!exporter.exportOptions.compress, label: 'DRACO Options', children: [ { type: 'slider', label: 'Encode Speed', bounds: [1, 10], property: [exporter.exportOptions.dracoOptions, 'encodeSpeed'], }, { type: 'dropdown', label: 'Encoder Method', property: [exporter.exportOptions.dracoOptions, 'method'], children: Object.entries(EncoderMethod).map(([k, v]) => ({label: k, value: v})), }, { type: 'dropdown', label: 'Quantization Volume', property: [exporter.exportOptions.dracoOptions, 'quantizationVolume'], children: ['mesh', 'scene', 'bbox'].map(v => ({label: v})), }, { type: 'folder', label: 'Quantization Bits', children: Object.keys(exporter.exportOptions.dracoOptions?.quantizationBits || {}).map(k => ({ type: 'slider', label: k, bounds: [1, 16], stepSize: 1, property: [exporter.exportOptions.dracoOptions?.quantizationBits, k], })), }, ], }, ] }