OBJLoader2.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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. this.manager = ( manager !== undefined && manager !== null ) ? manager : DefaultLoadingManager;
  21. this.logging = {
  22. enabled: true,
  23. debug: false
  24. };
  25. this.modelName = '';
  26. this.instanceNo = 0;
  27. this.path = undefined;
  28. this.resourcePath = undefined;
  29. this.useIndices = false;
  30. this.disregardNormals = false;
  31. this.materialPerSmoothingGroup = false;
  32. this.useOAsMesh = false;
  33. this.baseObject3d = new Group();
  34. this.callbacks = {
  35. onParseProgress: undefined,
  36. genericErrorHandler: undefined
  37. };
  38. this.materialHandler = new MaterialHandler();
  39. this.meshReceiver = new MeshReceiver( this.materialHandler );
  40. };
  41. OBJLoader2.OBJLOADER2_VERSION = '3.0.0-beta2';
  42. console.info( 'Using OBJLoader2 version: ' + OBJLoader2.OBJLOADER2_VERSION );
  43. OBJLoader2.prototype = {
  44. constructor: OBJLoader2,
  45. /**
  46. * Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
  47. *
  48. * @param {boolean} enabled True or false.
  49. * @param {boolean} debug True or false.
  50. */
  51. setLogging: function ( enabled, debug ) {
  52. this.logging.enabled = enabled === true;
  53. this.logging.debug = debug === true;
  54. return this;
  55. },
  56. /**
  57. * Set the name of the model.
  58. *
  59. * @param {string} modelName
  60. */
  61. setModelName: function ( modelName ) {
  62. this.modelName = modelName ? modelName : this.modelName;
  63. return this;
  64. },
  65. /**
  66. * The URL of the base path.
  67. *
  68. * @param {string} path URL
  69. */
  70. setPath: function ( path ) {
  71. this.path = path ? path : this.path;
  72. return this;
  73. },
  74. /**
  75. * Allow to specify resourcePath for dependencies of specified resource.
  76. * @param {string} resourcePath
  77. */
  78. setResourcePath: function ( resourcePath ) {
  79. this.resourcePath = resourcePath ? resourcePath : this.resourcePath;
  80. },
  81. /**
  82. * Set the node where the loaded objects will be attached directly.
  83. *
  84. * @param {Object3D} baseObject3d Object already attached to scenegraph where new meshes will be attached to
  85. */
  86. setBaseObject3d: function ( baseObject3d ) {
  87. this.baseObject3d = ( baseObject3d === undefined || baseObject3d === null ) ? this.baseObject3d : baseObject3d;
  88. return this;
  89. },
  90. /**
  91. * Add materials as associated array.
  92. *
  93. * @param materials Object with named {@link Material}
  94. */
  95. addMaterials: function ( materials ) {
  96. this.materialHandler.addMaterials( materials );
  97. },
  98. /**
  99. * Instructs loaders to create indexed {@link BufferGeometry}.
  100. *
  101. * @param {boolean} useIndices=false
  102. */
  103. setUseIndices: function ( useIndices ) {
  104. this.useIndices = useIndices === true;
  105. return this;
  106. },
  107. /**
  108. * Tells whether normals should be completely disregarded and regenerated.
  109. *
  110. * @param {boolean} disregardNormals=false
  111. */
  112. setDisregardNormals: function ( disregardNormals ) {
  113. this.disregardNormals = disregardNormals === true;
  114. return this;
  115. },
  116. /**
  117. * Tells whether a material shall be created per smoothing group.
  118. *
  119. * @param {boolean} materialPerSmoothingGroup=false
  120. */
  121. setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) {
  122. this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
  123. return this;
  124. },
  125. /**
  126. * Usually 'o' is meta-information and does not result in creation of new meshes, but mesh creation on occurrence of "o" can be enforced.
  127. *
  128. * @param {boolean} useOAsMesh=false
  129. */
  130. setUseOAsMesh: function ( useOAsMesh ) {
  131. this.useOAsMesh = useOAsMesh === true;
  132. return this;
  133. },
  134. /**
  135. * Register an generic error handler that is called if available instead of throwing an exception
  136. * @param {Function} genericErrorHandler
  137. */
  138. setGenericErrorHandler: function ( genericErrorHandler ) {
  139. if ( genericErrorHandler !== undefined && genericErrorHandler !== null ) {
  140. this.callbacks.genericErrorHandler = genericErrorHandler;
  141. }
  142. },
  143. /**
  144. *
  145. * @private
  146. *
  147. * @param {Function} [onParseProgress]
  148. * @param {Function} [onMeshAlter]
  149. * @param {Function} [onLoadMaterials]
  150. * @private
  151. */
  152. _setCallbacks: function ( onParseProgress, onMeshAlter, onLoadMaterials ) {
  153. if ( onParseProgress !== undefined && onParseProgress !== null ) {
  154. this.callbacks.onParseProgress = onParseProgress;
  155. }
  156. this.meshReceiver._setCallbacks( onParseProgress, onMeshAlter );
  157. this.materialHandler._setCallbacks( onLoadMaterials );
  158. },
  159. /**
  160. * Announce feedback which is give to the registered callbacks.
  161. * @private
  162. *
  163. * @param {string} type The type of event
  164. * @param {string} text Textual description of the event
  165. * @param {number} numericalValue Numerical value describing the progress
  166. */
  167. _onProgress: function ( type, text, numericalValue ) {
  168. let message = text ? text : '';
  169. let event = {
  170. detail: {
  171. type: type,
  172. modelName: this.modelName,
  173. instanceNo: this.instanceNo,
  174. text: message,
  175. numericalValue: numericalValue
  176. }
  177. };
  178. if ( this.callbacks.onParseProgress ) {
  179. this.callbacks.onParseProgress( event );
  180. }
  181. if ( this.logging.enabled && this.logging.debug ) {
  182. console.log( message );
  183. }
  184. },
  185. /**
  186. * Announce error feedback which is given to the generic error handler to the registered callbacks.
  187. * @private
  188. *
  189. * @param {String} errorMessage The event containing the error
  190. */
  191. _onError: function ( errorMessage ) {
  192. if ( this.callbacks.genericErrorHandler ) {
  193. this.callbacks.genericErrorHandler( errorMessage );
  194. }
  195. if ( this.logging.enabled && this.logging.debug ) {
  196. console.log( errorMessage );
  197. }
  198. },
  199. /**
  200. * Use this convenient method to load a file at the given URL. By default the fileLoader uses an ArrayBuffer.
  201. *
  202. * @param {string} url A string containing the path/URL of the file to be loaded.
  203. * @param {function} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument.
  204. * @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.
  205. * @param {function} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
  206. * @param {function} [onMeshAlter] Called after every single mesh is made available by the parser
  207. */
  208. load: function ( url, onLoad, onFileLoadProgress, onError, onMeshAlter ) {
  209. let scope = this;
  210. if ( onError === null || onError === undefined ) {
  211. onError = function ( event ) {
  212. let errorMessage = event;
  213. if ( event.currentTarget && event.currentTarget.statusText !== null ) {
  214. errorMessage = 'Error occurred while downloading!\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText;
  215. }
  216. scope._onError( errorMessage );
  217. };
  218. }
  219. if ( ! url ) {
  220. onError( 'An invalid url was provided. Unable to continue!' );
  221. }
  222. let urlFull = new URL( url, window.location.href ).href;
  223. let filename = urlFull;
  224. let urlParts = urlFull.split( '/' );
  225. if ( urlParts.length > 2 ) {
  226. filename = urlParts[ urlParts.length - 1 ];
  227. let urlPartsPath = urlParts.slice( 0, urlParts.length - 1 ).join( '/' ) + '/';
  228. if ( urlPartsPath !== undefined && urlPartsPath !== null ) this.path = urlPartsPath;
  229. }
  230. if ( onFileLoadProgress === null || onFileLoadProgress === undefined ) {
  231. let numericalValueRef = 0;
  232. let numericalValue = 0;
  233. onFileLoadProgress = function ( event ) {
  234. if ( ! event.lengthComputable ) return;
  235. numericalValue = event.loaded / event.total;
  236. if ( numericalValue > numericalValueRef ) {
  237. numericalValueRef = numericalValue;
  238. let output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
  239. scope._onProgress( 'progressLoad', output, numericalValue );
  240. }
  241. };
  242. }
  243. this._setCallbacks( null, onMeshAlter, null );
  244. let fileLoaderOnLoad = function ( content ) {
  245. onLoad( scope.parse( content ) );
  246. };
  247. let fileLoader = new FileLoader( this.manager );
  248. fileLoader.setPath( this.path || this.resourcePath );
  249. fileLoader.setResponseType( 'arraybuffer' );
  250. fileLoader.load( filename, fileLoaderOnLoad, onFileLoadProgress, onError );
  251. },
  252. /**
  253. * Parses OBJ data synchronously from arraybuffer or string.
  254. *
  255. * @param {arraybuffer|string} content OBJ data as Uint8Array or String
  256. */
  257. parse: function ( content ) {
  258. // fast-fail in case of illegal data
  259. if ( content === null || content === undefined ) {
  260. throw 'Provided content is not a valid ArrayBuffer or String. Unable to continue parsing';
  261. }
  262. if ( this.logging.enabled ) {
  263. console.time( 'OBJLoader parse: ' + this.modelName );
  264. }
  265. let parser = new OBJLoader2Parser();
  266. parser.setLogging( this.logging.enabled, this.logging.debug );
  267. parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
  268. parser.setUseOAsMesh( this.useOAsMesh );
  269. parser.setUseIndices( this.useIndices );
  270. parser.setDisregardNormals( this.disregardNormals );
  271. // sync code works directly on the material references
  272. parser.setMaterials( this.materialHandler.getMaterials() );
  273. let scope = this;
  274. let scopedOnAssetAvailable = function ( payload ) {
  275. scope._onAssetAvailable( payload );
  276. };
  277. let onProgressScoped = function ( text, numericalValue ) {
  278. scope._onProgress( 'progressParse', text, numericalValue );
  279. };
  280. let onErrorScoped = function ( message ) {
  281. scope._onError( message );
  282. };
  283. parser.setCallbackOnAssetAvailable( scopedOnAssetAvailable );
  284. parser.setCallbackOnProgress( onProgressScoped );
  285. parser.setCallbackOnError( onErrorScoped );
  286. if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
  287. if ( this.logging.enabled ) console.info( 'Parsing arrayBuffer...' );
  288. parser.parse( content );
  289. } else if ( typeof ( content ) === 'string' || content instanceof String ) {
  290. if ( this.logging.enabled ) console.info( 'Parsing text...' );
  291. parser.parseText( content );
  292. } else {
  293. scope._onError( 'Provided content was neither of type String nor Uint8Array! Aborting...' );
  294. }
  295. if ( this.logging.enabled ) {
  296. console.timeEnd( 'OBJLoader parse: ' + this.modelName );
  297. }
  298. return this.baseObject3d;
  299. },
  300. _onAssetAvailable: function ( payload ) {
  301. if ( payload.cmd !== 'assetAvailable' ) return;
  302. if ( payload.type === 'mesh' ) {
  303. let meshes = this.meshReceiver.buildMeshes( payload );
  304. for ( let mesh of meshes ) {
  305. this.baseObject3d.add( mesh );
  306. }
  307. } else if ( payload.type === 'material' ) {
  308. this.materialHandler.addPayloadMaterials( payload );
  309. }
  310. }
  311. };
  312. export { OBJLoader2 };