OBJLoader2Parser.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  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. for ( let code, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i ++ ) {
  257. code = arrayBufferView[ i ];
  258. switch ( code ) {
  259. // space
  260. case 32:
  261. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  262. word = '';
  263. break;
  264. // slash
  265. case 47:
  266. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  267. slashesCount ++;
  268. word = '';
  269. break;
  270. // LF
  271. case 10:
  272. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  273. word = '';
  274. this.globalCounts.lineByte = this.globalCounts.currentByte;
  275. this.globalCounts.currentByte = i;
  276. this._processLine( buffer, bufferPointer, slashesCount );
  277. bufferPointer = 0;
  278. slashesCount = 0;
  279. break;
  280. // CR
  281. case 13:
  282. break;
  283. default:
  284. word += String.fromCharCode( code );
  285. break;
  286. }
  287. }
  288. this._finalizeParsing();
  289. if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.execute' );
  290. },
  291. /**
  292. * Parse the provided text
  293. *
  294. * @param {string} text OBJ data as string
  295. */
  296. executeLegacy: function ( text ) {
  297. if ( this.logging.enabled ) console.time( 'OBJLoader2Parser.executeLegacy' );
  298. this._configure();
  299. this.legacyMode = true;
  300. this.contentRef = text;
  301. let length = text.length;
  302. this.globalCounts.totalBytes = length;
  303. let buffer = new Array( 128 );
  304. for ( let char, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i ++ ) {
  305. char = text[ i ];
  306. switch ( char ) {
  307. case ' ':
  308. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  309. word = '';
  310. break;
  311. case '/':
  312. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  313. slashesCount ++;
  314. word = '';
  315. break;
  316. case '\n':
  317. if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
  318. word = '';
  319. this.globalCounts.lineByte = this.globalCounts.currentByte;
  320. this.globalCounts.currentByte = i;
  321. this._processLine( buffer, bufferPointer, slashesCount );
  322. bufferPointer = 0;
  323. slashesCount = 0;
  324. break;
  325. case '\r':
  326. break;
  327. default:
  328. word += char;
  329. }
  330. }
  331. this._finalizeParsing();
  332. if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.executeLegacy' );
  333. },
  334. _processLine: function ( buffer, bufferPointer, slashesCount ) {
  335. if ( bufferPointer < 1 ) return;
  336. let reconstructString = function ( content, legacyMode, start, stop ) {
  337. let line = '';
  338. if ( stop > start ) {
  339. let i;
  340. if ( legacyMode ) {
  341. for ( i = start; i < stop; i ++ ) line += content[ i ];
  342. } else {
  343. for ( i = start; i < stop; i ++ ) line += String.fromCharCode( content[ i ] );
  344. }
  345. line = line.trim();
  346. }
  347. return line;
  348. };
  349. let bufferLength, length, i, lineDesignation;
  350. lineDesignation = buffer[ 0 ];
  351. switch ( lineDesignation ) {
  352. case 'v':
  353. this.vertices.push( parseFloat( buffer[ 1 ] ) );
  354. this.vertices.push( parseFloat( buffer[ 2 ] ) );
  355. this.vertices.push( parseFloat( buffer[ 3 ] ) );
  356. if ( bufferPointer > 4 ) {
  357. this.colors.push( parseFloat( buffer[ 4 ] ) );
  358. this.colors.push( parseFloat( buffer[ 5 ] ) );
  359. this.colors.push( parseFloat( buffer[ 6 ] ) );
  360. }
  361. break;
  362. case 'vt':
  363. this.uvs.push( parseFloat( buffer[ 1 ] ) );
  364. this.uvs.push( parseFloat( buffer[ 2 ] ) );
  365. break;
  366. case 'vn':
  367. this.normals.push( parseFloat( buffer[ 1 ] ) );
  368. this.normals.push( parseFloat( buffer[ 2 ] ) );
  369. this.normals.push( parseFloat( buffer[ 3 ] ) );
  370. break;
  371. case 'f':
  372. bufferLength = bufferPointer - 1;
  373. // "f vertex ..."
  374. if ( slashesCount === 0 ) {
  375. this._checkFaceType( 0 );
  376. for ( i = 2, length = bufferLength; i < length; i ++ ) {
  377. this._buildFace( buffer[ 1 ] );
  378. this._buildFace( buffer[ i ] );
  379. this._buildFace( buffer[ i + 1 ] );
  380. }
  381. // "f vertex/uv ..."
  382. } else if ( bufferLength === slashesCount * 2 ) {
  383. this._checkFaceType( 1 );
  384. for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
  385. this._buildFace( buffer[ 1 ], buffer[ 2 ] );
  386. this._buildFace( buffer[ i ], buffer[ i + 1 ] );
  387. this._buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
  388. }
  389. // "f vertex/uv/normal ..."
  390. } else if ( bufferLength * 2 === slashesCount * 3 ) {
  391. this._checkFaceType( 2 );
  392. for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
  393. this._buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
  394. this._buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
  395. this._buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
  396. }
  397. // "f vertex//normal ..."
  398. } else {
  399. this._checkFaceType( 3 );
  400. for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
  401. this._buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
  402. this._buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
  403. this._buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
  404. }
  405. }
  406. break;
  407. case 'l':
  408. case 'p':
  409. bufferLength = bufferPointer - 1;
  410. if ( bufferLength === slashesCount * 2 ) {
  411. this._checkFaceType( 4 );
  412. for ( i = 1, length = bufferLength + 1; i < length; i += 2 ) this._buildFace( buffer[ i ], buffer[ i + 1 ] );
  413. } else {
  414. this._checkFaceType( ( lineDesignation === 'l' ) ? 5 : 6 );
  415. for ( i = 1, length = bufferLength + 1; i < length; i ++ ) this._buildFace( buffer[ i ] );
  416. }
  417. break;
  418. case 's':
  419. this._pushSmoothingGroup( buffer[ 1 ] );
  420. break;
  421. case 'g':
  422. // 'g' leads to creation of mesh if valid data (faces declaration was done before), otherwise only groupName gets set
  423. this._processCompletedMesh();
  424. this.rawMesh.groupName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
  425. break;
  426. case 'o':
  427. // 'o' is meta-information and usually does not result in creation of new meshes, but can be enforced with "useOAsMesh"
  428. if ( this.useOAsMesh ) this._processCompletedMesh();
  429. this.rawMesh.objectName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
  430. break;
  431. case 'mtllib':
  432. this.rawMesh.mtllibName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
  433. break;
  434. case 'usemtl':
  435. let mtlName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
  436. if ( mtlName !== '' && this.rawMesh.activeMtlName !== mtlName ) {
  437. this.rawMesh.activeMtlName = mtlName;
  438. this.rawMesh.counts.mtlCount ++;
  439. this._checkSubGroup();
  440. }
  441. break;
  442. default:
  443. break;
  444. }
  445. },
  446. _pushSmoothingGroup: function ( smoothingGroup ) {
  447. let smoothingGroupInt = parseInt( smoothingGroup );
  448. if ( isNaN( smoothingGroupInt ) ) {
  449. smoothingGroupInt = smoothingGroup === "off" ? 0 : 1;
  450. }
  451. let smoothCheck = this.rawMesh.smoothingGroup.normalized;
  452. this.rawMesh.smoothingGroup.normalized = this.rawMesh.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;
  453. this.rawMesh.smoothingGroup.real = smoothingGroupInt;
  454. if ( smoothCheck !== smoothingGroupInt ) {
  455. this.rawMesh.counts.smoothingGroupCount ++;
  456. this._checkSubGroup();
  457. }
  458. },
  459. /**
  460. * Expanded faceTypes include all four face types, both line types and the point type
  461. * faceType = 0: "f vertex ..."
  462. * faceType = 1: "f vertex/uv ..."
  463. * faceType = 2: "f vertex/uv/normal ..."
  464. * faceType = 3: "f vertex//normal ..."
  465. * faceType = 4: "l vertex/uv ..." or "l vertex ..."
  466. * faceType = 5: "l vertex ..."
  467. * faceType = 6: "p vertex ..."
  468. */
  469. _checkFaceType: function ( faceType ) {
  470. if ( this.rawMesh.faceType !== faceType ) {
  471. this._processCompletedMesh();
  472. this.rawMesh.faceType = faceType;
  473. this._checkSubGroup();
  474. }
  475. },
  476. _checkSubGroup: function () {
  477. let index = this.rawMesh.activeMtlName + '|' + this.rawMesh.smoothingGroup.normalized;
  478. this.rawMesh.subGroupInUse = this.rawMesh.subGroups[ index ];
  479. if ( this.rawMesh.subGroupInUse === undefined || this.rawMesh.subGroupInUse === null ) {
  480. this.rawMesh.subGroupInUse = {
  481. index: index,
  482. objectName: this.rawMesh.objectName,
  483. groupName: this.rawMesh.groupName,
  484. materialName: this.rawMesh.activeMtlName,
  485. smoothingGroup: this.rawMesh.smoothingGroup.normalized,
  486. vertices: [],
  487. indexMappingsCount: 0,
  488. indexMappings: [],
  489. indices: [],
  490. colors: [],
  491. uvs: [],
  492. normals: []
  493. };
  494. this.rawMesh.subGroups[ index ] = this.rawMesh.subGroupInUse;
  495. }
  496. },
  497. _buildFace: function ( faceIndexV, faceIndexU, faceIndexN ) {
  498. let subGroupInUse = this.rawMesh.subGroupInUse;
  499. let scope = this;
  500. let updateSubGroupInUse = function () {
  501. let faceIndexVi = parseInt( faceIndexV );
  502. let indexPointerV = 3 * ( faceIndexVi > 0 ? faceIndexVi - 1 : faceIndexVi + scope.vertices.length / 3 );
  503. let indexPointerC = scope.colors.length > 0 ? indexPointerV : null;
  504. let vertices = subGroupInUse.vertices;
  505. vertices.push( scope.vertices[ indexPointerV ++ ] );
  506. vertices.push( scope.vertices[ indexPointerV ++ ] );
  507. vertices.push( scope.vertices[ indexPointerV ] );
  508. if ( indexPointerC !== null ) {
  509. let colors = subGroupInUse.colors;
  510. colors.push( scope.colors[ indexPointerC ++ ] );
  511. colors.push( scope.colors[ indexPointerC ++ ] );
  512. colors.push( scope.colors[ indexPointerC ] );
  513. }
  514. if ( faceIndexU ) {
  515. let faceIndexUi = parseInt( faceIndexU );
  516. let indexPointerU = 2 * ( faceIndexUi > 0 ? faceIndexUi - 1 : faceIndexUi + scope.uvs.length / 2 );
  517. let uvs = subGroupInUse.uvs;
  518. uvs.push( scope.uvs[ indexPointerU ++ ] );
  519. uvs.push( scope.uvs[ indexPointerU ] );
  520. }
  521. if ( faceIndexN && ! scope.disregardNormals ) {
  522. let faceIndexNi = parseInt( faceIndexN );
  523. let indexPointerN = 3 * ( faceIndexNi > 0 ? faceIndexNi - 1 : faceIndexNi + scope.normals.length / 3 );
  524. let normals = subGroupInUse.normals;
  525. normals.push( scope.normals[ indexPointerN ++ ] );
  526. normals.push( scope.normals[ indexPointerN ++ ] );
  527. normals.push( scope.normals[ indexPointerN ] );
  528. }
  529. };
  530. if ( this.useIndices ) {
  531. if ( this.disregardNormals ) faceIndexN = undefined;
  532. let mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
  533. let indicesPointer = subGroupInUse.indexMappings[ mappingName ];
  534. if ( indicesPointer === undefined || indicesPointer === null ) {
  535. indicesPointer = this.rawMesh.subGroupInUse.vertices.length / 3;
  536. updateSubGroupInUse();
  537. subGroupInUse.indexMappings[ mappingName ] = indicesPointer;
  538. subGroupInUse.indexMappingsCount ++;
  539. } else {
  540. this.rawMesh.counts.doubleIndicesCount ++;
  541. }
  542. subGroupInUse.indices.push( indicesPointer );
  543. } else {
  544. updateSubGroupInUse();
  545. }
  546. this.rawMesh.counts.faceCount ++;
  547. },
  548. _createRawMeshReport: function ( inputObjectCount ) {
  549. return 'Input Object number: ' + inputObjectCount +
  550. '\n\tObject name: ' + this.rawMesh.objectName +
  551. '\n\tGroup name: ' + this.rawMesh.groupName +
  552. '\n\tMtllib name: ' + this.rawMesh.mtllibName +
  553. '\n\tVertex count: ' + this.vertices.length / 3 +
  554. '\n\tNormal count: ' + this.normals.length / 3 +
  555. '\n\tUV count: ' + this.uvs.length / 2 +
  556. '\n\tSmoothingGroup count: ' + this.rawMesh.counts.smoothingGroupCount +
  557. '\n\tMaterial count: ' + this.rawMesh.counts.mtlCount +
  558. '\n\tReal MeshOutputGroup count: ' + this.rawMesh.subGroups.length;
  559. },
  560. /**
  561. * Clear any empty subGroup and calculate absolute vertex, normal and uv counts
  562. */
  563. _finalizeRawMesh: function () {
  564. let meshOutputGroupTemp = [];
  565. let meshOutputGroup;
  566. let absoluteVertexCount = 0;
  567. let absoluteIndexMappingsCount = 0;
  568. let absoluteIndexCount = 0;
  569. let absoluteColorCount = 0;
  570. let absoluteNormalCount = 0;
  571. let absoluteUvCount = 0;
  572. let indices;
  573. for ( let name in this.rawMesh.subGroups ) {
  574. meshOutputGroup = this.rawMesh.subGroups[ name ];
  575. if ( meshOutputGroup.vertices.length > 0 ) {
  576. indices = meshOutputGroup.indices;
  577. if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {
  578. for ( let i = 0; i < indices.length; i ++ ) {
  579. indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;
  580. }
  581. }
  582. meshOutputGroupTemp.push( meshOutputGroup );
  583. absoluteVertexCount += meshOutputGroup.vertices.length;
  584. absoluteIndexMappingsCount += meshOutputGroup.indexMappingsCount;
  585. absoluteIndexCount += meshOutputGroup.indices.length;
  586. absoluteColorCount += meshOutputGroup.colors.length;
  587. absoluteUvCount += meshOutputGroup.uvs.length;
  588. absoluteNormalCount += meshOutputGroup.normals.length;
  589. }
  590. }
  591. // do not continue if no result
  592. let result = null;
  593. if ( meshOutputGroupTemp.length > 0 ) {
  594. result = {
  595. name: this.rawMesh.groupName !== '' ? this.rawMesh.groupName : this.rawMesh.objectName,
  596. subGroups: meshOutputGroupTemp,
  597. absoluteVertexCount: absoluteVertexCount,
  598. absoluteIndexCount: absoluteIndexCount,
  599. absoluteColorCount: absoluteColorCount,
  600. absoluteNormalCount: absoluteNormalCount,
  601. absoluteUvCount: absoluteUvCount,
  602. faceCount: this.rawMesh.counts.faceCount,
  603. doubleIndicesCount: this.rawMesh.counts.doubleIndicesCount
  604. };
  605. }
  606. return result;
  607. },
  608. _processCompletedMesh: function () {
  609. let result = this._finalizeRawMesh();
  610. let haveMesh = result !== null;
  611. if ( haveMesh ) {
  612. if ( this.colors.length > 0 && this.colors.length !== this.vertices.length ) {
  613. this.callbacks.onError( 'Vertex Colors were detected, but vertex count and color count do not match!' );
  614. }
  615. if ( this.logging.enabled && this.logging.debug ) console.debug( this._createRawMeshReport( this.inputObjectCount ) );
  616. this.inputObjectCount ++;
  617. this._buildMesh( result );
  618. let progressBytesPercent = this.globalCounts.currentByte / this.globalCounts.totalBytes;
  619. this._onProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName + '' +
  620. '] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%' );
  621. this._resetRawMesh();
  622. }
  623. return haveMesh;
  624. },
  625. /**
  626. * SubGroups are transformed to too intermediate format that is forwarded to the MeshReceiver.
  627. * It is ensured that SubGroups only contain objects with vertices (no need to check).
  628. *
  629. * @param result
  630. */
  631. _buildMesh: function ( result ) {
  632. let meshOutputGroups = result.subGroups;
  633. let vertexFA = new Float32Array( result.absoluteVertexCount );
  634. this.globalCounts.vertices += result.absoluteVertexCount / 3;
  635. this.globalCounts.faces += result.faceCount;
  636. this.globalCounts.doubleIndicesCount += result.doubleIndicesCount;
  637. let indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
  638. let colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
  639. let normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
  640. let uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
  641. let haveVertexColors = colorFA !== null;
  642. let meshOutputGroup;
  643. let materialNames = [];
  644. let createMultiMaterial = ( meshOutputGroups.length > 1 );
  645. let materialIndex = 0;
  646. let materialIndexMapping = [];
  647. let selectedMaterialIndex;
  648. let materialGroup;
  649. let materialGroups = [];
  650. let vertexFAOffset = 0;
  651. let indexUAOffset = 0;
  652. let colorFAOffset = 0;
  653. let normalFAOffset = 0;
  654. let uvFAOffset = 0;
  655. let materialGroupOffset = 0;
  656. let materialGroupLength = 0;
  657. let materialOrg, material, materialName, materialNameOrg;
  658. // only one specific face type
  659. for ( let oodIndex in meshOutputGroups ) {
  660. if ( ! meshOutputGroups.hasOwnProperty( oodIndex ) ) continue;
  661. meshOutputGroup = meshOutputGroups[ oodIndex ];
  662. materialNameOrg = meshOutputGroup.materialName;
  663. if ( this.rawMesh.faceType < 4 ) {
  664. materialName = materialNameOrg + ( haveVertexColors ? '_vertexColor' : '' ) + ( meshOutputGroup.smoothingGroup === 0 ? '_flat' : '' );
  665. } else {
  666. materialName = this.rawMesh.faceType === 6 ? 'defaultPointMaterial' : 'defaultLineMaterial';
  667. }
  668. materialOrg = this.materials[ materialNameOrg ];
  669. material = this.materials[ materialName ];
  670. // both original and derived names do not lead to an existing material => need to use a default material
  671. if ( ( materialOrg === undefined || materialOrg === null ) && ( material === undefined || material === null ) ) {
  672. materialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial';
  673. material = this.materials[ materialName ];
  674. if ( this.logging.enabled ) {
  675. console.info( 'object_group "' + meshOutputGroup.objectName + '_' +
  676. meshOutputGroup.groupName + '" was defined with unresolvable material "' +
  677. materialNameOrg + '"! Assigning "' + materialName + '".' );
  678. }
  679. }
  680. if ( material === undefined || material === null ) {
  681. let materialCloneInstructions = {
  682. materialNameOrg: materialNameOrg,
  683. materialName: materialName,
  684. materialProperties: {
  685. vertexColors: haveVertexColors ? 2 : 0,
  686. flatShading: meshOutputGroup.smoothingGroup === 0
  687. }
  688. };
  689. let payload = {
  690. cmd: 'assetAvailable',
  691. type: 'material',
  692. materials: {
  693. materialCloneInstructions: materialCloneInstructions
  694. }
  695. };
  696. this.callbacks.onAssetAvailable( payload );
  697. // only set materials if they don't exist, yet
  698. let matCheck = this.materials[ materialName ];
  699. if ( matCheck === undefined || matCheck === null ) {
  700. this.materials[ materialName ] = materialCloneInstructions;
  701. }
  702. }
  703. if ( createMultiMaterial ) {
  704. // re-use material if already used before. Reduces materials array size and eliminates duplicates
  705. selectedMaterialIndex = materialIndexMapping[ materialName ];
  706. if ( ! selectedMaterialIndex ) {
  707. selectedMaterialIndex = materialIndex;
  708. materialIndexMapping[ materialName ] = materialIndex;
  709. materialNames.push( materialName );
  710. materialIndex ++;
  711. }
  712. materialGroupLength = this.useIndices ? meshOutputGroup.indices.length : meshOutputGroup.vertices.length / 3;
  713. materialGroup = {
  714. start: materialGroupOffset,
  715. count: materialGroupLength,
  716. index: selectedMaterialIndex
  717. };
  718. materialGroups.push( materialGroup );
  719. materialGroupOffset += materialGroupLength;
  720. } else {
  721. materialNames.push( materialName );
  722. }
  723. vertexFA.set( meshOutputGroup.vertices, vertexFAOffset );
  724. vertexFAOffset += meshOutputGroup.vertices.length;
  725. if ( indexUA ) {
  726. indexUA.set( meshOutputGroup.indices, indexUAOffset );
  727. indexUAOffset += meshOutputGroup.indices.length;
  728. }
  729. if ( colorFA ) {
  730. colorFA.set( meshOutputGroup.colors, colorFAOffset );
  731. colorFAOffset += meshOutputGroup.colors.length;
  732. }
  733. if ( normalFA ) {
  734. normalFA.set( meshOutputGroup.normals, normalFAOffset );
  735. normalFAOffset += meshOutputGroup.normals.length;
  736. }
  737. if ( uvFA ) {
  738. uvFA.set( meshOutputGroup.uvs, uvFAOffset );
  739. uvFAOffset += meshOutputGroup.uvs.length;
  740. }
  741. if ( this.logging.enabled && this.logging.debug ) {
  742. let materialIndexLine = ( selectedMaterialIndex === undefined || selectedMaterialIndex === null ) ? '' : '\n\t\tmaterialIndex: ' + selectedMaterialIndex;
  743. let createdReport = '\tOutput Object no.: ' + this.outputObjectCount +
  744. '\n\t\tgroupName: ' + meshOutputGroup.groupName +
  745. '\n\t\tIndex: ' + meshOutputGroup.index +
  746. '\n\t\tfaceType: ' + this.rawMesh.faceType +
  747. '\n\t\tmaterialName: ' + meshOutputGroup.materialName +
  748. '\n\t\tsmoothingGroup: ' + meshOutputGroup.smoothingGroup +
  749. materialIndexLine +
  750. '\n\t\tobjectName: ' + meshOutputGroup.objectName +
  751. '\n\t\t#vertices: ' + meshOutputGroup.vertices.length / 3 +
  752. '\n\t\t#indices: ' + meshOutputGroup.indices.length +
  753. '\n\t\t#colors: ' + meshOutputGroup.colors.length / 3 +
  754. '\n\t\t#uvs: ' + meshOutputGroup.uvs.length / 2 +
  755. '\n\t\t#normals: ' + meshOutputGroup.normals.length / 3;
  756. console.debug( createdReport );
  757. }
  758. }
  759. this.outputObjectCount ++;
  760. this.callbacks.onAssetAvailable(
  761. {
  762. cmd: 'assetAvailable',
  763. type: 'mesh',
  764. progress: {
  765. numericalValue: this.globalCounts.currentByte / this.globalCounts.totalBytes
  766. },
  767. params: {
  768. meshName: result.name
  769. },
  770. materials: {
  771. multiMaterial: createMultiMaterial,
  772. materialNames: materialNames,
  773. materialGroups: materialGroups
  774. },
  775. buffers: {
  776. vertices: vertexFA,
  777. indices: indexUA,
  778. colors: colorFA,
  779. normals: normalFA,
  780. uvs: uvFA
  781. },
  782. // 0: mesh, 1: line, 2: point
  783. geometryType: this.rawMesh.faceType < 4 ? 0 : ( this.rawMesh.faceType === 6 ) ? 2 : 1
  784. },
  785. [ vertexFA.buffer ],
  786. indexUA !== null ? [ indexUA.buffer ] : null,
  787. colorFA !== null ? [ colorFA.buffer ] : null,
  788. normalFA !== null ? [ normalFA.buffer ] : null,
  789. uvFA !== null ? [ uvFA.buffer ] : null
  790. );
  791. },
  792. _finalizeParsing: function () {
  793. if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.outputObjectCount );
  794. if ( this._processCompletedMesh() && this.logging.enabled ) {
  795. let parserFinalReport = 'Overall counts: ' +
  796. '\n\tVertices: ' + this.globalCounts.vertices +
  797. '\n\tFaces: ' + this.globalCounts.faces +
  798. '\n\tMultiple definitions: ' + this.globalCounts.doubleIndicesCount;
  799. console.info( parserFinalReport );
  800. }
  801. }
  802. };
  803. export { OBJLoader2Parser };