OBJLoader2Parser.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124
  1. /**
  2. * @author Kai Salmen / https://kaisalmen.de
  3. * Development repository: https://github.com/kaisalmen/WWOBJLoader
  4. */
  5. /**
  6. * Parse OBJ data either from ArrayBuffer or string
  7. */
  8. const OBJLoader2Parser = function () {
  9. this.logging = {
  10. enabled: false,
  11. debug: false
  12. };
  13. let scope = this;
  14. this.callbacks = {
  15. onProgress: function ( text ) {
  16. scope._onProgress( text );
  17. },
  18. onAssetAvailable: function ( payload ) {
  19. scope._onAssetAvailable( payload );
  20. },
  21. onError: function ( errorMessage ) {
  22. scope._onError( errorMessage );
  23. },
  24. onLoad: function ( object3d, message ) {
  25. scope._onLoad( object3d, message );
  26. },
  27. };
  28. this.contentRef = null;
  29. this.legacyMode = false;
  30. this.materials = {};
  31. this.materialPerSmoothingGroup = false;
  32. this.useOAsMesh = false;
  33. this.useIndices = false;
  34. this.disregardNormals = false;
  35. this.vertices = [];
  36. this.colors = [];
  37. this.normals = [];
  38. this.uvs = [];
  39. this.rawMesh = {
  40. objectName: '',
  41. groupName: '',
  42. activeMtlName: '',
  43. mtllibName: '',
  44. // reset with new mesh
  45. faceType: - 1,
  46. subGroups: [],
  47. subGroupInUse: null,
  48. smoothingGroup: {
  49. splitMaterials: false,
  50. normalized: - 1,
  51. real: - 1
  52. },
  53. counts: {
  54. doubleIndicesCount: 0,
  55. faceCount: 0,
  56. mtlCount: 0,
  57. smoothingGroupCount: 0
  58. }
  59. };
  60. this.inputObjectCount = 1;
  61. this.outputObjectCount = 1;
  62. this.globalCounts = {
  63. vertices: 0,
  64. faces: 0,
  65. doubleIndicesCount: 0,
  66. lineByte: 0,
  67. currentByte: 0,
  68. totalBytes: 0
  69. };
  70. };
  71. OBJLoader2Parser.prototype = {
  72. constructor: OBJLoader2Parser,
  73. _resetRawMesh: function () {
  74. // faces are stored according combined index of group, material and smoothingGroup (0 or not)
  75. this.rawMesh.subGroups = [];
  76. this.rawMesh.subGroupInUse = null;
  77. this.rawMesh.smoothingGroup.normalized = - 1;
  78. this.rawMesh.smoothingGroup.real = - 1;
  79. // this default index is required as it is possible to define faces without 'g' or 'usemtl'
  80. this._pushSmoothingGroup( 1 );
  81. this.rawMesh.counts.doubleIndicesCount = 0;
  82. this.rawMesh.counts.faceCount = 0;
  83. this.rawMesh.counts.mtlCount = 0;
  84. this.rawMesh.counts.smoothingGroupCount = 0;
  85. },
  86. /**
  87. * Tells whether a material shall be created per smoothing group.
  88. *
  89. * @param {boolean} materialPerSmoothingGroup=false
  90. * @return {OBJLoader2Parser}
  91. */
  92. setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) {
  93. this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
  94. return this;
  95. },
  96. /**
  97. * Usually 'o' is meta-information and does not result in creation of new meshes, but mesh creation on occurrence of "o" can be enforced.
  98. *
  99. * @param {boolean} useOAsMesh=false
  100. * @return {OBJLoader2Parser}
  101. */
  102. setUseOAsMesh: function ( useOAsMesh ) {
  103. this.useOAsMesh = useOAsMesh === true;
  104. return this;
  105. },
  106. /**
  107. * Instructs loaders to create indexed {@link BufferGeometry}.
  108. *
  109. * @param {boolean} useIndices=false
  110. * @return {OBJLoader2Parser}
  111. */
  112. setUseIndices: function ( useIndices ) {
  113. this.useIndices = useIndices === true;
  114. return this;
  115. },
  116. /**
  117. * Tells whether normals should be completely disregarded and regenerated.
  118. *
  119. * @param {boolean} disregardNormals=false
  120. * @return {OBJLoader2Parser}
  121. */
  122. setDisregardNormals: function ( disregardNormals ) {
  123. this.disregardNormals = disregardNormals === true;
  124. return this;
  125. },
  126. /**
  127. * Clears materials object and sets the new ones.
  128. *
  129. * @param {Object} materials Object with named materials
  130. */
  131. setMaterials: function ( materials ) {
  132. this.materials = Object.assign( {}, materials );
  133. },
  134. /**
  135. * Register a function that is called once an asset (mesh/material) becomes available.
  136. *
  137. * @param onAssetAvailable
  138. * @return {OBJLoader2Parser}
  139. */
  140. setCallbackOnAssetAvailable: function ( onAssetAvailable ) {
  141. if ( onAssetAvailable !== null && onAssetAvailable !== undefined && onAssetAvailable instanceof Function ) {
  142. this.callbacks.onAssetAvailable = onAssetAvailable;
  143. }
  144. return this;
  145. },
  146. /**
  147. * Register a function that is used to report overall processing progress.
  148. *
  149. * @param {Function} onProgress
  150. * @return {OBJLoader2Parser}
  151. */
  152. setCallbackOnProgress: function ( onProgress ) {
  153. if ( onProgress !== null && onProgress !== undefined && onProgress instanceof Function ) {
  154. this.callbacks.onProgress = onProgress;
  155. }
  156. return this;
  157. },
  158. /**
  159. * Register an error handler function that is called if errors occur. It can decide to just log or to throw an exception.
  160. *
  161. * @param {Function} onError
  162. * @return {OBJLoader2Parser}
  163. */
  164. setCallbackOnError: function ( onError ) {
  165. if ( onError !== null && onError !== undefined && onError instanceof Function ) {
  166. this.callbacks.onError = onError;
  167. }
  168. return this;
  169. },
  170. /**
  171. * Register a function that is called when parsing was completed.
  172. *
  173. * @param {Function} onLoad
  174. * @return {OBJLoader2Parser}
  175. */
  176. setCallbackOnLoad: function ( onLoad ) {
  177. if ( onLoad !== null && onLoad !== undefined && onLoad instanceof Function ) {
  178. this.callbacks.onLoad = onLoad;
  179. }
  180. return this;
  181. },
  182. /**
  183. * Announce parse progress feedback which is logged to the console.
  184. * @private
  185. *
  186. * @param {string} text Textual description of the event
  187. */
  188. _onProgress: function ( text ) {
  189. let message = text ? text : '';
  190. if ( this.logging.enabled && this.logging.debug ) {
  191. console.log( message );
  192. }
  193. },
  194. /**
  195. * Announce error feedback which is logged as error message.
  196. * @private
  197. *
  198. * @param {String} errorMessage The event containing the error
  199. */
  200. _onError: function ( errorMessage ) {
  201. if ( this.logging.enabled && this.logging.debug ) {
  202. console.error( errorMessage );
  203. }
  204. },
  205. _onAssetAvailable: function ( payload ) {
  206. let errorMessage = 'OBJLoader2Parser does not provide implementation for onAssetAvailable. Aborting...';
  207. this.callbacks.onError( errorMessage );
  208. throw errorMessage;
  209. },
  210. _onLoad: function ( object3d, message ) {
  211. console.log( "You reached parser default onLoad callback: " + message );
  212. },
  213. /**
  214. * Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
  215. *
  216. * @param {boolean} enabled True or false.
  217. * @param {boolean} debug True or false.
  218. *
  219. * @return {OBJLoader2Parser}
  220. */
  221. setLogging: function ( enabled, debug ) {
  222. this.logging.enabled = enabled === true;
  223. this.logging.debug = debug === true;
  224. return this;
  225. },
  226. _configure: function () {
  227. this._pushSmoothingGroup( 1 );
  228. if ( this.logging.enabled ) {
  229. let matKeys = Object.keys( this.materials );
  230. let matNames = ( matKeys.length > 0 ) ? '\n\tmaterialNames:\n\t\t- ' + matKeys.join( '\n\t\t- ' ) : '\n\tmaterialNames: None';
  231. let printedConfig = 'OBJLoader.Parser configuration:'
  232. + matNames
  233. + '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup
  234. + '\n\tuseOAsMesh: ' + this.useOAsMesh
  235. + '\n\tuseIndices: ' + this.useIndices
  236. + '\n\tdisregardNormals: ' + this.disregardNormals;
  237. printedConfig += '\n\tcallbacks.onProgress: ' + this.callbacks.onProgress.name;
  238. printedConfig += '\n\tcallbacks.onAssetAvailable: ' + this.callbacks.onAssetAvailable.name;
  239. printedConfig += '\n\tcallbacks.onError: ' + this.callbacks.onError.name;
  240. console.info( printedConfig );
  241. }
  242. },
  243. /**
  244. * Parse the provided arraybuffer
  245. *
  246. * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
  247. */
  248. execute: function ( arrayBuffer ) {
  249. if ( this.logging.enabled ) console.time( 'OBJLoader2Parser.execute' );
  250. this._configure();
  251. let arrayBufferView = new Uint8Array( arrayBuffer );
  252. this.contentRef = arrayBufferView;
  253. let length = arrayBufferView.byteLength;
  254. this.globalCounts.totalBytes = length;
  255. let buffer = new Array( 128 );
  256. let bufferPointer = 0;
  257. let slashesCount = 0;
  258. let word = '';
  259. let currentByte = 0;
  260. for ( let code, currentByte = 0; currentByte < length; currentByte ++ ) {
  261. code = arrayBufferView[ currentByte ];
  262. switch ( code ) {
  263. // space
  264. case 32:
  265. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  266. word = '';
  267. break;
  268. // slash
  269. case 47:
  270. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  271. slashesCount ++;
  272. word = '';
  273. break;
  274. // LF
  275. case 10:
  276. this._processLine( buffer, bufferPointer, slashesCount, word, currentByte );
  277. word = '';
  278. bufferPointer = 0;
  279. slashesCount = 0;
  280. break;
  281. // CR
  282. case 13:
  283. break;
  284. default:
  285. word += String.fromCharCode( code );
  286. break;
  287. }
  288. }
  289. this._processLine( buffer, bufferPointer, slashesCount, word, currentByte );
  290. this._finalizeParsing();
  291. if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.execute' );
  292. },
  293. /**
  294. * Parse the provided text
  295. *
  296. * @param {string} text OBJ data as string
  297. */
  298. executeLegacy: function ( text ) {
  299. if ( this.logging.enabled ) console.time( 'OBJLoader2Parser.executeLegacy' );
  300. this._configure();
  301. this.legacyMode = true;
  302. this.contentRef = text;
  303. let length = text.length;
  304. this.globalCounts.totalBytes = length;
  305. let buffer = new Array( 128 );
  306. let bufferPointer = 0;
  307. let slashesCount = 0;
  308. let word = '';
  309. let currentByte = 0;
  310. for ( let char; currentByte < length; currentByte ++ ) {
  311. char = text[ currentByte ];
  312. switch ( char ) {
  313. case ' ':
  314. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  315. word = '';
  316. break;
  317. case '/':
  318. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  319. slashesCount ++;
  320. word = '';
  321. break;
  322. case '\n':
  323. this._processLine( buffer, bufferPointer, slashesCount, word, currentByte );
  324. word = '';
  325. bufferPointer = 0;
  326. slashesCount = 0;
  327. break;
  328. case '\r':
  329. break;
  330. default:
  331. word += char;
  332. }
  333. }
  334. this._processLine( buffer, bufferPointer, word, slashesCount );
  335. this._finalizeParsing();
  336. if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.executeLegacy' );
  337. },
  338. _processLine: function ( buffer, bufferPointer, slashesCount, word, currentByte ) {
  339. this.globalCounts.lineByte = this.globalCounts.currentByte;
  340. this.globalCounts.currentByte = currentByte;
  341. if ( bufferPointer < 1 ) return;
  342. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  343. let reconstructString = function ( content, legacyMode, start, stop ) {
  344. let line = '';
  345. if ( stop > start ) {
  346. let i;
  347. if ( legacyMode ) {
  348. for ( i = start; i < stop; i ++ ) line += content[ i ];
  349. } else {
  350. for ( i = start; i < stop; i ++ ) line += String.fromCharCode( content[ i ] );
  351. }
  352. line = line.trim();
  353. }
  354. return line;
  355. };
  356. let bufferLength, length, i, lineDesignation;
  357. lineDesignation = buffer[ 0 ];
  358. switch ( lineDesignation ) {
  359. case 'v':
  360. this.vertices.push( parseFloat( buffer[ 1 ] ) );
  361. this.vertices.push( parseFloat( buffer[ 2 ] ) );
  362. this.vertices.push( parseFloat( buffer[ 3 ] ) );
  363. if ( bufferPointer > 4 ) {
  364. this.colors.push( parseFloat( buffer[ 4 ] ) );
  365. this.colors.push( parseFloat( buffer[ 5 ] ) );
  366. this.colors.push( parseFloat( buffer[ 6 ] ) );
  367. }
  368. break;
  369. case 'vt':
  370. this.uvs.push( parseFloat( buffer[ 1 ] ) );
  371. this.uvs.push( parseFloat( buffer[ 2 ] ) );
  372. break;
  373. case 'vn':
  374. this.normals.push( parseFloat( buffer[ 1 ] ) );
  375. this.normals.push( parseFloat( buffer[ 2 ] ) );
  376. this.normals.push( parseFloat( buffer[ 3 ] ) );
  377. break;
  378. case 'f':
  379. bufferLength = bufferPointer - 1;
  380. // "f vertex ..."
  381. if ( slashesCount === 0 ) {
  382. this._checkFaceType( 0 );
  383. for ( i = 2, length = bufferLength; i < length; i ++ ) {
  384. this._buildFace( buffer[ 1 ] );
  385. this._buildFace( buffer[ i ] );
  386. this._buildFace( buffer[ i + 1 ] );
  387. }
  388. // "f vertex/uv ..."
  389. } else if ( bufferLength === slashesCount * 2 ) {
  390. this._checkFaceType( 1 );
  391. for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
  392. this._buildFace( buffer[ 1 ], buffer[ 2 ] );
  393. this._buildFace( buffer[ i ], buffer[ i + 1 ] );
  394. this._buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
  395. }
  396. // "f vertex/uv/normal ..."
  397. } else if ( bufferLength * 2 === slashesCount * 3 ) {
  398. this._checkFaceType( 2 );
  399. for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
  400. this._buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
  401. this._buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
  402. this._buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
  403. }
  404. // "f vertex//normal ..."
  405. } else {
  406. this._checkFaceType( 3 );
  407. for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
  408. this._buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
  409. this._buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
  410. this._buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
  411. }
  412. }
  413. break;
  414. case 'l':
  415. case 'p':
  416. bufferLength = bufferPointer - 1;
  417. if ( bufferLength === slashesCount * 2 ) {
  418. this._checkFaceType( 4 );
  419. for ( i = 1, length = bufferLength + 1; i < length; i += 2 ) this._buildFace( buffer[ i ], buffer[ i + 1 ] );
  420. } else {
  421. this._checkFaceType( ( lineDesignation === 'l' ) ? 5 : 6 );
  422. for ( i = 1, length = bufferLength + 1; i < length; i ++ ) this._buildFace( buffer[ i ] );
  423. }
  424. break;
  425. case 's':
  426. this._pushSmoothingGroup( buffer[ 1 ] );
  427. break;
  428. case 'g':
  429. // 'g' leads to creation of mesh if valid data (faces declaration was done before), otherwise only groupName gets set
  430. this._processCompletedMesh();
  431. this.rawMesh.groupName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
  432. break;
  433. case 'o':
  434. // 'o' is meta-information and usually does not result in creation of new meshes, but can be enforced with "useOAsMesh"
  435. if ( this.useOAsMesh ) this._processCompletedMesh();
  436. this.rawMesh.objectName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
  437. break;
  438. case 'mtllib':
  439. this.rawMesh.mtllibName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
  440. break;
  441. case 'usemtl':
  442. let mtlName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
  443. if ( mtlName !== '' && this.rawMesh.activeMtlName !== mtlName ) {
  444. this.rawMesh.activeMtlName = mtlName;
  445. this.rawMesh.counts.mtlCount ++;
  446. this._checkSubGroup();
  447. }
  448. break;
  449. default:
  450. break;
  451. }
  452. },
  453. _pushSmoothingGroup: function ( smoothingGroup ) {
  454. let smoothingGroupInt = parseInt( smoothingGroup );
  455. if ( isNaN( smoothingGroupInt ) ) {
  456. smoothingGroupInt = smoothingGroup === "off" ? 0 : 1;
  457. }
  458. let smoothCheck = this.rawMesh.smoothingGroup.normalized;
  459. this.rawMesh.smoothingGroup.normalized = this.rawMesh.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;
  460. this.rawMesh.smoothingGroup.real = smoothingGroupInt;
  461. if ( smoothCheck !== smoothingGroupInt ) {
  462. this.rawMesh.counts.smoothingGroupCount ++;
  463. this._checkSubGroup();
  464. }
  465. },
  466. /**
  467. * Expanded faceTypes include all four face types, both line types and the point type
  468. * faceType = 0: "f vertex ..."
  469. * faceType = 1: "f vertex/uv ..."
  470. * faceType = 2: "f vertex/uv/normal ..."
  471. * faceType = 3: "f vertex//normal ..."
  472. * faceType = 4: "l vertex/uv ..." or "l vertex ..."
  473. * faceType = 5: "l vertex ..."
  474. * faceType = 6: "p vertex ..."
  475. */
  476. _checkFaceType: function ( faceType ) {
  477. if ( this.rawMesh.faceType !== faceType ) {
  478. this._processCompletedMesh();
  479. this.rawMesh.faceType = faceType;
  480. this._checkSubGroup();
  481. }
  482. },
  483. _checkSubGroup: function () {
  484. let index = this.rawMesh.activeMtlName + '|' + this.rawMesh.smoothingGroup.normalized;
  485. this.rawMesh.subGroupInUse = this.rawMesh.subGroups[ index ];
  486. if ( this.rawMesh.subGroupInUse === undefined || this.rawMesh.subGroupInUse === null ) {
  487. this.rawMesh.subGroupInUse = {
  488. index: index,
  489. objectName: this.rawMesh.objectName,
  490. groupName: this.rawMesh.groupName,
  491. materialName: this.rawMesh.activeMtlName,
  492. smoothingGroup: this.rawMesh.smoothingGroup.normalized,
  493. vertices: [],
  494. indexMappingsCount: 0,
  495. indexMappings: [],
  496. indices: [],
  497. colors: [],
  498. uvs: [],
  499. normals: []
  500. };
  501. this.rawMesh.subGroups[ index ] = this.rawMesh.subGroupInUse;
  502. }
  503. },
  504. _buildFace: function ( faceIndexV, faceIndexU, faceIndexN ) {
  505. let subGroupInUse = this.rawMesh.subGroupInUse;
  506. let scope = this;
  507. let updateSubGroupInUse = function () {
  508. let faceIndexVi = parseInt( faceIndexV );
  509. let indexPointerV = 3 * ( faceIndexVi > 0 ? faceIndexVi - 1 : faceIndexVi + scope.vertices.length / 3 );
  510. let indexPointerC = scope.colors.length > 0 ? indexPointerV : null;
  511. let vertices = subGroupInUse.vertices;
  512. vertices.push( scope.vertices[ indexPointerV ++ ] );
  513. vertices.push( scope.vertices[ indexPointerV ++ ] );
  514. vertices.push( scope.vertices[ indexPointerV ] );
  515. if ( indexPointerC !== null ) {
  516. let colors = subGroupInUse.colors;
  517. colors.push( scope.colors[ indexPointerC ++ ] );
  518. colors.push( scope.colors[ indexPointerC ++ ] );
  519. colors.push( scope.colors[ indexPointerC ] );
  520. }
  521. if ( faceIndexU ) {
  522. let faceIndexUi = parseInt( faceIndexU );
  523. let indexPointerU = 2 * ( faceIndexUi > 0 ? faceIndexUi - 1 : faceIndexUi + scope.uvs.length / 2 );
  524. let uvs = subGroupInUse.uvs;
  525. uvs.push( scope.uvs[ indexPointerU ++ ] );
  526. uvs.push( scope.uvs[ indexPointerU ] );
  527. }
  528. if ( faceIndexN && ! scope.disregardNormals ) {
  529. let faceIndexNi = parseInt( faceIndexN );
  530. let indexPointerN = 3 * ( faceIndexNi > 0 ? faceIndexNi - 1 : faceIndexNi + scope.normals.length / 3 );
  531. let normals = subGroupInUse.normals;
  532. normals.push( scope.normals[ indexPointerN ++ ] );
  533. normals.push( scope.normals[ indexPointerN ++ ] );
  534. normals.push( scope.normals[ indexPointerN ] );
  535. }
  536. };
  537. if ( this.useIndices ) {
  538. if ( this.disregardNormals ) faceIndexN = undefined;
  539. let mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
  540. let indicesPointer = subGroupInUse.indexMappings[ mappingName ];
  541. if ( indicesPointer === undefined || indicesPointer === null ) {
  542. indicesPointer = this.rawMesh.subGroupInUse.vertices.length / 3;
  543. updateSubGroupInUse();
  544. subGroupInUse.indexMappings[ mappingName ] = indicesPointer;
  545. subGroupInUse.indexMappingsCount ++;
  546. } else {
  547. this.rawMesh.counts.doubleIndicesCount ++;
  548. }
  549. subGroupInUse.indices.push( indicesPointer );
  550. } else {
  551. updateSubGroupInUse();
  552. }
  553. this.rawMesh.counts.faceCount ++;
  554. },
  555. _createRawMeshReport: function ( inputObjectCount ) {
  556. return 'Input Object number: ' + inputObjectCount +
  557. '\n\tObject name: ' + this.rawMesh.objectName +
  558. '\n\tGroup name: ' + this.rawMesh.groupName +
  559. '\n\tMtllib name: ' + this.rawMesh.mtllibName +
  560. '\n\tVertex count: ' + this.vertices.length / 3 +
  561. '\n\tNormal count: ' + this.normals.length / 3 +
  562. '\n\tUV count: ' + this.uvs.length / 2 +
  563. '\n\tSmoothingGroup count: ' + this.rawMesh.counts.smoothingGroupCount +
  564. '\n\tMaterial count: ' + this.rawMesh.counts.mtlCount +
  565. '\n\tReal MeshOutputGroup count: ' + this.rawMesh.subGroups.length;
  566. },
  567. /**
  568. * Clear any empty subGroup and calculate absolute vertex, normal and uv counts
  569. */
  570. _finalizeRawMesh: function () {
  571. let meshOutputGroupTemp = [];
  572. let meshOutputGroup;
  573. let absoluteVertexCount = 0;
  574. let absoluteIndexMappingsCount = 0;
  575. let absoluteIndexCount = 0;
  576. let absoluteColorCount = 0;
  577. let absoluteNormalCount = 0;
  578. let absoluteUvCount = 0;
  579. let indices;
  580. for ( let name in this.rawMesh.subGroups ) {
  581. meshOutputGroup = this.rawMesh.subGroups[ name ];
  582. if ( meshOutputGroup.vertices.length > 0 ) {
  583. indices = meshOutputGroup.indices;
  584. if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {
  585. for ( let i = 0; i < indices.length; i ++ ) {
  586. indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;
  587. }
  588. }
  589. meshOutputGroupTemp.push( meshOutputGroup );
  590. absoluteVertexCount += meshOutputGroup.vertices.length;
  591. absoluteIndexMappingsCount += meshOutputGroup.indexMappingsCount;
  592. absoluteIndexCount += meshOutputGroup.indices.length;
  593. absoluteColorCount += meshOutputGroup.colors.length;
  594. absoluteUvCount += meshOutputGroup.uvs.length;
  595. absoluteNormalCount += meshOutputGroup.normals.length;
  596. }
  597. }
  598. // do not continue if no result
  599. let result = null;
  600. if ( meshOutputGroupTemp.length > 0 ) {
  601. result = {
  602. name: this.rawMesh.groupName !== '' ? this.rawMesh.groupName : this.rawMesh.objectName,
  603. subGroups: meshOutputGroupTemp,
  604. absoluteVertexCount: absoluteVertexCount,
  605. absoluteIndexCount: absoluteIndexCount,
  606. absoluteColorCount: absoluteColorCount,
  607. absoluteNormalCount: absoluteNormalCount,
  608. absoluteUvCount: absoluteUvCount,
  609. faceCount: this.rawMesh.counts.faceCount,
  610. doubleIndicesCount: this.rawMesh.counts.doubleIndicesCount
  611. };
  612. }
  613. return result;
  614. },
  615. _processCompletedMesh: function () {
  616. let result = this._finalizeRawMesh();
  617. let haveMesh = result !== null;
  618. if ( haveMesh ) {
  619. if ( this.colors.length > 0 && this.colors.length !== this.vertices.length ) {
  620. this.callbacks.onError( 'Vertex Colors were detected, but vertex count and color count do not match!' );
  621. }
  622. if ( this.logging.enabled && this.logging.debug ) console.debug( this._createRawMeshReport( this.inputObjectCount ) );
  623. this.inputObjectCount ++;
  624. this._buildMesh( result );
  625. let progressBytesPercent = this.globalCounts.currentByte / this.globalCounts.totalBytes;
  626. this._onProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName + '' +
  627. '] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%' );
  628. this._resetRawMesh();
  629. }
  630. return haveMesh;
  631. },
  632. /**
  633. * SubGroups are transformed to too intermediate format that is forwarded to the MeshReceiver.
  634. * It is ensured that SubGroups only contain objects with vertices (no need to check).
  635. *
  636. * @param result
  637. */
  638. _buildMesh: function ( result ) {
  639. let meshOutputGroups = result.subGroups;
  640. let vertexFA = new Float32Array( result.absoluteVertexCount );
  641. this.globalCounts.vertices += result.absoluteVertexCount / 3;
  642. this.globalCounts.faces += result.faceCount;
  643. this.globalCounts.doubleIndicesCount += result.doubleIndicesCount;
  644. let indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
  645. let colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
  646. let normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
  647. let uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
  648. let haveVertexColors = colorFA !== null;
  649. let meshOutputGroup;
  650. let materialNames = [];
  651. let createMultiMaterial = ( meshOutputGroups.length > 1 );
  652. let materialIndex = 0;
  653. let materialIndexMapping = [];
  654. let selectedMaterialIndex;
  655. let materialGroup;
  656. let materialGroups = [];
  657. let vertexFAOffset = 0;
  658. let indexUAOffset = 0;
  659. let colorFAOffset = 0;
  660. let normalFAOffset = 0;
  661. let uvFAOffset = 0;
  662. let materialGroupOffset = 0;
  663. let materialGroupLength = 0;
  664. let materialOrg, material, materialName, materialNameOrg;
  665. // only one specific face type
  666. for ( let oodIndex in meshOutputGroups ) {
  667. if ( ! meshOutputGroups.hasOwnProperty( oodIndex ) ) continue;
  668. meshOutputGroup = meshOutputGroups[ oodIndex ];
  669. materialNameOrg = meshOutputGroup.materialName;
  670. if ( this.rawMesh.faceType < 4 ) {
  671. materialName = materialNameOrg + ( haveVertexColors ? '_vertexColor' : '' ) + ( meshOutputGroup.smoothingGroup === 0 ? '_flat' : '' );
  672. } else {
  673. materialName = this.rawMesh.faceType === 6 ? 'defaultPointMaterial' : 'defaultLineMaterial';
  674. }
  675. materialOrg = this.materials[ materialNameOrg ];
  676. material = this.materials[ materialName ];
  677. // both original and derived names do not lead to an existing material => need to use a default material
  678. if ( ( materialOrg === undefined || materialOrg === null ) && ( material === undefined || material === null ) ) {
  679. materialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial';
  680. material = this.materials[ materialName ];
  681. if ( this.logging.enabled ) {
  682. console.info( 'object_group "' + meshOutputGroup.objectName + '_' +
  683. meshOutputGroup.groupName + '" was defined with unresolvable material "' +
  684. materialNameOrg + '"! Assigning "' + materialName + '".' );
  685. }
  686. }
  687. if ( material === undefined || material === null ) {
  688. let materialCloneInstructions = {
  689. materialNameOrg: materialNameOrg,
  690. materialName: materialName,
  691. materialProperties: {
  692. vertexColors: haveVertexColors ? 2 : 0,
  693. flatShading: meshOutputGroup.smoothingGroup === 0
  694. }
  695. };
  696. let payload = {
  697. cmd: 'assetAvailable',
  698. type: 'material',
  699. materials: {
  700. materialCloneInstructions: materialCloneInstructions
  701. }
  702. };
  703. this.callbacks.onAssetAvailable( payload );
  704. // only set materials if they don't exist, yet
  705. let matCheck = this.materials[ materialName ];
  706. if ( matCheck === undefined || matCheck === null ) {
  707. this.materials[ materialName ] = materialCloneInstructions;
  708. }
  709. }
  710. if ( createMultiMaterial ) {
  711. // re-use material if already used before. Reduces materials array size and eliminates duplicates
  712. selectedMaterialIndex = materialIndexMapping[ materialName ];
  713. if ( ! selectedMaterialIndex ) {
  714. selectedMaterialIndex = materialIndex;
  715. materialIndexMapping[ materialName ] = materialIndex;
  716. materialNames.push( materialName );
  717. materialIndex ++;
  718. }
  719. materialGroupLength = this.useIndices ? meshOutputGroup.indices.length : meshOutputGroup.vertices.length / 3;
  720. materialGroup = {
  721. start: materialGroupOffset,
  722. count: materialGroupLength,
  723. index: selectedMaterialIndex
  724. };
  725. materialGroups.push( materialGroup );
  726. materialGroupOffset += materialGroupLength;
  727. } else {
  728. materialNames.push( materialName );
  729. }
  730. vertexFA.set( meshOutputGroup.vertices, vertexFAOffset );
  731. vertexFAOffset += meshOutputGroup.vertices.length;
  732. if ( indexUA ) {
  733. indexUA.set( meshOutputGroup.indices, indexUAOffset );
  734. indexUAOffset += meshOutputGroup.indices.length;
  735. }
  736. if ( colorFA ) {
  737. colorFA.set( meshOutputGroup.colors, colorFAOffset );
  738. colorFAOffset += meshOutputGroup.colors.length;
  739. }
  740. if ( normalFA ) {
  741. normalFA.set( meshOutputGroup.normals, normalFAOffset );
  742. normalFAOffset += meshOutputGroup.normals.length;
  743. }
  744. if ( uvFA ) {
  745. uvFA.set( meshOutputGroup.uvs, uvFAOffset );
  746. uvFAOffset += meshOutputGroup.uvs.length;
  747. }
  748. if ( this.logging.enabled && this.logging.debug ) {
  749. let materialIndexLine = '';
  750. if ( selectedMaterialIndex ) {
  751. materialIndexLine = '\n\t\tmaterialIndex: ' + selectedMaterialIndex;
  752. }
  753. let createdReport = '\tOutput Object no.: ' + this.outputObjectCount +
  754. '\n\t\tgroupName: ' + meshOutputGroup.groupName +
  755. '\n\t\tIndex: ' + meshOutputGroup.index +
  756. '\n\t\tfaceType: ' + this.rawMesh.faceType +
  757. '\n\t\tmaterialName: ' + meshOutputGroup.materialName +
  758. '\n\t\tsmoothingGroup: ' + meshOutputGroup.smoothingGroup +
  759. materialIndexLine +
  760. '\n\t\tobjectName: ' + meshOutputGroup.objectName +
  761. '\n\t\t#vertices: ' + meshOutputGroup.vertices.length / 3 +
  762. '\n\t\t#indices: ' + meshOutputGroup.indices.length +
  763. '\n\t\t#colors: ' + meshOutputGroup.colors.length / 3 +
  764. '\n\t\t#uvs: ' + meshOutputGroup.uvs.length / 2 +
  765. '\n\t\t#normals: ' + meshOutputGroup.normals.length / 3;
  766. console.debug( createdReport );
  767. }
  768. }
  769. this.outputObjectCount ++;
  770. this.callbacks.onAssetAvailable(
  771. {
  772. cmd: 'assetAvailable',
  773. type: 'mesh',
  774. progress: {
  775. numericalValue: this.globalCounts.currentByte / this.globalCounts.totalBytes
  776. },
  777. params: {
  778. meshName: result.name
  779. },
  780. materials: {
  781. multiMaterial: createMultiMaterial,
  782. materialNames: materialNames,
  783. materialGroups: materialGroups
  784. },
  785. buffers: {
  786. vertices: vertexFA,
  787. indices: indexUA,
  788. colors: colorFA,
  789. normals: normalFA,
  790. uvs: uvFA
  791. },
  792. // 0: mesh, 1: line, 2: point
  793. geometryType: this.rawMesh.faceType < 4 ? 0 : ( this.rawMesh.faceType === 6 ) ? 2 : 1
  794. },
  795. [ vertexFA.buffer ],
  796. indexUA !== null ? [ indexUA.buffer ] : null,
  797. colorFA !== null ? [ colorFA.buffer ] : null,
  798. normalFA !== null ? [ normalFA.buffer ] : null,
  799. uvFA !== null ? [ uvFA.buffer ] : null
  800. );
  801. },
  802. _finalizeParsing: function () {
  803. if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.outputObjectCount );
  804. if ( this._processCompletedMesh() && this.logging.enabled ) {
  805. let parserFinalReport = 'Overall counts: ' +
  806. '\n\tVertices: ' + this.globalCounts.vertices +
  807. '\n\tFaces: ' + this.globalCounts.faces +
  808. '\n\tMultiple definitions: ' + this.globalCounts.doubleIndicesCount;
  809. console.info( parserFinalReport );
  810. }
  811. }
  812. };
  813. export { OBJLoader2Parser };