MTLLoader.js
1 /** 2 * Loads a Wavefront .mtl file specifying materials 3 * 4 * @author angelxuanchang 5 */ 6 7 THREE.MTLLoader = function( baseUrl, options, crossOrigin ) { 8 9 this.baseUrl = baseUrl; 10 this.options = options; 11 this.crossOrigin = crossOrigin; 12 13 }; 14 15 THREE.MTLLoader.prototype = { 16 17 constructor: THREE.MTLLoader, 18 19 load: function ( url, onLoad, onProgress, onError ) { 20 21 var scope = this; 22 23 var loader = new THREE.XHRLoader(); 24 loader.setCrossOrigin( this.crossOrigin ); 25 loader.load( url, function ( text ) { 26 27 onLoad( scope.parse( text ) ); 28 29 }, onProgress, onError ); 30 31 }, 32 33 /** 34 * Parses loaded MTL file 35 * @param text - Content of MTL file 36 * @return {THREE.MTLLoader.MaterialCreator} 37 */ 38 parse: function ( text ) { 39 40 var lines = text.split( "\n" ); 41 var info = {}; 42 var delimiter_pattern = /\s+/; 43 var materialsInfo = {}; 44 45 for ( var i = 0; i < lines.length; i ++ ) { 46 47 var line = lines[ i ]; 48 line = line.trim(); 49 50 if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 51 52 // Blank line or comment ignore 53 continue; 54 55 } 56 57 var pos = line.indexOf( ' ' ); 58 59 var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; 60 key = key.toLowerCase(); 61 62 var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ""; 63 value = value.trim(); 64 65 if ( key === "newmtl" ) { 66 67 // New material 68 69 info = { name: value }; 70 materialsInfo[ value ] = info; 71 72 } else if ( info ) { 73 74 if ( key === "ka" || key === "kd" || key === "ks" ) { 75 76 var ss = value.split( delimiter_pattern, 3 ); 77 info[ key ] = [ parseFloat( ss[0] ), parseFloat( ss[1] ), parseFloat( ss[2] ) ]; 78 79 } else { 80 81 info[ key ] = value; 82 83 } 84 85 } 86 87 } 88 89 var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.options ); 90 materialCreator.crossOrigin = this.crossOrigin 91 materialCreator.setMaterials( materialsInfo ); 92 return materialCreator; 93 94 } 95 96 }; 97 98 /** 99 * Create a new THREE-MTLLoader.MaterialCreator 100 * @param baseUrl - Url relative to which textures are loaded 101 * @param options - Set of options on how to construct the materials 102 * side: Which side to apply the material 103 * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 104 * wrap: What type of wrapping to apply for textures 105 * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 106 * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 107 * Default: false, assumed to be already normalized 108 * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 109 * Default: false 110 * invertTransparency: If transparency need to be inverted (inversion is needed if d = 0 is fully opaque) 111 * Default: false (d = 1 is fully opaque) 112 * @constructor 113 */ 114 115 THREE.MTLLoader.MaterialCreator = function( baseUrl, options ) { 116 117 this.baseUrl = baseUrl; 118 this.options = options; 119 this.materialsInfo = {}; 120 this.materials = {}; 121 this.materialsArray = []; 122 this.nameLookup = {}; 123 124 this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; 125 this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; 126 127 }; 128 129 THREE.MTLLoader.MaterialCreator.prototype = { 130 131 constructor: THREE.MTLLoader.MaterialCreator, 132 133 setMaterials: function( materialsInfo ) { 134 135 this.materialsInfo = this.convert( materialsInfo ); 136 this.materials = {}; 137 this.materialsArray = []; 138 this.nameLookup = {}; 139 140 }, 141 142 convert: function( materialsInfo ) { 143 144 if ( !this.options ) return materialsInfo; 145 146 var converted = {}; 147 148 for ( var mn in materialsInfo ) { 149 150 // Convert materials info into normalized form based on options 151 152 var mat = materialsInfo[ mn ]; 153 154 var covmat = {}; 155 156 converted[ mn ] = covmat; 157 158 for ( var prop in mat ) { 159 160 var save = true; 161 var value = mat[ prop ]; 162 var lprop = prop.toLowerCase(); 163 164 switch ( lprop ) { 165 166 case 'kd': 167 case 'ka': 168 case 'ks': 169 170 // Diffuse color (color under white light) using RGB values 171 172 if ( this.options && this.options.normalizeRGB ) { 173 174 value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; 175 176 } 177 178 if ( this.options && this.options.ignoreZeroRGBs ) { 179 180 if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 1 ] === 0 ) { 181 182 // ignore 183 184 save = false; 185 186 } 187 } 188 189 break; 190 191 case 'd': 192 193 // According to MTL format (http://paulbourke.net/dataformats/mtl/): 194 // d is dissolve for current material 195 // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) 196 197 if ( this.options && this.options.invertTransparency ) { 198 199 value = 1 - value; 200 201 } 202 203 break; 204 205 default: 206 207 break; 208 } 209 210 if ( save ) { 211 212 covmat[ lprop ] = value; 213 214 } 215 216 } 217 218 } 219 220 return converted; 221 222 }, 223 224 preload: function () { 225 226 for ( var mn in this.materialsInfo ) { 227 228 this.create( mn ); 229 230 } 231 232 }, 233 234 getIndex: function( materialName ) { 235 236 return this.nameLookup[ materialName ]; 237 238 }, 239 240 getAsArray: function() { 241 242 var index = 0; 243 244 for ( var mn in this.materialsInfo ) { 245 246 this.materialsArray[ index ] = this.create( mn ); 247 this.nameLookup[ mn ] = index; 248 index ++; 249 250 } 251 252 return this.materialsArray; 253 254 }, 255 256 create: function ( materialName ) { 257 258 if ( this.materials[ materialName ] === undefined ) { 259 260 this.createMaterial_( materialName ); 261 262 } 263 264 return this.materials[ materialName ]; 265 266 }, 267 268 createMaterial_: function ( materialName ) { 269 270 // Create material 271 272 var mat = this.materialsInfo[ materialName ]; 273 var params = { 274 275 name: materialName, 276 side: this.side 277 278 }; 279 280 for ( var prop in mat ) { 281 282 var value = mat[ prop ]; 283 284 switch ( prop.toLowerCase() ) { 285 286 // Ns is material specular exponent 287 288 case 'kd': 289 290 // Diffuse color (color under white light) using RGB values 291 292 params[ 'diffuse' ] = new THREE.Color().fromArray( value ); 293 294 break; 295 296 case 'ka': 297 298 // Ambient color (color under shadow) using RGB values 299 300 break; 301 302 case 'ks': 303 304 // Specular color (color when light is reflected from shiny surface) using RGB values 305 params[ 'specular' ] = new THREE.Color().fromArray( value ); 306 307 break; 308 309 case 'map_kd': 310 311 // Diffuse texture map 312 313 params[ 'map' ] = this.loadTexture( this.baseUrl + value ); 314 params[ 'map' ].wrapS = this.wrap; 315 params[ 'map' ].wrapT = this.wrap; 316 317 break; 318 319 case 'ns': 320 321 // The specular exponent (defines the focus of the specular highlight) 322 // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 323 324 params['shininess'] = value; 325 326 break; 327 328 case 'd': 329 330 // According to MTL format (http://paulbourke.net/dataformats/mtl/): 331 // d is dissolve for current material 332 // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) 333 334 if ( value < 1 ) { 335 336 params['transparent'] = true; 337 params['opacity'] = value; 338 339 } 340 341 break; 342 343 case 'map_bump': 344 case 'bump': 345 346 // Bump texture map 347 348 if ( params[ 'bumpMap' ] ) break; // Avoid loading twice. 349 350 params[ 'bumpMap' ] = this.loadTexture( this.baseUrl + value ); 351 params[ 'bumpMap' ].wrapS = this.wrap; 352 params[ 'bumpMap' ].wrapT = this.wrap; 353 354 break; 355 356 default: 357 break; 358 359 } 360 361 } 362 363 if ( params[ 'diffuse' ] ) { 364 365 params[ 'color' ] = params[ 'diffuse' ]; 366 367 } 368 369 this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); 370 return this.materials[ materialName ]; 371 372 }, 373 374 375 loadTexture: function ( url, mapping, onLoad, onError ) { 376 377 var texture; 378 var loader = THREE.Loader.Handlers.get( url ); 379 380 if ( loader !== null ) { 381 382 texture = loader.load( url, onLoad ); 383 384 } else { 385 386 texture = new THREE.Texture(); 387 388 loader = new THREE.ImageLoader(); 389 loader.crossOrigin = this.crossOrigin; 390 loader.load( url, function ( image ) { 391 392 texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image ); 393 texture.needsUpdate = true; 394 395 if ( onLoad ) onLoad( texture ); 396 397 } ); 398 399 } 400 401 if ( mapping !== undefined ) texture.mapping = mapping; 402 403 return texture; 404 405 } 406 407 }; 408 409 THREE.MTLLoader.ensurePowerOfTwo_ = function ( image ) { 410 411 if ( ! THREE.Math.isPowerOfTwo( image.width ) || ! THREE.Math.isPowerOfTwo( image.height ) ) { 412 413 var canvas = document.createElement( "canvas" ); 414 canvas.width = THREE.MTLLoader.nextHighestPowerOfTwo_( image.width ); 415 canvas.height = THREE.MTLLoader.nextHighestPowerOfTwo_( image.height ); 416 417 var ctx = canvas.getContext("2d"); 418 ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); 419 return canvas; 420 421 } 422 423 return image; 424 425 }; 426 427 THREE.MTLLoader.nextHighestPowerOfTwo_ = function( x ) { 428 429 -- x; 430 431 for ( var i = 1; i < 32; i <<= 1 ) { 432 433 x = x | x >> i; 434 435 } 436 437 return x + 1; 438 439 }; 440 441 THREE.EventDispatcher.prototype.apply( THREE.MTLLoader.prototype );