Commit cad1f76c authored by alteredq's avatar alteredq

Merge remote-tracking branch 'remotes/angelxuanchang/master' into new-loaders

parents 72859982 d9c7e685
/**
* Loads a Wavefront .mtl file specifying materials
*
* @author angelxuanchang
*/
THREE.MTLLoader = function(baseUrl, options) {
THREE.EventTarget.call( this );
this.baseUrl = baseUrl;
this.options = options;
};
THREE.MTLLoader.prototype = {
/**
* Loads a MTL file
*
* Loading progress is indicated by the following events:
* "load" event (successful loading): type = 'load', content = THREE.MTLLoader.MaterialCreator
* "error" event (error loading): type = 'load', message
* "progress" event (progress loading): type = 'progress', loaded, total
*
* @param url - location of MTL file
*/
load: function(url) {
var scope = this;
var xhr = new XMLHttpRequest();
function onloaded(event) {
if (event.target.status === 200 || event.target.status === 0) {
var materialCreator = scope.parse(event.target.responseText);
// Notify caller, that I'm done
scope.dispatchEvent( { type: 'load', content: materialCreator } );
} else {
scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']',
response: event.target.responseText } );
}
}
xhr.addEventListener( 'load', onloaded, false );
xhr.addEventListener( 'progress', function ( event ) {
scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
}, false );
xhr.addEventListener( 'error', function () {
scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
}, false );
xhr.open( 'GET', url, true );
xhr.send( null );
},
/**
* Parses loaded MTL file
* @param text - Content of MTL file
* @return {THREE.MTLLoader.MaterialCreator}
*/
parse: function(text) {
var lines = text.split("\n");
var info = {};
var delimiter_pattern = /\s+/;
var materialsInfo = {};
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
line = $.trim(line);
if (line.length == 0 || line.charAt(0) == '#') {
// Blank line or comment ignore
continue;
}
var pos = line.indexOf(' ');
var key = (pos >= 0)? line.substring(0,pos):line;
key = key.toLowerCase();
var value = (pos >= 0)? line.substring(pos+1):"";
value = $.trim(value);
if (key === "newmtl") {
// New material
info = { name: value };
materialsInfo[value] = info;
} else if (info) {
if (key === "ka" || key === "kd" || key === "ks") {
var ss = value.split(delimiter_pattern, 3);
info[key] = [ parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];
} else {
info[key] = value;
}
}
}
var materialCreator = new THREE.MTLLoader.MaterialCreator(this.baseUrl, this.options);
materialCreator.setMaterials(materialsInfo);
return materialCreator;
}
};
/**
* 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
* invertTransparency: If transparency need to be inverted (inversion is needed if d = 0 is fully opaque)
* Default: false (d = 1 is fully opaque)
* @constructor
*/
THREE.MTLLoader.MaterialCreator = function(baseUrl, options) {
THREE.EventTarget.call( this );
this.baseUrl = baseUrl;
this.options = options;
this.materialsInfo = {};
this.materials = {};
this.materialsArray = [];
this.nameLookup = {};
this.side = (this.options && this.options.side)? this.options.side: THREE.FrontSide;
this.wrap = (this.options && this.options.wrap)? this.options.wrap: THREE.RepeatWrapping;
};
THREE.MTLLoader.MaterialCreator.prototype = {
setMaterials: function(materialsInfo) {
this.materialsInfo = this.convert(materialsInfo);
this.materials = {};
this.materialsArray = [];
this.nameLookup = {};
},
convert: function(materialsInfo) {
if (!this.options) return materialsInfo;
var converted = {};
for (var mn in materialsInfo) {
// Convert materials info into normalized form based on options
var mat = materialsInfo[mn];
var covmat = {};
converted[mn] = covmat;
for (var prop in mat) {
var save = true;
var value = mat[prop];
var lprop = prop.toLowerCase();
switch (lprop) {
case 'kd':
case 'ka':
case 'ks':
// Diffuse color (color under white light) using RGB values
if (this.options && this.options.normalizeRGB) {
value = [ value[0]/255, value[1]/255, value[2]/255 ];
}
if (this.options && this.options.ignoreZeroRGBs) {
if (value[0] === 0.0 && value[1] === 0.0 && value[1] === 0.0) {
// ignore
save = false;
}
}
break;
case 'd':
// According to MTL format (http://paulbourke.net/dataformats/mtl/):
// d is dissolve for current material
// factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent)
if (this.options && this.options.invertTransparency) {
value = 1 - value;
}
break;
default:
break;
}
if (save) {
covmat[lprop] = value;
}
}
}
return converted;
},
preload: function () {
for (var mn in this.materialsInfo) {
this.create(mn);
}
},
getIndex: function(materialName) {
return this.nameLookup[materialName];
},
getAsArray: function() {
var index = 0;
for (var mn in this.materialsInfo) {
this.materialsArray[index] = this.create(mn);
this.nameLookup[mn] = index;
index++;
}
return this.materialsArray;
},
create: function (materialName) {
if (this.materials[materialName] == undefined) {
this.createMaterial_(materialName);
}
return this.materials[materialName];
},
createMaterial_: function (materialName) {
// Create material
var mat = this.materialsInfo[materialName];
var params = {
name: materialName,
side: this.side
};
for (var prop in mat) {
var value = mat[prop];
switch (prop.toLowerCase()) {
// Ns is material specular exponent
case 'kd':
// Diffuse color (color under white light) using RGB values
params['diffuse'] = new THREE.Color().setRGB(value[0], value[1], value[2]);
break;
case 'ka':
// Ambient color (color under shadow) using RGB values
params['ambient'] = new THREE.Color().setRGB(value[0], value[1], value[2]);
break;
case 'ks':
// specular color (color when light is reflected from shiny surface) using RGB values
params['specular'] = new THREE.Color().setRGB(value[0], value[1], value[2]);
break;
case 'map_kd':
// Diffuse texture map
params['map'] = THREE.MTLLoader.loadTexture( this.baseUrl + value );
params['map'].wrapS = this.wrap;
params['map'].wrapT = this.wrap;
break;
case 'Ns':
// 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.
params['shininess'] = value;
break;
case 'd':
// According to MTL format (http://paulbourke.net/dataformats/mtl/):
// d is dissolve for current material
// factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent)
if (value < 1) {
params['transparent'] = true;
params['opacity'] = value;
}
break;
default:
break;
}
}
if (params['diffuse']) {
if (!params['ambient']) params['ambient'] = params['diffuse'];
params['color'] = params['diffuse'];
}
this.materials[materialName] = new THREE.MeshPhongMaterial(params);
return this.materials[materialName];
}
};
THREE.MTLLoader.loadTexture = function ( url, mapping, onLoad, onError ) {
var image = new Image();
var texture = new THREE.Texture( image, mapping );
var loader = new THREE.ImageLoader();
loader.addEventListener( 'load', function ( event ) {
texture.image = THREE.MTLLoader.ensurePowerOfTwo_(event.content);
texture.needsUpdate = true;
if ( onLoad ) onLoad( texture );
} );
loader.addEventListener( 'error', function ( event ) {
if ( onError ) onError( event.message );
} );
loader.crossOrigin = this.crossOrigin;
loader.load( url, image );
return texture;
};
THREE.MTLLoader.ensurePowerOfTwo_ = function (image)
{
if (!THREE.MTLLoader.isPowerOfTwo_(image.width) || !THREE.MTLLoader.isPowerOfTwo_(image.height))
{
var canvas = document.createElement("canvas");
canvas.width = THREE.MTLLoader.nextHighestPowerOfTwo_(image.width);
canvas.height = THREE.MTLLoader.nextHighestPowerOfTwo_(image.height);
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
return canvas;
}
return image;
};
THREE.MTLLoader.isPowerOfTwo_ = function (x)
{
return (x & (x - 1)) == 0;
};
THREE.MTLLoader.nextHighestPowerOfTwo_ = function(x)
{
--x;
for (var i = 1; i < 32; i <<= 1) {
x = x | x >> i;
}
return x + 1;
};
/**
* Loads a Wavefront .obj file with materials
*
* @author mrdoob / http://mrdoob.com/
* @author angelxuanchang
*/
THREE.OBJMTLLoader = function ( ) {
THREE.EventTarget.call( this );
};
THREE.OBJMTLLoader.prototype = {
constructor: THREE.OBJMTLLoader,
/**
* Load a Wavefront OBJ file with materials (MTL file)
*
* Loading progress is indicated by the following events:
* "load" event (successful loading): type = 'load', content = THREE.Object3D
* "error" event (error loading): type = 'load', message
* "progress" event (progress loading): type = 'progress', loaded, total
*
* If the MTL file cannot be loaded, then a MeshLambertMaterial is used as a default
* @param url - Location of OBJ file to load
* @param mtlfileurl - MTL file to load (optional, if not specified, attempts to use MTL specified in OBJ file)
* @param options - Options on how to interpret the material (see THREE.MTLLoader.MaterialCreator )
*
*/
load: function ( url, mtlfileurl, options ) {
var scope = this;
var xhr = new XMLHttpRequest();
var mtlDone; // Is the MTL done (true if no MTL, error loading MTL, or MTL actually loaded)
var obj3d; // Loaded model (from obj file)
var materialsCreator; // Material creator is created when MTL file is loaded
// Loader for MTL
var mtlLoader = new THREE.MTLLoader(url.substr(0,url.lastIndexOf("/")+1), options);
mtlLoader.addEventListener( 'load', waitReady );
mtlLoader.addEventListener( 'error', waitReady );
// Try to load mtlfile
if (mtlfileurl) {
mtlLoader.load(mtlfileurl);
mtlDone = false;
} else {
mtlDone = true;
}
function waitReady(event) {
if (event.type === 'load') {
if (event.content instanceof THREE.MTLLoader.MaterialCreator) {
// MTL file is loaded
mtlDone = true;
materialsCreator = event.content;
materialsCreator.preload();
} else {
// OBJ file is loaded
if (event.target.status == 200 || event.target.status == 0) {
var objContent = event.target.responseText;
if (mtlfileurl) {
// Parse with passed in MTL file
obj3d = scope.parse(objContent);
} else {
// No passed in MTL file, look for mtlfile in obj file
obj3d = scope.parse(objContent, function(mtlfile) {
mtlDone = false;
mtlLoader.load(mtlLoader.baseUrl + mtlfile);
});
}
} else {
// Error loading OBJ file....
scope.dispatchEvent( {
type: 'error',
message: 'Couldn\'t load URL [' + url + ']',
response: event.target.responseText } );
}
}
} else if (event.type === 'error') {
// MTL failed to load -- oh well, we will just not have material...
mtlDone = true;
}
if (mtlDone && obj3d) {
// MTL file is loaded and OBJ file is loaded
// Apply materials to model
if (materialsCreator) {
THREE.SceneUtils.traverseHierarchy(
obj3d, function(node) {
if (node instanceof THREE.Mesh) {
if (node.material.name) {
var material = materialsCreator.create(node.material.name);
if (material) node.material = material;
}
}
}
)
}
// Notify listeners
scope.dispatchEvent( { type: 'load', content: obj3d } );
}
}
xhr.addEventListener( 'load', waitReady, false );
xhr.addEventListener( 'progress', function ( event ) {
scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
}, false );
xhr.addEventListener( 'error', function () {
scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
}, false );
xhr.open( 'GET', url, true );
xhr.send( null );
},
/**
* Parses loaded .obj file
* @param data - content of .obj file
* @param mtllibCallback - callback to handle mtllib declaration (optional)
* @return {THREE.Object3D} - Object3D (with default material)
*/
parse: function ( data, mtllibCallback ) {
function vector( x, y, z ) {
return new THREE.Vector3( x, y, z );
}
function uv( u, v ) {
return new THREE.UV( u, v );
}
function face3( a, b, c, normals ) {
return new THREE.Face3( a, b, c, normals );
}
function face4( a, b, c, d, normals ) {
return new THREE.Face4( a, b, c, d, normals );
}
function finalize_mesh( group, mesh_info ) {
mesh_info.geometry.computeCentroids();
mesh_info.geometry.computeFaceNormals();
mesh_info.geometry.computeBoundingSphere();
group.add( new THREE.Mesh( mesh_info.geometry, mesh_info.material ) );
}
var vertices = [];
var normals = [];
var uvs = [];
// v float float float
var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( [\d|\.|\+|\-|e]+)( [\d|\.|\+|\-|e]+)/;
// vn float float float
var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( [\d|\.|\+|\-|e]+)( [\d|\.|\+|\-|e]+)/;
// vt float float
var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( [\d|\.|\+|\-|e]+)/;
// f vertex vertex vertex ...
var face_pattern1 = /f( +[\d]+)( [\d]+)( [\d]+)( [\d]+)?/;
// f vertex/uv vertex/uv vertex/uv ...
var face_pattern2 = /f( +([\d]+)\/([\d]+))( ([\d]+)\/([\d]+))( ([\d]+)\/([\d]+))( ([\d]+)\/([\d]+))?/;
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
var face_pattern3 = /f( +([\d]+)\/([\d]+)\/([\d]+))( ([\d]+)\/([\d]+)\/([\d]+))( ([\d]+)\/([\d]+)\/([\d]+))( ([\d]+)\/([\d]+)\/([\d]+))?/;
// f vertex//normal vertex//normal vertex//normal ...
var face_pattern4 = /f( +([\d]+)\/\/([\d]+))( ([\d]+)\/\/([\d]+))( ([\d]+)\/\/([\d]+))( ([\d]+)\/\/([\d]+))?/;
var final_model = new THREE.Object3D();
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
var cur_mesh = {
material: new THREE.MeshLambertMaterial(),
geometry: geometry
};
var lines = data.split("\n");
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
line = $.trim(line);
// temporary variable storing pattern matching result
var result;
if (line.length === 0 || line.charAt(0) === '#') {
continue;
} else if ((result = vertex_pattern.exec(line)) != null) {
// ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
vertices.push( vector(
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] ),
parseFloat( result[ 3 ] )
) );
} else if ((result = normal_pattern.exec(line)) != null) {
// ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
normals.push( vector(
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] ),
parseFloat( result[ 3 ] )
) );
} else if (( result = uv_pattern.exec(line)) != null) {
// ["vt 0.1 0.2", "0.1", "0.2"]
uvs.push( uv(
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] )
) );
} else if (( result = face_pattern1.exec( line ) ) != null ) {
// ["f 1 2 3", "1", "2", "3", undefined]
if ( result[ 4 ] === undefined ) {
geometry.faces.push( face3(
parseInt( result[ 1 ] ) - 1,
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 3 ] ) - 1
) );
} else {
geometry.faces.push( face4(
parseInt( result[ 1 ] ) - 1,
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 3 ] ) - 1,
parseInt( result[ 4 ] ) - 1
) );
}
} else if ( ( result = face_pattern2.exec( line ) ) != null ) {
// ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
if ( result[ 10 ] === undefined ) {
geometry.faces.push( face3(
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 5 ] ) - 1,
parseInt( result[ 8 ] ) - 1
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 6 ] ) - 1 ],
uvs[ parseInt( result[ 9 ] ) - 1 ]
] );
} else {
geometry.faces.push( face4(
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 5 ] ) - 1,
parseInt( result[ 8 ] ) - 1,
parseInt( result[ 11 ] ) - 1
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 6 ] ) - 1 ],
uvs[ parseInt( result[ 9 ] ) - 1 ],
uvs[ parseInt( result[ 12 ] ) - 1 ]
] );
}
} else if ( ( result = face_pattern3.exec( line ) ) != null ) {
// ["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", undefined, undefined, undefined, undefined]
if ( result[ 13 ] === undefined ) {
geometry.faces.push( face3(
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 6 ] ) - 1,
parseInt( result[ 10 ] ) - 1,
[
normals[ parseInt( result[ 4 ] ) - 1 ],
normals[ parseInt( result[ 8 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ]
]
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 7 ] ) - 1 ],
uvs[ parseInt( result[ 11 ] ) - 1 ]
] );
} else {
geometry.faces.push( face4(
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 6 ] ) - 1,
parseInt( result[ 10 ] ) - 1,
parseInt( result[ 14 ] ) - 1,
[
normals[ parseInt( result[ 4 ] ) - 1 ],
normals[ parseInt( result[ 8 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ],
normals[ parseInt( result[ 16 ] ) - 1 ]
]
) );
geometry.faceVertexUvs[ 0 ].push( [
uvs[ parseInt( result[ 3 ] ) - 1 ],
uvs[ parseInt( result[ 7 ] ) - 1 ],
uvs[ parseInt( result[ 11 ] ) - 1 ],
uvs[ parseInt( result[ 15 ] ) - 1 ]
] );
}
} else if ( ( result = face_pattern4.exec( line ) ) != null ) {
// ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]
if ( result[ 10 ] === undefined ) {
geometry.faces.push( face3(
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 5 ] ) - 1,
parseInt( result[ 8 ] ) - 1,
[
normals[ parseInt( result[ 3 ] ) - 1 ],
normals[ parseInt( result[ 6 ] ) - 1 ],
normals[ parseInt( result[ 9 ] ) - 1 ]
]
) );
} else {
geometry.faces.push( face4(
parseInt( result[ 2 ] ) - 1,
parseInt( result[ 5 ] ) - 1,
parseInt( result[ 8 ] ) - 1,
parseInt( result[ 11 ] ) - 1,
[
normals[ parseInt( result[ 3 ] ) - 1 ],
normals[ parseInt( result[ 6 ] ) - 1 ],
normals[ parseInt( result[ 9 ] ) - 1 ],
normals[ parseInt( result[ 12 ] ) - 1 ]
]
) );
}
} else if (line.startsWith("usemtl ")) {
var material_name = line.substring(7);
material_name = $.trim(material_name);
var material = new THREE.MeshLambertMaterial();
material.name = material_name;
if (geometry.faces.length > 0) {
// Finalize previous geometry and add to model
finalize_mesh(final_model, cur_mesh);
geometry = new THREE.Geometry();
geometry.vertices = vertices;
cur_mesh = {
geometry: geometry
};
}
cur_mesh.material = material;
//material_index = materialsCreator.getIndex(material_name);
} else if (line.startsWith("g ")) {
// Polygon group for object
var group_name = line.substring(2);
group_name = $.trim(group_name);
} else if (line.startsWith("o ")) {
// Object
var object_name = line.substring(2);
object_name = $.trim(object_name);
} else if (line.startsWith("s ")) {
// Smooth shading
} else if (line.startsWith("mtllib ")) {
// mtl file
if (mtllibCallback) {
var mtlfile = line.substring(7);
mtlfile = $.trim(mtlfile);
mtllibCallback(mtlfile);
}
} else {
console.error("Unhandled line " + line);
}
}
finalize_mesh(final_model, cur_mesh);
return final_model;
}
};
if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str){
return this.slice(0, str.length) == str;
};
}
\ No newline at end of file
This diff is collapsed.
......@@ -65,7 +65,7 @@ THREE.SceneUtils = {
} else if ( source instanceof THREE.SkinnedMesh ) {
object = new THREE.SkinnedMesh( source.geometry, source.material );
object = new THREE.SkinnedMesh( source.geometry, source.material, source.useVertexTexture );
} else if ( source instanceof THREE.Mesh ) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment