OBJLoader2.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /**
  2. * @author Kai Salmen / https://kaisalmen.de
  3. * Development repository: https://github.com/kaisalmen/WWOBJLoader
  4. */
  5. import {
  6. DefaultLoadingManager,
  7. FileLoader,
  8. Group
  9. } from "../../../build/three.module.js";
  10. import { OBJLoader2Parser } from "./obj2/worker/parallel/OBJLoader2Parser.js";
  11. import { MeshReceiver } from "./obj2/shared/MeshReceiver.js";
  12. import { MaterialHandler } from "./obj2/shared/MaterialHandler.js";
  13. /**
  14. * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
  15. * @class
  16. *
  17. * @param {DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link DefaultLoadingManager}
  18. */
  19. const OBJLoader2 = function ( manager ) {
  20. OBJLoader2Parser.call( this );
  21. this.manager = ( manager !== undefined && manager !== null ) ? manager : DefaultLoadingManager;
  22. this.modelName = '';
  23. this.instanceNo = 0;
  24. this.path = undefined;
  25. this.resourcePath = undefined;
  26. this.baseObject3d = new Group();
  27. this.materialHandler = new MaterialHandler();
  28. this.meshReceiver = new MeshReceiver( this.materialHandler );
  29. };
  30. OBJLoader2.OBJLOADER2_VERSION = '3.0.0';
  31. console.info( 'Using OBJLoader2 version: ' + OBJLoader2.OBJLOADER2_VERSION );
  32. OBJLoader2.prototype = Object.create( OBJLoader2Parser.prototype );
  33. OBJLoader2.prototype.constructor = OBJLoader2;
  34. /**
  35. * Set the name of the model.
  36. *
  37. * @param {string} modelName
  38. * @return {OBJLoader2}
  39. */
  40. OBJLoader2.prototype.setModelName = function ( modelName ) {
  41. this.modelName = modelName ? modelName : this.modelName;
  42. return this;
  43. };
  44. /**
  45. * The URL of the base path.
  46. *
  47. * @param {string} path URL
  48. * @return {OBJLoader2}
  49. */
  50. OBJLoader2.prototype.setPath = function ( path ) {
  51. this.path = path ? path : this.path;
  52. return this;
  53. };
  54. /**
  55. * Allow to specify resourcePath for dependencies of specified resource.
  56. *
  57. * @param {string} resourcePath
  58. * @return {OBJLoader2}
  59. */
  60. OBJLoader2.prototype.setResourcePath = function ( resourcePath ) {
  61. this.resourcePath = resourcePath ? resourcePath : this.resourcePath;
  62. return this;
  63. };
  64. /**
  65. * Set the node where the loaded objects will be attached directly.
  66. *
  67. * @param {Object3D} baseObject3d Object already attached to scenegraph where new meshes will be attached to
  68. * @return {OBJLoader2}
  69. */
  70. OBJLoader2.prototype.setBaseObject3d = function ( baseObject3d ) {
  71. this.baseObject3d = ( baseObject3d === undefined || baseObject3d === null ) ? this.baseObject3d : baseObject3d;
  72. return this;
  73. };
  74. /**
  75. * Add materials as associated array.
  76. *
  77. * @param {Object} materials Object with named {@link Material}
  78. * @return {OBJLoader2}
  79. */
  80. OBJLoader2.prototype.addMaterials = function ( materials ) {
  81. this.materialHandler.addMaterials( materials );
  82. return this;
  83. };
  84. /**
  85. * Register a function that is called once a single mesh is available and it could be altered by the supplied function.
  86. *
  87. * @param {Function} [onMeshAlter]
  88. * @return {OBJLoader2}
  89. */
  90. OBJLoader2.prototype.setCallbackOnMeshAlter = function ( onMeshAlter ) {
  91. this.meshReceiver._setCallbacks( this.callbacks.onProgress, onMeshAlter );
  92. return this;
  93. };
  94. /**
  95. * Register a function that is called once all materials have been loaded and they could be altered by the supplied function.
  96. *
  97. * @param {Function} [onLoadMaterials]
  98. * @return {OBJLoader2}
  99. */
  100. OBJLoader2.prototype.setCallbackOnLoadMaterials = function ( onLoadMaterials ) {
  101. this.materialHandler._setCallbacks( onLoadMaterials );
  102. return this;
  103. };
  104. /**
  105. * Use this convenient method to load a file at the given URL. By default the fileLoader uses an ArrayBuffer.
  106. *
  107. * @param {string} url A string containing the path/URL of the file to be loaded.
  108. * @param {function} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument.
  109. * @param {function} [onFileLoadProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes.
  110. * @param {function} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
  111. * @param {function} [onMeshAlter] Called after every single mesh is made available by the parser
  112. */
  113. OBJLoader2.prototype.load = function ( url, onLoad, onFileLoadProgress, onError, onMeshAlter ) {
  114. let scope = this;
  115. if ( onLoad === null || onLoad === undefined || ! ( onLoad instanceof Function ) ) {
  116. let errorMessage = 'onLoad is not a function! Aborting...';
  117. scope.callbacks.onError( errorMessage );
  118. throw errorMessage
  119. }
  120. if ( onError === null || onError === undefined || ! ( onError instanceof Function ) ) {
  121. onError = function ( event ) {
  122. let errorMessage = event;
  123. if ( event.currentTarget && event.currentTarget.statusText !== null ) {
  124. errorMessage = 'Error occurred while downloading!\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText;
  125. }
  126. scope.callbacks.onError( errorMessage );
  127. };
  128. }
  129. if ( ! url ) {
  130. onError( 'An invalid url was provided. Unable to continue!' );
  131. }
  132. let urlFull = new URL( url, window.location.href ).href;
  133. let filename = urlFull;
  134. let urlParts = urlFull.split( '/' );
  135. if ( urlParts.length > 2 ) {
  136. filename = urlParts[ urlParts.length - 1 ];
  137. let urlPartsPath = urlParts.slice( 0, urlParts.length - 1 ).join( '/' ) + '/';
  138. if ( urlPartsPath !== undefined && urlPartsPath !== null ) this.path = urlPartsPath;
  139. }
  140. if ( onFileLoadProgress === null || onFileLoadProgress === undefined || ! ( onFileLoadProgress instanceof Function ) ) {
  141. let numericalValueRef = 0;
  142. let numericalValue = 0;
  143. onFileLoadProgress = function ( event ) {
  144. if ( ! event.lengthComputable ) return;
  145. numericalValue = event.loaded / event.total;
  146. if ( numericalValue > numericalValueRef ) {
  147. numericalValueRef = numericalValue;
  148. let output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
  149. scope.callbacks.onProgress( 'progressLoad', output, numericalValue );
  150. }
  151. };
  152. }
  153. this.setCallbackOnMeshAlter( onMeshAlter );
  154. let fileLoaderOnLoad = function ( content ) {
  155. onLoad( scope.parse( content ) );
  156. };
  157. let fileLoader = new FileLoader( this.manager );
  158. fileLoader.setPath( this.path || this.resourcePath );
  159. fileLoader.setResponseType( 'arraybuffer' );
  160. fileLoader.load( filename, fileLoaderOnLoad, onFileLoadProgress, onError );
  161. };
  162. /**
  163. * Parses OBJ data synchronously from arraybuffer or string.
  164. *
  165. * @param {arraybuffer|string} content OBJ data as Uint8Array or String
  166. */
  167. OBJLoader2.prototype.parse = function ( content ) {
  168. // fast-fail in case of illegal data
  169. if ( content === null || content === undefined ) {
  170. throw 'Provided content is not a valid ArrayBuffer or String. Unable to continue parsing';
  171. }
  172. if ( this.logging.enabled ) {
  173. console.time( 'OBJLoader parse: ' + this.modelName );
  174. }
  175. // sync code works directly on the material references
  176. this._setMaterials( this.materialHandler.getMaterials() );
  177. if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
  178. if ( this.logging.enabled ) console.info( 'Parsing arrayBuffer...' );
  179. this.execute( content );
  180. } else if ( typeof ( content ) === 'string' || content instanceof String ) {
  181. if ( this.logging.enabled ) console.info( 'Parsing text...' );
  182. this.executeLegacy( content );
  183. } else {
  184. this.callbacks.onError( 'Provided content was neither of type String nor Uint8Array! Aborting...' );
  185. }
  186. if ( this.logging.enabled ) {
  187. console.timeEnd( 'OBJLoader parse: ' + this.modelName );
  188. }
  189. return this.baseObject3d;
  190. };
  191. OBJLoader2.prototype._onAssetAvailable = function ( payload ) {
  192. if ( payload.cmd !== 'assetAvailable' ) return;
  193. if ( payload.type === 'mesh' ) {
  194. let meshes = this.meshReceiver.buildMeshes( payload );
  195. for ( let mesh of meshes ) {
  196. this.baseObject3d.add( mesh );
  197. }
  198. } else if ( payload.type === 'material' ) {
  199. this.materialHandler.addPayloadMaterials( payload );
  200. }
  201. };
  202. export { OBJLoader2 };