Spaces:
Running
Running
/** | |
* @author Kai Salmen / https://kaisalmen.de | |
* Development repository: https://github.com/kaisalmen/WWOBJLoader | |
*/ | |
; | |
if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} } | |
if ( THREE.LoaderSupport === undefined ) console.error( '"THREE.LoaderSupport" is not available. "THREE.OBJLoader2" requires it. Please include "LoaderSupport.js" in your HTML.' ); | |
/** | |
* Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer | |
* @class | |
* | |
* @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager} | |
*/ | |
THREE.OBJLoader2 = function ( manager ) { | |
console.info( 'Using THREE.OBJLoader2 version: ' + THREE.OBJLoader2.OBJLOADER2_VERSION ); | |
this.manager = THREE.LoaderSupport.Validator.verifyInput( manager, THREE.DefaultLoadingManager ); | |
this.logging = { | |
enabled: true, | |
debug: false | |
}; | |
this.modelName = ''; | |
this.instanceNo = 0; | |
this.path; | |
this.resourcePath; | |
this.useIndices = false; | |
this.disregardNormals = false; | |
this.materialPerSmoothingGroup = false; | |
this.useOAsMesh = false; | |
this.loaderRootNode = new THREE.Group(); | |
this.meshBuilder = new THREE.LoaderSupport.MeshBuilder(); | |
this.callbacks = new THREE.LoaderSupport.Callbacks(); | |
this.workerSupport = new THREE.LoaderSupport.WorkerSupport(); | |
this.terminateWorkerOnLoad = true; | |
}; | |
THREE.OBJLoader2.OBJLOADER2_VERSION = '2.5.0'; | |
THREE.OBJLoader2.prototype = { | |
constructor: THREE.OBJLoader2, | |
/** | |
* Enable or disable logging in general (except warn and error), plus enable or disable debug logging. | |
* | |
* @param {boolean} enabled True or false. | |
* @param {boolean} debug True or false. | |
*/ | |
setLogging: function ( enabled, debug ) { | |
this.logging.enabled = enabled === true; | |
this.logging.debug = debug === true; | |
this.meshBuilder.setLogging( this.logging.enabled, this.logging.debug ); | |
}, | |
/** | |
* Set the name of the model. | |
* | |
* @param {string} modelName | |
*/ | |
setModelName: function ( modelName ) { | |
this.modelName = THREE.LoaderSupport.Validator.verifyInput( modelName, this.modelName ); | |
}, | |
/** | |
* The URL of the base path. | |
* | |
* @param {string} path URL | |
*/ | |
setPath: function ( path ) { | |
this.path = THREE.LoaderSupport.Validator.verifyInput( path, this.path ); | |
}, | |
/** | |
* Allows to specify resourcePath for dependencies of specified resource. | |
* @param {string} resourcePath | |
*/ | |
setResourcePath: function ( resourcePath ) { | |
this.resourcePath = THREE.LoaderSupport.Validator.verifyInput( resourcePath, this.resourcePath ); | |
}, | |
/** | |
* Set the node where the loaded objects will be attached directly. | |
* | |
* @param {THREE.Object3D} streamMeshesTo Object already attached to scenegraph where new meshes will be attached to | |
*/ | |
setStreamMeshesTo: function ( streamMeshesTo ) { | |
this.loaderRootNode = THREE.LoaderSupport.Validator.verifyInput( streamMeshesTo, this.loaderRootNode ); | |
}, | |
/** | |
* Set materials loaded by MTLLoader or any other supplier of an Array of {@link THREE.Material}. | |
* | |
* @param {THREE.Material[]} materials Array of {@link THREE.Material} | |
*/ | |
setMaterials: function ( materials ) { | |
this.meshBuilder.setMaterials( materials ); | |
}, | |
/** | |
* Instructs loaders to create indexed {@link THREE.BufferGeometry}. | |
* | |
* @param {boolean} useIndices=false | |
*/ | |
setUseIndices: function ( useIndices ) { | |
this.useIndices = useIndices === true; | |
}, | |
/** | |
* Tells whether normals should be completely disregarded and regenerated. | |
* | |
* @param {boolean} disregardNormals=false | |
*/ | |
setDisregardNormals: function ( disregardNormals ) { | |
this.disregardNormals = disregardNormals === true; | |
}, | |
/** | |
* Tells whether a material shall be created per smoothing group. | |
* | |
* @param {boolean} materialPerSmoothingGroup=false | |
*/ | |
setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) { | |
this.materialPerSmoothingGroup = materialPerSmoothingGroup === true; | |
}, | |
/** | |
* Usually 'o' is meta-information and does not result in creation of new meshes, but mesh creation on occurrence of "o" can be enforced. | |
* | |
* @param {boolean} useOAsMesh=false | |
*/ | |
setUseOAsMesh: function ( useOAsMesh ) { | |
this.useOAsMesh = useOAsMesh === true; | |
}, | |
_setCallbacks: function ( callbacks ) { | |
if ( THREE.LoaderSupport.Validator.isValid( callbacks.onProgress ) ) this.callbacks.setCallbackOnProgress( callbacks.onProgress ); | |
if ( THREE.LoaderSupport.Validator.isValid( callbacks.onReportError ) ) this.callbacks.setCallbackOnReportError( callbacks.onReportError ); | |
if ( THREE.LoaderSupport.Validator.isValid( callbacks.onMeshAlter ) ) this.callbacks.setCallbackOnMeshAlter( callbacks.onMeshAlter ); | |
if ( THREE.LoaderSupport.Validator.isValid( callbacks.onLoad ) ) this.callbacks.setCallbackOnLoad( callbacks.onLoad ); | |
if ( THREE.LoaderSupport.Validator.isValid( callbacks.onLoadMaterials ) ) this.callbacks.setCallbackOnLoadMaterials( callbacks.onLoadMaterials ); | |
this.meshBuilder._setCallbacks( this.callbacks ); | |
}, | |
/** | |
* Announce feedback which is give to the registered callbacks. | |
* @private | |
* | |
* @param {string} type The type of event | |
* @param {string} text Textual description of the event | |
* @param {number} numericalValue Numerical value describing the progress | |
*/ | |
onProgress: function ( type, text, numericalValue ) { | |
var content = THREE.LoaderSupport.Validator.isValid( text ) ? text: ''; | |
var event = { | |
detail: { | |
type: type, | |
modelName: this.modelName, | |
instanceNo: this.instanceNo, | |
text: content, | |
numericalValue: numericalValue | |
} | |
}; | |
if ( THREE.LoaderSupport.Validator.isValid( this.callbacks.onProgress ) ) this.callbacks.onProgress( event ); | |
if ( this.logging.enabled && this.logging.debug ) console.debug( content ); | |
}, | |
_onError: function ( event ) { | |
var output = 'Error occurred while downloading!'; | |
if ( event.currentTarget && event.currentTarget.statusText !== null ) { | |
output += '\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText; | |
} | |
this.onProgress( 'error', output, -1 ); | |
this._throwError( output ); | |
}, | |
_throwError: function ( errorMessage ) { | |
if ( THREE.LoaderSupport.Validator.isValid( this.callbacks.onReportError ) ) { | |
this.callbacks.onReportError( errorMessage ); | |
} else { | |
throw errorMessage; | |
} | |
}, | |
/** | |
* 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 {callback} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument. | |
* @param {callback} [onProgress] 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 {callback} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument. | |
* @param {callback} [onMeshAlter] A function to be called after a new mesh raw data becomes available for alteration. | |
* @param {boolean} [useAsync] If true, uses async loading with worker, if false loads data synchronously. | |
*/ | |
load: function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) { | |
var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'OBJ' ); | |
this._loadObj( resource, onLoad, onProgress, onError, onMeshAlter, useAsync ); | |
}, | |
_loadObj: function ( resource, onLoad, onProgress, onError, onMeshAlter, useAsync ) { | |
var scope = this; | |
if ( ! THREE.LoaderSupport.Validator.isValid( onError ) ) { | |
onError = function ( event ) { | |
scope._onError( event ); | |
} | |
} | |
// fast-fail | |
if ( ! THREE.LoaderSupport.Validator.isValid( resource ) ) onError( 'An invalid ResourceDescriptor was provided. Unable to continue!' ); | |
var fileLoaderOnLoad = function ( content ) { | |
resource.content = content; | |
if ( useAsync ) { | |
scope.parseAsync( content, onLoad ); | |
} else { | |
var callbacks = new THREE.LoaderSupport.Callbacks(); | |
callbacks.setCallbackOnMeshAlter( onMeshAlter ); | |
scope._setCallbacks( callbacks ); | |
onLoad( | |
{ | |
detail: { | |
loaderRootNode: scope.parse( content ), | |
modelName: scope.modelName, | |
instanceNo: scope.instanceNo | |
} | |
} | |
); | |
} | |
}; | |
this.setPath( resource.path ); | |
this.setResourcePath( resource.resourcePath ); | |
// fast-fail | |
if ( ! THREE.LoaderSupport.Validator.isValid( resource.url ) || THREE.LoaderSupport.Validator.isValid( resource.content ) ) { | |
fileLoaderOnLoad( THREE.LoaderSupport.Validator.isValid( resource.content ) ? resource.content : null ); | |
} else { | |
if ( ! THREE.LoaderSupport.Validator.isValid( onProgress ) ) { | |
var numericalValueRef = 0; | |
var numericalValue = 0; | |
onProgress = function ( event ) { | |
if ( ! event.lengthComputable ) return; | |
numericalValue = event.loaded / event.total; | |
if ( numericalValue > numericalValueRef ) { | |
numericalValueRef = numericalValue; | |
var output = 'Download of "' + resource.url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%'; | |
scope.onProgress( 'progressLoad', output, numericalValue ); | |
} | |
}; | |
} | |
var fileLoader = new THREE.FileLoader( this.manager ); | |
fileLoader.setPath( this.path || this.resourcePath ); | |
fileLoader.setResponseType( 'arraybuffer' ); | |
fileLoader.load( resource.name, fileLoaderOnLoad, onProgress, onError ); | |
} | |
}, | |
/** | |
* Run the loader according the provided instructions. | |
* | |
* @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution | |
* @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport | |
*/ | |
run: function ( prepData, workerSupportExternal ) { | |
this._applyPrepData( prepData ); | |
var available = prepData.checkResourceDescriptorFiles( prepData.resources, | |
[ | |
{ ext: "obj", type: "ArrayBuffer", ignore: false }, | |
{ ext: "mtl", type: "String", ignore: false }, | |
{ ext: "zip", type: "String", ignore: true } | |
] | |
); | |
if ( THREE.LoaderSupport.Validator.isValid( workerSupportExternal ) ) { | |
this.terminateWorkerOnLoad = false; | |
this.workerSupport = workerSupportExternal; | |
this.logging.enabled = this.workerSupport.logging.enabled; | |
this.logging.debug = this.workerSupport.logging.debug; | |
} | |
var scope = this; | |
var onMaterialsLoaded = function ( materials ) { | |
if ( materials !== null ) scope.meshBuilder.setMaterials( materials ); | |
scope._loadObj( available.obj, scope.callbacks.onLoad, null, null, scope.callbacks.onMeshAlter, prepData.useAsync ); | |
}; | |
this._loadMtl( available.mtl, onMaterialsLoaded, null, null, prepData.crossOrigin, prepData.materialOptions ); | |
}, | |
_applyPrepData: function ( prepData ) { | |
if ( THREE.LoaderSupport.Validator.isValid( prepData ) ) { | |
this.setLogging( prepData.logging.enabled, prepData.logging.debug ); | |
this.setModelName( prepData.modelName ); | |
this.setStreamMeshesTo( prepData.streamMeshesTo ); | |
this.meshBuilder.setMaterials( prepData.materials ); | |
this.setUseIndices( prepData.useIndices ); | |
this.setDisregardNormals( prepData.disregardNormals ); | |
this.setMaterialPerSmoothingGroup( prepData.materialPerSmoothingGroup ); | |
this.setUseOAsMesh( prepData.useOAsMesh ); | |
this._setCallbacks( prepData.getCallbacks() ); | |
} | |
}, | |
/** | |
* Parses OBJ data synchronously from arraybuffer or string. | |
* | |
* @param {arraybuffer|string} content OBJ data as Uint8Array or String | |
*/ | |
parse: function ( content ) { | |
// fast-fail in case of illegal data | |
if ( ! THREE.LoaderSupport.Validator.isValid( content ) ) { | |
console.warn( 'Provided content is not a valid ArrayBuffer or String.' ); | |
return this.loaderRootNode; | |
} | |
if ( this.logging.enabled ) console.time( 'OBJLoader2 parse: ' + this.modelName ); | |
this.meshBuilder.init(); | |
var parser = new THREE.OBJLoader2.Parser(); | |
parser.setLogging( this.logging.enabled, this.logging.debug ); | |
parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup ); | |
parser.setUseOAsMesh( this.useOAsMesh ); | |
parser.setUseIndices( this.useIndices ); | |
parser.setDisregardNormals( this.disregardNormals ); | |
// sync code works directly on the material references | |
parser.setMaterials( this.meshBuilder.getMaterials() ); | |
var scope = this; | |
var onMeshLoaded = function ( payload ) { | |
var meshes = scope.meshBuilder.processPayload( payload ); | |
var mesh; | |
for ( var i in meshes ) { | |
mesh = meshes[ i ]; | |
scope.loaderRootNode.add( mesh ); | |
} | |
}; | |
parser.setCallbackMeshBuilder( onMeshLoaded ); | |
var onProgressScoped = function ( text, numericalValue ) { | |
scope.onProgress( 'progressParse', text, numericalValue ); | |
}; | |
parser.setCallbackProgress( onProgressScoped ); | |
if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) { | |
if ( this.logging.enabled ) console.info( 'Parsing arrayBuffer...' ); | |
parser.parse( content ); | |
} else if ( typeof( content ) === 'string' || content instanceof String ) { | |
if ( this.logging.enabled ) console.info( 'Parsing text...' ); | |
parser.parseText( content ); | |
} else { | |
this._throwError( 'Provided content was neither of type String nor Uint8Array! Aborting...' ); | |
} | |
if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2 parse: ' + this.modelName ); | |
return this.loaderRootNode; | |
}, | |
/** | |
* Parses OBJ content asynchronously from arraybuffer. | |
* | |
* @param {arraybuffer} content OBJ data as Uint8Array | |
* @param {callback} onLoad Called after worker successfully completed loading | |
*/ | |
parseAsync: function ( content, onLoad ) { | |
var scope = this; | |
var measureTime = false; | |
var scopedOnLoad = function () { | |
onLoad( | |
{ | |
detail: { | |
loaderRootNode: scope.loaderRootNode, | |
modelName: scope.modelName, | |
instanceNo: scope.instanceNo | |
} | |
} | |
); | |
if ( measureTime && scope.logging.enabled ) console.timeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName ); | |
}; | |
// fast-fail in case of illegal data | |
if ( ! THREE.LoaderSupport.Validator.isValid( content ) ) { | |
console.warn( 'Provided content is not a valid ArrayBuffer.' ); | |
scopedOnLoad() | |
} else { | |
measureTime = true; | |
} | |
if ( measureTime && this.logging.enabled ) console.time( 'OBJLoader2 parseAsync: ' + this.modelName ); | |
this.meshBuilder.init(); | |
var scopedOnMeshLoaded = function ( payload ) { | |
var meshes = scope.meshBuilder.processPayload( payload ); | |
var mesh; | |
for ( var i in meshes ) { | |
mesh = meshes[ i ]; | |
scope.loaderRootNode.add( mesh ); | |
} | |
}; | |
var buildCode = function ( codeSerializer ) { | |
var workerCode = ''; | |
workerCode += '/**\n'; | |
workerCode += ' * This code was constructed by OBJLoader2 buildCode.\n'; | |
workerCode += ' */\n\n'; | |
workerCode += 'THREE = { LoaderSupport: {}, OBJLoader2: {} };\n\n'; | |
workerCode += codeSerializer.serializeObject( 'THREE.LoaderSupport.Validator', THREE.LoaderSupport.Validator ); | |
workerCode += codeSerializer.serializeClass( 'THREE.OBJLoader2.Parser', THREE.OBJLoader2.Parser ); | |
return workerCode; | |
}; | |
this.workerSupport.validate( buildCode, 'THREE.OBJLoader2.Parser' ); | |
this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad ); | |
if ( scope.terminateWorkerOnLoad ) this.workerSupport.setTerminateRequested( true ); | |
var materialNames = {}; | |
var materials = this.meshBuilder.getMaterials(); | |
for ( var materialName in materials ) { | |
materialNames[ materialName ] = materialName; | |
} | |
this.workerSupport.run( | |
{ | |
params: { | |
useAsync: true, | |
materialPerSmoothingGroup: this.materialPerSmoothingGroup, | |
useOAsMesh: this.useOAsMesh, | |
useIndices: this.useIndices, | |
disregardNormals: this.disregardNormals | |
}, | |
logging: { | |
enabled: this.logging.enabled, | |
debug: this.logging.debug | |
}, | |
materials: { | |
// in async case only material names are supplied to parser | |
materials: materialNames | |
}, | |
data: { | |
input: content, | |
options: null | |
} | |
} | |
); | |
}, | |
/** | |
* Utility method for loading an mtl file according resource description. Provide url or content. | |
* | |
* @param {string} url URL to the file | |
* @param {Object} content The file content as arraybuffer or text | |
* @param {function} onLoad Callback to be called after successful load | |
* @param {callback} [onProgress] 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 {callback} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument. | |
* @param {string} [crossOrigin] CORS value | |
* @param {Object} [materialOptions] Set material loading options for MTLLoader | |
*/ | |
loadMtl: function ( url, content, onLoad, onProgress, onError, crossOrigin, materialOptions ) { | |
var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' ); | |
resource.setContent( content ); | |
this._loadMtl( resource, onLoad, onProgress, onError, crossOrigin, materialOptions ); | |
}, | |
_loadMtl: function ( resource, onLoad, onProgress, onError, crossOrigin, materialOptions ) { | |
if ( THREE.MTLLoader === undefined ) console.error( '"THREE.MTLLoader" is not available. "THREE.OBJLoader2" requires it for loading MTL files.' ); | |
if ( THREE.LoaderSupport.Validator.isValid( resource ) && this.logging.enabled ) console.time( 'Loading MTL: ' + resource.name ); | |
var materials = []; | |
var scope = this; | |
var processMaterials = function ( materialCreator ) { | |
var materialCreatorMaterials = []; | |
if ( THREE.LoaderSupport.Validator.isValid( materialCreator ) ) { | |
materialCreator.preload(); | |
materialCreatorMaterials = materialCreator.materials; | |
for ( var materialName in materialCreatorMaterials ) { | |
if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) { | |
materials[ materialName ] = materialCreatorMaterials[ materialName ]; | |
} | |
} | |
} | |
if ( THREE.LoaderSupport.Validator.isValid( resource ) && scope.logging.enabled ) console.timeEnd( 'Loading MTL: ' + resource.name ); | |
onLoad( materials, materialCreator ); | |
}; | |
// fast-fail | |
if ( ! THREE.LoaderSupport.Validator.isValid( resource ) || ( ! THREE.LoaderSupport.Validator.isValid( resource.content ) && ! THREE.LoaderSupport.Validator.isValid( resource.url ) ) ) { | |
processMaterials(); | |
} else { | |
var mtlLoader = new THREE.MTLLoader( this.manager ); | |
crossOrigin = THREE.LoaderSupport.Validator.verifyInput( crossOrigin, 'anonymous' ); | |
mtlLoader.setCrossOrigin( crossOrigin ); | |
mtlLoader.setResourcePath( resource.resourcePath || resource.path ); | |
if ( THREE.LoaderSupport.Validator.isValid( materialOptions ) ) mtlLoader.setMaterialOptions( materialOptions ); | |
var parseTextWithMtlLoader = function ( content ) { | |
var contentAsText = content; | |
if ( typeof( content ) !== 'string' && ! ( content instanceof String ) ) { | |
if ( content.length > 0 || content.byteLength > 0 ) { | |
contentAsText = THREE.LoaderUtils.decodeText( content ); | |
} else { | |
this._throwError( 'Unable to parse mtl as it it seems to be neither a String, an Array or an ArrayBuffer!' ); | |
} | |
} | |
processMaterials( mtlLoader.parse( contentAsText ) ); | |
}; | |
if ( THREE.LoaderSupport.Validator.isValid( resource.content ) ) { | |
parseTextWithMtlLoader( resource.content ); | |
} else if ( THREE.LoaderSupport.Validator.isValid( resource.url ) ) { | |
var fileLoader = new THREE.FileLoader( this.manager ); | |
if ( ! THREE.LoaderSupport.Validator.isValid( onError ) ) { | |
onError = function ( event ) { | |
scope._onError( event ); | |
} | |
} | |
if ( ! THREE.LoaderSupport.Validator.isValid( onProgress ) ) { | |
var numericalValueRef = 0; | |
var numericalValue = 0; | |
onProgress = function ( event ) { | |
if ( ! event.lengthComputable ) return; | |
numericalValue = event.loaded / event.total; | |
if ( numericalValue > numericalValueRef ) { | |
numericalValueRef = numericalValue; | |
var output = 'Download of "' + resource.url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%'; | |
scope.onProgress( 'progressLoad', output, numericalValue ); | |
} | |
}; | |
} | |
fileLoader.load( resource.url, parseTextWithMtlLoader, onProgress, onError ); | |
} | |
} | |
} | |
}; | |
/** | |
* Parse OBJ data either from ArrayBuffer or string | |
* @class | |
*/ | |
THREE.OBJLoader2.Parser = function () { | |
this.callbackProgress = null; | |
this.callbackMeshBuilder = null; | |
this.contentRef = null; | |
this.legacyMode = false; | |
this.materials = {}; | |
this.useAsync = false; | |
this.materialPerSmoothingGroup = false; | |
this.useOAsMesh = false; | |
this.useIndices = false; | |
this.disregardNormals = false; | |
this.vertices = []; | |
this.colors = []; | |
this.normals = []; | |
this.uvs = []; | |
this.rawMesh = { | |
objectName: '', | |
groupName: '', | |
activeMtlName: '', | |
mtllibName: '', | |
// reset with new mesh | |
faceType: -1, | |
subGroups: [], | |
subGroupInUse: null, | |
smoothingGroup: { | |
splitMaterials: false, | |
normalized: -1, | |
real: -1 | |
}, | |
counts: { | |
doubleIndicesCount: 0, | |
faceCount: 0, | |
mtlCount: 0, | |
smoothingGroupCount: 0 | |
} | |
}; | |
this.inputObjectCount = 1; | |
this.outputObjectCount = 1; | |
this.globalCounts = { | |
vertices: 0, | |
faces: 0, | |
doubleIndicesCount: 0, | |
lineByte: 0, | |
currentByte: 0, | |
totalBytes: 0 | |
}; | |
this.logging = { | |
enabled: true, | |
debug: false | |
}; | |
}; | |
THREE.OBJLoader2.Parser.prototype = { | |
constructor: THREE.OBJLoader2.Parser, | |
resetRawMesh: function () { | |
// faces are stored according combined index of group, material and smoothingGroup (0 or not) | |
this.rawMesh.subGroups = []; | |
this.rawMesh.subGroupInUse = null; | |
this.rawMesh.smoothingGroup.normalized = -1; | |
this.rawMesh.smoothingGroup.real = -1; | |
// this default index is required as it is possible to define faces without 'g' or 'usemtl' | |
this.pushSmoothingGroup( 1 ); | |
this.rawMesh.counts.doubleIndicesCount = 0; | |
this.rawMesh.counts.faceCount = 0; | |
this.rawMesh.counts.mtlCount = 0; | |
this.rawMesh.counts.smoothingGroupCount = 0; | |
}, | |
setUseAsync: function ( useAsync ) { | |
this.useAsync = useAsync; | |
}, | |
setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) { | |
this.materialPerSmoothingGroup = materialPerSmoothingGroup; | |
}, | |
setUseOAsMesh: function ( useOAsMesh ) { | |
this.useOAsMesh = useOAsMesh; | |
}, | |
setUseIndices: function ( useIndices ) { | |
this.useIndices = useIndices; | |
}, | |
setDisregardNormals: function ( disregardNormals ) { | |
this.disregardNormals = disregardNormals; | |
}, | |
setMaterials: function ( materials ) { | |
this.materials = THREE.LoaderSupport.Validator.verifyInput( materials, this.materials ); | |
this.materials = THREE.LoaderSupport.Validator.verifyInput( this.materials, {} ); | |
}, | |
setCallbackMeshBuilder: function ( callbackMeshBuilder ) { | |
if ( ! THREE.LoaderSupport.Validator.isValid( callbackMeshBuilder ) ) { | |
this._throwError( 'Unable to run as no "MeshBuilder" callback is set.' ); | |
} | |
this.callbackMeshBuilder = callbackMeshBuilder; | |
}, | |
setCallbackProgress: function ( callbackProgress ) { | |
this.callbackProgress = callbackProgress; | |
}, | |
setLogging: function ( enabled, debug ) { | |
this.logging.enabled = enabled === true; | |
this.logging.debug = debug === true; | |
}, | |
configure: function () { | |
this.pushSmoothingGroup( 1 ); | |
if ( this.logging.enabled ) { | |
var matKeys = Object.keys( this.materials ); | |
var matNames = ( matKeys.length > 0 ) ? '\n\tmaterialNames:\n\t\t- ' + matKeys.join( '\n\t\t- ' ) : '\n\tmaterialNames: None'; | |
var printedConfig = 'OBJLoader2.Parser configuration:' | |
+ matNames | |
+ '\n\tuseAsync: ' + this.useAsync | |
+ '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup | |
+ '\n\tuseOAsMesh: ' + this.useOAsMesh | |
+ '\n\tuseIndices: ' + this.useIndices | |
+ '\n\tdisregardNormals: ' + this.disregardNormals | |
+ '\n\tcallbackMeshBuilderName: ' + this.callbackMeshBuilder.name | |
+ '\n\tcallbackProgressName: ' + this.callbackProgress.name; | |
console.info( printedConfig ); | |
} | |
}, | |
/** | |
* Parse the provided arraybuffer | |
* | |
* @param {Uint8Array} arrayBuffer OBJ data as Uint8Array | |
*/ | |
parse: function ( arrayBuffer ) { | |
if ( this.logging.enabled ) console.time( 'OBJLoader2.Parser.parse' ); | |
this.configure(); | |
var arrayBufferView = new Uint8Array( arrayBuffer ); | |
this.contentRef = arrayBufferView; | |
var length = arrayBufferView.byteLength; | |
this.globalCounts.totalBytes = length; | |
var buffer = new Array( 128 ); | |
for ( var code, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i++ ) { | |
code = arrayBufferView[ i ]; | |
switch ( code ) { | |
// space | |
case 32: | |
if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; | |
word = ''; | |
break; | |
// slash | |
case 47: | |
if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; | |
slashesCount++; | |
word = ''; | |
break; | |
// LF | |
case 10: | |
if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; | |
word = ''; | |
this.globalCounts.lineByte = this.globalCounts.currentByte; | |
this.globalCounts.currentByte = i; | |
this.processLine( buffer, bufferPointer, slashesCount ); | |
bufferPointer = 0; | |
slashesCount = 0; | |
break; | |
// CR | |
case 13: | |
break; | |
default: | |
word += String.fromCharCode( code ); | |
break; | |
} | |
} | |
this.finalizeParsing(); | |
if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2.Parser.parse' ); | |
}, | |
/** | |
* Parse the provided text | |
* | |
* @param {string} text OBJ data as string | |
*/ | |
parseText: function ( text ) { | |
if ( this.logging.enabled ) console.time( 'OBJLoader2.Parser.parseText' ); | |
this.configure(); | |
this.legacyMode = true; | |
this.contentRef = text; | |
var length = text.length; | |
this.globalCounts.totalBytes = length; | |
var buffer = new Array( 128 ); | |
for ( var char, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i++ ) { | |
char = text[ i ]; | |
switch ( char ) { | |
case ' ': | |
if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; | |
word = ''; | |
break; | |
case '/': | |
if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; | |
slashesCount++; | |
word = ''; | |
break; | |
case '\n': | |
if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; | |
word = ''; | |
this.globalCounts.lineByte = this.globalCounts.currentByte; | |
this.globalCounts.currentByte = i; | |
this.processLine( buffer, bufferPointer, slashesCount ); | |
bufferPointer = 0; | |
slashesCount = 0; | |
break; | |
case '\r': | |
break; | |
default: | |
word += char; | |
} | |
} | |
this.finalizeParsing(); | |
if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2.Parser.parseText' ); | |
}, | |
processLine: function ( buffer, bufferPointer, slashesCount ) { | |
if ( bufferPointer < 1 ) return; | |
var reconstructString = function ( content, legacyMode, start, stop ) { | |
var line = ''; | |
if ( stop > start ) { | |
var i; | |
if ( legacyMode ) { | |
for ( i = start; i < stop; i++ ) line += content[ i ]; | |
} else { | |
for ( i = start; i < stop; i++ ) line += String.fromCharCode( content[ i ] ); | |
} | |
line = line.trim(); | |
} | |
return line; | |
}; | |
var bufferLength, length, i, lineDesignation; | |
lineDesignation = buffer [ 0 ]; | |
switch ( lineDesignation ) { | |
case 'v': | |
this.vertices.push( parseFloat( buffer[ 1 ] ) ); | |
this.vertices.push( parseFloat( buffer[ 2 ] ) ); | |
this.vertices.push( parseFloat( buffer[ 3 ] ) ); | |
if ( bufferPointer > 4 ) { | |
this.colors.push( parseFloat( buffer[ 4 ] ) ); | |
this.colors.push( parseFloat( buffer[ 5 ] ) ); | |
this.colors.push( parseFloat( buffer[ 6 ] ) ); | |
} | |
break; | |
case 'vt': | |
this.uvs.push( parseFloat( buffer[ 1 ] ) ); | |
this.uvs.push( parseFloat( buffer[ 2 ] ) ); | |
break; | |
case 'vn': | |
this.normals.push( parseFloat( buffer[ 1 ] ) ); | |
this.normals.push( parseFloat( buffer[ 2 ] ) ); | |
this.normals.push( parseFloat( buffer[ 3 ] ) ); | |
break; | |
case 'f': | |
bufferLength = bufferPointer - 1; | |
// "f vertex ..." | |
if ( slashesCount === 0 ) { | |
this.checkFaceType( 0 ); | |
for ( i = 2, length = bufferLength; i < length; i ++ ) { | |
this.buildFace( buffer[ 1 ] ); | |
this.buildFace( buffer[ i ] ); | |
this.buildFace( buffer[ i + 1 ] ); | |
} | |
// "f vertex/uv ..." | |
} else if ( bufferLength === slashesCount * 2 ) { | |
this.checkFaceType( 1 ); | |
for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) { | |
this.buildFace( buffer[ 1 ], buffer[ 2 ] ); | |
this.buildFace( buffer[ i ], buffer[ i + 1 ] ); | |
this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] ); | |
} | |
// "f vertex/uv/normal ..." | |
} else if ( bufferLength * 2 === slashesCount * 3 ) { | |
this.checkFaceType( 2 ); | |
for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) { | |
this.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] ); | |
this.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] ); | |
this.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] ); | |
} | |
// "f vertex//normal ..." | |
} else { | |
this.checkFaceType( 3 ); | |
for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) { | |
this.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] ); | |
this.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] ); | |
this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] ); | |
} | |
} | |
break; | |
case 'l': | |
case 'p': | |
bufferLength = bufferPointer - 1; | |
if ( bufferLength === slashesCount * 2 ) { | |
this.checkFaceType( 4 ); | |
for ( i = 1, length = bufferLength + 1; i < length; i += 2 ) this.buildFace( buffer[ i ], buffer[ i + 1 ] ); | |
} else { | |
this.checkFaceType( ( lineDesignation === 'l' ) ? 5 : 6 ); | |
for ( i = 1, length = bufferLength + 1; i < length; i ++ ) this.buildFace( buffer[ i ] ); | |
} | |
break; | |
case 's': | |
this.pushSmoothingGroup( buffer[ 1 ] ); | |
break; | |
case 'g': | |
// 'g' leads to creation of mesh if valid data (faces declaration was done before), otherwise only groupName gets set | |
this.processCompletedMesh(); | |
this.rawMesh.groupName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte ); | |
break; | |
case 'o': | |
// 'o' is meta-information and usually does not result in creation of new meshes, but can be enforced with "useOAsMesh" | |
if ( this.useOAsMesh ) this.processCompletedMesh(); | |
this.rawMesh.objectName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte ); | |
break; | |
case 'mtllib': | |
this.rawMesh.mtllibName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte ); | |
break; | |
case 'usemtl': | |
var mtlName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte ); | |
if ( mtlName !== '' && this.rawMesh.activeMtlName !== mtlName ) { | |
this.rawMesh.activeMtlName = mtlName; | |
this.rawMesh.counts.mtlCount++; | |
this.checkSubGroup(); | |
} | |
break; | |
default: | |
break; | |
} | |
}, | |
pushSmoothingGroup: function ( smoothingGroup ) { | |
var smoothingGroupInt = parseInt( smoothingGroup ); | |
if ( isNaN( smoothingGroupInt ) ) { | |
smoothingGroupInt = smoothingGroup === "off" ? 0 : 1; | |
} | |
var smoothCheck = this.rawMesh.smoothingGroup.normalized; | |
this.rawMesh.smoothingGroup.normalized = this.rawMesh.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1; | |
this.rawMesh.smoothingGroup.real = smoothingGroupInt; | |
if ( smoothCheck !== smoothingGroupInt ) { | |
this.rawMesh.counts.smoothingGroupCount++; | |
this.checkSubGroup(); | |
} | |
}, | |
/** | |
* Expanded faceTypes include all four face types, both line types and the point type | |
* faceType = 0: "f vertex ..." | |
* faceType = 1: "f vertex/uv ..." | |
* faceType = 2: "f vertex/uv/normal ..." | |
* faceType = 3: "f vertex//normal ..." | |
* faceType = 4: "l vertex/uv ..." or "l vertex ..." | |
* faceType = 5: "l vertex ..." | |
* faceType = 6: "p vertex ..." | |
*/ | |
checkFaceType: function ( faceType ) { | |
if ( this.rawMesh.faceType !== faceType ) { | |
this.processCompletedMesh(); | |
this.rawMesh.faceType = faceType; | |
this.checkSubGroup(); | |
} | |
}, | |
checkSubGroup: function () { | |
var index = this.rawMesh.activeMtlName + '|' + this.rawMesh.smoothingGroup.normalized; | |
this.rawMesh.subGroupInUse = this.rawMesh.subGroups[ index ]; | |
if ( ! THREE.LoaderSupport.Validator.isValid( this.rawMesh.subGroupInUse ) ) { | |
this.rawMesh.subGroupInUse = { | |
index: index, | |
objectName: this.rawMesh.objectName, | |
groupName: this.rawMesh.groupName, | |
materialName: this.rawMesh.activeMtlName, | |
smoothingGroup: this.rawMesh.smoothingGroup.normalized, | |
vertices: [], | |
indexMappingsCount: 0, | |
indexMappings: [], | |
indices: [], | |
colors: [], | |
uvs: [], | |
normals: [] | |
}; | |
this.rawMesh.subGroups[ index ] = this.rawMesh.subGroupInUse; | |
} | |
}, | |
buildFace: function ( faceIndexV, faceIndexU, faceIndexN ) { | |
if ( this.disregardNormals ) faceIndexN = undefined; | |
var scope = this; | |
var updateSubGroupInUse = function () { | |
var faceIndexVi = parseInt( faceIndexV ); | |
var indexPointerV = 3 * ( faceIndexVi > 0 ? faceIndexVi - 1 : faceIndexVi + scope.vertices.length / 3 ); | |
var indexPointerC = scope.colors.length > 0 ? indexPointerV : null; | |
var vertices = scope.rawMesh.subGroupInUse.vertices; | |
vertices.push( scope.vertices[ indexPointerV++ ] ); | |
vertices.push( scope.vertices[ indexPointerV++ ] ); | |
vertices.push( scope.vertices[ indexPointerV ] ); | |
if ( indexPointerC !== null ) { | |
var colors = scope.rawMesh.subGroupInUse.colors; | |
colors.push( scope.colors[ indexPointerC++ ] ); | |
colors.push( scope.colors[ indexPointerC++ ] ); | |
colors.push( scope.colors[ indexPointerC ] ); | |
} | |
if ( faceIndexU ) { | |
var faceIndexUi = parseInt( faceIndexU ); | |
var indexPointerU = 2 * ( faceIndexUi > 0 ? faceIndexUi - 1 : faceIndexUi + scope.uvs.length / 2 ); | |
var uvs = scope.rawMesh.subGroupInUse.uvs; | |
uvs.push( scope.uvs[ indexPointerU++ ] ); | |
uvs.push( scope.uvs[ indexPointerU ] ); | |
} | |
if ( faceIndexN ) { | |
var faceIndexNi = parseInt( faceIndexN ); | |
var indexPointerN = 3 * ( faceIndexNi > 0 ? faceIndexNi - 1 : faceIndexNi + scope.normals.length / 3 ); | |
var normals = scope.rawMesh.subGroupInUse.normals; | |
normals.push( scope.normals[ indexPointerN++ ] ); | |
normals.push( scope.normals[ indexPointerN++ ] ); | |
normals.push( scope.normals[ indexPointerN ] ); | |
} | |
}; | |
if ( this.useIndices ) { | |
var mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' ); | |
var indicesPointer = this.rawMesh.subGroupInUse.indexMappings[ mappingName ]; | |
if ( THREE.LoaderSupport.Validator.isValid( indicesPointer ) ) { | |
this.rawMesh.counts.doubleIndicesCount++; | |
} else { | |
indicesPointer = this.rawMesh.subGroupInUse.vertices.length / 3; | |
updateSubGroupInUse(); | |
this.rawMesh.subGroupInUse.indexMappings[ mappingName ] = indicesPointer; | |
this.rawMesh.subGroupInUse.indexMappingsCount++; | |
} | |
this.rawMesh.subGroupInUse.indices.push( indicesPointer ); | |
} else { | |
updateSubGroupInUse(); | |
} | |
this.rawMesh.counts.faceCount++; | |
}, | |
createRawMeshReport: function ( inputObjectCount ) { | |
return 'Input Object number: ' + inputObjectCount + | |
'\n\tObject name: ' + this.rawMesh.objectName + | |
'\n\tGroup name: ' + this.rawMesh.groupName + | |
'\n\tMtllib name: ' + this.rawMesh.mtllibName + | |
'\n\tVertex count: ' + this.vertices.length / 3 + | |
'\n\tNormal count: ' + this.normals.length / 3 + | |
'\n\tUV count: ' + this.uvs.length / 2 + | |
'\n\tSmoothingGroup count: ' + this.rawMesh.counts.smoothingGroupCount + | |
'\n\tMaterial count: ' + this.rawMesh.counts.mtlCount + | |
'\n\tReal MeshOutputGroup count: ' + this.rawMesh.subGroups.length; | |
}, | |
/** | |
* Clear any empty subGroup and calculate absolute vertex, normal and uv counts | |
*/ | |
finalizeRawMesh: function () { | |
var meshOutputGroupTemp = []; | |
var meshOutputGroup; | |
var absoluteVertexCount = 0; | |
var absoluteIndexMappingsCount = 0; | |
var absoluteIndexCount = 0; | |
var absoluteColorCount = 0; | |
var absoluteNormalCount = 0; | |
var absoluteUvCount = 0; | |
var indices; | |
for ( var name in this.rawMesh.subGroups ) { | |
meshOutputGroup = this.rawMesh.subGroups[ name ]; | |
if ( meshOutputGroup.vertices.length > 0 ) { | |
indices = meshOutputGroup.indices; | |
if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) { | |
for ( var i in indices ) indices[ i ] = indices[ i ] + absoluteIndexMappingsCount; | |
} | |
meshOutputGroupTemp.push( meshOutputGroup ); | |
absoluteVertexCount += meshOutputGroup.vertices.length; | |
absoluteIndexMappingsCount += meshOutputGroup.indexMappingsCount; | |
absoluteIndexCount += meshOutputGroup.indices.length; | |
absoluteColorCount += meshOutputGroup.colors.length; | |
absoluteUvCount += meshOutputGroup.uvs.length; | |
absoluteNormalCount += meshOutputGroup.normals.length; | |
} | |
} | |
// do not continue if no result | |
var result = null; | |
if ( meshOutputGroupTemp.length > 0 ) { | |
result = { | |
name: this.rawMesh.groupName !== '' ? this.rawMesh.groupName : this.rawMesh.objectName, | |
subGroups: meshOutputGroupTemp, | |
absoluteVertexCount: absoluteVertexCount, | |
absoluteIndexCount: absoluteIndexCount, | |
absoluteColorCount: absoluteColorCount, | |
absoluteNormalCount: absoluteNormalCount, | |
absoluteUvCount: absoluteUvCount, | |
faceCount: this.rawMesh.counts.faceCount, | |
doubleIndicesCount: this.rawMesh.counts.doubleIndicesCount | |
}; | |
} | |
return result; | |
}, | |
processCompletedMesh: function () { | |
var result = this.finalizeRawMesh(); | |
if ( THREE.LoaderSupport.Validator.isValid( result ) ) { | |
if ( this.colors.length > 0 && this.colors.length !== this.vertices.length ) { | |
this._throwError( 'Vertex Colors were detected, but vertex count and color count do not match!' ); | |
} | |
if ( this.logging.enabled && this.logging.debug ) console.debug( this.createRawMeshReport( this.inputObjectCount ) ); | |
this.inputObjectCount++; | |
this.buildMesh( result ); | |
var progressBytesPercent = this.globalCounts.currentByte / this.globalCounts.totalBytes; | |
this.callbackProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName + '] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent ); | |
this.resetRawMesh(); | |
return true; | |
} else { | |
return false; | |
} | |
}, | |
/** | |
* SubGroups are transformed to too intermediate format that is forwarded to the MeshBuilder. | |
* It is ensured that SubGroups only contain objects with vertices (no need to check). | |
* | |
* @param result | |
*/ | |
buildMesh: function ( result ) { | |
var meshOutputGroups = result.subGroups; | |
var vertexFA = new Float32Array( result.absoluteVertexCount ); | |
this.globalCounts.vertices += result.absoluteVertexCount / 3; | |
this.globalCounts.faces += result.faceCount; | |
this.globalCounts.doubleIndicesCount += result.doubleIndicesCount; | |
var indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null; | |
var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null; | |
var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null; | |
var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null; | |
var haveVertexColors = THREE.LoaderSupport.Validator.isValid( colorFA ); | |
var meshOutputGroup; | |
var materialNames = []; | |
var createMultiMaterial = ( meshOutputGroups.length > 1 ); | |
var materialIndex = 0; | |
var materialIndexMapping = []; | |
var selectedMaterialIndex; | |
var materialGroup; | |
var materialGroups = []; | |
var vertexFAOffset = 0; | |
var indexUAOffset = 0; | |
var colorFAOffset = 0; | |
var normalFAOffset = 0; | |
var uvFAOffset = 0; | |
var materialGroupOffset = 0; | |
var materialGroupLength = 0; | |
var materialOrg, material, materialName, materialNameOrg; | |
// only one specific face type | |
for ( var oodIndex in meshOutputGroups ) { | |
if ( ! meshOutputGroups.hasOwnProperty( oodIndex ) ) continue; | |
meshOutputGroup = meshOutputGroups[ oodIndex ]; | |
materialNameOrg = meshOutputGroup.materialName; | |
if ( this.rawMesh.faceType < 4 ) { | |
materialName = materialNameOrg + ( haveVertexColors ? '_vertexColor' : '' ) + ( meshOutputGroup.smoothingGroup === 0 ? '_flat' : '' ); | |
} else { | |
materialName = this.rawMesh.faceType === 6 ? 'defaultPointMaterial' : 'defaultLineMaterial'; | |
} | |
materialOrg = this.materials[ materialNameOrg ]; | |
material = this.materials[ materialName ]; | |
// both original and derived names do not lead to an existing material => need to use a default material | |
if ( ! THREE.LoaderSupport.Validator.isValid( materialOrg ) && ! THREE.LoaderSupport.Validator.isValid( material ) ) { | |
var defaultMaterialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial'; | |
materialOrg = this.materials[ defaultMaterialName ]; | |
if ( this.logging.enabled ) console.warn( 'object_group "' + meshOutputGroup.objectName + '_' + | |
meshOutputGroup.groupName + '" was defined with unresolvable material "' + | |
materialNameOrg + '"! Assigning "' + defaultMaterialName + '".' ); | |
materialNameOrg = defaultMaterialName; | |
// if names are identical then there is no need for later manipulation | |
if ( materialNameOrg === materialName ) { | |
material = materialOrg; | |
materialName = defaultMaterialName; | |
} | |
} | |
if ( ! THREE.LoaderSupport.Validator.isValid( material ) ) { | |
var materialCloneInstructions = { | |
materialNameOrg: materialNameOrg, | |
materialName: materialName, | |
materialProperties: { | |
vertexColors: haveVertexColors ? 2 : 0, | |
flatShading: meshOutputGroup.smoothingGroup === 0 | |
} | |
}; | |
var payload = { | |
cmd: 'materialData', | |
materials: { | |
materialCloneInstructions: materialCloneInstructions | |
} | |
}; | |
this.callbackMeshBuilder( payload ); | |
// fake entry for async; sync Parser always works on material references (Builder update directly visible here) | |
if ( this.useAsync ) this.materials[ materialName ] = materialCloneInstructions; | |
} | |
if ( createMultiMaterial ) { | |
// re-use material if already used before. Reduces materials array size and eliminates duplicates | |
selectedMaterialIndex = materialIndexMapping[ materialName ]; | |
if ( ! selectedMaterialIndex ) { | |
selectedMaterialIndex = materialIndex; | |
materialIndexMapping[ materialName ] = materialIndex; | |
materialNames.push( materialName ); | |
materialIndex++; | |
} | |
materialGroupLength = this.useIndices ? meshOutputGroup.indices.length : meshOutputGroup.vertices.length / 3; | |
materialGroup = { | |
start: materialGroupOffset, | |
count: materialGroupLength, | |
index: selectedMaterialIndex | |
}; | |
materialGroups.push( materialGroup ); | |
materialGroupOffset += materialGroupLength; | |
} else { | |
materialNames.push( materialName ); | |
} | |
vertexFA.set( meshOutputGroup.vertices, vertexFAOffset ); | |
vertexFAOffset += meshOutputGroup.vertices.length; | |
if ( indexUA ) { | |
indexUA.set( meshOutputGroup.indices, indexUAOffset ); | |
indexUAOffset += meshOutputGroup.indices.length; | |
} | |
if ( colorFA ) { | |
colorFA.set( meshOutputGroup.colors, colorFAOffset ); | |
colorFAOffset += meshOutputGroup.colors.length; | |
} | |
if ( normalFA ) { | |
normalFA.set( meshOutputGroup.normals, normalFAOffset ); | |
normalFAOffset += meshOutputGroup.normals.length; | |
} | |
if ( uvFA ) { | |
uvFA.set( meshOutputGroup.uvs, uvFAOffset ); | |
uvFAOffset += meshOutputGroup.uvs.length; | |
} | |
if ( this.logging.enabled && this.logging.debug ) { | |
var materialIndexLine = THREE.LoaderSupport.Validator.isValid( selectedMaterialIndex ) ? '\n\t\tmaterialIndex: ' + selectedMaterialIndex : ''; | |
var createdReport = '\tOutput Object no.: ' + this.outputObjectCount + | |
'\n\t\tgroupName: ' + meshOutputGroup.groupName + | |
'\n\t\tIndex: ' + meshOutputGroup.index + | |
'\n\t\tfaceType: ' + this.rawMesh.faceType + | |
'\n\t\tmaterialName: ' + meshOutputGroup.materialName + | |
'\n\t\tsmoothingGroup: ' + meshOutputGroup.smoothingGroup + | |
materialIndexLine + | |
'\n\t\tobjectName: ' + meshOutputGroup.objectName + | |
'\n\t\t#vertices: ' + meshOutputGroup.vertices.length / 3 + | |
'\n\t\t#indices: ' + meshOutputGroup.indices.length + | |
'\n\t\t#colors: ' + meshOutputGroup.colors.length / 3 + | |
'\n\t\t#uvs: ' + meshOutputGroup.uvs.length / 2 + | |
'\n\t\t#normals: ' + meshOutputGroup.normals.length / 3; | |
console.debug( createdReport ); | |
} | |
} | |
this.outputObjectCount++; | |
this.callbackMeshBuilder( | |
{ | |
cmd: 'meshData', | |
progress: { | |
numericalValue: this.globalCounts.currentByte / this.globalCounts.totalBytes | |
}, | |
params: { | |
meshName: result.name | |
}, | |
materials: { | |
multiMaterial: createMultiMaterial, | |
materialNames: materialNames, | |
materialGroups: materialGroups | |
}, | |
buffers: { | |
vertices: vertexFA, | |
indices: indexUA, | |
colors: colorFA, | |
normals: normalFA, | |
uvs: uvFA | |
}, | |
// 0: mesh, 1: line, 2: point | |
geometryType: this.rawMesh.faceType < 4 ? 0 : ( this.rawMesh.faceType === 6 ) ? 2 : 1 | |
}, | |
[ vertexFA.buffer ], | |
THREE.LoaderSupport.Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null, | |
THREE.LoaderSupport.Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null, | |
THREE.LoaderSupport.Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null, | |
THREE.LoaderSupport.Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null | |
); | |
}, | |
finalizeParsing: function () { | |
if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.outputObjectCount ); | |
if ( this.processCompletedMesh() && this.logging.enabled ) { | |
var parserFinalReport = 'Overall counts: ' + | |
'\n\tVertices: ' + this.globalCounts.vertices + | |
'\n\tFaces: ' + this.globalCounts.faces + | |
'\n\tMultiple definitions: ' + this.globalCounts.doubleIndicesCount; | |
console.info( parserFinalReport ); | |
} | |
} | |
}; | |