OBJMTLLoader.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /**
  2. * Loads a Wavefront .obj file with materials
  3. *
  4. * @author mrdoob / http://mrdoob.com/
  5. * @author angelxuanchang
  6. */
  7. THREE.OBJMTLLoader = function () {};
  8. THREE.OBJMTLLoader.prototype = {
  9. constructor: THREE.OBJMTLLoader,
  10. /**
  11. * Load a Wavefront OBJ file with materials (MTL file)
  12. *
  13. * Loading progress is indicated by the following events:
  14. * "load" event (successful loading): type = 'load', content = THREE.Object3D
  15. * "error" event (error loading): type = 'load', message
  16. * "progress" event (progress loading): type = 'progress', loaded, total
  17. *
  18. * If the MTL file cannot be loaded, then a MeshLambertMaterial is used as a default
  19. * @param url - Location of OBJ file to load
  20. * @param mtlfileurl - MTL file to load (optional, if not specified, attempts to use MTL specified in OBJ file)
  21. * @param options - Options on how to interpret the material (see THREE.MTLLoader.MaterialCreator )
  22. */
  23. load: function ( url, mtlfileurl, options ) {
  24. var scope = this;
  25. var xhr = new XMLHttpRequest();
  26. var mtlDone; // Is the MTL done (true if no MTL, error loading MTL, or MTL actually loaded)
  27. var obj3d; // Loaded model (from obj file)
  28. var materialsCreator; // Material creator is created when MTL file is loaded
  29. // Loader for MTL
  30. var mtlLoader = new THREE.MTLLoader( url.substr( 0, url.lastIndexOf( "/" ) + 1 ), options );
  31. mtlLoader.addEventListener( 'load', waitReady );
  32. mtlLoader.addEventListener( 'error', waitReady );
  33. // Try to load mtlfile
  34. if ( mtlfileurl ) {
  35. mtlLoader.load( mtlfileurl );
  36. mtlDone = false;
  37. } else {
  38. mtlDone = true;
  39. }
  40. function waitReady( event ) {
  41. if ( event.type === 'load' ) {
  42. if ( event.content instanceof THREE.MTLLoader.MaterialCreator ) {
  43. // MTL file is loaded
  44. mtlDone = true;
  45. materialsCreator = event.content;
  46. materialsCreator.preload();
  47. } else {
  48. // OBJ file is loaded
  49. if ( event.target.status === 200 || event.target.status === 0 ) {
  50. var objContent = event.target.responseText;
  51. if ( mtlfileurl ) {
  52. // Parse with passed in MTL file
  53. obj3d = scope.parse( objContent );
  54. } else {
  55. // No passed in MTL file, look for mtlfile in obj file
  56. obj3d = scope.parse( objContent, function( mtlfile ) {
  57. mtlDone = false;
  58. mtlLoader.load( mtlLoader.baseUrl + mtlfile );
  59. } );
  60. }
  61. } else {
  62. // Error loading OBJ file....
  63. scope.dispatchEvent( {
  64. type: 'error',
  65. message: 'Couldn\'t load URL [' + url + ']',
  66. response: event.target.responseText } );
  67. }
  68. }
  69. } else if ( event.type === 'error' ) {
  70. // MTL failed to load -- oh well, we will just not have material ...
  71. mtlDone = true;
  72. }
  73. if ( mtlDone && obj3d ) {
  74. // MTL file is loaded and OBJ file is loaded
  75. // Apply materials to model
  76. if ( materialsCreator ) {
  77. obj3d.traverse( function( object ) {
  78. if ( object instanceof THREE.Mesh ) {
  79. if ( object.material.name ) {
  80. var material = materialsCreator.create( object.material.name );
  81. if ( material ) {
  82. object.material = material;
  83. }
  84. }
  85. }
  86. } );
  87. }
  88. // Notify listeners
  89. scope.dispatchEvent( { type: 'load', content: obj3d } );
  90. }
  91. }
  92. xhr.addEventListener( 'load', waitReady, false );
  93. xhr.addEventListener( 'progress', function ( event ) {
  94. scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
  95. }, false );
  96. xhr.addEventListener( 'error', function () {
  97. scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
  98. }, false );
  99. xhr.open( 'GET', url, true );
  100. xhr.send( null );
  101. },
  102. /**
  103. * Parses loaded .obj file
  104. * @param data - content of .obj file
  105. * @param mtllibCallback - callback to handle mtllib declaration (optional)
  106. * @return {THREE.Object3D} - Object3D (with default material)
  107. */
  108. parse: function ( data, mtllibCallback ) {
  109. // fixes
  110. data = data.replace( /\ \\\r\n/g, '' ); // rhino adds ' \\r\n' some times.
  111. var replacement = '/f$1$2$4\n/f$2$3$4'; // quads to tris
  112. data = data.replace( /f( +\d+)( +\d+)( +\d+)( +\d+)/g, replacement );
  113. data = data.replace( /f( +\d+\/\d+)( +\d+\/\d+)( +\d+\/\d+)( +\d+\/\d+)/g, replacement );
  114. data = data.replace( /f( +\d+\/\d+\/\d+)( +\d+\/\d+\/\d+)( +\d+\/\d+\/\d+)( +\d+\/\d+\/\d+)/g, replacement );
  115. data = data.replace( /f( +\d+\/\/\d+)( +\d+\/\/\d+)( +\d+\/\/\d+)( +\d+\/\/\d+)/g, replacement );
  116. //
  117. function vector( x, y, z ) {
  118. return new THREE.Vector3( x, y, z );
  119. }
  120. function uv( u, v ) {
  121. return new THREE.Vector2( u, v );
  122. }
  123. function face3( a, b, c, normals ) {
  124. return new THREE.Face3( a, b, c, normals );
  125. }
  126. function meshN( meshName, materialName ) {
  127. if ( geometry.vertices.length > 0 ) {
  128. geometry.mergeVertices();
  129. geometry.computeCentroids();
  130. geometry.computeFaceNormals();
  131. geometry.computeBoundingSphere();
  132. object.add( mesh );
  133. geometry = new THREE.Geometry();
  134. mesh = new THREE.Mesh( geometry, material );
  135. verticesCount = 0;
  136. }
  137. if ( meshName !== undefined ) mesh.name = meshName;
  138. if ( materialName !== undefined ) {
  139. material = new THREE.MeshLambertMaterial();
  140. material.name = materialName;
  141. mesh.material = material;
  142. }
  143. }
  144. var group = new THREE.Object3D();
  145. var object = group;
  146. var geometry = new THREE.Geometry();
  147. var material = new THREE.MeshLambertMaterial();
  148. var mesh = new THREE.Mesh( geometry, material );
  149. var vertices = [];
  150. var verticesCount = 0;
  151. var normals = [];
  152. var uvs = [];
  153. // v float float float
  154. var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
  155. // vn float float float
  156. var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
  157. // vt float float
  158. var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/
  159. // f vertex vertex vertex
  160. var face_pattern1 = /f( +\d+)( +\d+)( +\d+)/
  161. // f vertex/uv vertex/uv vertex/uv
  162. var face_pattern2 = /f( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))/;
  163. // f vertex/uv/normal vertex/uv/normal vertex/uv/normal
  164. var face_pattern3 = /f( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))/;
  165. // f vertex//normal vertex//normal vertex//normal
  166. var face_pattern4 = /f( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))/;
  167. //
  168. var lines = data.split( "\n" );
  169. for ( var i = 0; i < lines.length; i ++ ) {
  170. var line = lines[ i ];
  171. line = line.trim();
  172. var result;
  173. if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
  174. continue;
  175. } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) {
  176. // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
  177. vertices.push( vector(
  178. parseFloat( result[ 1 ] ),
  179. parseFloat( result[ 2 ] ),
  180. parseFloat( result[ 3 ] )
  181. ) );
  182. } else if ( ( result = normal_pattern.exec( line ) ) !== null ) {
  183. // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
  184. normals.push( vector(
  185. parseFloat( result[ 1 ] ),
  186. parseFloat( result[ 2 ] ),
  187. parseFloat( result[ 3 ] )
  188. ) );
  189. } else if ( ( result = uv_pattern.exec( line ) ) !== null ) {
  190. // ["vt 0.1 0.2", "0.1", "0.2"]
  191. uvs.push( uv(
  192. parseFloat( result[ 1 ] ),
  193. parseFloat( result[ 2 ] )
  194. ) );
  195. } else if ( ( result = face_pattern1.exec( line ) ) !== null ) {
  196. // ["f 1 2 3", "1", "2", "3"]
  197. geometry.vertices.push(
  198. vertices[ parseInt( result[ 1 ] ) - 1 ],
  199. vertices[ parseInt( result[ 2 ] ) - 1 ],
  200. vertices[ parseInt( result[ 3 ] ) - 1 ]
  201. );
  202. geometry.faces.push( face3(
  203. verticesCount ++,
  204. verticesCount ++,
  205. verticesCount ++
  206. ) );
  207. } else if ( ( result = face_pattern2.exec( line ) ) !== null ) {
  208. // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3"]
  209. geometry.vertices.push(
  210. vertices[ parseInt( result[ 2 ] ) - 1 ],
  211. vertices[ parseInt( result[ 5 ] ) - 1 ],
  212. vertices[ parseInt( result[ 8 ] ) - 1 ]
  213. );
  214. geometry.faces.push( face3(
  215. verticesCount ++,
  216. verticesCount ++,
  217. verticesCount ++
  218. ) );
  219. geometry.faceVertexUvs[ 0 ].push( [
  220. uvs[ parseInt( result[ 3 ] ) - 1 ],
  221. uvs[ parseInt( result[ 6 ] ) - 1 ],
  222. uvs[ parseInt( result[ 9 ] ) - 1 ]
  223. ] );
  224. } else if ( ( result = face_pattern3.exec( line ) ) !== null ) {
  225. // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3"]
  226. geometry.vertices.push(
  227. vertices[ parseInt( result[ 2 ] ) - 1 ],
  228. vertices[ parseInt( result[ 6 ] ) - 1 ],
  229. vertices[ parseInt( result[ 10 ] ) - 1 ]
  230. );
  231. geometry.faces.push( face3(
  232. verticesCount ++,
  233. verticesCount ++,
  234. verticesCount ++,
  235. [
  236. normals[ parseInt( result[ 4 ] ) - 1 ],
  237. normals[ parseInt( result[ 8 ] ) - 1 ],
  238. normals[ parseInt( result[ 12 ] ) - 1 ]
  239. ]
  240. ) );
  241. geometry.faceVertexUvs[ 0 ].push( [
  242. uvs[ parseInt( result[ 3 ] ) - 1 ],
  243. uvs[ parseInt( result[ 7 ] ) - 1 ],
  244. uvs[ parseInt( result[ 11 ] ) - 1 ]
  245. ] );
  246. } else if ( ( result = face_pattern4.exec( line ) ) !== null ) {
  247. // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3"]
  248. geometry.vertices.push(
  249. vertices[ parseInt( result[ 2 ] ) - 1 ],
  250. vertices[ parseInt( result[ 5 ] ) - 1 ],
  251. vertices[ parseInt( result[ 8 ] ) - 1 ]
  252. );
  253. geometry.faces.push( face3(
  254. verticesCount ++,
  255. verticesCount ++,
  256. verticesCount ++,
  257. [
  258. normals[ parseInt( result[ 3 ] ) - 1 ],
  259. normals[ parseInt( result[ 6 ] ) - 1 ],
  260. normals[ parseInt( result[ 9 ] ) - 1 ]
  261. ]
  262. ) );
  263. } else if ( /^o /.test( line ) ) {
  264. // object
  265. object = new THREE.Object3D();
  266. object.name = line.substring( 2 ).trim();
  267. group.add( object );
  268. } else if ( /^g /.test( line ) ) {
  269. // group
  270. meshN( line.substring( 2 ).trim(), undefined );
  271. } else if ( /^usemtl /.test( line ) ) {
  272. // material
  273. meshN( undefined, line.substring( 7 ).trim() );
  274. } else if ( /^mtllib /.test( line ) ) {
  275. // mtl file
  276. if ( mtllibCallback ) {
  277. var mtlfile = line.substring( 7 );
  278. mtlfile = mtlfile.trim();
  279. mtllibCallback( mtlfile );
  280. }
  281. } else if ( /^s /.test( line ) ) {
  282. // Smooth shading
  283. } else {
  284. console.log( "THREE.OBJMTLLoader: Unhandled line " + line );
  285. }
  286. }
  287. //Add last object
  288. meshN(undefined, undefined);
  289. return group;
  290. }
  291. };
  292. THREE.EventDispatcher.prototype.apply( THREE.OBJMTLLoader.prototype );