OBJLoader2.js 9.3 KB

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