UNPKG

polygonjs-engine

Version:

node-based webgl 3D engine https://polygonjs.com

314 lines (269 loc) 9.83 kB
import {FileLoader} from 'three/src/loaders/FileLoader'; import {Loader} from 'three/src/loaders/Loader'; import {Object3D} from 'three/src/core/Object3D'; /** * Development repository: https://github.com/kaisalmen/WWOBJLoader */ import {OBJLoader2Parser} from './obj2/OBJLoader2Parser.js'; import {MeshReceiver} from './obj2/shared/MeshReceiver.js'; import {MaterialHandler} from './obj2/shared/MaterialHandler.js'; /** * Creates a new OBJLoader2. Use it to load OBJ data from files or to parse OBJ data from arraybuffer or text. * * @param {LoadingManager} [manager] The loadingManager for the loader to use. Default is {@link LoadingManager} * @constructor */ const OBJLoader2 = function (manager) { Loader.call(this, manager); this.parser = new OBJLoader2Parser(); this.modelName = ''; this.instanceNo = 0; this.baseObject3d = new Object3D(); this.materialHandler = new MaterialHandler(); this.meshReceiver = new MeshReceiver(this.materialHandler); // as OBJLoader2 is no longer derived from OBJLoader2Parser, we need to override the default onAssetAvailable callback const scope = this; const defaultOnAssetAvailable = function (payload) { scope._onAssetAvailable(payload); }; this.parser.setCallbackOnAssetAvailable(defaultOnAssetAvailable); }; OBJLoader2.OBJLOADER2_VERSION = '3.2.0'; console.info('Using OBJLoader2 version: ' + OBJLoader2.OBJLOADER2_VERSION); OBJLoader2.prototype = Object.assign(Object.create(Loader.prototype), { constructor: OBJLoader2, /** * See {@link OBJLoader2Parser.setLogging} * @return {OBJLoader2} */ setLogging: function (enabled, debug) { this.parser.setLogging(enabled, debug); return this; }, /** * See {@link OBJLoader2Parser.setMaterialPerSmoothingGroup} * @return {OBJLoader2} */ setMaterialPerSmoothingGroup: function (materialPerSmoothingGroup) { this.parser.setMaterialPerSmoothingGroup(materialPerSmoothingGroup); return this; }, /** * See {@link OBJLoader2Parser.setUseOAsMesh} * @return {OBJLoader2} */ setUseOAsMesh: function (useOAsMesh) { this.parser.setUseOAsMesh(useOAsMesh); return this; }, /** * See {@link OBJLoader2Parser.setUseIndices} * @return {OBJLoader2} */ setUseIndices: function (useIndices) { this.parser.setUseIndices(useIndices); return this; }, /** * See {@link OBJLoader2Parser.setDisregardNormals} * @return {OBJLoader2} */ setDisregardNormals: function (disregardNormals) { this.parser.setDisregardNormals(disregardNormals); return this; }, /** * Set the name of the model. * * @param {string} modelName * @return {OBJLoader2} */ setModelName: function (modelName) { this.modelName = modelName ? modelName : this.modelName; return this; }, /** * Set the node where the loaded objects will be attached directly. * * @param {Object3D} baseObject3d Object already attached to scenegraph where new meshes will be attached to * @return {OBJLoader2} */ setBaseObject3d: function (baseObject3d) { this.baseObject3d = baseObject3d === undefined || baseObject3d === null ? this.baseObject3d : baseObject3d; return this; }, /** * Add materials as associated array. * * @param {Object} materials Object with named {@link Material} * @param overrideExisting boolean Override existing material * @return {OBJLoader2} */ addMaterials: function (materials, overrideExisting) { this.materialHandler.addMaterials(materials, overrideExisting); return this; }, /** * See {@link OBJLoader2Parser.setCallbackOnAssetAvailable} * @return {OBJLoader2} */ setCallbackOnAssetAvailable: function (onAssetAvailable) { this.parser.setCallbackOnAssetAvailable(onAssetAvailable); return this; }, /** * See {@link OBJLoader2Parser.setCallbackOnProgress} * @return {OBJLoader2} */ setCallbackOnProgress: function (onProgress) { this.parser.setCallbackOnProgress(onProgress); return this; }, /** * See {@link OBJLoader2Parser.setCallbackOnError} * @return {OBJLoader2} */ setCallbackOnError: function (onError) { this.parser.setCallbackOnError(onError); return this; }, /** * See {@link OBJLoader2Parser.setCallbackOnLoad} * @return {OBJLoader2} */ setCallbackOnLoad: function (onLoad) { this.parser.setCallbackOnLoad(onLoad); return this; }, /** * Register a function that is called once a single mesh is available and it could be altered by the supplied function. * * @param {Function} [onMeshAlter] * @return {OBJLoader2} */ setCallbackOnMeshAlter: function (onMeshAlter) { this.meshReceiver._setCallbacks(this.parser.callbacks.onProgress, onMeshAlter); return this; }, /** * Register a function that is called once all materials have been loaded and they could be altered by the supplied function. * * @param {Function} [onLoadMaterials] * @return {OBJLoader2} */ setCallbackOnLoadMaterials: function (onLoadMaterials) { this.materialHandler._setCallbacks(onLoadMaterials); return this; }, /** * Use this convenient method to load a file at the given URL. By default the fileLoader uses an ArrayBuffer. * * @param {string} url A string containing the path/URL of the file to be loaded. * @param {function} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument. * @param {function} [onFileLoadProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes. * @param {function} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument. * @param {function} [onMeshAlter] Called after every single mesh is made available by the parser */ load: function (url, onLoad, onFileLoadProgress, onError, onMeshAlter) { const scope = this; if (onLoad === null || onLoad === undefined || !(onLoad instanceof Function)) { const errorMessage = 'onLoad is not a function! Aborting...'; scope.parser.callbacks.onError(errorMessage); throw errorMessage; } else { this.parser.setCallbackOnLoad(onLoad); } if (onError === null || onError === undefined || !(onError instanceof Function)) { onError = function (event) { let errorMessage = event; if (event.currentTarget && event.currentTarget.statusText !== null) { errorMessage = 'Error occurred while downloading!\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText; } scope.parser.callbacks.onError(errorMessage); }; } if (!url) { onError('An invalid url was provided. Unable to continue!'); } const urlFull = new URL(url, window.location.href).href; let filename = urlFull; const urlParts = urlFull.split('/'); if (urlParts.length > 2) { filename = urlParts[urlParts.length - 1]; this.path = urlParts.slice(0, urlParts.length - 1).join('/') + '/'; } if ( onFileLoadProgress === null || onFileLoadProgress === undefined || !(onFileLoadProgress instanceof Function) ) { let numericalValueRef = 0; let numericalValue = 0; onFileLoadProgress = function (event) { if (!event.lengthComputable) return; numericalValue = event.loaded / event.total; if (numericalValue > numericalValueRef) { numericalValueRef = numericalValue; const output = 'Download of "' + url + '": ' + (numericalValue * 100).toFixed(2) + '%'; scope.parser.callbacks.onProgress('progressLoad', output, numericalValue); } }; } this.setCallbackOnMeshAlter(onMeshAlter); const fileLoaderOnLoad = function (content) { scope.parser.callbacks.onLoad(scope.parse(content), 'OBJLoader2#load: Parsing completed'); }; const fileLoader = new FileLoader(this.manager); fileLoader.setPath(this.path || this.resourcePath); fileLoader.setResponseType('arraybuffer'); fileLoader.load(filename, fileLoaderOnLoad, onFileLoadProgress, onError); }, /** * Parses OBJ data synchronously from arraybuffer or string and returns the {@link Object3D}. * * @param {arraybuffer|string} content OBJ data as Uint8Array or String * @return {Object3D} */ parse: function (content) { // fast-fail in case of illegal data if (content === null || content === undefined) { throw 'Provided content is not a valid ArrayBuffer or String. Unable to continue parsing'; } if (this.parser.logging.enabled) { console.time('OBJLoader parse: ' + this.modelName); } // Create default materials beforehand, but do not override previously set materials (e.g. during init) this.materialHandler.createDefaultMaterials(false); // code works directly on the material references, parser clear its materials before updating this.parser.setMaterials(this.materialHandler.getMaterials()); if (content instanceof ArrayBuffer || content instanceof Uint8Array) { if (this.parser.logging.enabled) console.info('Parsing arrayBuffer...'); this.parser.execute(content); } else if (typeof content === 'string' || content instanceof String) { if (this.parser.logging.enabled) console.info('Parsing text...'); this.parser.executeLegacy(content); } else { this.parser.callbacks.onError('Provided content was neither of type String nor Uint8Array! Aborting...'); } if (this.parser.logging.enabled) { console.timeEnd('OBJLoader parse: ' + this.modelName); } return this.baseObject3d; }, _onAssetAvailable: function (payload) { if (payload.cmd !== 'assetAvailable') return; if (payload.type === 'mesh') { const meshes = this.meshReceiver.buildMeshes(payload); for (const mesh of meshes) { this.baseObject3d.add(mesh); } } else if (payload.type === 'material') { this.materialHandler.addPayloadMaterials(payload); } }, }); export {OBJLoader2};