MTLLoader.js 11.2 KB
Newer Older
1 2 3 4 5 6
/**
 * Loads a Wavefront .mtl file specifying materials
 *
 * @author angelxuanchang
 */

7
THREE.MTLLoader = function ( manager ) {
8

9
	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
10

11 12
};

13 14 15
THREE.MTLLoader.prototype = {

	constructor: THREE.MTLLoader,
16

17 18
	crossOrigin: 'anonymous',

J
Jonne Nauha 已提交
19 20 21 22 23 24 25 26
	/**
	 * Loads and parses a MTL asset from a URL.
	 *
	 * @param {String} url - URL to the MTL file.
	 * @param {Function} [onLoad] - Callback invoked with the loaded object.
	 * @param {Function} [onProgress] - Callback for download progress.
	 * @param {Function} [onError] - Callback for download errors.
	 *
27
	 * @see setPath setResourcePath
J
Jonne Nauha 已提交
28 29
	 *
	 * @note In order for relative texture references to resolve correctly
30
	 * you must call setResourcePath() explicitly prior to load.
J
Jonne Nauha 已提交
31
	 */
32
	load: function ( url, onLoad, onProgress, onError ) {
33

34
		var scope = this;
35

36 37
		var path = ( this.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;

38
		var loader = new THREE.FileLoader( this.manager );
M
Mr.doob 已提交
39
		loader.setPath( this.path );
40
		loader.load( url, function ( text ) {
41

42
			onLoad( scope.parse( text, path ) );
43

44
		}, onProgress, onError );
45

46
	},
47

J
Jonne Nauha 已提交
48 49 50 51
	/**
	 * Set base path for resolving references.
	 * If set this path will be prepended to each loaded and found reference.
	 *
52
	 * @see setResourcePath
J
Jonne Nauha 已提交
53
	 * @param {String} path
W
WestLangley 已提交
54
	 * @return {THREE.MTLLoader}
J
Jonne Nauha 已提交
55 56 57 58 59 60 61 62
	 *
	 * @example
	 *     mtlLoader.setPath( 'assets/obj/' );
	 *     mtlLoader.load( 'my.mtl', ... );
	 */
	setPath: function ( path ) {

		this.path = path;
W
WestLangley 已提交
63
		return this;
J
Jonne Nauha 已提交
64 65 66 67

	},

	/**
68
	 * Set base path for additional resources like textures.
J
Jonne Nauha 已提交
69 70 71
	 *
	 * @see setPath
	 * @param {String} path
W
WestLangley 已提交
72
	 * @return {THREE.MTLLoader}
J
Jonne Nauha 已提交
73 74 75
	 *
	 * @example
	 *     mtlLoader.setPath( 'assets/obj/' );
76
	 *     mtlLoader.setResourcePath( 'assets/textures/' );
J
Jonne Nauha 已提交
77 78
	 *     mtlLoader.load( 'my.mtl', ... );
	 */
79
	setResourcePath: function ( path ) {
M
Mr.doob 已提交
80

81
		this.resourcePath = path;
W
WestLangley 已提交
82
		return this;
M
Mr.doob 已提交
83 84 85

	},

86
	setTexturePath: function ( path ) {
M
Mr.doob 已提交
87

88 89
		console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' );
		return this.setResourcePath( path );
90 91 92 93 94 95

	},

	setCrossOrigin: function ( value ) {

		this.crossOrigin = value;
W
WestLangley 已提交
96
		return this;
97 98 99 100 101 102

	},

	setMaterialOptions: function ( value ) {

		this.materialOptions = value;
W
WestLangley 已提交
103
		return this;
104 105 106

	},

107
	/**
J
Jonne Nauha 已提交
108 109 110
	 * Parses a MTL file.
	 *
	 * @param {String} text - Content of MTL file
111
	 * @return {THREE.MTLLoader.MaterialCreator}
J
Jonne Nauha 已提交
112
	 *
113
	 * @see setPath setResourcePath
J
Jonne Nauha 已提交
114 115
	 *
	 * @note In order for relative texture references to resolve correctly
116
	 * you must call setResourcePath() explicitly prior to parse.
117
	 */
118
	parse: function ( text, path ) {
119

J
Jonne Nauha 已提交
120
		var lines = text.split( '\n' );
121 122 123
		var info = {};
		var delimiter_pattern = /\s+/;
		var materialsInfo = {};
124

125
		for ( var i = 0; i < lines.length; i ++ ) {
126 127

			var line = lines[ i ];
128
			line = line.trim();
129

130
			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
131

132 133
				// Blank line or comment ignore
				continue;
134

135
			}
136

137
			var pos = line.indexOf( ' ' );
138

139
			var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
140
			key = key.toLowerCase();
141

J
Jonne Nauha 已提交
142
			var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
143
			value = value.trim();
144

J
Jonne Nauha 已提交
145
			if ( key === 'newmtl' ) {
146

147
				// New material
148

149 150
				info = { name: value };
				materialsInfo[ value ] = info;
151

M
Mugen87 已提交
152
			} else {
153

M
Mugen87 已提交
154
				if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
155

156
					var ss = value.split( delimiter_pattern, 3 );
G
gero3 已提交
157
					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
158

159
				} else {
160

161
					info[ key ] = value;
162

163
				}
164

165
			}
166

167
		}
168

169
		var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
170 171
		materialCreator.setCrossOrigin( this.crossOrigin );
		materialCreator.setManager( this.manager );
172 173
		materialCreator.setMaterials( materialsInfo );
		return materialCreator;
174

175
	}
176

177
};
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

/**
 * Create a new THREE-MTLLoader.MaterialCreator
 * @param baseUrl - Url relative to which textures are loaded
 * @param options - Set of options on how to construct the materials
 *                  side: Which side to apply the material
 *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
 *                  wrap: What type of wrapping to apply for textures
 *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
 *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
 *                                Default: false, assumed to be already normalized
 *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
 *                                  Default: false
 * @constructor
 */
193

194
THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {
195

J
Jonne Nauha 已提交
196
	this.baseUrl = baseUrl || '';
197 198 199 200 201
	this.options = options;
	this.materialsInfo = {};
	this.materials = {};
	this.materialsArray = [];
	this.nameLookup = {};
202

G
gero3 已提交
203 204
	this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
	this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;
205

206 207 208
};

THREE.MTLLoader.MaterialCreator.prototype = {
209

210 211
	constructor: THREE.MTLLoader.MaterialCreator,

212
	crossOrigin: 'anonymous',
213

214 215 216
	setCrossOrigin: function ( value ) {

		this.crossOrigin = value;
217
		return this;
218 219 220 221 222 223 224 225 226

	},

	setManager: function ( value ) {

		this.manager = value;

	},

227
	setMaterials: function ( materialsInfo ) {
228

229 230 231 232
		this.materialsInfo = this.convert( materialsInfo );
		this.materials = {};
		this.materialsArray = [];
		this.nameLookup = {};
233

234
	},
235

236
	convert: function ( materialsInfo ) {
237

G
gero3 已提交
238
		if ( ! this.options ) return materialsInfo;
239

240
		var converted = {};
241

242
		for ( var mn in materialsInfo ) {
243

244
			// Convert materials info into normalized form based on options
245

246
			var mat = materialsInfo[ mn ];
247

248
			var covmat = {};
249

250
			converted[ mn ] = covmat;
251

252
			for ( var prop in mat ) {
253

254 255 256
				var save = true;
				var value = mat[ prop ];
				var lprop = prop.toLowerCase();
257

258
				switch ( lprop ) {
259

260 261 262
					case 'kd':
					case 'ka':
					case 'ks':
263

264
						// Diffuse color (color under white light) using RGB values
265

266
						if ( this.options && this.options.normalizeRGB ) {
267

268
							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
269

270
						}
271

272
						if ( this.options && this.options.ignoreZeroRGBs ) {
273

274
							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
275

276
								// ignore
277

278
								save = false;
279

280
							}
G
gero3 已提交
281

282
						}
283

284
						break;
285

286
					default:
287

288
						break;
289

290
				}
291

292
				if ( save ) {
293

294
					covmat[ lprop ] = value;
295

296
				}
297

298
			}
299

300
		}
301

302
		return converted;
303

304
	},
305

306
	preload: function () {
307

308
		for ( var mn in this.materialsInfo ) {
309

310
			this.create( mn );
311

312
		}
313

314
	},
315

316
	getIndex: function ( materialName ) {
317

318
		return this.nameLookup[ materialName ];
319

320
	},
321

322
	getAsArray: function () {
323

324
		var index = 0;
325

326
		for ( var mn in this.materialsInfo ) {
327

328 329 330
			this.materialsArray[ index ] = this.create( mn );
			this.nameLookup[ mn ] = index;
			index ++;
331

332
		}
333

334
		return this.materialsArray;
335

336
	},
337

338
	create: function ( materialName ) {
339

340
		if ( this.materials[ materialName ] === undefined ) {
341

342
			this.createMaterial_( materialName );
343

344
		}
345

346
		return this.materials[ materialName ];
347

348
	},
349

350
	createMaterial_: function ( materialName ) {
351

352
		// Create material
353

354
		var scope = this;
355 356
		var mat = this.materialsInfo[ materialName ];
		var params = {
357

358 359
			name: materialName,
			side: this.side
360

361
		};
362

363
		function resolveURL( baseUrl, url ) {
J
Jonne Nauha 已提交
364 365 366 367 368

			if ( typeof url !== 'string' || url === '' )
				return '';

			// Absolute URL
369
			if ( /^https?:\/\//i.test( url ) ) return url;
J
Jonne Nauha 已提交
370 371

			return baseUrl + url;
372 373 374 375

		}

		function setMapForType( mapType, value ) {
376 377 378 379 380

			if ( params[ mapType ] ) return; // Keep the first encountered texture

			var texParams = scope.getTextureParams( value, params );
			var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
381

382 383 384 385 386
			map.repeat.copy( texParams.scale );
			map.offset.copy( texParams.offset );

			map.wrapS = scope.wrap;
			map.wrapT = scope.wrap;
387

388
			params[ mapType ] = map;
389

390
		}
J
Jonne Nauha 已提交
391

392
		for ( var prop in mat ) {
393

394
			var value = mat[ prop ];
395
			var n;
396

M
Mr.doob 已提交
397
			if ( value === '' ) continue;
J
Jens Arps 已提交
398

399
			switch ( prop.toLowerCase() ) {
400

401
				// Ns is material specular exponent
402

403
				case 'kd':
404 405 406

					// Diffuse color (color under white light) using RGB values

J
Jonne Nauha 已提交
407
					params.color = new THREE.Color().fromArray( value );
408 409 410

					break;

411
				case 'ks':
412

413
					// Specular color (color when light is reflected from shiny surface) using RGB values
J
Jonne Nauha 已提交
414
					params.specular = new THREE.Color().fromArray( value );
415

416
					break;
417

418 419 420 421 422 423 424
				case 'ke':

					// Emissive using RGB values
					params.emissive = new THREE.Color().fromArray( value );

					break;

425
				case 'map_kd':
426

427
					// Diffuse texture map
428

429
					setMapForType( "map", value );
430 431 432

					break;

D
David 已提交
433 434 435
				case 'map_ks':

					// Specular map
436

437
					setMapForType( "specularMap", value );
D
David 已提交
438

439
					break;
D
David 已提交
440

441 442 443 444 445 446 447 448
				case 'map_ke':

					// Emissive map

					setMapForType( "emissiveMap", value );

					break;

449 450 451 452 453 454
				case 'norm':

					setMapForType( "normalMap", value );

					break;

455 456 457
				case 'map_bump':
				case 'bump':

458 459
					// Bump texture map

460
					setMapForType( "bumpMap", value );
D
David 已提交
461 462 463

					break;

W
WestLangley 已提交
464 465 466 467 468 469 470 471 472
				case 'map_d':

					// Alpha map

					setMapForType( "alphaMap", value );
					params.transparent = true;

					break;

473
				case 'ns':
474

475 476
					// The specular exponent (defines the focus of the specular highlight)
					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
477

J
Jonne Nauha 已提交
478
					params.shininess = parseFloat( value );
479 480 481

					break;

482
				case 'd':
M
Mugen87 已提交
483
					n = parseFloat( value );
484

485
					if ( n < 1 ) {
486

487
						params.opacity = n;
J
Jonne Nauha 已提交
488
						params.transparent = true;
489 490 491 492 493

					}

					break;

494
				case 'tr':
M
Mugen87 已提交
495
					n = parseFloat( value );
496

497
					if ( this.options && this.options.invertTrProperty ) n = 1 - n;
498

W
Wentao Lyu 已提交
499
					if ( n > 0 ) {
500

W
Wentao Lyu 已提交
501
						params.opacity = 1 - n;
J
Jonne Nauha 已提交
502
						params.transparent = true;
503

504
					}
505

506
					break;
507

508 509
				default:
					break;
510

511
			}
512

513
		}
514

515 516
		this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
		return this.materials[ materialName ];
517

518 519
	},

520
	getTextureParams: function ( value, matParams ) {
521 522 523 524

		var texParams = {

			scale: new THREE.Vector2( 1, 1 ),
525
			offset: new THREE.Vector2( 0, 0 )
526 527 528

		 };

529
		var items = value.split( /\s+/ );
530 531
		var pos;

532 533 534
		pos = items.indexOf( '-bm' );

		if ( pos >= 0 ) {
535

536
			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
537 538 539 540
			items.splice( pos, 2 );

		}

541
		pos = items.indexOf( '-s' );
542

543 544 545
		if ( pos >= 0 ) {

			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
546 547 548 549
			items.splice( pos, 4 ); // we expect 3 parameters here!

		}

550 551 552
		pos = items.indexOf( '-o' );

		if ( pos >= 0 ) {
553

554
			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
555 556 557 558
			items.splice( pos, 4 ); // we expect 3 parameters here!

		}

559
		texParams.url = items.join( ' ' ).trim();
560
		return texParams;
561

562
	},
563

564
	loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
565

M
Mr.doob 已提交
566 567
		var texture;
		var loader = THREE.Loader.Handlers.get( url );
568
		var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
569

M
Mr.doob 已提交
570
		if ( loader === null ) {
571

M
Mr.doob 已提交
572
			loader = new THREE.TextureLoader( manager );
573

574 575
		}

M
Mr.doob 已提交
576
		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
M
Mr.doob 已提交
577 578
		texture = loader.load( url, onLoad, onProgress, onError );

M
Mr.doob 已提交
579
		if ( mapping !== undefined ) texture.mapping = mapping;
M
Mr.doob 已提交
580

581
		return texture;
582

583
	}
584 585

};