OBJLoader2.js 9.8 KB

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