WWOBJLoader2.js 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  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. THREE.OBJLoader2.version = '1.0.6';
  8. /**
  9. * OBJ data will be loaded by dynamically created web worker.
  10. * First feed instructions with: prepareRun
  11. * Then: Execute with: run
  12. * @class
  13. */
  14. THREE.OBJLoader2.WWOBJLoader2 = (function () {
  15. function WWOBJLoader2() {
  16. this._init();
  17. }
  18. WWOBJLoader2.prototype._init = function () {
  19. // check worker support first
  20. if ( window.Worker === undefined ) throw "This browser does not support web workers!";
  21. if ( window.Blob === undefined ) throw "This browser does not support Blob!";
  22. if ( ! typeof window.URL.createObjectURL === 'function' ) throw "This browser does not support Object creation from URL!";
  23. this.instanceNo = 0;
  24. this.worker = null;
  25. this.workerCode = null;
  26. this.debug = false;
  27. this.sceneGraphBaseNode = null;
  28. this.streamMeshes = true;
  29. this.meshStore = null;
  30. this.modelName = 'none';
  31. this.validated = false;
  32. this.running = false;
  33. this.requestTerminate = false;
  34. this.callbacks = {
  35. progress: null,
  36. completedLoading: null,
  37. errorWhileLoading: null,
  38. materialsLoaded: null,
  39. meshLoaded: null,
  40. director: {
  41. completedLoading: null,
  42. errorWhileLoading: null
  43. }
  44. };
  45. this.manager = THREE.DefaultLoadingManager;
  46. this.fileLoader = new THREE.FileLoader( this.manager );
  47. this.mtlLoader = null;
  48. this.crossOrigin = null;
  49. this.dataAvailable = false;
  50. this.objAsArrayBuffer = null;
  51. this.fileObj = null;
  52. this.pathObj = null;
  53. this.fileMtl = null;
  54. this.mtlAsString = null;
  55. this.texturePath = null;
  56. this.materials = [];
  57. this.counter = 0;
  58. };
  59. /**
  60. * Set enable or disable debug logging
  61. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  62. *
  63. * @param {boolean} enabled
  64. */
  65. WWOBJLoader2.prototype.setDebug = function ( enabled ) {
  66. this.debug = enabled;
  67. };
  68. /**
  69. * Sets the CORS string to be used.
  70. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  71. *
  72. * @param {string} crossOrigin CORS value
  73. */
  74. WWOBJLoader2.prototype.setCrossOrigin = function ( crossOrigin ) {
  75. this.crossOrigin = crossOrigin;
  76. };
  77. /**
  78. * Register callback function that is invoked by internal function "_announceProgress" to print feedback
  79. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  80. *
  81. * @param {callback} callbackProgress Callback function for described functionality
  82. */
  83. WWOBJLoader2.prototype.registerCallbackProgress = function ( callbackProgress ) {
  84. if ( callbackProgress != null ) this.callbacks.progress = callbackProgress;
  85. };
  86. /**
  87. * Register callback function that is called once loading of the complete model is completed
  88. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  89. *
  90. * @param {callback} callbackCompletedLoading Callback function for described functionality
  91. */
  92. WWOBJLoader2.prototype.registerCallbackCompletedLoading = function ( callbackCompletedLoading ) {
  93. if ( callbackCompletedLoading != null ) this.callbacks.completedLoading = callbackCompletedLoading;
  94. };
  95. /**
  96. * Register callback function that is called once materials have been loaded. It allows to alter and return materials
  97. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  98. *
  99. * @param {callback} callbackMaterialsLoaded Callback function for described functionality
  100. */
  101. WWOBJLoader2.prototype.registerCallbackMaterialsLoaded = function ( callbackMaterialsLoaded ) {
  102. if ( callbackMaterialsLoaded != null ) this.callbacks.materialsLoaded = callbackMaterialsLoaded;
  103. };
  104. /**
  105. * Register callback function that is called every time a mesh was loaded
  106. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  107. *
  108. * @param {callback} callbackMeshLoaded Callback function for described functionality
  109. */
  110. WWOBJLoader2.prototype.registerCallbackMeshLoaded = function ( callbackMeshLoaded ) {
  111. if ( callbackMeshLoaded != null ) this.callbacks.meshLoaded = callbackMeshLoaded;
  112. };
  113. /**
  114. * Report if an error prevented loading
  115. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  116. *
  117. * @param {callback} callbackErrorWhileLoading Callback function for described functionality
  118. */
  119. WWOBJLoader2.prototype.registerCallbackErrorWhileLoading = function ( callbackErrorWhileLoading ) {
  120. if ( callbackErrorWhileLoading != null ) this.callbacks.errorWhileLoading = callbackErrorWhileLoading;
  121. };
  122. /**
  123. * Call requestTerminate to terminate the web worker and free local resource after execution
  124. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  125. *
  126. * @param {boolean} requestTerminate
  127. */
  128. WWOBJLoader2.prototype.setRequestTerminate = function ( requestTerminate ) {
  129. this.requestTerminate = ( requestTerminate != null && requestTerminate ) ? true : false;
  130. };
  131. WWOBJLoader2.prototype._validate = function () {
  132. if ( this.validated ) return;
  133. if ( this.worker == null ) {
  134. this._buildWebWorkerCode();
  135. var blob = new Blob( [ this.workerCode ], { type: 'text/plain' } );
  136. this.worker = new Worker( window.URL.createObjectURL( blob ) );
  137. var scope = this;
  138. var scopeFunction = function ( e ) {
  139. scope._receiveWorkerMessage( e );
  140. };
  141. this.worker.addEventListener( 'message', scopeFunction, false );
  142. }
  143. this.sceneGraphBaseNode = null;
  144. this.streamMeshes = true;
  145. this.meshStore = [];
  146. this.modelName = 'none';
  147. this.validated = true;
  148. this.running = true;
  149. this.requestTerminate = false;
  150. this.fileLoader = ( this.fileLoader == null ) ? new THREE.FileLoader( this.manager ) : this.fileLoader;
  151. this.mtlLoader = ( this.mtlLoader == null ) ? new THREE.MTLLoader() : this.mtlLoader;
  152. if ( this.crossOrigin != null ) this.mtlLoader.setCrossOrigin( this.crossOrigin );
  153. this.dataAvailable = false;
  154. this.fileObj = null;
  155. this.pathObj = null;
  156. this.fileMtl = null;
  157. this.texturePath = null;
  158. this.objAsArrayBuffer = null;
  159. this.mtlAsString = null;
  160. this.materials = [];
  161. var defaultMaterial = new THREE.MeshStandardMaterial( { color: 0xDCF1FF } );
  162. defaultMaterial.name = 'defaultMaterial';
  163. this.materials[ defaultMaterial.name ] = defaultMaterial;
  164. this.counter = 0;
  165. };
  166. /**
  167. * Set all parameters for required for execution of "run".
  168. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  169. *
  170. * @param {Object} params Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile}
  171. */
  172. WWOBJLoader2.prototype.prepareRun = function ( params ) {
  173. this._validate();
  174. this.dataAvailable = params.dataAvailable;
  175. this.modelName = params.modelName;
  176. console.time( 'WWOBJLoader2' );
  177. if ( this.dataAvailable ) {
  178. // fast-fail on bad type
  179. if ( ! params.objAsArrayBuffer instanceof Uint8Array ) {
  180. throw 'Provided input is not of type arraybuffer! Aborting...';
  181. }
  182. this.worker.postMessage( {
  183. cmd: 'init',
  184. debug: this.debug
  185. } );
  186. this.objAsArrayBuffer = params.objAsArrayBuffer;
  187. this.mtlAsString = params.mtlAsString;
  188. } else {
  189. // fast-fail on bad type
  190. if ( ! ( typeof( params.fileObj ) === 'string' || params.fileObj instanceof String ) ) {
  191. throw 'Provided file is not properly defined! Aborting...';
  192. }
  193. this.worker.postMessage( {
  194. cmd: 'init',
  195. debug: this.debug
  196. } );
  197. this.fileObj = params.fileObj;
  198. this.pathObj = params.pathObj;
  199. this.fileMtl = params.fileMtl;
  200. }
  201. this.setRequestTerminate( params.requestTerminate );
  202. this.pathTexture = params.pathTexture;
  203. this.sceneGraphBaseNode = params.sceneGraphBaseNode;
  204. this.streamMeshes = params.streamMeshes;
  205. if ( ! this.streamMeshes ) this.meshStore = [];
  206. };
  207. /**
  208. * Run the loader according the preparation instruction provided in "prepareRun".
  209. * @memberOf THREE.OBJLoader2.WWOBJLoader2
  210. */
  211. WWOBJLoader2.prototype.run = function () {
  212. var scope = this;
  213. var processLoadedMaterials = function ( materialCreator ) {
  214. var materialCreatorMaterials = [];
  215. var materialNames = [];
  216. if ( materialCreator != null ) {
  217. materialCreator.preload();
  218. materialCreatorMaterials = materialCreator.materials;
  219. for ( var materialName in materialCreatorMaterials ) {
  220. if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
  221. materialNames.push( materialName );
  222. scope.materials[ materialName ] = materialCreatorMaterials[ materialName ];
  223. }
  224. }
  225. }
  226. scope.worker.postMessage( {
  227. cmd: 'setMaterials',
  228. materialNames: materialNames
  229. } );
  230. if ( scope.callbacks.materialsLoaded != null ) {
  231. var materialsCallback = scope.callbacks.materialsLoaded( scope.materials );
  232. if ( materialsCallback != null ) scope.materials = materialsCallback;
  233. }
  234. if ( scope.dataAvailable && scope.objAsArrayBuffer ) {
  235. scope.worker.postMessage({
  236. cmd: 'run',
  237. objAsArrayBuffer: scope.objAsArrayBuffer
  238. }, [ scope.objAsArrayBuffer.buffer ] );
  239. } else {
  240. var refPercentComplete = 0;
  241. var percentComplete = 0;
  242. var output;
  243. var onLoad = function ( objAsArrayBuffer ) {
  244. scope._announceProgress( 'Running web worker!' );
  245. scope.objAsArrayBuffer = new Uint8Array( objAsArrayBuffer );
  246. scope.worker.postMessage( {
  247. cmd: 'run',
  248. objAsArrayBuffer: scope.objAsArrayBuffer
  249. }, [ scope.objAsArrayBuffer.buffer ] );
  250. };
  251. var onProgress = function ( event ) {
  252. if ( ! event.lengthComputable ) return;
  253. percentComplete = Math.round( event.loaded / event.total * 100 );
  254. if ( percentComplete > refPercentComplete ) {
  255. refPercentComplete = percentComplete;
  256. output = 'Download of "' + scope.fileObj + '": ' + percentComplete + '%';
  257. console.log( output );
  258. scope._announceProgress( output );
  259. }
  260. };
  261. var onError = function ( event ) {
  262. output = 'Error occurred while downloading "' + scope.fileObj + '"';
  263. console.error( output + ': ' + event );
  264. scope._announceProgress( output );
  265. scope._finalize( 'error' );
  266. };
  267. scope.fileLoader.setPath( scope.pathObj );
  268. scope.fileLoader.setResponseType( 'arraybuffer' );
  269. scope.fileLoader.load( scope.fileObj, onLoad, onProgress, onError );
  270. }
  271. console.timeEnd( 'Loading MTL textures' );
  272. };
  273. this.mtlLoader.setPath( this.pathTexture );
  274. if ( this.dataAvailable ) {
  275. processLoadedMaterials( ( this.mtlAsString != null ) ? this.mtlLoader.parse( this.mtlAsString ) : null );
  276. } else {
  277. if ( this.fileMtl == null ) {
  278. processLoadedMaterials();
  279. } else {
  280. this.mtlLoader.load( this.fileMtl, processLoadedMaterials );
  281. }
  282. }
  283. };
  284. WWOBJLoader2.prototype._receiveWorkerMessage = function ( event ) {
  285. var payload = event.data;
  286. switch ( payload.cmd ) {
  287. case 'objData':
  288. this.counter ++;
  289. var bufferGeometry = new THREE.BufferGeometry();
  290. bufferGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( payload.vertices ), 3 ) );
  291. if ( payload.normals !== null ) {
  292. bufferGeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( payload.normals ), 3 ) );
  293. } else {
  294. bufferGeometry.computeVertexNormals();
  295. }
  296. if ( payload.uvs !== null ) {
  297. bufferGeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( payload.uvs ), 2 ) );
  298. }
  299. var materialDescriptions = payload.materialDescriptions;
  300. var materialDescription;
  301. var material;
  302. var materialName;
  303. var createMultiMaterial = payload.multiMaterial;
  304. var multiMaterials = [];
  305. for ( var key in materialDescriptions ) {
  306. materialDescription = materialDescriptions[ key ];
  307. material = this.materials[ materialDescription.name ];
  308. if ( materialDescription.default ) {
  309. material = this.materials[ 'defaultMaterial' ];
  310. } else if ( materialDescription.clone ) {
  311. materialName = material.name + '_flat';
  312. var materialClone = this.materials[ materialName ];
  313. if ( ! materialClone ) {
  314. materialClone = material.clone();
  315. materialClone.name = materialName;
  316. materialClone.shading = THREE.FlatShading;
  317. this.materials[ materialName ] = name;
  318. }
  319. } else if ( ! material ) {
  320. material = this.materials[ 'defaultMaterial' ];
  321. }
  322. if ( createMultiMaterial ) multiMaterials.push( material );
  323. }
  324. if ( createMultiMaterial ) {
  325. material = new THREE.MultiMaterial( multiMaterials );
  326. var materialGroups = payload.materialGroups;
  327. var materialGroup;
  328. for ( var key in materialGroups ) {
  329. materialGroup = materialGroups[ key ];
  330. bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index );
  331. }
  332. }
  333. if ( this.callbacks.meshLoaded !== null ) {
  334. var materialOverride = this.callbacks.meshLoaded( payload.meshName, material );
  335. if ( materialOverride != null ) material = materialOverride;
  336. }
  337. var mesh = new THREE.Mesh( bufferGeometry, material );
  338. mesh.name = payload.meshName;
  339. if ( this.streamMeshes ) {
  340. this.sceneGraphBaseNode.add( mesh );
  341. } else {
  342. this.meshStore.push( mesh );
  343. }
  344. var output = '(' + this.counter + '): ' + payload.meshName;
  345. this._announceProgress( 'Adding mesh', output );
  346. break;
  347. case 'complete':
  348. if ( ! this.streamMeshes ) {
  349. for ( var key in this.meshStore ) {
  350. this.sceneGraphBaseNode.add( this.meshStore[ key ] );
  351. }
  352. }
  353. console.timeEnd( 'WWOBJLoader2' );
  354. if ( payload.msg != null ) {
  355. this._announceProgress( payload.msg );
  356. } else {
  357. this._announceProgress( '' );
  358. }
  359. this._finalize( 'complete' );
  360. break;
  361. case 'report_progress':
  362. this._announceProgress( '', payload.output );
  363. break;
  364. default:
  365. console.error( 'Received unknown command: ' + payload.cmd );
  366. break;
  367. }
  368. };
  369. WWOBJLoader2.prototype._terminate = function () {
  370. if ( this.worker != null ) {
  371. if ( this.running ) throw 'Unable to gracefully terminate worker as it is currently running!';
  372. this.worker.terminate();
  373. this.worker = null;
  374. this.workerCode = null;
  375. this._finalize( 'terminate' );
  376. }
  377. this.fileLoader = null;
  378. this.mtlLoader = null;
  379. };
  380. WWOBJLoader2.prototype._finalize = function ( reason, requestTerminate ) {
  381. this.running = false;
  382. if ( reason === 'complete' ) {
  383. if ( this.callbacks.completedLoading != null ) this.callbacks.completedLoading( this.modelName, this.instanceNo, this.requestTerminate );
  384. if ( this.callbacks.director.completedLoading != null ) this.callbacks.director.completedLoading( this.modelName, this.instanceNo, this.requestTerminate );
  385. } else if ( reason === 'error' ) {
  386. if ( this.callbacks.errorWhileLoading != null ) this.callbacks.errorWhileLoading( this.modelName, this.instanceNo, this.requestTerminate );
  387. if ( this.callbacks.director.errorWhileLoading != null ) this.callbacks.director.errorWhileLoading( this.modelName, this.instanceNo, this.requestTerminate );
  388. }
  389. this.validated = false;
  390. this.setRequestTerminate( requestTerminate );
  391. if ( this.requestTerminate ) {
  392. this._terminate();
  393. }
  394. };
  395. WWOBJLoader2.prototype._announceProgress = function ( baseText, text ) {
  396. var output = "";
  397. if ( baseText !== null && baseText !== undefined ) {
  398. output = baseText;
  399. }
  400. if ( text !== null && text !== undefined ) {
  401. output = output + " " + text;
  402. }
  403. if ( this.callbacks.progress !== null ) {
  404. this.callbacks.progress( output );
  405. }
  406. if ( this.debug ) {
  407. console.log( output );
  408. }
  409. };
  410. WWOBJLoader2.prototype._buildWebWorkerCode = function ( existingWorkerCode ) {
  411. if ( existingWorkerCode != null ) this.workerCode = existingWorkerCode;
  412. if ( this.workerCode == null ) {
  413. console.time( 'buildWebWorkerCode' );
  414. var wwDef = (function () {
  415. function OBJLoader() {
  416. this.meshCreator = new MeshCreator();
  417. this.parser = new Parser( this.meshCreator );
  418. this.validated = false;
  419. this.cmdState = 'created';
  420. this.debug = false;
  421. }
  422. /**
  423. * Allows to set debug mode for the parser and the meshCreatorDebug
  424. *
  425. * @param parserDebug
  426. * @param meshCreatorDebug
  427. */
  428. OBJLoader.prototype._setDebug = function ( parserDebug, meshCreatorDebug ) {
  429. this.parser._setDebug( parserDebug );
  430. this.meshCreator._setDebug( meshCreatorDebug );
  431. };
  432. /**
  433. * Validate status, then parse arrayBuffer, finalize and return objGroup
  434. *
  435. * @param arrayBuffer
  436. */
  437. OBJLoader.prototype.parse = function ( arrayBuffer ) {
  438. console.log( 'Parsing arrayBuffer...' );
  439. console.time( 'parseArrayBuffer' );
  440. this._validate();
  441. this.parser.parseArrayBuffer( arrayBuffer );
  442. var objGroup = this._finalize();
  443. console.timeEnd( 'parseArrayBuffer' );
  444. return objGroup;
  445. };
  446. OBJLoader.prototype._validate = function () {
  447. if ( this.validated ) return;
  448. this.parser._validate();
  449. this.meshCreator._validate();
  450. this.validated = true;
  451. };
  452. OBJLoader.prototype._finalize = function () {
  453. console.log( 'Global output object count: ' + this.meshCreator.globalObjectCount );
  454. this.parser._finalize();
  455. this.meshCreator._finalize();
  456. this.validated = false;
  457. };
  458. OBJLoader.prototype.init = function ( payload ) {
  459. this.cmdState = 'init';
  460. this._setDebug( payload.debug, payload.debug );
  461. };
  462. OBJLoader.prototype.setMaterials = function ( payload ) {
  463. this.cmdState = 'setMaterials';
  464. this.meshCreator._setMaterials( payload.materialNames );
  465. };
  466. OBJLoader.prototype.run = function ( payload ) {
  467. this.cmdState = 'run';
  468. this.parse( payload.objAsArrayBuffer );
  469. console.log( 'OBJ loading complete!' );
  470. this.cmdState = 'complete';
  471. self.postMessage( {
  472. cmd: this.cmdState,
  473. msg: null
  474. } );
  475. };
  476. return OBJLoader;
  477. })();
  478. var wwMeshCreatorDef = (function () {
  479. function MeshCreator() {
  480. this.materials = null;
  481. this.debug = false;
  482. this.globalObjectCount = 1;
  483. this.validated = false;
  484. }
  485. MeshCreator.prototype._setMaterials = function ( materials ) {
  486. this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials;
  487. };
  488. MeshCreator.prototype._setDebug = function ( debug ) {
  489. this.debug = ( debug == null ) ? this.debug : debug;
  490. };
  491. MeshCreator.prototype._validate = function () {
  492. if ( this.validated ) return;
  493. this._setMaterials( null );
  494. this._setDebug( null );
  495. this.globalObjectCount = 1;
  496. };
  497. MeshCreator.prototype._finalize = function () {
  498. this.materials = null;
  499. this.validated = false;
  500. };
  501. /**
  502. * RawObjectDescriptions are transformed to THREE.Mesh.
  503. * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
  504. *
  505. * @param rawObjectDescriptions
  506. * @param inputObjectCount
  507. * @param absoluteVertexCount
  508. * @param absoluteNormalCount
  509. * @param absoluteUvCount
  510. */
  511. MeshCreator.prototype._buildMesh = function ( rawObjectDescriptions, inputObjectCount, absoluteVertexCount, absoluteNormalCount, absoluteUvCount ) {
  512. if ( this.debug ) console.log( 'OBJLoader.buildMesh:\nInput object no.: ' + inputObjectCount );
  513. var vertexFa = new Float32Array( absoluteVertexCount );
  514. var normalFA = ( absoluteNormalCount > 0 ) ? new Float32Array( absoluteNormalCount ) : null;
  515. var uvFA = ( absoluteUvCount > 0 ) ? new Float32Array( absoluteUvCount ) : null;
  516. var rawObjectDescription;
  517. var materialDescription;
  518. var materialDescriptions = [];
  519. var createMultiMaterial = ( rawObjectDescriptions.length > 1 );
  520. var materialIndex = 0;
  521. var materialIndexMapping = [];
  522. var selectedMaterialIndex;
  523. var materialGroup;
  524. var materialGroups = [];
  525. var vertexBAOffset = 0;
  526. var vertexGroupOffset = 0;
  527. var vertexLength;
  528. var normalOffset = 0;
  529. var uvOffset = 0;
  530. for ( var oodIndex in rawObjectDescriptions ) {
  531. rawObjectDescription = rawObjectDescriptions[ oodIndex ];
  532. materialDescription = { name: rawObjectDescription.materialName, flat: false, default: false };
  533. if ( this.materials[ materialDescription.name ] === null ) {
  534. materialDescription.default = true;
  535. console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName + '" was defined without material! Assigning "defaultMaterial".' );
  536. }
  537. // Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0
  538. if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;
  539. vertexLength = rawObjectDescription.vertices.length;
  540. if ( createMultiMaterial ) {
  541. // re-use material if already used before. Reduces materials array size and eliminates duplicates
  542. selectedMaterialIndex = materialIndexMapping[ materialDescription.name ];
  543. if ( ! selectedMaterialIndex ) {
  544. selectedMaterialIndex = materialIndex;
  545. materialIndexMapping[ materialDescription.name ] = materialIndex;
  546. materialDescriptions.push( materialDescription );
  547. materialIndex++;
  548. }
  549. materialGroup = {
  550. start: vertexGroupOffset,
  551. count: vertexLength / 3,
  552. index: selectedMaterialIndex
  553. };
  554. materialGroups.push( materialGroup );
  555. vertexGroupOffset += vertexLength / 3;
  556. } else {
  557. materialDescriptions.push( materialDescription );
  558. }
  559. vertexFa.set( rawObjectDescription.vertices, vertexBAOffset );
  560. vertexBAOffset += vertexLength;
  561. if ( normalFA ) {
  562. normalFA.set( rawObjectDescription.normals, normalOffset );
  563. normalOffset += rawObjectDescription.normals.length;
  564. }
  565. if ( uvFA ) {
  566. uvFA.set( rawObjectDescription.uvs, uvOffset );
  567. uvOffset += rawObjectDescription.uvs.length;
  568. }
  569. if ( this.debug ) this.printReport( rawObjectDescription, selectedMaterialIndex );
  570. }
  571. self.postMessage( {
  572. cmd: 'objData',
  573. meshName: rawObjectDescription.objectName,
  574. multiMaterial: createMultiMaterial,
  575. materialDescriptions: materialDescriptions,
  576. materialGroups: materialGroups,
  577. vertices: vertexFa,
  578. normals: normalFA,
  579. uvs: uvFA
  580. }, [ vertexFa.buffer ], normalFA !== null ? [ normalFA.buffer ] : null, uvFA !== null ? [ uvFA.buffer ] : null );
  581. this.globalObjectCount++;
  582. };
  583. return MeshCreator;
  584. })();
  585. var wwLoaderRunnerDef = (function () {
  586. function OBJLoaderRunner() {
  587. self.addEventListener( 'message', this.runner, false );
  588. }
  589. OBJLoaderRunner.prototype.runner = function ( event ) {
  590. var payload = event.data;
  591. console.log( 'Command state before: ' + OBJLoaderRef.cmdState );
  592. switch ( payload.cmd ) {
  593. case 'init':
  594. OBJLoaderRef.init( payload );
  595. break;
  596. case 'setMaterials':
  597. OBJLoaderRef.setMaterials( payload );
  598. break;
  599. case 'run':
  600. OBJLoaderRef.run( payload );
  601. break;
  602. default:
  603. console.error( 'OBJLoader: Received unknown command: ' + payload.cmd );
  604. break;
  605. }
  606. console.log( 'Command state after: ' + OBJLoaderRef.cmdState );
  607. };
  608. return OBJLoaderRunner;
  609. })();
  610. var buildObject = function ( fullName, object ) {
  611. var objectString = fullName + ' = {\n';
  612. var part;
  613. for ( var name in object ) {
  614. part = object[ name ];
  615. if ( typeof( part ) === 'string' || part instanceof String ) {
  616. part = part.replace( '\n', '\\n' );
  617. part = part.replace( '\r', '\\r' );
  618. objectString += '\t' + name + ': "' + part + '",\n';
  619. } else if ( part instanceof Array ) {
  620. objectString += '\t' + name + ': [' + part + '],\n';
  621. } else if ( Number.isInteger( part ) ) {
  622. objectString += '\t' + name + ': ' + part + ',\n';
  623. } else if ( typeof part === 'function' ) {
  624. objectString += '\t' + name + ': ' + part + ',\n';
  625. }
  626. }
  627. objectString += '}\n\n';
  628. return objectString;
  629. };
  630. var buildSingelton = function ( fullName, internalName, object ) {
  631. var objectString = fullName + ' = (function () {\n\n';
  632. objectString += '\t' + object.prototype.constructor.toString() + '\n\n';
  633. var funcString;
  634. var objectPart;
  635. for ( var name in object.prototype ) {
  636. objectPart = object.prototype[ name ];
  637. if ( typeof objectPart === 'function' ) {
  638. funcString = objectPart.toString();
  639. objectString += '\t' + internalName + '.prototype.' + name + ' = ' + funcString + ';\n\n';
  640. }
  641. }
  642. objectString += '\treturn ' + internalName + ';\n';
  643. objectString += '})();\n\n';
  644. return objectString;
  645. };
  646. this.workerCode = '';
  647. this.workerCode += '/**\n';
  648. this.workerCode += ' * This code was constructed by WWOBJLoader2._buildWebWorkerCode\n';
  649. this.workerCode += ' */\n\n';
  650. // parser re-construction
  651. var objLoaderHelper = new THREE.OBJLoader2();
  652. this.workerCode += objLoaderHelper._buildWebWorkerCode( buildObject, buildSingelton );
  653. // web worker construction
  654. this.workerCode += buildSingelton( 'OBJLoader', 'OBJLoader', wwDef );
  655. this.workerCode += buildSingelton( 'MeshCreator', 'MeshCreator', wwMeshCreatorDef );
  656. this.workerCode += 'OBJLoaderRef = new OBJLoader();\n\n';
  657. this.workerCode += buildSingelton( 'OBJLoaderRunner', 'OBJLoaderRunner', wwLoaderRunnerDef );
  658. this.workerCode += 'new OBJLoaderRunner();\n\n';
  659. console.timeEnd( 'buildWebWorkerCode' );
  660. }
  661. return this.workerCode;
  662. };
  663. return WWOBJLoader2;
  664. })();
  665. /**
  666. * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ from given ArrayBuffer and MTL from given String
  667. *
  668. * @param {string} modelName Overall name of the model
  669. * @param {Uint8Array} objAsArrayBuffer OBJ file content as ArrayBuffer
  670. * @param {string} pathTexture Path to texture files
  671. * @param {string} mtlAsString MTL file content as string
  672. * @param {THREE.Object3D} sceneGraphBaseNode {@link THREE.Object3D} where meshes will be attached
  673. * @param {boolean} streamMeshes=true Singles meshes are directly integrated into scene when loaded or later
  674. * @param {boolean} [requestTerminate=false] Request termination of web worker and free local resources after execution
  675. *
  676. * @returns {{modelName: string, dataAvailable: boolean, objAsArrayBuffer: null, pathTexture: null, mtlAsString: null, sceneGraphBaseNode: null, streamMeshes: boolean, requestTerminate: boolean}}
  677. * @constructor
  678. */
  679. THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer = function ( modelName, objAsArrayBuffer, pathTexture, mtlAsString, sceneGraphBaseNode, streamMeshes, requestTerminate ) {
  680. var data = {
  681. modelName: ( modelName == null ) ? 'none' : modelName,
  682. dataAvailable: true,
  683. objAsArrayBuffer: ( objAsArrayBuffer == null ) ? null : objAsArrayBuffer,
  684. pathTexture: ( pathTexture == null ) ? null : pathTexture,
  685. mtlAsString: ( mtlAsString == null ) ? null : mtlAsString,
  686. sceneGraphBaseNode: ( sceneGraphBaseNode == null ) ? null : sceneGraphBaseNode,
  687. streamMeshes: ( streamMeshes == null ) ? true : streamMeshes,
  688. requestTerminate: ( requestTerminate == null ) ? false : requestTerminate
  689. };
  690. return data;
  691. };
  692. /**
  693. * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ and MTL from files
  694. *
  695. * @param {string} modelName Overall name of the model
  696. * @param {string} pathObj Path to OBJ file
  697. * @param {string} fileObj OBJ file name
  698. * @param {string} pathTexture Path to texture files
  699. * @param {string} fileMtl MTL file name
  700. * @param {THREE.Object3D} sceneGraphBaseNode {@link THREE.Object3D} where meshes will be attached
  701. * @param {boolean} streamMeshes=true Singles meshes are directly integrated into scene when loaded or later
  702. * @param {boolean} [requestTerminate=false] Request termination of web worker and free local resources after execution
  703. *
  704. * @returns {{modelName: string, dataAvailable: boolean, pathObj: null, fileObj: null, pathTexture: null, fileMtl: null, sceneGraphBaseNode: null, streamMeshes: boolean, requestTerminate: boolean}}
  705. * @constructor
  706. */
  707. THREE.OBJLoader2.WWOBJLoader2.PrepDataFile = function ( modelName, pathObj, fileObj, pathTexture, fileMtl, sceneGraphBaseNode, streamMeshes, requestTerminate ) {
  708. var data = {
  709. modelName: ( modelName == null ) ? 'none' : modelName,
  710. dataAvailable: false,
  711. pathObj: ( pathObj == null ) ? null : pathObj,
  712. fileObj: ( fileObj == null ) ? null : fileObj,
  713. pathTexture: ( pathTexture == null ) ? null : pathTexture,
  714. fileMtl: ( fileMtl == null ) ? null : fileMtl,
  715. sceneGraphBaseNode: ( sceneGraphBaseNode == null ) ? null : sceneGraphBaseNode,
  716. streamMeshes: ( streamMeshes == null ) ? true : streamMeshes,
  717. requestTerminate: ( requestTerminate == null ) ? false : requestTerminate
  718. };
  719. return data;
  720. };
  721. /**
  722. * Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16).
  723. * Use:
  724. * prepareWorkers
  725. * enqueueForRun
  726. * processQueue
  727. * deregister
  728. *
  729. * @class
  730. */
  731. THREE.OBJLoader2.WWOBJLoader2Director = (function () {
  732. var MAX_WEB_WORKER = 16;
  733. var MAX_QUEUE_SIZE = 1024;
  734. function WWOBJLoader2Director() {
  735. this.maxQueueSize = MAX_QUEUE_SIZE ;
  736. this.maxWebWorkers = MAX_WEB_WORKER;
  737. this.crossOrigin = null;
  738. this.workerDescription = {
  739. prototypeDef: THREE.OBJLoader2.WWOBJLoader2.prototype,
  740. callbacks: {},
  741. webWorkers: [],
  742. codeBuffer: null
  743. };
  744. this.objectsCompleted = 0;
  745. this.instructionQueue = [];
  746. }
  747. /**
  748. * Returns the maximum length of the instruction queue.
  749. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  750. *
  751. * @returns {number}
  752. */
  753. WWOBJLoader2Director.prototype.getMaxQueueSize = function () {
  754. return this.maxQueueSize;
  755. };
  756. /**
  757. * Returns the maximum number of workers.
  758. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  759. *
  760. * @returns {number}
  761. */
  762. WWOBJLoader2Director.prototype.getMaxWebWorkers = function () {
  763. return this.maxWebWorkers;
  764. };
  765. /**
  766. * Sets the CORS string to be used.
  767. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  768. *
  769. * @param {string} crossOrigin CORS value
  770. */
  771. WWOBJLoader2Director.prototype.setCrossOrigin = function ( crossOrigin ) {
  772. this.crossOrigin = crossOrigin;
  773. };
  774. /**
  775. * Create or destroy workers according limits. Set the name and register callbacks for dynamically created web workers.
  776. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  777. *
  778. * @param {callback[]} callbacks Register callbacks for all web workers:
  779. * { progress: null, completedLoading: null, errorWhileLoading: null, materialsLoaded: null, meshLoaded: null }
  780. * @param {number} maxQueueSize Set the maximum size of the instruction queue (1-1024)
  781. * @param {number} maxWebWorkers Set the maximum amount of workers (1-16)
  782. */
  783. WWOBJLoader2Director.prototype.prepareWorkers = function ( callbacks, maxQueueSize, maxWebWorkers ) {
  784. if ( callbacks != null ) {
  785. for ( var key in callbacks ) {
  786. if ( callbacks.hasOwnProperty( key ) ) this.workerDescription.callbacks[ key ] = callbacks[ key ];
  787. }
  788. }
  789. this.maxQueueSize = Math.min( maxQueueSize, MAX_QUEUE_SIZE );
  790. this.maxWebWorkers = Math.min( maxWebWorkers, MAX_WEB_WORKER );
  791. this.objectsCompleted = 0;
  792. this.instructionQueue = [];
  793. var start = this.workerDescription.webWorkers.length;
  794. if ( start < this.maxWebWorkers ) {
  795. for ( i = start; i < this.maxWebWorkers; i ++ ) {
  796. webWorker = this._buildWebWorker();
  797. this.workerDescription.webWorkers[ i ] = webWorker;
  798. }
  799. } else {
  800. for ( var webWorker, i = start - 1; i >= this.maxWebWorkers; i-- ) {
  801. webWorker = this.workerDescription.webWorkers[ i ];
  802. webWorker.setRequestTerminate( true );
  803. this.workerDescription.webWorkers.pop();
  804. }
  805. }
  806. };
  807. /**
  808. * Store run instructions in internal instructionQueue
  809. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  810. *
  811. * @param {Object} params Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile}
  812. */
  813. WWOBJLoader2Director.prototype.enqueueForRun = function ( runParams ) {
  814. if ( this.instructionQueue.length < this.maxQueueSize ) {
  815. this.instructionQueue.push( runParams );
  816. }
  817. };
  818. /**
  819. * Process the instructionQueue until it is depleted
  820. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  821. */
  822. WWOBJLoader2Director.prototype.processQueue = function () {
  823. if ( this.instructionQueue.length === 0 ) return;
  824. var webWorker;
  825. var runParams;
  826. var length = Math.min( this.maxWebWorkers, this.instructionQueue.length );
  827. for ( var i = 0; i < length; i++ ) {
  828. webWorker = this.workerDescription.webWorkers[ i ];
  829. runParams = this.instructionQueue[ 0 ];
  830. webWorker.prepareRun( runParams );
  831. webWorker.run();
  832. this.instructionQueue.shift();
  833. }
  834. };
  835. WWOBJLoader2Director.prototype._buildWebWorker = function () {
  836. var webWorker = Object.create( this.workerDescription.prototypeDef );
  837. webWorker._init();
  838. if ( this.crossOrigin != null ) webWorker.setCrossOrigin( this.crossOrigin );
  839. // Ensure code string is built once and then it is just passed on to every new instance
  840. if ( this.workerDescription.codeBuffer == null ) {
  841. this.workerDescription.codeBuffer = webWorker._buildWebWorkerCode();
  842. } else {
  843. webWorker._buildWebWorkerCode( this.workerDescription.codeBuffer );
  844. }
  845. for ( var key in this.workerDescription.callbacks ) {
  846. if ( webWorker.callbacks.hasOwnProperty( key ) && this.workerDescription.callbacks.hasOwnProperty( key ) ) {
  847. webWorker.callbacks[ key ] = this.workerDescription.callbacks[ key ];
  848. }
  849. }
  850. var scope = this;
  851. var managerCompletedLoading = function ( modelName, instanceNo, requestTerminate ) {
  852. scope.objectsCompleted++;
  853. if ( ! requestTerminate ) {
  854. var rekick = scope.workerDescription.webWorkers[ instanceNo ];
  855. var runParams = scope.instructionQueue[ 0 ];
  856. if ( runParams != null ) {
  857. rekick.prepareRun( runParams );
  858. rekick.run();
  859. scope.instructionQueue.shift();
  860. }
  861. }
  862. };
  863. webWorker.callbacks.director[ 'completedLoading' ] = managerCompletedLoading;
  864. webWorker.instanceNo = this.workerDescription.webWorkers.length;
  865. this.workerDescription.webWorkers.push( webWorker );
  866. return webWorker;
  867. };
  868. /**
  869. * Terminate all workers
  870. * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
  871. */
  872. WWOBJLoader2Director.prototype.deregister = function () {
  873. console.log( 'WWOBJLoader2Director received the unregister call. Terminating all workers!' );
  874. for ( var i = 0, webWorker, length = this.workerDescription.webWorkers.length; i < length; i++ ) {
  875. webWorker = this.workerDescription.webWorkers[ i ];
  876. webWorker.setRequestTerminate( true );
  877. }
  878. this.workerDescription.callbacks = {};
  879. this.workerDescription.webWorkers = [];
  880. this.workerDescription.codeBuffer = null;
  881. };
  882. return WWOBJLoader2Director;
  883. })();