From 35ff882ff2da163e13b0e84012e813479c8d734c Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Wed, 2 Jul 2025 00:25:18 -0400 Subject: [PATCH] [3d] better solution to support reading extra resource/texture (#4209) --- src/components/load3d/Load3D.vue | 15 +- src/components/load3d/Load3DAnimation.vue | 6 +- src/extensions/core/load3d.ts | 188 +++--- .../core/load3d/Load3DConfiguration.ts | 18 + src/extensions/core/load3d/Load3d.ts | 6 +- src/extensions/core/load3d/Load3dUtils.ts | 13 +- src/extensions/core/load3d/LoaderManager.ts | 69 ++- .../threejsOverride/OverrideMTLLoader.js | 533 ------------------ 8 files changed, 206 insertions(+), 642 deletions(-) delete mode 100644 src/extensions/core/load3d/threejsOverride/OverrideMTLLoader.js diff --git a/src/components/load3d/Load3D.vue b/src/components/load3d/Load3D.vue index 88ed84a57..8129a0f9a 100644 --- a/src/components/load3d/Load3D.vue +++ b/src/components/load3d/Load3D.vue @@ -206,7 +206,11 @@ const handleBackgroundImageUpdate = async (file: File | null) => { return } - backgroundImage.value = await Load3dUtils.uploadFile(file) + const resourceFolder = (node.properties['Resource Folder'] as string) || '' + + const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : '3d' + + backgroundImage.value = await Load3dUtils.uploadFile(file, subfolder) node.properties['Background Image'] = backgroundImage.value } @@ -218,7 +222,14 @@ const handleUploadTexture = async (file: File) => { } try { - const texturePath = await Load3dUtils.uploadFile(file) + const resourceFolder = (node.properties['Resource Folder'] as string) || '' + + const subfolder = resourceFolder.trim() + ? `3d/${resourceFolder.trim()}` + : '3d' + + const texturePath = await Load3dUtils.uploadFile(file, subfolder) + await load3DSceneRef.value.load3d.applyTexture(texturePath) node.properties['Texture'] = texturePath diff --git a/src/components/load3d/Load3DAnimation.vue b/src/components/load3d/Load3DAnimation.vue index bc23b53d6..945436bbb 100644 --- a/src/components/load3d/Load3DAnimation.vue +++ b/src/components/load3d/Load3DAnimation.vue @@ -238,7 +238,11 @@ const handleBackgroundImageUpdate = async (file: File | null) => { return } - backgroundImage.value = await Load3dUtils.uploadFile(file) + const resourceFolder = (node.properties['Resource Folder'] as string) || '' + + const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : '3d' + + backgroundImage.value = await Load3dUtils.uploadFile(file, subfolder) node.properties['Background Image'] = backgroundImage.value } diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index caa52c143..794cd1c03 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -1,7 +1,4 @@ -import type { - IComboWidget, - IStringWidget -} from '@comfyorg/litegraph/dist/types/widgets' +import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets' import { nextTick } from 'vue' import Load3D from '@/components/load3d/Load3D.vue' @@ -17,6 +14,80 @@ import { useExtensionService } from '@/services/extensionService' import { useLoad3dService } from '@/services/load3dService' import { useToastStore } from '@/stores/toastStore' +async function handleModelUpload(files: FileList, node: any) { + if (!files?.length) return + + const modelWidget = node.widgets?.find( + (w: any) => w.name === 'model_file' + ) as IStringWidget + + node.properties['Texture'] = undefined + + try { + const resourceFolder = (node.properties['Resource Folder'] as string) || '' + + const subfolder = resourceFolder.trim() + ? `3d/${resourceFolder.trim()}` + : '3d' + + const uploadPath = await Load3dUtils.uploadFile(files[0], subfolder) + + if (!uploadPath) { + useToastStore().addAlert(t('toastMessages.fileUploadFailed')) + return + } + + const modelUrl = api.apiURL( + Load3dUtils.getResourceURL( + ...Load3dUtils.splitFilePath(uploadPath), + 'input' + ) + ) + + await useLoad3dService().getLoad3d(node)?.loadModel(modelUrl) + + if (uploadPath && modelWidget) { + if (!modelWidget.options?.values?.includes(uploadPath)) { + modelWidget.options?.values?.push(uploadPath) + } + + modelWidget.value = uploadPath + } + } catch (error) { + console.error('Model upload failed:', error) + useToastStore().addAlert(t('toastMessages.fileUploadFailed')) + } +} + +async function handleResourcesUpload(files: FileList, node: any) { + if (!files?.length) return + + try { + const resourceFolder = (node.properties['Resource Folder'] as string) || '' + + const subfolder = resourceFolder.trim() + ? `3d/${resourceFolder.trim()}` + : '3d' + + await Load3dUtils.uploadMultipleFiles(files, subfolder) + } catch (error) { + console.error('Extra resources upload failed:', error) + useToastStore().addAlert(t('toastMessages.extraResourcesUploadFailed')) + } +} + +function createFileInput( + accept: string, + multiple: boolean = false +): HTMLInputElement { + const input = document.createElement('input') + input.type = 'file' + input.accept = accept + input.multiple = multiple + input.style.display = 'none' + return input +} + useExtensionService().registerExtension({ name: 'Comfy.Load3D', settings: [ @@ -110,49 +181,34 @@ useExtensionService().registerExtension({ getCustomWidgets() { return { LOAD_3D(node) { - const fileInput = document.createElement('input') - fileInput.type = 'file' - fileInput.accept = '.gltf,.glb,.obj,.fbx,.stl' - fileInput.style.display = 'none' + const fileInput = createFileInput('.gltf,.glb,.obj,.fbx,.stl', false) + + node.properties['Resource Folder'] = '' fileInput.onchange = async () => { - if (fileInput.files?.length) { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model_file' - ) as IComboWidget & { options: { values: string[] } } - - node.properties['Texture'] = undefined - - const uploadPath = await Load3dUtils.uploadFile( - fileInput.files[0] - ).catch((error) => { - console.error('File upload failed:', error) - useToastStore().addAlert(t('toastMessages.fileUploadFailed')) - }) - - const modelUrl = api.apiURL( - Load3dUtils.getResourceURL( - ...Load3dUtils.splitFilePath(uploadPath), - 'input' - ) - ) - - await useLoad3dService().getLoad3d(node)?.loadModel(modelUrl) - - if (uploadPath && modelWidget) { - if (!modelWidget.options?.values?.includes(uploadPath)) { - modelWidget.options?.values?.push(uploadPath) - } - - modelWidget.value = uploadPath - } - } + await handleModelUpload(fileInput.files!, node) } node.addWidget('button', 'upload 3d model', 'upload3dmodel', () => { fileInput.click() }) + const resourcesInput = createFileInput('*', true) + + resourcesInput.onchange = async () => { + await handleResourcesUpload(resourcesInput.files!, node) + resourcesInput.value = '' + } + + node.addWidget( + 'button', + 'upload extra resources', + 'uploadExtraResources', + () => { + resourcesInput.click() + } + ) + node.addWidget('button', 'clear', 'clear', () => { useLoad3dService().getLoad3d(node)?.clearModel() @@ -264,46 +320,34 @@ useExtensionService().registerExtension({ getCustomWidgets() { return { LOAD_3D_ANIMATION(node) { - const fileInput = document.createElement('input') - fileInput.type = 'file' - fileInput.accept = '.gltf,.glb,.fbx' - fileInput.style.display = 'none' + const fileInput = createFileInput('.gltf,.glb,.fbx', false) + + node.properties['Resource Folder'] = '' + fileInput.onchange = async () => { - if (fileInput.files?.length) { - const modelWidget = node.widgets?.find( - (w) => w.name === 'model_file' - ) as IStringWidget - - const uploadPath = await Load3dUtils.uploadFile( - fileInput.files[0] - ).catch((error) => { - console.error('File upload failed:', error) - useToastStore().addAlert(t('toastMessages.fileUploadFailed')) - }) - - const modelUrl = api.apiURL( - Load3dUtils.getResourceURL( - ...Load3dUtils.splitFilePath(uploadPath), - 'input' - ) - ) - - await useLoad3dService().getLoad3d(node)?.loadModel(modelUrl) - - if (uploadPath && modelWidget) { - if (!modelWidget.options?.values?.includes(uploadPath)) { - modelWidget.options?.values?.push(uploadPath) - } - - modelWidget.value = uploadPath - } - } + await handleModelUpload(fileInput.files!, node) } node.addWidget('button', 'upload 3d model', 'upload3dmodel', () => { fileInput.click() }) + const resourcesInput = createFileInput('*', true) + + resourcesInput.onchange = async () => { + await handleResourcesUpload(resourcesInput.files!, node) + resourcesInput.value = '' + } + + node.addWidget( + 'button', + 'upload extra resources', + 'uploadExtraResources', + () => { + resourcesInput.click() + } + ) + node.addWidget('button', 'clear', 'clear', () => { useLoad3dService().getLoad3d(node)?.clearModel() diff --git a/src/extensions/core/load3d/Load3DConfiguration.ts b/src/extensions/core/load3d/Load3DConfiguration.ts index 374b309d0..2f84da780 100644 --- a/src/extensions/core/load3d/Load3DConfiguration.ts +++ b/src/extensions/core/load3d/Load3DConfiguration.ts @@ -128,6 +128,9 @@ class Load3DConfiguration { if (!value) return const filename = value as string + + this.setResourceFolder(filename) + const modelUrl = api.apiURL( Load3dUtils.getResourceURL( ...Load3dUtils.splitFilePath(filename), @@ -173,6 +176,21 @@ class Load3DConfiguration { } } } + + private setResourceFolder(filename: string): void { + const pathParts = filename.split('/').filter((part) => part.trim()) + + if (pathParts.length <= 2) { + return + } + + const subfolderParts = pathParts.slice(1, -1) + const subfolder = subfolderParts.join('/') + + if (subfolder) { + this.load3d.node.properties['Resource Folder'] = subfolder + } + } } export default Load3DConfiguration diff --git a/src/extensions/core/load3d/Load3d.ts b/src/extensions/core/load3d/Load3d.ts index 0a14dcc31..30e81af42 100644 --- a/src/extensions/core/load3d/Load3d.ts +++ b/src/extensions/core/load3d/Load3d.ts @@ -118,11 +118,7 @@ class Load3d { options ) - this.loaderManager = new LoaderManager( - this.modelManager, - this.eventManager, - options - ) + this.loaderManager = new LoaderManager(this.modelManager, this.eventManager) this.recordingManager = new RecordingManager( this.sceneManager.scene, diff --git a/src/extensions/core/load3d/Load3dUtils.ts b/src/extensions/core/load3d/Load3dUtils.ts index 7f3ef7a60..213019293 100644 --- a/src/extensions/core/load3d/Load3dUtils.ts +++ b/src/extensions/core/load3d/Load3dUtils.ts @@ -34,13 +34,14 @@ class Load3dUtils { return await resp.json() } - static async uploadFile(file: File) { + static async uploadFile(file: File, subfolder: string) { let uploadPath try { const body = new FormData() body.append('image', file) - body.append('subfolder', '3d') + + body.append('subfolder', subfolder) const resp = await api.fetchApi('/upload/image', { method: 'POST', @@ -96,6 +97,14 @@ class Load3dUtils { return `/view?${params}` } + + static async uploadMultipleFiles(files: FileList, subfolder: string = '3d') { + const uploadPromises = Array.from(files).map((file) => + this.uploadFile(file, subfolder) + ) + + await Promise.all(uploadPromises) + } } export default Load3dUtils diff --git a/src/extensions/core/load3d/LoaderManager.ts b/src/extensions/core/load3d/LoaderManager.ts index 7b7752337..d9bc66c36 100644 --- a/src/extensions/core/load3d/LoaderManager.ts +++ b/src/extensions/core/load3d/LoaderManager.ts @@ -1,16 +1,15 @@ import * as THREE from 'three' import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' +import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' -import { OverrideMTLLoader } from '@/extensions/core/load3d/threejsOverride/OverrideMTLLoader' import { t } from '@/i18n' import { useToastStore } from '@/stores/toastStore' import { EventManagerInterface, - Load3DOptions, LoaderManagerInterface, ModelManagerInterface } from './interfaces' @@ -18,7 +17,7 @@ import { export class LoaderManager implements LoaderManagerInterface { gltfLoader: GLTFLoader objLoader: OBJLoader - mtlLoader: OverrideMTLLoader + mtlLoader: MTLLoader fbxLoader: FBXLoader stlLoader: STLLoader @@ -27,21 +26,14 @@ export class LoaderManager implements LoaderManagerInterface { constructor( modelManager: ModelManagerInterface, - eventManager: EventManagerInterface, - options: Load3DOptions + eventManager: EventManagerInterface ) { - let loadRootFolder = 'input' - - if (options && options.inputSpec?.isPreview) { - loadRootFolder = 'output' - } - this.modelManager = modelManager this.eventManager = eventManager this.gltfLoader = new GLTFLoader() this.objLoader = new OBJLoader() - this.mtlLoader = new OverrideMTLLoader(loadRootFolder) + this.mtlLoader = new MTLLoader() this.fbxLoader = new FBXLoader() this.stlLoader = new STLLoader() } @@ -100,9 +92,31 @@ export class LoaderManager implements LoaderManagerInterface { ): Promise { let model: THREE.Object3D | null = null + const params = new URLSearchParams(url.split('?')[1]) + + const filename = params.get('filename') + + if (!filename) { + console.error('Missing filename in URL:', url) + + return null + } + + const loadRootFolder = params.get('type') === 'output' ? 'output' : 'input' + + const subfolder = params.get('subfolder') ?? '' + + const path = + 'api/view?type=' + + loadRootFolder + + '&subfolder=' + + encodeURIComponent(subfolder) + + '&filename=' + switch (fileExtension) { case 'stl': - const geometry = await this.stlLoader.loadAsync(url) + this.stlLoader.setPath(path) + const geometry = await this.stlLoader.loadAsync(filename) this.modelManager.setOriginalModel(geometry) geometry.computeVertexNormals() @@ -117,7 +131,10 @@ export class LoaderManager implements LoaderManagerInterface { break case 'fbx': - const fbxModel = await this.fbxLoader.loadAsync(url) + this.fbxLoader.setPath(path) + + const fbxModel = await this.fbxLoader.loadAsync(filename) + this.modelManager.setOriginalModel(fbxModel) model = fbxModel @@ -130,18 +147,12 @@ export class LoaderManager implements LoaderManagerInterface { case 'obj': if (this.modelManager.materialMode === 'original') { - const mtlUrl = url.replace(/(filename=.*?)\.obj/, '$1.mtl') - - const subfolderMatch = url.match(/[?&]subfolder=([^&]*)/) - - const subfolder = subfolderMatch - ? decodeURIComponent(subfolderMatch[1]) - : '3d' - - this.mtlLoader.setSubfolder(subfolder) - try { - const materials = await this.mtlLoader.loadAsync(mtlUrl) + this.mtlLoader.setPath(path) + + const mtlFileName = filename.replace(/\.obj$/, '.mtl') + + const materials = await this.mtlLoader.loadAsync(mtlFileName) materials.preload() this.objLoader.setMaterials(materials) } catch (e) { @@ -151,7 +162,8 @@ export class LoaderManager implements LoaderManagerInterface { } } - model = await this.objLoader.loadAsync(url) + this.objLoader.setPath(path) + model = await this.objLoader.loadAsync(filename) model.traverse((child) => { if (child instanceof THREE.Mesh) { this.modelManager.originalMaterials.set(child, child.material) @@ -161,7 +173,10 @@ export class LoaderManager implements LoaderManagerInterface { case 'gltf': case 'glb': - const gltf = await this.gltfLoader.loadAsync(url) + this.gltfLoader.setPath(path) + + const gltf = await this.gltfLoader.loadAsync(filename) + this.modelManager.setOriginalModel(gltf) model = gltf.scene diff --git a/src/extensions/core/load3d/threejsOverride/OverrideMTLLoader.js b/src/extensions/core/load3d/threejsOverride/OverrideMTLLoader.js deleted file mode 100644 index 47b7a7f05..000000000 --- a/src/extensions/core/load3d/threejsOverride/OverrideMTLLoader.js +++ /dev/null @@ -1,533 +0,0 @@ -import { - Color, - ColorManagement, - DefaultLoadingManager, - FileLoader, - FrontSide, - Loader, - LoaderUtils, - MeshPhongMaterial, - RepeatWrapping, - SRGBColorSpace, - TextureLoader, - Vector2 -} from 'three' - -/** - * A loader for the MTL format. - * - * The Material Template Library format (MTL) or .MTL File Format is a companion file format - * to OBJ that describes surface shading (material) properties of objects within one or more - * OBJ files. - * - * ```js - * const loader = new MTLLoader(); - * const materials = await loader.loadAsync( 'models/obj/male02/male02.mtl' ); - * - * const objLoader = new OBJLoader(); - * objLoader.setMaterials( materials ); - * ``` - * - * @augments Loader - * @three_import import { MTLLoader } from 'three/addons/loaders/MTLLoader.js'; - */ -class OverrideMTLLoader extends Loader { - constructor(loadRootFolder, manager) { - super(manager) - - this.loadRootFolder = loadRootFolder - } - - setSubfolder(subfolder) { - this.subfolder = subfolder - } - - /** - * Starts loading from the given URL and passes the loaded MTL asset - * to the `onLoad()` callback. - * - * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. - * @param {function(MaterialCreator)} onLoad - Executed when the loading process has been finished. - * @param {onProgressCallback} onProgress - Executed while the loading is in progress. - * @param {onErrorCallback} onError - Executed when errors occur. - */ - load(url, onLoad, onProgress, onError) { - const scope = this - - const path = this.path === '' ? LoaderUtils.extractUrlBase(url) : this.path - - const loader = new FileLoader(this.manager) - loader.setPath(this.path) - loader.setRequestHeader(this.requestHeader) - loader.setWithCredentials(this.withCredentials) - loader.load( - url, - function (text) { - try { - onLoad(scope.parse(text, path)) - } catch (e) { - if (onError) { - onError(e) - } else { - console.error(e) - } - - scope.manager.itemError(url) - } - }, - onProgress, - onError - ) - } - - /** - * Sets the material options. - * - * @param {MTLLoader~MaterialOptions} value - The material options. - * @return {MTLLoader} A reference to this loader. - */ - setMaterialOptions(value) { - this.materialOptions = value - return this - } - - /** - * Parses the given MTL data and returns the resulting material creator. - * - * @param {string} text - The raw MTL data as a string. - * @param {string} path - The URL base path. - * @return {MaterialCreator} The material creator. - */ - parse(text, path) { - const lines = text.split('\n') - let info = {} - const delimiter_pattern = /\s+/ - const materialsInfo = {} - - for (let i = 0; i < lines.length; i++) { - let line = lines[i] - line = line.trim() - - if (line.length === 0 || line.charAt(0) === '#') { - // Blank line or comment ignore - continue - } - - const pos = line.indexOf(' ') - - let key = pos >= 0 ? line.substring(0, pos) : line - key = key.toLowerCase() - - let value = pos >= 0 ? line.substring(pos + 1) : '' - value = value.trim() - - if (key === 'newmtl') { - // New material - - info = { name: value } - materialsInfo[value] = info - } else { - if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') { - const ss = value.split(delimiter_pattern, 3) - info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])] - } else { - info[key] = value - } - } - } - - const materialCreator = new OverrideMaterialCreator( - this.resourcePath || path, - this.materialOptions, - this.loadRootFolder, - this.subfolder - ) - materialCreator.setCrossOrigin(this.crossOrigin) - materialCreator.setManager(this.manager) - materialCreator.setMaterials(materialsInfo) - return materialCreator - } -} - -/** - * Material options of `MTLLoader`. - * - * @typedef {Object} MTLLoader~MaterialOptions - * @property {(FrontSide|BackSide|DoubleSide)} [side=FrontSide] - Which side to apply the material. - * @property {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} [wrap=RepeatWrapping] - What type of wrapping to apply for textures. - * @property {boolean} [normalizeRGB=false] - Whether RGB colors should be normalized to `0-1` from `0-255`. - * @property {boolean} [ignoreZeroRGBs=false] - Ignore values of RGBs (Ka,Kd,Ks) that are all 0's. - */ - -class OverrideMaterialCreator { - constructor(baseUrl = '', options = {}, loadRootFolder, subfolder) { - this.baseUrl = baseUrl - this.options = options - this.materialsInfo = {} - this.materials = {} - this.materialsArray = [] - this.nameLookup = {} - - this.loadRootFolder = loadRootFolder - this.subfolder = subfolder - - this.crossOrigin = 'anonymous' - - this.side = this.options.side !== undefined ? this.options.side : FrontSide - this.wrap = - this.options.wrap !== undefined ? this.options.wrap : RepeatWrapping - } - - setCrossOrigin(value) { - this.crossOrigin = value - return this - } - - setManager(value) { - this.manager = value - } - - setMaterials(materialsInfo) { - this.materialsInfo = this.convert(materialsInfo) - this.materials = {} - this.materialsArray = [] - this.nameLookup = {} - } - - convert(materialsInfo) { - if (!this.options) return materialsInfo - - const converted = {} - - for (const mn in materialsInfo) { - // Convert materials info into normalized form based on options - - const mat = materialsInfo[mn] - - const covmat = {} - - converted[mn] = covmat - - for (const prop in mat) { - let save = true - let value = mat[prop] - const lprop = prop.toLowerCase() - - switch (lprop) { - case 'kd': - case 'ka': - case 'ks': - // Diffuse color (color under white light) using RGB values - - if (this.options && this.options.normalizeRGB) { - value = [value[0] / 255, value[1] / 255, value[2] / 255] - } - - if (this.options && this.options.ignoreZeroRGBs) { - if (value[0] === 0 && value[1] === 0 && value[2] === 0) { - // ignore - - save = false - } - } - - break - - default: - break - } - - if (save) { - covmat[lprop] = value - } - } - } - - return converted - } - - preload() { - for (const mn in this.materialsInfo) { - this.create(mn) - } - } - - getIndex(materialName) { - return this.nameLookup[materialName] - } - - getAsArray() { - let index = 0 - - for (const mn in this.materialsInfo) { - this.materialsArray[index] = this.create(mn) - this.nameLookup[mn] = index - index++ - } - - return this.materialsArray - } - - create(materialName) { - if (this.materials[materialName] === undefined) { - this.createMaterial_(materialName) - } - - return this.materials[materialName] - } - - createMaterial_(materialName) { - // Create material - - const scope = this - const mat = this.materialsInfo[materialName] - const params = { - name: materialName, - side: this.side - } - - /** - * Override for ComfyUI api url - */ - function resolveURL(baseUrl, url, loadRootFolder, subfolder) { - if (typeof url !== 'string' || url === '') return '' - - if (baseUrl.endsWith('/')) { - baseUrl = baseUrl.slice(0, -1) - } - - if (!baseUrl.endsWith('api')) { - baseUrl = '/api' - } - - baseUrl = - baseUrl + - '/view?filename=' + - url + - '&type=' + - loadRootFolder + - '&subfolder=' + - subfolder - - return baseUrl - } - - function setMapForType(mapType, value) { - if (params[mapType]) return // Keep the first encountered texture - - const texParams = scope.getTextureParams(value, params) - const map = scope.loadTexture( - resolveURL( - scope.baseUrl, - texParams.url, - scope.loadRootFolder, - scope.subfolder - ) - ) - - map.repeat.copy(texParams.scale) - map.offset.copy(texParams.offset) - - map.wrapS = scope.wrap - map.wrapT = scope.wrap - - if (mapType === 'map' || mapType === 'emissiveMap') { - map.colorSpace = SRGBColorSpace - } - - params[mapType] = map - } - - for (const prop in mat) { - const value = mat[prop] - let n - - if (value === '') continue - - switch (prop.toLowerCase()) { - // Ns is material specular exponent - - case 'kd': - // Diffuse color (color under white light) using RGB values - - params.color = ColorManagement.toWorkingColorSpace( - new Color().fromArray(value), - SRGBColorSpace - ) - - break - - case 'ks': - // Specular color (color when light is reflected from shiny surface) using RGB values - params.specular = ColorManagement.toWorkingColorSpace( - new Color().fromArray(value), - SRGBColorSpace - ) - - break - - case 'ke': - // Emissive using RGB values - params.emissive = ColorManagement.toWorkingColorSpace( - new Color().fromArray(value), - SRGBColorSpace - ) - - break - - case 'map_kd': - // Diffuse texture map - - setMapForType('map', value) - - break - - case 'map_ks': - // Specular map - - setMapForType('specularMap', value) - - break - - case 'map_ke': - // Emissive map - - setMapForType('emissiveMap', value) - - break - - case 'norm': - setMapForType('normalMap', value) - - break - - case 'map_bump': - case 'bump': - // Bump texture map - - setMapForType('bumpMap', value) - - break - - case 'disp': - // Displacement texture map - - setMapForType('displacementMap', value) - - break - - case 'map_d': - // Alpha map - - setMapForType('alphaMap', value) - params.transparent = true - - break - - case 'ns': - // The specular exponent (defines the focus of the specular highlight) - // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. - - params.shininess = parseFloat(value) - - break - - case 'd': - n = parseFloat(value) - - if (n < 1) { - params.opacity = n - params.transparent = true - } - - break - - case 'tr': - n = parseFloat(value) - - if (this.options && this.options.invertTrProperty) n = 1 - n - - if (n > 0) { - params.opacity = 1 - n - params.transparent = true - } - - break - - default: - break - } - } - - this.materials[materialName] = new MeshPhongMaterial(params) - return this.materials[materialName] - } - - getTextureParams(value, matParams) { - const texParams = { - scale: new Vector2(1, 1), - offset: new Vector2(0, 0) - } - - const items = value.split(/\s+/) - let pos - - pos = items.indexOf('-bm') - - if (pos >= 0) { - matParams.bumpScale = parseFloat(items[pos + 1]) - items.splice(pos, 2) - } - - pos = items.indexOf('-mm') - - if (pos >= 0) { - matParams.displacementBias = parseFloat(items[pos + 1]) - matParams.displacementScale = parseFloat(items[pos + 2]) - items.splice(pos, 3) - } - - pos = items.indexOf('-s') - - if (pos >= 0) { - texParams.scale.set( - parseFloat(items[pos + 1]), - parseFloat(items[pos + 2]) - ) - items.splice(pos, 4) // we expect 3 parameters here! - } - - pos = items.indexOf('-o') - - if (pos >= 0) { - texParams.offset.set( - parseFloat(items[pos + 1]), - parseFloat(items[pos + 2]) - ) - items.splice(pos, 4) // we expect 3 parameters here! - } - - texParams.url = items.join(' ').trim() - return texParams - } - - loadTexture(url, mapping, onLoad, onProgress, onError) { - const manager = - this.manager !== undefined ? this.manager : DefaultLoadingManager - let loader = manager.getHandler(url) - - if (loader === null) { - loader = new TextureLoader(manager) - } - - if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin) - - const texture = loader.load(url, onLoad, onProgress, onError) - - if (mapping !== undefined) texture.mapping = mapping - - return texture - } -} - -export { OverrideMTLLoader }