OBJLoader2.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  1. /**
  2. * @author Kai Salmen / https://kaisalmen.de
  3. * Development repository: https://github.com/kaisalmen/WWOBJLoader
  4. */
  5. 'use strict';
  6. if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
  7. /**
  8. * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
  9. * @class
  10. *
  11. * @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}
  12. */
  13. THREE.OBJLoader2 = (function () {
  14. var OBJLOADER2_VERSION = '2.0.0-dev';
  15. var Validator = THREE.LoaderSupport.Validator;
  16. var Commons = THREE.LoaderSupport.Commons;
  17. OBJLoader2.prototype = Object.create( THREE.LoaderSupport.Commons.prototype );
  18. OBJLoader2.prototype.constructor = OBJLoader2;
  19. function OBJLoader2( manager ) {
  20. THREE.LoaderSupport.Commons.call( this, manager );
  21. console.log( "Using THREE.OBJLoader2 version: " + OBJLOADER2_VERSION );
  22. this.materialPerSmoothingGroup = false;
  23. this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );
  24. this.workerSupport = null;
  25. };
  26. /**
  27. * Tells whether a material shall be created per smoothing group
  28. * @memberOf THREE.OBJLoader2
  29. *
  30. * @param {boolean} materialPerSmoothingGroup=false Default is false
  31. */
  32. OBJLoader2.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
  33. this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
  34. };
  35. /**
  36. * Sets debug mode for the parser
  37. * @memberOf THREE.OBJLoader2
  38. *
  39. * @param {boolean} enabled
  40. */
  41. OBJLoader2.prototype.setDebug = function ( enabled ) {
  42. THREE.LoaderSupport.Commons.prototype.setDebug.call( this, enabled );
  43. };
  44. /**
  45. * Use this convenient method to load an OBJ file at the given URL. Per default the fileLoader uses an arraybuffer
  46. * @memberOf THREE.OBJLoader2
  47. *
  48. * @param {string} url URL of the file to load
  49. * @param {callback} onLoad Called after loading was successfully completed
  50. * @param {callback} onProgress Called to report progress of loading. The argument will be the XMLHttpRequest instance, which contains {integer total} and {integer loaded} bytes.
  51. * @param {callback} onError Called after an error occurred during loading
  52. * @param {callback} onMeshAlter Called after a new mesh raw data becomes available
  53. * @param {boolean} useAsync Set this to use async loading
  54. */
  55. OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
  56. var scope = this;
  57. if ( ! Validator.isValid( onProgress ) ) {
  58. var refPercentComplete = 0;
  59. var percentComplete = 0;
  60. onProgress = function ( event ) {
  61. if ( ! event.lengthComputable ) return;
  62. percentComplete = Math.round( event.loaded / event.total * 100 );
  63. if ( percentComplete > refPercentComplete ) {
  64. refPercentComplete = percentComplete;
  65. var output = 'Download of "' + url + '": ' + percentComplete + '%';
  66. console.log( output );
  67. scope.onProgress( output );
  68. }
  69. };
  70. }
  71. if ( ! Validator.isValid( onError ) ) {
  72. onError = function ( event ) {
  73. var output = 'Error occurred while downloading "' + url + '"';
  74. console.error( output + ': ' + event );
  75. scope.onProgress( output );
  76. };
  77. }
  78. this.fileLoader.setPath( this.path );
  79. this.fileLoader.setResponseType( 'arraybuffer' );
  80. this.fileLoader.load( url, function ( content ) {
  81. if ( useAsync ) {
  82. scope.parseAsync( content, onLoad );
  83. } else {
  84. scope._setCallbacks( null, onMeshAlter, null );
  85. onLoad( scope.parse( content ), scope.modelName, scope.instanceNo );
  86. }
  87. }, onProgress, onError );
  88. };
  89. /**
  90. * Run the loader according the provided instructions.
  91. * @memberOf THREE.OBJLoader2
  92. *
  93. * @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution
  94. * @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport
  95. */
  96. OBJLoader2.prototype.run = function ( prepData, workerSupportExternal ) {
  97. this._applyPrepData( prepData );
  98. var available = this._checkFiles( prepData.resources );
  99. this.workerSupport = Validator.verifyInput( workerSupportExternal, this.workerSupport );
  100. var scope = this;
  101. var onMaterialsLoaded = function ( materials ) {
  102. scope.builder.setMaterials( materials );
  103. if ( Validator.isValid( available.obj.content ) ) {
  104. if ( prepData.useAsync ) {
  105. scope.parseAsync( available.obj.content, scope.callbacks.onLoad );
  106. } else {
  107. scope.parse( available.obj.content );
  108. }
  109. } else {
  110. scope.setPath( available.obj.path );
  111. scope.load( available.obj.name, scope.callbacks.onLoad, null, null, scope.callbacks.onMeshAlter, prepData.useAsync );
  112. }
  113. };
  114. this._loadMtl( available.mtl, onMaterialsLoaded, prepData.crossOrigin );
  115. };
  116. OBJLoader2.prototype._applyPrepData = function ( prepData ) {
  117. THREE.LoaderSupport.Commons.prototype._applyPrepData.call( this, prepData );
  118. if ( Validator.isValid( prepData ) ) {
  119. this.setMaterialPerSmoothingGroup( prepData.materialPerSmoothingGroup );
  120. }
  121. };
  122. /**
  123. * Parses OBJ content synchronously.
  124. * @memberOf THREE.OBJLoader2
  125. *
  126. * @param content
  127. */
  128. OBJLoader2.prototype.parse = function ( content ) {
  129. console.time( 'OBJLoader2 parse: ' + this.modelName );
  130. this.parser = new Parser();
  131. this.parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
  132. this.parser.setMaterialNames( this.builder.materialNames );
  133. this.parser.setDebug( this.debug );
  134. var scope = this;
  135. var onMeshLoaded = function ( payload ) {
  136. var meshes = scope.builder.buildMeshes( payload );
  137. var mesh;
  138. for ( var i in meshes ) {
  139. mesh = meshes[ i ];
  140. scope.loaderRootNode.add( mesh );
  141. }
  142. };
  143. this.parser.setCallbackBuilder( onMeshLoaded );
  144. var onProgressScoped = function ( message ) {
  145. scope.onProgress( message );
  146. };
  147. this.parser.setCallbackProgress( onProgressScoped );
  148. if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
  149. console.log( 'Parsing arrayBuffer...' );
  150. this.parser.parse( content );
  151. } else if ( typeof( content ) === 'string' || content instanceof String ) {
  152. console.log( 'Parsing text...' );
  153. this.parser.parseText( content );
  154. } else {
  155. throw 'Provided content was neither of type String nor Uint8Array! Aborting...';
  156. }
  157. console.timeEnd( 'OBJLoader2 parse: ' + this.modelName );
  158. return this.loaderRootNode;
  159. };
  160. /**
  161. * Parses OBJ content asynchronously.
  162. * @memberOf THREE.OBJLoader2
  163. *
  164. * @param {arraybuffer} content
  165. * @param {callback} onLoad
  166. */
  167. OBJLoader2.prototype.parseAsync = function ( content, onLoad ) {
  168. console.time( 'OBJLoader2 parseAsync: ' + this.modelName);
  169. var scope = this;
  170. var scopedOnLoad = function ( message ) {
  171. onLoad( scope.loaderRootNode, scope.modelName, scope.instanceNo, message );
  172. console.timeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
  173. };
  174. var scopedOnMeshLoaded = function ( payload ) {
  175. var meshes = scope.builder.buildMeshes( payload );
  176. var mesh;
  177. for ( var i in meshes ) {
  178. mesh = meshes[ i ];
  179. scope.loaderRootNode.add( mesh );
  180. }
  181. };
  182. this.workerSupport = Validator.verifyInput( this.workerSupport, new THREE.LoaderSupport.WorkerSupport() );
  183. var buildCode = function ( funcBuildObject, funcBuildSingelton ) {
  184. var workerCode = '';
  185. workerCode += '/**\n';
  186. workerCode += ' * This code was constructed by OBJLoader2 buildWorkerCode.\n';
  187. workerCode += ' */\n\n';
  188. workerCode += funcBuildSingelton( 'Commons', 'Commons', Commons );
  189. workerCode += funcBuildObject( 'Consts', Consts );
  190. workerCode += funcBuildObject( 'Validator', Validator );
  191. workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
  192. workerCode += funcBuildSingelton( 'RawObject', 'RawObject', RawObject );
  193. workerCode += funcBuildSingelton( 'RawObjectDescription', 'RawObjectDescription', RawObjectDescription );
  194. return workerCode;
  195. };
  196. this.workerSupport.validate( buildCode, false );
  197. this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
  198. this.workerSupport.run(
  199. {
  200. cmd: 'run',
  201. params: {
  202. debug: this.debug,
  203. materialPerSmoothingGroup: this.materialPerSmoothingGroup
  204. },
  205. materials: {
  206. materialNames: this.builder.materialNames
  207. },
  208. buffers: {
  209. input: content
  210. }
  211. },
  212. [ content.buffer ]
  213. );
  214. };
  215. /**
  216. * Constants used by THREE.OBJLoader2
  217. */
  218. var Consts = {
  219. CODE_LF: 10,
  220. CODE_CR: 13,
  221. CODE_SPACE: 32,
  222. CODE_SLASH: 47,
  223. STRING_LF: '\n',
  224. STRING_CR: '\r',
  225. STRING_SPACE: ' ',
  226. STRING_SLASH: '/',
  227. LINE_F: 'f',
  228. LINE_G: 'g',
  229. LINE_L: 'l',
  230. LINE_O: 'o',
  231. LINE_S: 's',
  232. LINE_V: 'v',
  233. LINE_VT: 'vt',
  234. LINE_VN: 'vn',
  235. LINE_MTLLIB: 'mtllib',
  236. LINE_USEMTL: 'usemtl'
  237. };
  238. /**
  239. * Parse OBJ data either from ArrayBuffer or string
  240. * @class
  241. */
  242. var Parser = (function () {
  243. function Parser() {
  244. this.callbackProgress = null;
  245. this.inputObjectCount = 1;
  246. this.debug = false;
  247. this.materialPerSmoothingGroup = false;
  248. this.rawObject = new RawObject( this.materialPerSmoothingGroup );
  249. // build mesh related
  250. this.callbackBuilder = null;
  251. this.materialNames = [];
  252. this.outputObjectCount = 1;
  253. };
  254. Parser.prototype.setDebug = function ( debug ) {
  255. if ( debug === true || debug === false ) this.debug = debug;
  256. };
  257. Parser.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
  258. this.materialPerSmoothingGroup = materialPerSmoothingGroup;
  259. this.rawObject.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
  260. };
  261. Parser.prototype.setMaterialNames = function ( materialNames ) {
  262. this.materialNames = Validator.verifyInput( materialNames, this.materialNames );
  263. this.materialNames = Validator.verifyInput( this.materialNames, [] );
  264. };
  265. Parser.prototype.setCallbackBuilder = function ( callbackBuilder ) {
  266. this.callbackBuilder = callbackBuilder;
  267. if ( ! Validator.isValid( this.callbackBuilder ) ) throw 'Unable to run as no "builder" callback is set.';
  268. };
  269. Parser.prototype.setCallbackProgress = function ( callbackProgress ) {
  270. this.callbackProgress = callbackProgress;
  271. };
  272. /**
  273. * Parse the provided arraybuffer
  274. * @memberOf Parser
  275. *
  276. * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
  277. */
  278. Parser.prototype.parse = function ( arrayBuffer ) {
  279. console.time( 'OBJLoader2.Parser.parse' );
  280. var arrayBufferView = new Uint8Array( arrayBuffer );
  281. var length = arrayBufferView.byteLength;
  282. var buffer = new Array( 128 );
  283. var bufferPointer = 0;
  284. var slashesCount = 0;
  285. var reachedFaces = false;
  286. var code;
  287. var word = '';
  288. for ( var i = 0; i < length; i++ ) {
  289. code = arrayBufferView[ i ];
  290. switch ( code ) {
  291. case Consts.CODE_SPACE:
  292. if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
  293. word = '';
  294. break;
  295. case Consts.CODE_SLASH:
  296. slashesCount++;
  297. if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
  298. word = '';
  299. break;
  300. case Consts.CODE_LF:
  301. if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
  302. word = '';
  303. reachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces );
  304. bufferPointer = 0;
  305. slashesCount = 0;
  306. break;
  307. case Consts.CODE_CR:
  308. break;
  309. default:
  310. word += String.fromCharCode( code );
  311. break;
  312. }
  313. }
  314. this.finalize();
  315. console.timeEnd( 'OBJLoader2.Parser.parse' );
  316. };
  317. /**
  318. * Parse the provided text
  319. * @memberOf Parser
  320. *
  321. * @param {string} text OBJ data as string
  322. */
  323. Parser.prototype.parseText = function ( text ) {
  324. console.time( 'OBJLoader2.Parser.parseText' );
  325. var length = text.length;
  326. var buffer = new Array( 128 );
  327. var bufferPointer = 0;
  328. var slashesCount = 0;
  329. var reachedFaces = false;
  330. var char;
  331. var word = '';
  332. for ( var i = 0; i < length; i++ ) {
  333. char = text[ i ];
  334. switch ( char ) {
  335. case Consts.STRING_SPACE:
  336. if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
  337. word = '';
  338. break;
  339. case Consts.STRING_SLASH:
  340. slashesCount++;
  341. if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
  342. word = '';
  343. break;
  344. case Consts.STRING_LF:
  345. if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
  346. word = '';
  347. reachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces );
  348. bufferPointer = 0;
  349. slashesCount = 0;
  350. break;
  351. case Consts.STRING_CR:
  352. break;
  353. default:
  354. word += char;
  355. }
  356. }
  357. this.finalize();
  358. console.timeEnd( 'OBJLoader2.Parser.parseText' );
  359. };
  360. Parser.prototype.processLine = function ( buffer, bufferPointer, slashesCount, reachedFaces ) {
  361. if ( bufferPointer < 1 ) return reachedFaces;
  362. var bufferLength = bufferPointer - 1;
  363. var concatBuffer;
  364. switch ( buffer[ 0 ] ) {
  365. case Consts.LINE_V:
  366. // object complete instance required if reached faces already (= reached next block of v)
  367. if ( reachedFaces ) {
  368. if ( this.rawObject.colors.length > 0 && this.rawObject.colors.length !== this.rawObject.vertices.length ) {
  369. throw 'Vertex Colors were detected, but vertex count and color count do not match!';
  370. }
  371. this.processCompletedObject( null, this.rawObject.groupName );
  372. reachedFaces = false;
  373. }
  374. if ( bufferLength === 3 ) {
  375. this.rawObject.pushVertex( buffer )
  376. } else {
  377. this.rawObject.pushVertexAndVertextColors( buffer );
  378. }
  379. break;
  380. case Consts.LINE_VT:
  381. this.rawObject.pushUv( buffer );
  382. break;
  383. case Consts.LINE_VN:
  384. this.rawObject.pushNormal( buffer );
  385. break;
  386. case Consts.LINE_F:
  387. reachedFaces = true;
  388. this.rawObject.processFaces( buffer, bufferPointer, slashesCount );
  389. break;
  390. case Consts.LINE_L:
  391. if ( bufferLength === slashesCount * 2 ) {
  392. this.rawObject.buildLineVvt( buffer );
  393. } else {
  394. this.rawObject.buildLineV( buffer );
  395. }
  396. break;
  397. case Consts.LINE_S:
  398. this.rawObject.pushSmoothingGroup( buffer[ 1 ] );
  399. this.flushStringBuffer( buffer, bufferPointer );
  400. break;
  401. case Consts.LINE_G:
  402. concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
  403. this.processCompletedGroup( concatBuffer );
  404. this.flushStringBuffer( buffer, bufferPointer );
  405. break;
  406. case Consts.LINE_O:
  407. concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
  408. if ( this.rawObject.vertices.length > 0 ) {
  409. this.processCompletedObject( concatBuffer, null );
  410. reachedFaces = false;
  411. } else {
  412. this.rawObject.pushObject( concatBuffer );
  413. }
  414. this.flushStringBuffer( buffer, bufferPointer );
  415. break;
  416. case Consts.LINE_MTLLIB:
  417. concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
  418. this.rawObject.pushMtllib( concatBuffer );
  419. this.flushStringBuffer( buffer, bufferPointer );
  420. break;
  421. case Consts.LINE_USEMTL:
  422. concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
  423. this.rawObject.pushUsemtl( concatBuffer );
  424. this.flushStringBuffer( buffer, bufferPointer );
  425. break;
  426. default:
  427. break;
  428. }
  429. return reachedFaces;
  430. };
  431. Parser.prototype.flushStringBuffer = function ( buffer, bufferLength ) {
  432. for ( var i = 0; i < bufferLength; i++ ) {
  433. buffer[ i ] = '';
  434. }
  435. };
  436. Parser.prototype.processCompletedObject = function ( objectName, groupName ) {
  437. var result = this.rawObject.finalize( this.debug );
  438. if ( Validator.isValid( result ) ) {
  439. this.inputObjectCount++;
  440. if ( this.debug ) this.createReport( this.inputObjectCount, true );
  441. var message = this.buildMesh( result, this.inputObjectCount );
  442. this.onProgress( message );
  443. }
  444. this.rawObject = this.rawObject.newInstanceFromObject( objectName, groupName );
  445. };
  446. Parser.prototype.processCompletedGroup = function ( groupName ) {
  447. var result = this.rawObject.finalize();
  448. if ( Validator.isValid( result ) ) {
  449. this.inputObjectCount++;
  450. if ( this.debug ) this.createReport( this.inputObjectCount, true );
  451. var message = this.buildMesh( result, this.inputObjectCount );
  452. this.onProgress( message );
  453. this.rawObject = this.rawObject.newInstanceFromGroup( groupName );
  454. } else {
  455. // if a group was set that did not lead to object creation in finalize, then the group name has to be updated
  456. this.rawObject.pushGroup( groupName );
  457. }
  458. };
  459. Parser.prototype.finalize = function () {
  460. console.log( 'Global output object count: ' + this.outputObjectCount );
  461. var result = Validator.isValid( this.rawObject ) ? this.rawObject.finalize() : null;
  462. if ( Validator.isValid( result ) ) {
  463. this.inputObjectCount++;
  464. if ( this.debug ) this.createReport( this.inputObjectCount, true );
  465. var message = this.buildMesh( result, this.inputObjectCount );
  466. this.onProgress( message );
  467. }
  468. };
  469. Parser.prototype.onProgress = function ( text ) {
  470. if ( Validator.isValid( text ) && Validator.isValid( this.callbackProgress) ) this.callbackProgress( text );
  471. };
  472. /**
  473. * RawObjectDescriptions are transformed to too intermediate format that is forwarded to the Builder.
  474. * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
  475. *
  476. * @param result
  477. * @param inputObjectCount
  478. */
  479. Parser.prototype.buildMesh = function ( result, inputObjectCount ) {
  480. if ( this.debug ) console.log( 'OBJLoader.buildMesh:\nInput object no.: ' + inputObjectCount );
  481. var rawObjectDescriptions = result.rawObjectDescriptions;
  482. var vertexFA = new Float32Array( result.absoluteVertexCount );
  483. var indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
  484. var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
  485. var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
  486. var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
  487. var rawObjectDescription;
  488. var materialDescription;
  489. var materialDescriptions = [];
  490. var createMultiMaterial = ( rawObjectDescriptions.length > 1 );
  491. var materialIndex = 0;
  492. var materialIndexMapping = [];
  493. var selectedMaterialIndex;
  494. var materialGroup;
  495. var materialGroups = [];
  496. var vertexFAOffset = 0;
  497. var vertexGroupOffset = 0;
  498. var vertexLength;
  499. var indexUAOffset = 0;
  500. var colorFAOffset = 0;
  501. var normalFAOffset = 0;
  502. var uvFAOffset = 0;
  503. for ( var oodIndex in rawObjectDescriptions ) {
  504. if ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;
  505. rawObjectDescription = rawObjectDescriptions[ oodIndex ];
  506. materialDescription = {
  507. name: rawObjectDescription.materialName,
  508. flat: false,
  509. default: false
  510. };
  511. if ( this.materialNames[ materialDescription.name ] === null ) {
  512. materialDescription.default = true;
  513. console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName + '" was defined without material! Assigning "defaultMaterial".' );
  514. }
  515. // Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0
  516. if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;
  517. vertexLength = rawObjectDescription.vertices.length;
  518. if ( createMultiMaterial ) {
  519. // re-use material if already used before. Reduces materials array size and eliminates duplicates
  520. selectedMaterialIndex = materialIndexMapping[ materialDescription.name ];
  521. if ( ! selectedMaterialIndex ) {
  522. selectedMaterialIndex = materialIndex;
  523. materialIndexMapping[ materialDescription.name ] = materialIndex;
  524. materialDescriptions.push( materialDescription );
  525. materialIndex++;
  526. }
  527. materialGroup = {
  528. start: vertexGroupOffset,
  529. count: vertexLength / 3,
  530. index: selectedMaterialIndex
  531. };
  532. materialGroups.push( materialGroup );
  533. vertexGroupOffset += vertexLength / 3;
  534. } else {
  535. materialDescriptions.push( materialDescription );
  536. }
  537. vertexFA.set( rawObjectDescription.vertices, vertexFAOffset );
  538. vertexFAOffset += vertexLength;
  539. if ( indexUA ) {
  540. indexUA.set( rawObjectDescription.indices, indexUAOffset );
  541. indexUAOffset += rawObjectDescription.indices.length;
  542. }
  543. if ( colorFA ) {
  544. colorFA.set( rawObjectDescription.colors, colorFAOffset );
  545. colorFAOffset += rawObjectDescription.colors.length;
  546. }
  547. if ( normalFA ) {
  548. normalFA.set( rawObjectDescription.normals, normalFAOffset );
  549. normalFAOffset += rawObjectDescription.normals.length;
  550. }
  551. if ( uvFA ) {
  552. uvFA.set( rawObjectDescription.uvs, uvFAOffset );
  553. uvFAOffset += rawObjectDescription.uvs.length;
  554. }
  555. if ( this.debug ) this.printReport( rawObjectDescription, selectedMaterialIndex );
  556. }
  557. this.outputObjectCount++;
  558. this.callbackBuilder(
  559. {
  560. cmd: 'meshData',
  561. params: {
  562. meshName: rawObjectDescription.groupName !== '' ? rawObjectDescription.groupName : rawObjectDescription.objectName
  563. },
  564. materials: {
  565. multiMaterial: createMultiMaterial,
  566. materialDescriptions: materialDescriptions,
  567. materialGroups: materialGroups
  568. },
  569. buffers: {
  570. vertices: vertexFA,
  571. indices: indexUA,
  572. colors: colorFA,
  573. normals: normalFA,
  574. uvs: uvFA
  575. }
  576. },
  577. [ vertexFA.buffer ],
  578. Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
  579. Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
  580. Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
  581. Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
  582. );
  583. };
  584. Parser.prototype.printReport = function ( rawObjectDescription, selectedMaterialIndex ) {
  585. var materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\n materialIndex: ' + selectedMaterialIndex : '';
  586. console.log(
  587. ' Output Object no.: ' + this.outputObjectCount +
  588. '\n objectName: ' + rawObjectDescription.objectName +
  589. '\n groupName: ' + rawObjectDescription.groupName +
  590. '\n materialName: ' + rawObjectDescription.materialName +
  591. materialIndexLine +
  592. '\n smoothingGroup: ' + rawObjectDescription.smoothingGroup +
  593. '\n #vertices: ' + rawObjectDescription.vertices.length / 3 +
  594. '\n #colors: ' + rawObjectDescription.colors.length / 3 +
  595. '\n #uvs: ' + rawObjectDescription.uvs.length / 2 +
  596. '\n #normals: ' + rawObjectDescription.normals.length / 3
  597. );
  598. };
  599. return Parser;
  600. })();
  601. /**
  602. * {@link RawObject} is only used by {@link Parser}.
  603. * The user of OBJLoader2 does not need to care about this class.
  604. * It is defined publicly for inclusion in web worker based OBJ loader ({@link THREE.OBJLoader2.WWOBJLoader2})
  605. */
  606. var RawObject = (function () {
  607. function RawObject( materialPerSmoothingGroup, objectName, groupName, activeMtlName ) {
  608. this.globalVertexOffset = 1;
  609. this.globalUvOffset = 1;
  610. this.globalNormalOffset = 1;
  611. this.vertices = [];
  612. this.colors = [];
  613. this.normals = [];
  614. this.uvs = [];
  615. // faces are stored according combined index of group, material and smoothingGroup (0 or not)
  616. this.activeMtlName = Validator.verifyInput( activeMtlName, '' );
  617. this.objectName = Validator.verifyInput( objectName, '' );
  618. this.groupName = Validator.verifyInput( groupName, '' );
  619. this.mtllibName = '';
  620. this.activeSmoothingGroup = 1;
  621. this.materialPerSmoothingGroup = materialPerSmoothingGroup;
  622. this.mtlCount = 0;
  623. this.smoothingGroupCount = 0;
  624. this.rawObjectDescriptions = [];
  625. // this default index is required as it is possible to define faces without 'g' or 'usemtl'
  626. var index = this.buildIndex( this.activeMtlName, this.activeSmoothingGroup );
  627. this.rawObjectDescriptionInUse = new RawObjectDescription( this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup );
  628. this.rawObjectDescriptions[ index ] = this.rawObjectDescriptionInUse;
  629. }
  630. RawObject.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
  631. this.materialPerSmoothingGroup = materialPerSmoothingGroup;
  632. };
  633. RawObject.prototype.buildIndex = function ( materialName, smoothingGroup ) {
  634. var normalizedSmoothingGroup = this.materialPerSmoothingGroup ? smoothingGroup : ( smoothingGroup === 0 ) ? 0 : 1;
  635. return materialName + '|' + normalizedSmoothingGroup;
  636. };
  637. RawObject.prototype.newInstanceFromObject = function ( objectName, groupName ) {
  638. var newRawObject = new RawObject( this.materialPerSmoothingGroup, objectName, groupName, this.activeMtlName );
  639. // move indices forward
  640. newRawObject.globalVertexOffset = this.globalVertexOffset + this.vertices.length / 3;
  641. newRawObject.globalUvOffset = this.globalUvOffset + this.uvs.length / 2;
  642. newRawObject.globalNormalOffset = this.globalNormalOffset + this.normals.length / 3;
  643. return newRawObject;
  644. };
  645. RawObject.prototype.newInstanceFromGroup = function ( groupName ) {
  646. var newRawObject = new RawObject( this.materialPerSmoothingGroup, this.objectName, groupName, this.activeMtlName );
  647. // keep current buffers and indices forward
  648. newRawObject.vertices = this.vertices;
  649. newRawObject.colors = this.colors;
  650. newRawObject.uvs = this.uvs;
  651. newRawObject.normals = this.normals;
  652. newRawObject.globalVertexOffset = this.globalVertexOffset;
  653. newRawObject.globalUvOffset = this.globalUvOffset;
  654. newRawObject.globalNormalOffset = this.globalNormalOffset;
  655. return newRawObject;
  656. };
  657. RawObject.prototype.pushVertex = function ( buffer ) {
  658. this.vertices.push( parseFloat( buffer[ 1 ] ) );
  659. this.vertices.push( parseFloat( buffer[ 2 ] ) );
  660. this.vertices.push( parseFloat( buffer[ 3 ] ) );
  661. };
  662. RawObject.prototype.pushVertexAndVertextColors = function ( buffer ) {
  663. this.vertices.push( parseFloat( buffer[ 1 ] ) );
  664. this.vertices.push( parseFloat( buffer[ 2 ] ) );
  665. this.vertices.push( parseFloat( buffer[ 3 ] ) );
  666. this.colors.push( parseFloat( buffer[ 4 ] ) );
  667. this.colors.push( parseFloat( buffer[ 5 ] ) );
  668. this.colors.push( parseFloat( buffer[ 6 ] ) );
  669. };
  670. RawObject.prototype.pushUv = function ( buffer ) {
  671. this.uvs.push( parseFloat( buffer[ 1 ] ) );
  672. this.uvs.push( parseFloat( buffer[ 2 ] ) );
  673. };
  674. RawObject.prototype.pushNormal = function ( buffer ) {
  675. this.normals.push( parseFloat( buffer[ 1 ] ) );
  676. this.normals.push( parseFloat( buffer[ 2 ] ) );
  677. this.normals.push( parseFloat( buffer[ 3 ] ) );
  678. };
  679. RawObject.prototype.pushObject = function ( objectName ) {
  680. this.objectName = objectName;
  681. };
  682. RawObject.prototype.pushMtllib = function ( mtllibName ) {
  683. this.mtllibName = mtllibName;
  684. };
  685. RawObject.prototype.pushGroup = function ( groupName ) {
  686. this.groupName = groupName;
  687. this.verifyIndex();
  688. };
  689. RawObject.prototype.pushUsemtl = function ( mtlName ) {
  690. if ( this.activeMtlName === mtlName || ! Validator.isValid( mtlName ) ) return;
  691. this.activeMtlName = mtlName;
  692. this.mtlCount++;
  693. this.verifyIndex();
  694. };
  695. RawObject.prototype.pushSmoothingGroup = function ( activeSmoothingGroup ) {
  696. var normalized = parseInt( activeSmoothingGroup );
  697. if ( isNaN( normalized ) ) {
  698. normalized = activeSmoothingGroup === "off" ? 0 : 1;
  699. }
  700. if ( this.activeSmoothingGroup === normalized ) return;
  701. this.activeSmoothingGroup = normalized;
  702. this.smoothingGroupCount++;
  703. this.verifyIndex();
  704. };
  705. RawObject.prototype.verifyIndex = function () {
  706. var index = this.buildIndex( this.activeMtlName, this.activeSmoothingGroup );
  707. this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ];
  708. if ( ! Validator.isValid( this.rawObjectDescriptionInUse ) ) {
  709. this.rawObjectDescriptionInUse = new RawObjectDescription( this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup );
  710. this.rawObjectDescriptions[ index ] = this.rawObjectDescriptionInUse;
  711. }
  712. };
  713. RawObject.prototype.processFaces = function ( buffer, bufferPointer, slashesCount ) {
  714. var bufferLength = bufferPointer - 1;
  715. var i, length;
  716. // "f vertex ..."
  717. if ( slashesCount === 0 ) {
  718. for ( i = 2, length = bufferLength - 1; i < length; i ++ ) {
  719. this.buildFace( buffer[ 1 ] );
  720. this.buildFace( buffer[ i ] );
  721. this.buildFace( buffer[ i + 1 ] );
  722. }
  723. // "f vertex/uv ..."
  724. } else if ( bufferLength === slashesCount * 2 ) {
  725. for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
  726. this.buildFace( buffer[ 1 ], buffer[ 2 ] );
  727. this.buildFace( buffer[ i ], buffer[ i + 1 ] );
  728. this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
  729. }
  730. // "f vertex/uv/normal ..."
  731. } else if ( bufferLength * 2 === slashesCount * 3 ) {
  732. for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
  733. this.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
  734. this.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
  735. this.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
  736. }
  737. // "f vertex//normal ..."
  738. } else {
  739. for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
  740. this.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
  741. this.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
  742. this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
  743. }
  744. }
  745. };
  746. RawObject.prototype.buildFace = function ( faceIndexV, faceIndexU, faceIndexN ) {
  747. var indexV = ( parseInt( faceIndexV ) - this.globalVertexOffset ) * 3;
  748. var vertices = this.rawObjectDescriptionInUse.vertices;
  749. vertices.push( this.vertices[ indexV ++ ] );
  750. vertices.push( this.vertices[ indexV ++ ] );
  751. vertices.push( this.vertices[ indexV ] );
  752. if ( this.colors.length > 0 ) {
  753. indexV -= 2;
  754. var colors = this.rawObjectDescriptionInUse.colors;
  755. colors.push( this.colors[ indexV ++ ] );
  756. colors.push( this.colors[ indexV ++ ] );
  757. colors.push( this.colors[ indexV ] );
  758. }
  759. if ( faceIndexU ) {
  760. var indexU = ( parseInt( faceIndexU ) - this.globalUvOffset ) * 2;
  761. var uvs = this.rawObjectDescriptionInUse.uvs;
  762. uvs.push( this.uvs[ indexU ++ ] );
  763. uvs.push( this.uvs[ indexU ] );
  764. }
  765. if ( faceIndexN ) {
  766. var indexN = ( parseInt( faceIndexN ) - this.globalNormalOffset ) * 3;
  767. var normals = this.rawObjectDescriptionInUse.normals;
  768. normals.push( this.normals[ indexN ++ ] );
  769. normals.push( this.normals[ indexN ++ ] );
  770. normals.push( this.normals[ indexN ] );
  771. }
  772. };
  773. /*
  774. * Support for lines with or without texture. irst element in indexArray is the line identification
  775. * 0: "f vertex/uv vertex/uv ..."
  776. * 1: "f vertex vertex ..."
  777. */
  778. RawObject.prototype.buildLineVvt = function ( lineArray ) {
  779. for ( var i = 1, length = lineArray.length; i < length; i ++ ) {
  780. this.vertices.push( parseInt( lineArray[ i ] ) );
  781. this.uvs.push( parseInt( lineArray[ i ] ) );
  782. }
  783. };
  784. RawObject.prototype.buildLineV = function ( lineArray ) {
  785. for ( var i = 1, length = lineArray.length; i < length; i++ ) {
  786. this.vertices.push( parseInt( lineArray[ i ] ) );
  787. }
  788. };
  789. /**
  790. * Clear any empty rawObjectDescription and calculate absolute vertex, normal and uv counts
  791. */
  792. RawObject.prototype.finalize = function () {
  793. var temp = [];
  794. var rawObjectDescription;
  795. var absoluteVertexCount = 0;
  796. var absoluteIndexCount = 0;
  797. var absoluteColorCount = 0;
  798. var absoluteNormalCount = 0;
  799. var absoluteUvCount = 0;
  800. for ( var name in this.rawObjectDescriptions ) {
  801. rawObjectDescription = this.rawObjectDescriptions[ name ];
  802. if ( rawObjectDescription.vertices.length > 0 ) {
  803. temp.push( rawObjectDescription );
  804. absoluteVertexCount += rawObjectDescription.vertices.length;
  805. absoluteIndexCount += rawObjectDescription.indices.length;
  806. absoluteColorCount += rawObjectDescription.colors.length;
  807. absoluteUvCount += rawObjectDescription.uvs.length;
  808. absoluteNormalCount += rawObjectDescription.normals.length;
  809. }
  810. }
  811. // don not continue if no result
  812. var result = null;
  813. if ( temp.length > 0 ) {
  814. result = {
  815. rawObjectDescriptions: temp,
  816. absoluteVertexCount: absoluteVertexCount,
  817. absoluteIndexCount: absoluteIndexCount,
  818. absoluteColorCount: absoluteColorCount,
  819. absoluteNormalCount: absoluteNormalCount,
  820. absoluteUvCount: absoluteUvCount
  821. };
  822. }
  823. return result;
  824. };
  825. RawObject.prototype.createReport = function ( inputObjectCount, printDirectly ) {
  826. var report = {
  827. name: this.objectName ? this.objectName : 'groups',
  828. mtllibName: this.mtllibName,
  829. vertexCount: this.vertices.length / 3,
  830. indexCount: this.indices.length,
  831. normalCount: this.normals.length / 3,
  832. uvCount: this.uvs.length / 2,
  833. smoothingGroupCount: this.smoothingGroupCount,
  834. mtlCount: this.mtlCount,
  835. rawObjectDescriptions: this.rawObjectDescriptions.length
  836. };
  837. if ( printDirectly ) {
  838. console.log( 'Input Object number: ' + inputObjectCount + ' Object name: ' + report.name );
  839. console.log( 'Mtllib name: ' + report.mtllibName );
  840. console.log( 'Vertex count: ' + report.vertexCount );
  841. console.log( 'Index count: ' + report.indexCount );
  842. console.log( 'Normal count: ' + report.normalCount );
  843. console.log( 'UV count: ' + report.uvCount );
  844. console.log( 'SmoothingGroup count: ' + report.smoothingGroupCount );
  845. console.log( 'Material count: ' + report.mtlCount );
  846. console.log( 'Real RawObjectDescription count: ' + report.rawObjectDescriptions );
  847. console.log( '' );
  848. }
  849. return report;
  850. };
  851. return RawObject;
  852. })();
  853. /**
  854. * Descriptive information and data (vertices, normals, uvs) to passed on to mesh building function.
  855. * @class
  856. *
  857. * @param {string} objectName Name of the mesh
  858. * @param {string} groupName Name of the group
  859. * @param {string} materialName Name of the material
  860. * @param {number} smoothingGroup Normalized smoothingGroup (0: flat shading, 1: smooth shading)
  861. */
  862. var RawObjectDescription = (function () {
  863. function RawObjectDescription( objectName, groupName, materialName, smoothingGroup ) {
  864. this.objectName = objectName;
  865. this.groupName = groupName;
  866. this.materialName = materialName;
  867. this.smoothingGroup = smoothingGroup;
  868. this.vertices = [];
  869. this.indices = [];
  870. this.colors = [];
  871. this.uvs = [];
  872. this.normals = [];
  873. }
  874. return RawObjectDescription;
  875. })();
  876. OBJLoader2.prototype._checkFiles = function ( resources ) {
  877. var resource;
  878. var result = {
  879. mtl: null,
  880. obj: null
  881. };
  882. for ( var index in resources ) {
  883. resource = resources[ index ];
  884. if ( ! Validator.isValid( resource.name ) ) continue;
  885. if ( Validator.isValid( resource.content ) ) {
  886. if ( resource.extension === 'OBJ' ) {
  887. // fast-fail on bad type
  888. if ( ! ( resource.content instanceof Uint8Array ) ) throw 'Provided content is not of type arraybuffer! Aborting...';
  889. result.obj = resource;
  890. } else if ( resource.extension === 'MTL' && Validator.isValid( resource.name ) ) {
  891. if ( ! ( typeof( resource.content ) === 'string' || resource.content instanceof String ) ) throw 'Provided content is not of type String! Aborting...';
  892. result.mtl = resource;
  893. } else if ( resource.extension === "ZIP" ) {
  894. // ignore
  895. } else {
  896. throw 'Unidentified resource "' + resource.name + '": ' + resource.url;
  897. }
  898. } else {
  899. // fast-fail on bad type
  900. if ( ! ( typeof( resource.name ) === 'string' || resource.name instanceof String ) ) throw 'Provided file is not properly defined! Aborting...';
  901. if ( resource.extension === 'OBJ' ) {
  902. result.obj = resource;
  903. } else if ( resource.extension === 'MTL' ) {
  904. result.mtl = resource;
  905. } else if ( resource.extension === "ZIP" ) {
  906. // ignore
  907. } else {
  908. throw 'Unidentified resource "' + resource.name + '": ' + resource.url;
  909. }
  910. }
  911. }
  912. return result;
  913. };
  914. /**
  915. * Utility method for loading an mtl file according resource description.
  916. * @memberOf THREE.OBJLoader2
  917. *
  918. * @param {string} url URL to the file
  919. * @param {string} name The name of the object
  920. * @param {Object} content The file content as arraybuffer or text
  921. * @param {function} callbackOnLoad
  922. * @param {string} [crossOrigin] CORS value
  923. */
  924. OBJLoader2.prototype.loadMtl = function ( url, name, content, callbackOnLoad, crossOrigin ) {
  925. var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );
  926. resource.setContent( content );
  927. this._loadMtl( resource, callbackOnLoad, crossOrigin );
  928. };
  929. /**
  930. * Utility method for loading an mtl file according resource description.
  931. * @memberOf THREE.OBJLoader2
  932. *
  933. * @param {THREE.LoaderSupport.ResourceDescriptor} resource
  934. * @param {function} callbackOnLoad
  935. * @param {string} [crossOrigin] CORS value
  936. */
  937. OBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {
  938. if ( Validator.isValid( resource ) ) console.time( 'Loading MTL: ' + resource.name );
  939. var materials = [];
  940. var processMaterials = function ( materialCreator ) {
  941. var materialCreatorMaterials = [];
  942. if ( Validator.isValid( materialCreator ) ) {
  943. materialCreator.preload();
  944. materialCreatorMaterials = materialCreator.materials;
  945. for ( var materialName in materialCreatorMaterials ) {
  946. if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
  947. materials[ materialName ] = materialCreatorMaterials[ materialName ];
  948. }
  949. }
  950. }
  951. if ( Validator.isValid( resource ) ) console.timeEnd( 'Loading MTL: ' + resource.name );
  952. callbackOnLoad( materials );
  953. };
  954. var mtlLoader = new THREE.MTLLoader();
  955. crossOrigin = Validator.verifyInput( crossOrigin, 'anonymous' );
  956. mtlLoader.setCrossOrigin( crossOrigin );
  957. // fast-fail
  958. if ( ! Validator.isValid( resource ) || ( ! Validator.isValid( resource.content ) && ! Validator.isValid( resource.url ) ) ) {
  959. processMaterials();
  960. } else {
  961. mtlLoader.setPath( resource.path );
  962. if ( Validator.isValid( resource.content ) ) {
  963. processMaterials( Validator.isValid( resource.content ) ? mtlLoader.parse( resource.content ) : null );
  964. } else if ( Validator.isValid( resource.url ) ) {
  965. var onError = function ( event ) {
  966. var output = 'Error occurred while downloading "' + resource.url + '"';
  967. console.error( output + ': ' + event );
  968. throw output;
  969. };
  970. mtlLoader.load( resource.name, processMaterials, undefined, onError );
  971. }
  972. }
  973. };
  974. return OBJLoader2;
  975. })();