Repository: WrenchDE/dizzyRetroSnake-three.js
Branch: master
Commit: 5f02817ce206
Files: 11
Total size: 16.1 MB
Directory structure:
gitextract_1p4v6ikq/
├── 1.html
├── 2.html
├── 3.html
├── 4.html
├── MTLLoader.js
├── OBJLoader.js
├── OBJLoader2.js
├── OrbitControls.js
├── README.md
└── obj/
└── girl/
├── obj.mtl
└── obj.obj
================================================
FILE CONTENTS
================================================
================================================
FILE: 1.html
================================================
================================================
FILE: 2.html
================================================
================================================
FILE: 3.html
================================================
================================================
FILE: 4.html
================================================
我会让你眼晕,哈哈哈
================================================
FILE: MTLLoader.js
================================================
/**
* Loads a Wavefront .mtl file specifying materials
*
* @author angelxuanchang
*/
THREE.MTLLoader = function ( manager ) {
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
};
THREE.MTLLoader.prototype = {
constructor: THREE.MTLLoader,
/**
* 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.
*
* @see setPath setTexturePath
*
* @note In order for relative texture references to resolve correctly
* you must call setPath and/or setTexturePath explicitly prior to load.
*/
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new THREE.FileLoader( this.manager );
loader.setPath( this.path );
loader.load( url, function ( text ) {
onLoad( scope.parse( text ) );
}, onProgress, onError );
},
/**
* Set base path for resolving references.
* If set this path will be prepended to each loaded and found reference.
*
* @see setTexturePath
* @param {String} path
*
* @example
* mtlLoader.setPath( 'assets/obj/' );
* mtlLoader.load( 'my.mtl', ... );
*/
setPath: function ( path ) {
this.path = path;
},
/**
* Set base path for resolving texture references.
* If set this path will be prepended found texture reference.
* If not set and setPath is, it will be used as texture base path.
*
* @see setPath
* @param {String} path
*
* @example
* mtlLoader.setPath( 'assets/obj/' );
* mtlLoader.setTexturePath( 'assets/textures/' );
* mtlLoader.load( 'my.mtl', ... );
*/
setTexturePath: function ( path ) {
this.texturePath = path;
},
setBaseUrl: function ( path ) {
console.warn( 'THREE.MTLLoader: .setBaseUrl() is deprecated. Use .setTexturePath( path ) for texture path or .setPath( path ) for general base path instead.' );
this.setTexturePath( path );
},
setCrossOrigin: function ( value ) {
this.crossOrigin = value;
},
setMaterialOptions: function ( value ) {
this.materialOptions = value;
},
/**
* Parses a MTL file.
*
* @param {String} text - Content of MTL file
* @return {THREE.MTLLoader.MaterialCreator}
*
* @see setPath setTexturePath
*
* @note In order for relative texture references to resolve correctly
* you must call setPath and/or setTexturePath explicitly prior to parse.
*/
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 = line.trim();
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 = value.trim();
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.texturePath || this.path, this.materialOptions );
materialCreator.setCrossOrigin( this.crossOrigin );
materialCreator.setManager( this.manager );
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
* @constructor
*/
THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {
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 = {
constructor: THREE.MTLLoader.MaterialCreator,
crossOrigin: 'Anonymous',
setCrossOrigin: function ( value ) {
this.crossOrigin = value;
},
setManager: function ( value ) {
this.manager = value;
},
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 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
// ignore
save = false;
}
}
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 scope = this;
var mat = this.materialsInfo[ materialName ];
var params = {
name: materialName,
side: this.side
};
function resolveURL( baseUrl, url ) {
if ( typeof url !== 'string' || url === '' )
return '';
// Absolute URL
if ( /^https?:\/\//i.test( url ) ) return url;
return baseUrl + url;
}
function setMapForType( mapType, value ) {
if ( params[ mapType ] ) return; // Keep the first encountered texture
var texParams = scope.getTextureParams( value, params );
var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
map.repeat.copy( texParams.scale );
map.offset.copy( texParams.offset );
map.wrapS = scope.wrap;
map.wrapT = scope.wrap;
params[ mapType ] = map;
}
for ( var prop in mat ) {
var value = mat[ prop ];
var n;
if ( value === '' ) continue;
switch ( prop.toLowerCase() ) {
// Ns is material specular exponent
case 'kd':
// Diffuse color (color under white light) using RGB values
params.color = new THREE.Color().fromArray( value );
break;
case 'ks':
// Specular color (color when light is reflected from shiny surface) using RGB values
params.specular = new THREE.Color().fromArray( value );
break;
case 'map_kd':
// Diffuse texture map
setMapForType( "map", value );
break;
case 'map_ks':
// Specular map
setMapForType( "specularMap", value );
break;
case 'norm':
setMapForType( "normalMap", value );
break;
case 'map_bump':
case 'bump':
// Bump texture map
setMapForType( "bumpMap", value );
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 = parseFloat( value );
break;
case 'd':
n = parseFloat( value );
if ( n < 1 ) {
params.opacity = n;
params.transparent = true;
}
break;
case 'tr':
n = parseFloat( value );
if ( n > 0 ) {
params.opacity = 1 - n;
params.transparent = true;
}
break;
default:
break;
}
}
this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
return this.materials[ materialName ];
},
getTextureParams: function ( value, matParams ) {
var texParams = {
scale: new THREE.Vector2( 1, 1 ),
offset: new THREE.Vector2( 0, 0 )
};
var items = value.split( /\s+/ );
var pos;
pos = items.indexOf( '-bm' );
if ( pos >= 0 ) {
matParams.bumpScale = parseFloat( items[ pos + 1 ] );
items.splice( pos, 2 );
}
pos = items.indexOf( '-s' );
if ( pos >= 0 ) {
texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
items.splice( pos, 4 ); // we expect 3 parameters here!
}
pos = items.indexOf( '-o' );
if ( pos >= 0 ) {
texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
items.splice( pos, 4 ); // we expect 3 parameters here!
}
texParams.url = items.join( ' ' ).trim();
return texParams;
},
loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
var texture;
var loader = THREE.Loader.Handlers.get( url );
var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
if ( loader === null ) {
loader = new THREE.TextureLoader( manager );
}
if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
texture = loader.load( url, onLoad, onProgress, onError );
if ( mapping !== undefined ) texture.mapping = mapping;
return texture;
}
};
================================================
FILE: OBJLoader.js
================================================
/**
* @author mrdoob / http://mrdoob.com/
*/
THREE.OBJLoader = ( function () {
// o object_name | g group_name
var object_pattern = /^[og]\s*(.+)?/;
// mtllib file_reference
var material_library_pattern = /^mtllib /;
// usemtl material_name
var material_use_pattern = /^usemtl /;
function ParserState() {
var state = {
objects : [],
object : {},
vertices : [],
normals : [],
uvs : [],
materialLibraries : [],
startObject: function ( name, fromDeclaration ) {
// If the current object (initial from reset) is not from a g/o declaration in the parsed
// file. We need to use it for the first parsed g/o to keep things in sync.
if ( this.object && this.object.fromDeclaration === false ) {
this.object.name = name;
this.object.fromDeclaration = ( fromDeclaration !== false );
return;
}
var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
if ( this.object && typeof this.object._finalize === 'function' ) {
this.object._finalize( true );
}
this.object = {
name : name || '',
fromDeclaration : ( fromDeclaration !== false ),
geometry : {
vertices : [],
normals : [],
uvs : []
},
materials : [],
smooth : true,
startMaterial: function ( name, libraries ) {
var previous = this._finalize( false );
// New usemtl declaration overwrites an inherited material, except if faces were declared
// after the material, then it must be preserved for proper MultiMaterial continuation.
if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
this.materials.splice( previous.index, 1 );
}
var material = {
index : this.materials.length,
name : name || '',
mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
smooth : ( previous !== undefined ? previous.smooth : this.smooth ),
groupStart : ( previous !== undefined ? previous.groupEnd : 0 ),
groupEnd : -1,
groupCount : -1,
inherited : false,
clone: function ( index ) {
var cloned = {
index : ( typeof index === 'number' ? index : this.index ),
name : this.name,
mtllib : this.mtllib,
smooth : this.smooth,
groupStart : 0,
groupEnd : -1,
groupCount : -1,
inherited : false
};
cloned.clone = this.clone.bind(cloned);
return cloned;
}
};
this.materials.push( material );
return material;
},
currentMaterial: function () {
if ( this.materials.length > 0 ) {
return this.materials[ this.materials.length - 1 ];
}
return undefined;
},
_finalize: function ( end ) {
var lastMultiMaterial = this.currentMaterial();
if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) {
lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
lastMultiMaterial.inherited = false;
}
// Ignore objects tail materials if no face declarations followed them before a new o/g started.
if ( end && this.materials.length > 1 ) {
for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) {
if ( this.materials[ mi ].groupCount <= 0 ) {
this.materials.splice( mi, 1 );
}
}
}
// Guarantee at least one empty material, this makes the creation later more straight forward.
if ( end && this.materials.length === 0 ) {
this.materials.push({
name : '',
smooth : this.smooth
});
}
return lastMultiMaterial;
}
};
// Inherit previous objects material.
// Spec tells us that a declared material must be set to all objects until a new material is declared.
// If a usemtl declaration is encountered while this new object is being parsed, it will
// overwrite the inherited material. Exception being that there was already face declarations
// to the inherited material, then it will be preserved for proper MultiMaterial continuation.
if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
var declared = previousMaterial.clone( 0 );
declared.inherited = true;
this.object.materials.push( declared );
}
this.objects.push( this.object );
},
finalize: function () {
if ( this.object && typeof this.object._finalize === 'function' ) {
this.object._finalize( true );
}
},
parseVertexIndex: function ( value, len ) {
var index = parseInt( value, 10 );
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
},
parseNormalIndex: function ( value, len ) {
var index = parseInt( value, 10 );
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
},
parseUVIndex: function ( value, len ) {
var index = parseInt( value, 10 );
return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
},
addVertex: function ( a, b, c ) {
var src = this.vertices;
var dst = this.object.geometry.vertices;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
},
addVertexLine: function ( a ) {
var src = this.vertices;
var dst = this.object.geometry.vertices;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
},
addNormal: function ( a, b, c ) {
var src = this.normals;
var dst = this.object.geometry.normals;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
},
addUV: function ( a, b, c ) {
var src = this.uvs;
var dst = this.object.geometry.uvs;
dst.push( src[ a + 0 ], src[ a + 1 ] );
dst.push( src[ b + 0 ], src[ b + 1 ] );
dst.push( src[ c + 0 ], src[ c + 1 ] );
},
addUVLine: function ( a ) {
var src = this.uvs;
var dst = this.object.geometry.uvs;
dst.push( src[ a + 0 ], src[ a + 1 ] );
},
addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
var vLen = this.vertices.length;
var ia = this.parseVertexIndex( a, vLen );
var ib = this.parseVertexIndex( b, vLen );
var ic = this.parseVertexIndex( c, vLen );
this.addVertex( ia, ib, ic );
if ( ua !== undefined ) {
var uvLen = this.uvs.length;
ia = this.parseUVIndex( ua, uvLen );
ib = this.parseUVIndex( ub, uvLen );
ic = this.parseUVIndex( uc, uvLen );
this.addUV( ia, ib, ic );
}
if ( na !== undefined ) {
// Normals are many times the same. If so, skip function call and parseInt.
var nLen = this.normals.length;
ia = this.parseNormalIndex( na, nLen );
ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
this.addNormal( ia, ib, ic );
}
},
addLineGeometry: function ( vertices, uvs ) {
this.object.geometry.type = 'Line';
var vLen = this.vertices.length;
var uvLen = this.uvs.length;
for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
}
for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
}
}
};
state.startObject( '', false );
return state;
}
//
function OBJLoader( manager ) {
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
this.materials = null;
};
OBJLoader.prototype = {
constructor: OBJLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new THREE.FileLoader( scope.manager );
loader.setPath( this.path );
loader.load( url, function ( text ) {
onLoad( scope.parse( text ) );
}, onProgress, onError );
},
setPath: function ( value ) {
this.path = value;
},
setMaterials: function ( materials ) {
this.materials = materials;
return this;
},
parse: function ( text ) {
console.time( 'OBJLoader' );
var state = new ParserState();
if ( text.indexOf( '\r\n' ) !== - 1 ) {
// This is faster than String.split with regex that splits on both
text = text.replace( /\r\n/g, '\n' );
}
if ( text.indexOf( '\\\n' ) !== - 1) {
// join lines separated by a line continuation character (\)
text = text.replace( /\\\n/g, '' );
}
var lines = text.split( '\n' );
var line = '', lineFirstChar = '';
var lineLength = 0;
var result = [];
// Faster to just trim left side of the line. Use if available.
var trimLeft = ( typeof ''.trimLeft === 'function' );
for ( var i = 0, l = lines.length; i < l; i ++ ) {
line = lines[ i ];
line = trimLeft ? line.trimLeft() : line.trim();
lineLength = line.length;
if ( lineLength === 0 ) continue;
lineFirstChar = line.charAt( 0 );
// @todo invoke passed in handler if any
if ( lineFirstChar === '#' ) continue;
if ( lineFirstChar === 'v' ) {
var data = line.split( /\s+/ );
switch ( data[ 0 ] ) {
case 'v':
state.vertices.push(
parseFloat( data[ 1 ] ),
parseFloat( data[ 2 ] ),
parseFloat( data[ 3 ] )
);
break;
case 'vn':
state.normals.push(
parseFloat( data[ 1 ] ),
parseFloat( data[ 2 ] ),
parseFloat( data[ 3 ] )
);
break;
case 'vt':
state.uvs.push(
parseFloat( data[ 1 ] ),
parseFloat( data[ 2 ] )
);
break;
}
} else if ( lineFirstChar === 'f' ) {
var lineData = line.substr( 1 ).trim();
var vertexData = lineData.split( /\s+/ );
var faceVertices = [];
// Parse the face vertex data into an easy to work with format
for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {
var vertex = vertexData[ j ];
if ( vertex.length > 0 ) {
var vertexParts = vertex.split( '/' );
faceVertices.push( vertexParts );
}
}
// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
var v1 = faceVertices[ 0 ];
for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
var v2 = faceVertices[ j ];
var v3 = faceVertices[ j + 1 ];
state.addFace(
v1[ 0 ], v2[ 0 ], v3[ 0 ],
v1[ 1 ], v2[ 1 ], v3[ 1 ],
v1[ 2 ], v2[ 2 ], v3[ 2 ]
);
}
} else if ( lineFirstChar === 'l' ) {
var lineParts = line.substring( 1 ).trim().split( " " );
var lineVertices = [], lineUVs = [];
if ( line.indexOf( "/" ) === - 1 ) {
lineVertices = lineParts;
} else {
for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
var parts = lineParts[ li ].split( "/" );
if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );
}
}
state.addLineGeometry( lineVertices, lineUVs );
} else if ( ( result = object_pattern.exec( line ) ) !== null ) {
// o object_name
// or
// g group_name
// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
// var name = result[ 0 ].substr( 1 ).trim();
var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
state.startObject( name );
} else if ( material_use_pattern.test( line ) ) {
// material
state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
} else if ( material_library_pattern.test( line ) ) {
// mtl file
state.materialLibraries.push( line.substring( 7 ).trim() );
} else if ( lineFirstChar === 's' ) {
result = line.split( ' ' );
// smooth shading
// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
// but does not define a usemtl for each face set.
// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
// This requires some care to not create extra material on each smooth value for "normal" obj files.
// where explicit usemtl defines geometry groups.
// Example asset: examples/models/obj/cerberus/Cerberus.obj
/*
* http://paulbourke.net/dataformats/obj/
* or
* http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
*
* From chapter "Grouping" Syntax explanation "s group_number":
* "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
* Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
* surfaces, smoothing groups are either turned on or off; there is no difference between values greater
* than 0."
*/
if ( result.length > 1 ) {
var value = result[ 1 ].trim().toLowerCase();
state.object.smooth = ( value !== '0' && value !== 'off' );
} else {
// ZBrush can produce "s" lines #11707
state.object.smooth = true;
}
var material = state.object.currentMaterial();
if ( material ) material.smooth = state.object.smooth;
} else {
// Handle null terminated files without exception
if ( line === '\0' ) continue;
throw new Error( "Unexpected line: '" + line + "'" );
}
}
state.finalize();
var container = new THREE.Group();
container.materialLibraries = [].concat( state.materialLibraries );
for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
var object = state.objects[ i ];
var geometry = object.geometry;
var materials = object.materials;
var isLine = ( geometry.type === 'Line' );
// Skip o/g line declarations that did not follow with any faces
if ( geometry.vertices.length === 0 ) continue;
var buffergeometry = new THREE.BufferGeometry();
buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );
if ( geometry.normals.length > 0 ) {
buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );
} else {
buffergeometry.computeVertexNormals();
}
if ( geometry.uvs.length > 0 ) {
buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );
}
// Create materials
var createdMaterials = [];
for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {
var sourceMaterial = materials[ mi ];
var material = undefined;
if ( this.materials !== null ) {
material = this.materials.create( sourceMaterial.name );
// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
var materialLine = new THREE.LineBasicMaterial();
materialLine.copy( material );
material = materialLine;
}
}
if ( ! material ) {
material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() );
material.name = sourceMaterial.name;
}
material.flatShading = sourceMaterial.smooth ? false : true;
createdMaterials.push(material);
}
// Create mesh
var mesh;
if ( createdMaterials.length > 1 ) {
for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {
var sourceMaterial = materials[ mi ];
buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
}
mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials ) : new THREE.LineSegments( buffergeometry, createdMaterials ) );
} else {
mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) );
}
mesh.name = object.name;
container.add( mesh );
}
console.timeEnd( 'OBJLoader' );
return container;
}
};
return OBJLoader;
} )();
================================================
FILE: OBJLoader2.js
================================================
/**
* @author Kai Salmen / https://kaisalmen.de
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
'use strict';
if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
/**
* Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
* @class
*
* @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}
*/
THREE.OBJLoader2 = (function () {
var OBJLOADER2_VERSION = '2.0.0';
var Commons = THREE.LoaderSupport.Commons;
var Validator = THREE.LoaderSupport.Validator;
var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
OBJLoader2.prototype = Object.create( THREE.LoaderSupport.Commons.prototype );
OBJLoader2.prototype.constructor = OBJLoader2;
function OBJLoader2( logger, manager ) {
THREE.LoaderSupport.Commons.call( this, logger, manager );
this.logger.logInfo( 'Using THREE.OBJLoader2 version: ' + OBJLOADER2_VERSION );
this.materialPerSmoothingGroup = false;
this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );
this.workerSupport = null;
this.terminateWorkerOnLoad = true;
};
/**
* Tells whether a material shall be created per smoothing group.
* @memberOf THREE.OBJLoader2
*
* @param {boolean} materialPerSmoothingGroup=false
*/
OBJLoader2.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
};
/**
* Use this convenient method to load an OBJ file at the given URL. Per default the fileLoader uses an arraybuffer.
* @memberOf THREE.OBJLoader2
*
* @param {string} url URL of the file to load
* @param {callback} onLoad Called after loading was successfully completed
* @param {callback} onProgress Called to report progress of loading. The argument will be the XMLHttpRequest instance, which contains {integer total} and {integer loaded} bytes.
* @param {callback} onError Called after an error occurred during loading
* @param {callback} onMeshAlter Called after a new mesh raw data becomes available to allow alteration
* @param {boolean} useAsync If true uses async loading with worker, if false loads data synchronously
*/
OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
var scope = this;
if ( ! Validator.isValid( onProgress ) ) {
var numericalValueRef = 0;
var numericalValue = 0;
onProgress = function ( event ) {
if ( ! event.lengthComputable ) return;
numericalValue = event.loaded / event.total;
if ( numericalValue > numericalValueRef ) {
numericalValueRef = numericalValue;
var output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
scope.onProgress( 'progressLoad', output, numericalValue );
}
};
}
if ( ! Validator.isValid( onError ) ) {
onError = function ( event ) {
var output = 'Error occurred while downloading "' + url + '"';
scope.logger.logError( output + ': ' + event );
scope.onProgress( 'error', output, -1 );
};
}
this.fileLoader.setPath( this.path );
this.fileLoader.setResponseType( 'arraybuffer' );
this.fileLoader.load( url, function ( content ) {
if ( useAsync ) {
scope.parseAsync( content, onLoad );
} else {
scope._setCallbacks( null, onMeshAlter, null );
onLoad(
{
detail: {
loaderRootNode: scope.parse( content ),
modelName: scope.modelName,
instanceNo: scope.instanceNo
}
}
);
}
}, onProgress, onError );
};
/**
* Run the loader according the provided instructions.
* @memberOf THREE.OBJLoader2
*
* @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution
* @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport
*/
OBJLoader2.prototype.run = function ( prepData, workerSupportExternal ) {
this._applyPrepData( prepData );
var available = this._checkFiles( prepData.resources );
if ( Validator.isValid( workerSupportExternal ) ) {
this.terminateWorkerOnLoad = false;
this.workerSupport = workerSupportExternal;
this.logger = workerSupportExternal.logger;
} else {
this.terminateWorkerOnLoad = true;
}
var scope = this;
var onMaterialsLoaded = function ( materials ) {
scope.builder.setMaterials( materials );
if ( Validator.isValid( available.obj.content ) ) {
if ( prepData.useAsync ) {
scope.parseAsync( available.obj.content, scope.callbacks.onLoad );
} else {
scope.parse( available.obj.content );
}
} else {
scope.setPath( available.obj.path );
scope.load( available.obj.name, scope.callbacks.onLoad, null, null, scope.callbacks.onMeshAlter, prepData.useAsync );
}
};
this._loadMtl( available.mtl, onMaterialsLoaded, prepData.crossOrigin );
};
OBJLoader2.prototype._applyPrepData = function ( prepData ) {
THREE.LoaderSupport.Commons.prototype._applyPrepData.call( this, prepData );
if ( Validator.isValid( prepData ) ) {
this.setMaterialPerSmoothingGroup( prepData.materialPerSmoothingGroup );
}
};
/**
* Parses OBJ data synchronously from arraybuffer or string.
* @memberOf THREE.OBJLoader2
*
* @param {arraybuffer|string} content OBJ data as Uint8Array or String
*/
OBJLoader2.prototype.parse = function ( content ) {
this.logger.logTimeStart( 'OBJLoader2 parse: ' + this.modelName );
var parser = new Parser( this.logger );
parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
parser.setUseIndices( this.useIndices );
parser.setDisregardNormals( this.disregardNormals );
parser.setMaterialNames( this.builder.materialNames );
var scope = this;
var onMeshLoaded = function ( payload ) {
var meshes = scope.builder.buildMeshes( payload );
var mesh;
for ( var i in meshes ) {
mesh = meshes[ i ];
scope.loaderRootNode.add( mesh );
}
};
parser.setCallbackBuilder( onMeshLoaded );
var onProgressScoped = function ( text, numericalValue ) {
scope.onProgress( 'progressParse', text, numericalValue );
};
parser.setCallbackProgress( onProgressScoped );
if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
this.logger.logInfo( 'Parsing arrayBuffer...' );
parser.parse( content );
} else if ( typeof( content ) === 'string' || content instanceof String ) {
this.logger.logInfo( 'Parsing text...' );
parser.parseText( content );
} else {
throw 'Provided content was neither of type String nor Uint8Array! Aborting...';
}
this.logger.logTimeEnd( 'OBJLoader2 parse: ' + this.modelName );
return this.loaderRootNode;
};
/**
* Parses OBJ content asynchronously from arraybuffer.
* @memberOf THREE.OBJLoader2
*
* @param {arraybuffer} content OBJ data as Uint8Array
* @param {callback} onLoad Called after worker successfully completed loading
*/
OBJLoader2.prototype.parseAsync = function ( content, onLoad ) {
this.logger.logTimeStart( 'OBJLoader2 parseAsync: ' + this.modelName );
var scope = this;
var scopedOnLoad = function () {
onLoad(
{
detail: {
loaderRootNode: scope.loaderRootNode,
modelName: scope.modelName,
instanceNo: scope.instanceNo
}
}
);
if ( scope.terminateWorkerOnLoad ) scope.workerSupport.terminateWorker();
scope.logger.logTimeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
};
var scopedOnMeshLoaded = function ( payload ) {
var meshes = scope.builder.buildMeshes( payload );
var mesh;
for ( var i in meshes ) {
mesh = meshes[ i ];
scope.loaderRootNode.add( mesh );
}
};
this.workerSupport = Validator.verifyInput( this.workerSupport, new THREE.LoaderSupport.WorkerSupport( this.logger ) );
var buildCode = function ( funcBuildObject, funcBuildSingelton ) {
var workerCode = '';
workerCode += '/**\n';
workerCode += ' * This code was constructed by OBJLoader2 buildWorkerCode.\n';
workerCode += ' */\n\n';
workerCode += funcBuildSingelton( 'Commons', 'Commons', Commons );
workerCode += funcBuildObject( 'Consts', Consts );
workerCode += funcBuildObject( 'Validator', Validator );
workerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );
workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
workerCode += funcBuildSingelton( 'RawMesh', 'RawMesh', RawMesh );
workerCode += funcBuildSingelton( 'RawMeshSubGroup', 'RawMeshSubGroup', RawMeshSubGroup );
return workerCode;
};
this.workerSupport.validate( buildCode, false );
this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
this.workerSupport.run(
{
cmd: 'run',
params: {
materialPerSmoothingGroup: this.materialPerSmoothingGroup,
useIndices: this.useIndices,
disregardNormals: this.disregardNormals
},
logger: {
debug: this.logger.debug,
enabled: this.logger.enabled
},
materials: {
materialNames: this.builder.materialNames
},
buffers: {
input: content
}
},
[ content.buffer ]
);
};
/**
* Constants used by THREE.OBJLoader2
*/
var Consts = {
CODE_LF: 10,
CODE_CR: 13,
CODE_SPACE: 32,
CODE_SLASH: 47,
STRING_LF: '\n',
STRING_CR: '\r',
STRING_SPACE: ' ',
STRING_SLASH: '/',
LINE_F: 'f',
LINE_G: 'g',
LINE_L: 'l',
LINE_O: 'o',
LINE_S: 's',
LINE_V: 'v',
LINE_VT: 'vt',
LINE_VN: 'vn',
LINE_MTLLIB: 'mtllib',
LINE_USEMTL: 'usemtl'
};
/**
* Parse OBJ data either from ArrayBuffer or string
* @class
*/
var Parser = (function () {
function Parser( logger ) {
this.callbackProgress = null;
this.callbackBuilder = null;
this.materialNames = [];
this.rawMesh = null;
this.materialPerSmoothingGroup = false;
this.useIndices = false;
this.disregardNormals = false;
this.inputObjectCount = 1;
this.outputObjectCount = 1;
this.counts = {
vertices: 0,
faces: 0,
doubleIndicesCount: 0
};
this.logger = logger;
this.totalBytes = 0;
};
Parser.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
this.materialPerSmoothingGroup = materialPerSmoothingGroup;
};
Parser.prototype.setUseIndices = function ( useIndices ) {
this.useIndices = useIndices;
};
Parser.prototype.setDisregardNormals = function ( disregardNormals ) {
this.disregardNormals = disregardNormals;
};
Parser.prototype.setMaterialNames = function ( materialNames ) {
this.materialNames = Validator.verifyInput( materialNames, this.materialNames );
this.materialNames = Validator.verifyInput( this.materialNames, [] );
};
Parser.prototype.setCallbackBuilder = function ( callbackBuilder ) {
this.callbackBuilder = callbackBuilder;
if ( ! Validator.isValid( this.callbackBuilder ) ) throw 'Unable to run as no "builder" callback is set.';
};
Parser.prototype.setCallbackProgress = function ( callbackProgress ) {
this.callbackProgress = callbackProgress;
};
Parser.prototype.configure = function () {
this.rawMesh = new RawMesh( this.materialPerSmoothingGroup, this.useIndices, this.disregardNormals );
if ( this.logger.isEnabled() ) {
var matNames = ( this.materialNames.length > 0 ) ? '\n\tmaterialNames:\n\t\t- ' + this.materialNames.join( '\n\t\t- ' ) : '\n\tmaterialNames: None';
var printedConfig = 'OBJLoader2.Parser configuration:'
+ matNames
+ '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup
+ '\n\tuseIndices: ' + this.useIndices
+ '\n\tdisregardNormals: ' + this.disregardNormals
+ '\n\tcallbackBuilderName: ' + this.callbackBuilder.name
+ '\n\tcallbackProgressName: ' + this.callbackProgress.name;
this.logger.logInfo( printedConfig );
}
};
/**
* Parse the provided arraybuffer
* @memberOf Parser
*
* @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
*/
Parser.prototype.parse = function ( arrayBuffer ) {
this.logger.logTimeStart( 'OBJLoader2.Parser.parse' );
this.configure();
var arrayBufferView = new Uint8Array( arrayBuffer );
var length = arrayBufferView.byteLength;
this.totalBytes = length;
var buffer = new Array( 128 );
var bufferPointer = 0;
var slashesCount = 0;
var reachedFaces = false;
var code;
var word = '';
var i = 0;
for ( ; i < length; i ++ ) {
code = arrayBufferView[ i ];
switch ( code ) {
case Consts.CODE_SPACE:
if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
word = '';
break;
case Consts.CODE_SLASH:
slashesCount ++;
if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
word = '';
break;
case Consts.CODE_LF:
if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
word = '';
reachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces, i );
bufferPointer = 0;
slashesCount = 0;
break;
case Consts.CODE_CR:
break;
default:
word += String.fromCharCode( code );
break;
}
}
this.finalize( i );
this.logger.logTimeEnd( 'OBJLoader2.Parser.parse' );
};
/**
* Parse the provided text
* @memberOf Parser
*
* @param {string} text OBJ data as string
*/
Parser.prototype.parseText = function ( text ) {
this.logger.logTimeStart( 'OBJLoader2.Parser.parseText' );
this.configure();
var length = text.length;
this.totalBytes = length;
var buffer = new Array( 128 );
var bufferPointer = 0;
var slashesCount = 0;
var reachedFaces = false;
var char;
var word = '';
var i = 0;
for ( ; i < length; i ++ ) {
char = text[ i ];
switch ( char ) {
case Consts.STRING_SPACE:
if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
word = '';
break;
case Consts.STRING_SLASH:
slashesCount ++;
if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
word = '';
break;
case Consts.STRING_LF:
if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
word = '';
reachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces, i );
bufferPointer = 0;
slashesCount = 0;
break;
case Consts.STRING_CR:
break;
default:
word += char;
}
}
this.finalize( i );
this.logger.logTimeEnd( 'OBJLoader2.Parser.parseText' );
};
Parser.prototype.processLine = function ( buffer, bufferPointer, slashesCount, reachedFaces, currentByte ) {
if ( bufferPointer < 1 ) return reachedFaces;
var bufferLength = bufferPointer - 1;
var concatBuffer;
switch ( buffer[ 0 ] ) {
case Consts.LINE_V:
// object complete instance required if reached faces already (= reached next block of v)
if ( reachedFaces ) {
if ( this.rawMesh.colors.length > 0 && this.rawMesh.colors.length !== this.rawMesh.vertices.length ) {
throw 'Vertex Colors were detected, but vertex count and color count do not match!';
}
this.processCompletedObject( null, this.rawMesh.groupName, currentByte );
reachedFaces = false;
}
if ( bufferLength === 3 ) {
this.rawMesh.pushVertex( buffer )
} else {
this.rawMesh.pushVertexAndVertextColors( buffer );
}
break;
case Consts.LINE_VT:
this.rawMesh.pushUv( buffer );
break;
case Consts.LINE_VN:
this.rawMesh.pushNormal( buffer );
break;
case Consts.LINE_F:
reachedFaces = true;
this.rawMesh.processFaces( buffer, bufferPointer, slashesCount );
break;
case Consts.LINE_L:
if ( bufferLength === slashesCount * 2 ) {
this.rawMesh.buildLineVvt( buffer );
} else {
this.rawMesh.buildLineV( buffer );
}
break;
case Consts.LINE_S:
this.rawMesh.pushSmoothingGroup( buffer[ 1 ] );
this.flushStringBuffer( buffer, bufferPointer );
break;
case Consts.LINE_G:
concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
this.processCompletedGroup( concatBuffer, currentByte );
this.flushStringBuffer( buffer, bufferPointer );
break;
case Consts.LINE_O:
concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
if ( this.rawMesh.vertices.length > 0 ) {
this.processCompletedObject( concatBuffer, null, currentByte );
reachedFaces = false;
} else {
this.rawMesh.pushObject( concatBuffer );
}
this.flushStringBuffer( buffer, bufferPointer );
break;
case Consts.LINE_MTLLIB:
concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
this.rawMesh.pushMtllib( concatBuffer );
this.flushStringBuffer( buffer, bufferPointer );
break;
case Consts.LINE_USEMTL:
concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
this.rawMesh.pushUsemtl( concatBuffer );
this.flushStringBuffer( buffer, bufferPointer );
break;
default:
break;
}
return reachedFaces;
};
Parser.prototype.flushStringBuffer = function ( buffer, bufferLength ) {
for ( var i = 0; i < bufferLength; i ++ ) {
buffer[ i ] = '';
}
};
Parser.prototype.createRawMeshReport = function ( rawMesh , inputObjectCount ) {
var report = rawMesh.createReport( inputObjectCount );
return 'Input Object number: ' + inputObjectCount +
'\n\tObject name: ' + report.objectName +
'\n\tGroup name: ' + report.groupName +
'\n\tMtllib name: ' + report.mtllibName +
'\n\tVertex count: ' + report.vertexCount +
'\n\tNormal count: ' + report.normalCount +
'\n\tUV count: ' + report.uvCount +
'\n\tSmoothingGroup count: ' + report.smoothingGroupCount +
'\n\tMaterial count: ' + report.mtlCount +
'\n\tReal RawMeshSubGroup count: ' + report.subGroups;
};
Parser.prototype.processCompletedObject = function ( objectName, groupName, currentByte ) {
var result = this.rawMesh.finalize();
if ( Validator.isValid( result ) ) {
this.inputObjectCount++;
if ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );
this.buildMesh( result, currentByte );
var progressBytesPercent = currentByte / this.totalBytes;
this.callbackProgress( 'Completed object: ' + objectName + ' Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
}
this.rawMesh = this.rawMesh.newInstanceFromObject( objectName, groupName );
};
Parser.prototype.processCompletedGroup = function ( groupName, currentByte ) {
var result = this.rawMesh.finalize();
if ( Validator.isValid( result ) ) {
this.inputObjectCount++;
if ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );
this.buildMesh( result, currentByte );
var progressBytesPercent = currentByte / this.totalBytes;
this.callbackProgress( 'Completed group: ' + groupName + ' Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
this.rawMesh = this.rawMesh.newInstanceFromGroup( groupName );
} else {
// if a group was set that did not lead to object creation in finalize, then the group name has to be updated
this.rawMesh.pushGroup( groupName );
}
};
Parser.prototype.finalize = function ( currentByte ) {
this.logger.logInfo( 'Global output object count: ' + this.outputObjectCount );
var result = Validator.isValid( this.rawMesh ) ? this.rawMesh.finalize() : null;
if ( Validator.isValid( result ) ) {
this.inputObjectCount++;
if ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );
this.buildMesh( result, currentByte );
if ( this.logger.isEnabled() ) {
var parserFinalReport = 'Overall counts: ' +
'\n\tVertices: ' + this.counts.vertices +
'\n\tFaces: ' + this.counts.faces +
'\n\tMultiple definitions: ' + this.counts.doubleIndicesCount;
this.logger.logInfo( parserFinalReport );
}
var progressBytesPercent = currentByte / this.totalBytes;
this.callbackProgress( 'Completed Parsing: 100.00%', progressBytesPercent );
}
};
/**
* RawObjectDescriptions are transformed to too intermediate format that is forwarded to the Builder.
* It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
*
* @param result
*/
Parser.prototype.buildMesh = function ( result, currentByte ) {
var rawObjectDescriptions = result.subGroups;
var vertexFA = new Float32Array( result.absoluteVertexCount );
this.counts.vertices += result.absoluteVertexCount / 3;
this.counts.faces += result.faceCount;
this.counts.doubleIndicesCount += result.doubleIndicesCount;
var indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
var rawObjectDescription;
var materialDescription;
var materialDescriptions = [];
var createMultiMaterial = ( rawObjectDescriptions.length > 1 );
var materialIndex = 0;
var materialIndexMapping = [];
var selectedMaterialIndex;
var materialGroup;
var materialGroups = [];
var vertexFAOffset = 0;
var indexUAOffset = 0;
var colorFAOffset = 0;
var normalFAOffset = 0;
var uvFAOffset = 0;
var materialGroupOffset = 0;
var materialGroupLength = 0;
for ( var oodIndex in rawObjectDescriptions ) {
if ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;
rawObjectDescription = rawObjectDescriptions[ oodIndex ];
materialDescription = {
name: rawObjectDescription.materialName,
flat: false,
default: false
};
if ( this.materialNames[ materialDescription.name ] === null ) {
materialDescription.default = true;
this.logger.logWarn( 'object_group "' + rawObjectDescription.objectName + '_' +
rawObjectDescription.groupName +
'" was defined without material! Assigning "defaultMaterial".' );
}
// Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0
if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;
if ( createMultiMaterial ) {
// re-use material if already used before. Reduces materials array size and eliminates duplicates
selectedMaterialIndex = materialIndexMapping[ materialDescription.name ];
if ( ! selectedMaterialIndex ) {
selectedMaterialIndex = materialIndex;
materialIndexMapping[ materialDescription.name ] = materialIndex;
materialDescriptions.push( materialDescription );
materialIndex++;
}
materialGroupLength = this.useIndices ? rawObjectDescription.indices.length : rawObjectDescription.vertices.length / 3;
materialGroup = {
start: materialGroupOffset,
count: materialGroupLength,
index: selectedMaterialIndex
};
materialGroups.push( materialGroup );
materialGroupOffset += materialGroupLength;
} else {
materialDescriptions.push( materialDescription );
}
vertexFA.set( rawObjectDescription.vertices, vertexFAOffset );
vertexFAOffset += rawObjectDescription.vertices.length;
if ( indexUA ) {
indexUA.set( rawObjectDescription.indices, indexUAOffset );
indexUAOffset += rawObjectDescription.indices.length;
}
if ( colorFA ) {
colorFA.set( rawObjectDescription.colors, colorFAOffset );
colorFAOffset += rawObjectDescription.colors.length;
}
if ( normalFA ) {
normalFA.set( rawObjectDescription.normals, normalFAOffset );
normalFAOffset += rawObjectDescription.normals.length;
}
if ( uvFA ) {
uvFA.set( rawObjectDescription.uvs, uvFAOffset );
uvFAOffset += rawObjectDescription.uvs.length;
}
if ( this.logger.isDebug() ) {
var materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\n\t\tmaterialIndex: ' + selectedMaterialIndex : '';
var createdReport = 'Output Object no.: ' + this.outputObjectCount +
'\n\t\tobjectName: ' + rawObjectDescription.objectName +
'\n\t\tgroupName: ' + rawObjectDescription.groupName +
'\n\t\tmaterialName: ' + rawObjectDescription.materialName +
materialIndexLine +
'\n\t\tsmoothingGroup: ' + rawObjectDescription.smoothingGroup +
'\n\t\t#vertices: ' + rawObjectDescription.vertices.length / 3 +
'\n\t\t#indices: ' + rawObjectDescription.indices.length +
'\n\t\t#colors: ' + rawObjectDescription.colors.length / 3 +
'\n\t\t#uvs: ' + rawObjectDescription.uvs.length / 2 +
'\n\t\t#normals: ' + rawObjectDescription.normals.length / 3;
this.logger.logDebug( createdReport );
}
}
this.outputObjectCount++;
this.callbackBuilder(
{
cmd: 'meshData',
progress: {
numericalValue: currentByte / this.totalBytes
},
params: {
meshName: result.name
},
materials: {
multiMaterial: createMultiMaterial,
materialDescriptions: materialDescriptions,
materialGroups: materialGroups
},
buffers: {
vertices: vertexFA,
indices: indexUA,
colors: colorFA,
normals: normalFA,
uvs: uvFA
}
},
[ vertexFA.buffer ],
Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
);
};
return Parser;
})();
/**
* {@link RawMesh} is only used by {@link Parser}.
* The user of OBJLoader2 does not need to care about this class.
* It is defined publicly for inclusion in web worker based OBJ loader ({@link THREE.OBJLoader2.WWOBJLoader2})
*/
var RawMesh = (function () {
function RawMesh( materialPerSmoothingGroup, useIndices, disregardNormals, objectName, groupName, activeMtlName ) {
this.globalVertexOffset = 1;
this.globalUvOffset = 1;
this.globalNormalOffset = 1;
this.vertices = [];
this.colors = [];
this.normals = [];
this.uvs = [];
// faces are stored according combined index of group, material and smoothingGroup (0 or not)
this.activeMtlName = Validator.verifyInput( activeMtlName, '' );
this.objectName = Validator.verifyInput( objectName, '' );
this.groupName = Validator.verifyInput( groupName, '' );
this.mtllibName = '';
this.smoothingGroup = {
splitMaterials: materialPerSmoothingGroup === true,
normalized: -1,
real: -1
};
this.useIndices = useIndices === true;
this.disregardNormals = disregardNormals === true;
this.mtlCount = 0;
this.smoothingGroupCount = 0;
this.subGroups = [];
this.subGroupInUse = null;
// this default index is required as it is possible to define faces without 'g' or 'usemtl'
this.pushSmoothingGroup( 1 );
this.doubleIndicesCount = 0;
this.faceCount = 0;
}
RawMesh.prototype.newInstanceFromObject = function ( objectName, groupName ) {
var newRawObject = new RawMesh( this.smoothingGroup.splitMaterials, this.useIndices, this.disregardNormals, objectName, groupName, this.activeMtlName );
// move indices forward
newRawObject.globalVertexOffset = this.globalVertexOffset + this.vertices.length / 3;
newRawObject.globalUvOffset = this.globalUvOffset + this.uvs.length / 2;
newRawObject.globalNormalOffset = this.globalNormalOffset + this.normals.length / 3;
return newRawObject;
};
RawMesh.prototype.newInstanceFromGroup = function ( groupName ) {
var newRawObject = new RawMesh( this.smoothingGroup.splitMaterials, this.useIndices, this.disregardNormals, this.objectName, groupName, this.activeMtlName );
// keep current buffers and indices forward
newRawObject.vertices = this.vertices;
newRawObject.colors = this.colors;
newRawObject.uvs = this.uvs;
newRawObject.normals = this.normals;
newRawObject.globalVertexOffset = this.globalVertexOffset;
newRawObject.globalUvOffset = this.globalUvOffset;
newRawObject.globalNormalOffset = this.globalNormalOffset;
return newRawObject;
};
RawMesh.prototype.pushVertex = function ( buffer ) {
this.vertices.push( parseFloat( buffer[ 1 ] ) );
this.vertices.push( parseFloat( buffer[ 2 ] ) );
this.vertices.push( parseFloat( buffer[ 3 ] ) );
};
RawMesh.prototype.pushVertexAndVertextColors = function ( buffer ) {
this.vertices.push( parseFloat( buffer[ 1 ] ) );
this.vertices.push( parseFloat( buffer[ 2 ] ) );
this.vertices.push( parseFloat( buffer[ 3 ] ) );
this.colors.push( parseFloat( buffer[ 4 ] ) );
this.colors.push( parseFloat( buffer[ 5 ] ) );
this.colors.push( parseFloat( buffer[ 6 ] ) );
};
RawMesh.prototype.pushUv = function ( buffer ) {
this.uvs.push( parseFloat( buffer[ 1 ] ) );
this.uvs.push( parseFloat( buffer[ 2 ] ) );
};
RawMesh.prototype.pushNormal = function ( buffer ) {
this.normals.push( parseFloat( buffer[ 1 ] ) );
this.normals.push( parseFloat( buffer[ 2 ] ) );
this.normals.push( parseFloat( buffer[ 3 ] ) );
};
RawMesh.prototype.pushObject = function ( objectName ) {
this.objectName = Validator.verifyInput( objectName, '' );
};
RawMesh.prototype.pushMtllib = function ( mtllibName ) {
this.mtllibName = Validator.verifyInput( mtllibName, '' );
};
RawMesh.prototype.pushGroup = function ( groupName ) {
this.groupName = Validator.verifyInput( groupName, '' );
};
RawMesh.prototype.pushUsemtl = function ( mtlName ) {
if ( this.activeMtlName === mtlName || ! Validator.isValid( mtlName ) ) return;
this.activeMtlName = mtlName;
this.mtlCount++;
this.verifyIndex();
};
RawMesh.prototype.pushSmoothingGroup = function ( smoothingGroup ) {
var smoothingGroupInt = parseInt( smoothingGroup );
if ( isNaN( smoothingGroupInt ) ) {
smoothingGroupInt = smoothingGroup === "off" ? 0 : 1;
}
var smoothCheck = this.smoothingGroup.normalized;
this.smoothingGroup.normalized = this.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;
this.smoothingGroup.real = smoothingGroupInt;
if ( smoothCheck !== smoothingGroupInt ) {
this.smoothingGroupCount++;
this.verifyIndex();
}
};
RawMesh.prototype.verifyIndex = function () {
var index = this.activeMtlName + '|' + this.smoothingGroup.normalized;
this.subGroupInUse = this.subGroups[ index ];
if ( ! Validator.isValid( this.subGroupInUse ) ) {
this.subGroupInUse = new RawMeshSubGroup( this.objectName, this.groupName, this.activeMtlName, this.smoothingGroup.normalized );
this.subGroups[ index ] = this.subGroupInUse;
}
};
RawMesh.prototype.processFaces = function ( buffer, bufferPointer, slashesCount ) {
var bufferLength = bufferPointer - 1;
var i, length;
// "f vertex ..."
if ( slashesCount === 0 ) {
for ( i = 2, length = bufferLength - 1; i < length; i ++ ) {
this.buildFace( buffer[ 1 ] );
this.buildFace( buffer[ i ] );
this.buildFace( buffer[ i + 1 ] );
}
// "f vertex/uv ..."
} else if ( bufferLength === slashesCount * 2 ) {
for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
this.buildFace( buffer[ 1 ], buffer[ 2 ] );
this.buildFace( buffer[ i ], buffer[ i + 1 ] );
this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
}
// "f vertex/uv/normal ..."
} else if ( bufferLength * 2 === slashesCount * 3 ) {
for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
this.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
this.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
this.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
}
// "f vertex//normal ..."
} else {
for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
this.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
this.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
}
}
};
RawMesh.prototype.buildFace = function ( faceIndexV, faceIndexU, faceIndexN ) {
var sgiu = this.subGroupInUse;
if ( this.disregardNormals ) faceIndexN = undefined;
var scope = this;
var updateRawObjectDescriptionInUse = function () {
var indexPointerV = ( parseInt( faceIndexV ) - scope.globalVertexOffset ) * 3;
var indexPointerC = scope.colors.length > 0 ? indexPointerV : null;
var vertices = sgiu.vertices;
vertices.push( scope.vertices[ indexPointerV++ ] );
vertices.push( scope.vertices[ indexPointerV++ ] );
vertices.push( scope.vertices[ indexPointerV ] );
if ( indexPointerC !== null ) {
var colors = sgiu.colors;
colors.push( scope.colors[ indexPointerC++ ] );
colors.push( scope.colors[ indexPointerC++ ] );
colors.push( scope.colors[ indexPointerC ] );
}
if ( faceIndexU ) {
var indexPointerU = ( parseInt( faceIndexU ) - scope.globalUvOffset ) * 2;
var uvs = sgiu.uvs;
uvs.push( scope.uvs[ indexPointerU++ ] );
uvs.push( scope.uvs[ indexPointerU ] );
}
if ( faceIndexN ) {
var indexPointerN = ( parseInt( faceIndexN ) - scope.globalNormalOffset ) * 3;
var normals = sgiu.normals;
normals.push( scope.normals[ indexPointerN++ ] );
normals.push( scope.normals[ indexPointerN++ ] );
normals.push( scope.normals[ indexPointerN ] );
}
};
if ( this.useIndices ) {
var mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
var indicesPointer = sgiu.indexMappings[ mappingName ];
if ( Validator.isValid( indicesPointer ) ) {
this.doubleIndicesCount++;
} else {
indicesPointer = sgiu.vertices.length / 3;
updateRawObjectDescriptionInUse();
sgiu.indexMappings[ mappingName ] = indicesPointer;
sgiu.indexMappingsCount++;
}
sgiu.indices.push( indicesPointer );
} else {
updateRawObjectDescriptionInUse();
}
this.faceCount++;
};
/*
* Support for lines with or without texture. First element in indexArray is the line identification
* 0: "f vertex/uv vertex/uv ..."
* 1: "f vertex vertex ..."
*/
RawMesh.prototype.buildLineVvt = function ( lineArray ) {
for ( var i = 1, length = lineArray.length; i < length; i ++ ) {
this.vertices.push( parseInt( lineArray[ i ] ) );
this.uvs.push( parseInt( lineArray[ i ] ) );
}
};
RawMesh.prototype.buildLineV = function ( lineArray ) {
for ( var i = 1, length = lineArray.length; i < length; i++ ) {
this.vertices.push( parseInt( lineArray[ i ] ) );
}
};
/**
* Clear any empty rawObjectDescription and calculate absolute vertex, normal and uv counts
*/
RawMesh.prototype.finalize = function () {
var rawObjectDescriptionsTemp = [];
var rawObjectDescription;
var absoluteVertexCount = 0;
var absoluteIndexMappingsCount = 0;
var absoluteIndexCount = 0;
var absoluteColorCount = 0;
var absoluteNormalCount = 0;
var absoluteUvCount = 0;
var indices;
for ( var name in this.subGroups ) {
rawObjectDescription = this.subGroups[ name ];
if ( rawObjectDescription.vertices.length > 0 ) {
indices = rawObjectDescription.indices;
if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {
for ( var i in indices ) indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;
}
rawObjectDescriptionsTemp.push( rawObjectDescription );
absoluteVertexCount += rawObjectDescription.vertices.length;
absoluteIndexMappingsCount += rawObjectDescription.indexMappingsCount;
absoluteIndexCount += rawObjectDescription.indices.length;
absoluteColorCount += rawObjectDescription.colors.length;
absoluteUvCount += rawObjectDescription.uvs.length;
absoluteNormalCount += rawObjectDescription.normals.length;
}
}
// do not continue if no result
var result = null;
if ( rawObjectDescriptionsTemp.length > 0 ) {
result = {
name: this.groupName !== '' ? this.groupName : this.objectName,
subGroups: rawObjectDescriptionsTemp,
absoluteVertexCount: absoluteVertexCount,
absoluteIndexCount: absoluteIndexCount,
absoluteColorCount: absoluteColorCount,
absoluteNormalCount: absoluteNormalCount,
absoluteUvCount: absoluteUvCount,
faceCount: this.faceCount,
doubleIndicesCount: this.doubleIndicesCount
};
}
return result;
};
RawMesh.prototype.createReport = function () {
var report = {
objectName: this.objectName,
groupName: this.groupName,
mtllibName: this.mtllibName,
vertexCount: this.vertices.length / 3,
normalCount: this.normals.length / 3,
uvCount: this.uvs.length / 2,
smoothingGroupCount: this.smoothingGroupCount,
mtlCount: this.mtlCount,
subGroups: this.subGroups.length
};
return report;
};
return RawMesh;
})();
/**
* Descriptive information and data (vertices, normals, uvs) to passed on to mesh building function.
* @class
*
* @param {string} objectName Name of the mesh
* @param {string} groupName Name of the group
* @param {string} materialName Name of the material
* @param {number} smoothingGroup Normalized smoothingGroup (0: flat shading, 1: smooth shading)
*/
var RawMeshSubGroup = (function () {
function RawMeshSubGroup( objectName, groupName, materialName, smoothingGroup ) {
this.objectName = objectName;
this.groupName = groupName;
this.materialName = materialName;
this.smoothingGroup = smoothingGroup;
this.vertices = [];
this.indexMappingsCount = 0;
this.indexMappings = [];
this.indices = [];
this.colors = [];
this.uvs = [];
this.normals = [];
}
return RawMeshSubGroup;
})();
OBJLoader2.prototype._checkFiles = function ( resources ) {
var resource;
var result = {
mtl: null,
obj: null
};
for ( var index in resources ) {
resource = resources[ index ];
if ( ! Validator.isValid( resource.name ) ) continue;
if ( Validator.isValid( resource.content ) ) {
if ( resource.extension === 'OBJ' ) {
// fast-fail on bad type
if ( ! ( resource.content instanceof Uint8Array ) ) throw 'Provided content is not of type arraybuffer! Aborting...';
result.obj = resource;
} else if ( resource.extension === 'MTL' && Validator.isValid( resource.name ) ) {
if ( ! ( typeof( resource.content ) === 'string' || resource.content instanceof String ) ) throw 'Provided content is not of type String! Aborting...';
result.mtl = resource;
} else if ( resource.extension === "ZIP" ) {
// ignore
} else {
throw 'Unidentified resource "' + resource.name + '": ' + resource.url;
}
} else {
// fast-fail on bad type
if ( ! ( typeof( resource.name ) === 'string' || resource.name instanceof String ) ) throw 'Provided file is not properly defined! Aborting...';
if ( resource.extension === 'OBJ' ) {
result.obj = resource;
} else if ( resource.extension === 'MTL' ) {
result.mtl = resource;
} else if ( resource.extension === "ZIP" ) {
// ignore
} else {
throw 'Unidentified resource "' + resource.name + '": ' + resource.url;
}
}
}
return result;
};
/**
* Utility method for loading an mtl file according resource description.
* @memberOf THREE.OBJLoader2
*
* @param {string} url URL to the file
* @param {string} name The name of the object
* @param {Object} content The file content as arraybuffer or text
* @param {function} callbackOnLoad
* @param {string} [crossOrigin] CORS value
*/
OBJLoader2.prototype.loadMtl = function ( url, name, content, callbackOnLoad, crossOrigin ) {
var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );
resource.setContent( content );
this._loadMtl( resource, callbackOnLoad, crossOrigin );
};
/**
* Utility method for loading an mtl file according resource description.
* @memberOf THREE.OBJLoader2
*
* @param {THREE.LoaderSupport.ResourceDescriptor} resource
* @param {function} callbackOnLoad
* @param {string} [crossOrigin] CORS value
*/
OBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {
if ( Validator.isValid( resource ) ) this.logger.logTimeStart( 'Loading MTL: ' + resource.name );
var materials = [];
var scope = this;
var processMaterials = function ( materialCreator ) {
var materialCreatorMaterials = [];
if ( Validator.isValid( materialCreator ) ) {
materialCreator.preload();
materialCreatorMaterials = materialCreator.materials;
for ( var materialName in materialCreatorMaterials ) {
if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
materials[ materialName ] = materialCreatorMaterials[ materialName ];
}
}
}
if ( Validator.isValid( resource ) ) scope.logger.logTimeEnd( 'Loading MTL: ' + resource.name );
callbackOnLoad( materials );
};
var mtlLoader = new THREE.MTLLoader();
crossOrigin = Validator.verifyInput( crossOrigin, 'anonymous' );
mtlLoader.setCrossOrigin( crossOrigin );
// fast-fail
if ( ! Validator.isValid( resource ) || ( ! Validator.isValid( resource.content ) && ! Validator.isValid( resource.url ) ) ) {
processMaterials();
} else {
mtlLoader.setPath( resource.path );
if ( Validator.isValid( resource.content ) ) {
processMaterials( Validator.isValid( resource.content ) ? mtlLoader.parse( resource.content ) : null );
} else if ( Validator.isValid( resource.url ) ) {
var onError = function ( event ) {
var output = 'Error occurred while downloading "' + resource.url + '"';
this.logger.logError( output + ': ' + event );
throw output;
};
mtlLoader.load( resource.name, processMaterials, undefined, onError );
}
}
};
return OBJLoader2;
})();
================================================
FILE: OrbitControls.js
================================================
/**
* @author qiao / https://github.com/qiao
* @author mrdoob / http://mrdoob.com
* @author alteredq / http://alteredqualia.com/
* @author WestLangley / http://github.com/WestLangley
* @author erich666 / http://erichaines.com
*/
// This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
// Orbit - left mouse / touch: one finger move
// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
// Pan - right mouse, or arrow keys / touch: three finger swipe
THREE.OrbitControls = function ( object, domElement ) {
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// Set to false to disable this control
this.enabled = true;
// "target" sets the location of focus, where the object orbits around
this.target = new THREE.Vector3();
// How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0;
this.maxDistance = Infinity;
// How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0;
this.maxZoom = Infinity;
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
this.minAzimuthAngle = - Infinity; // radians
this.maxAzimuthAngle = Infinity; // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false;
this.dampingFactor = 0.25;
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
// Set to false to disable zooming
this.enableZoom = true;
this.zoomSpeed = 1.0;
// Set to false to disable rotating
this.enableRotate = true;
this.rotateSpeed = 1.0;
// Set to false to disable panning
this.enablePan = true;
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
// Set to true to automatically rotate around the target
// If auto-rotate is enabled, you must call controls.update() in your animation loop
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
// Set to false to disable use of the keys
this.enableKeys = true;
// The four arrow keys
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
// Mouse buttons
this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.zoom0 = this.object.zoom;
//
// public methods
//
this.getPolarAngle = function () {
return spherical.phi;
};
this.getAzimuthalAngle = function () {
return spherical.theta;
};
this.saveState = function () {
scope.target0.copy( scope.target );
scope.position0.copy( scope.object.position );
scope.zoom0 = scope.object.zoom;
};
this.reset = function () {
scope.target.copy( scope.target0 );
scope.object.position.copy( scope.position0 );
scope.object.zoom = scope.zoom0;
scope.object.updateProjectionMatrix();
scope.dispatchEvent( changeEvent );
scope.update();
state = STATE.NONE;
};
// this method is exposed, but perhaps it would be better if we can make it private...
this.update = function () {
var offset = new THREE.Vector3();
// so camera.up is the orbit axis
var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
var quatInverse = quat.clone().inverse();
var lastPosition = new THREE.Vector3();
var lastQuaternion = new THREE.Quaternion();
return function update() {
var position = scope.object.position;
offset.copy( position ).sub( scope.target );
// rotate offset to "y-axis-is-up" space
offset.applyQuaternion( quat );
// angle from z-axis around y-axis
spherical.setFromVector3( offset );
if ( scope.autoRotate && state === STATE.NONE ) {
rotateLeft( getAutoRotationAngle() );
}
spherical.theta += sphericalDelta.theta;
spherical.phi += sphericalDelta.phi;
// restrict theta to be between desired limits
spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
// restrict phi to be between desired limits
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
spherical.makeSafe();
spherical.radius *= scale;
// restrict radius to be between desired limits
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
// move target to panned location
scope.target.add( panOffset );
offset.setFromSpherical( spherical );
// rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion( quatInverse );
position.copy( scope.target ).add( offset );
scope.object.lookAt( scope.target );
if ( scope.enableDamping === true ) {
sphericalDelta.theta *= ( 1 - scope.dampingFactor );
sphericalDelta.phi *= ( 1 - scope.dampingFactor );
} else {
sphericalDelta.set( 0, 0, 0 );
}
scale = 1;
panOffset.set( 0, 0, 0 );
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if ( zoomChanged ||
lastPosition.distanceToSquared( scope.object.position ) > EPS ||
8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
scope.dispatchEvent( changeEvent );
lastPosition.copy( scope.object.position );
lastQuaternion.copy( scope.object.quaternion );
zoomChanged = false;
return true;
}
return false;
};
}();
this.dispose = function () {
scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
window.removeEventListener( 'keydown', onKeyDown, false );
//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
};
//
// internals
//
var scope = this;
var changeEvent = { type: 'change' };
var startEvent = { type: 'start' };
var endEvent = { type: 'end' };
var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
var state = STATE.NONE;
var EPS = 0.000001;
// current position in spherical coordinates
var spherical = new THREE.Spherical();
var sphericalDelta = new THREE.Spherical();
var scale = 1;
var panOffset = new THREE.Vector3();
var zoomChanged = false;
var rotateStart = new THREE.Vector2();
var rotateEnd = new THREE.Vector2();
var rotateDelta = new THREE.Vector2();
var panStart = new THREE.Vector2();
var panEnd = new THREE.Vector2();
var panDelta = new THREE.Vector2();
var dollyStart = new THREE.Vector2();
var dollyEnd = new THREE.Vector2();
var dollyDelta = new THREE.Vector2();
function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow( 0.95, scope.zoomSpeed );
}
function rotateLeft( angle ) {
sphericalDelta.theta -= angle;
}
function rotateUp( angle ) {
sphericalDelta.phi -= angle;
}
var panLeft = function () {
var v = new THREE.Vector3();
return function panLeft( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
v.multiplyScalar( - distance );
panOffset.add( v );
};
}();
var panUp = function () {
var v = new THREE.Vector3();
return function panUp( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
v.multiplyScalar( distance );
panOffset.add( v );
};
}();
// deltaX and deltaY are in pixels; right and down are positive
var pan = function () {
var offset = new THREE.Vector3();
return function pan( deltaX, deltaY ) {
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
if ( scope.object instanceof THREE.PerspectiveCamera ) {
// perspective
var position = scope.object.position;
offset.copy( position ).sub( scope.target );
var targetDistance = offset.length();
// half of the fov is center to top of screen
targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
// we actually don't use screenWidth, since perspective camera is fixed to screen height
panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
// orthographic
panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
} else {
// camera neither orthographic nor perspective
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
scope.enablePan = false;
}
};
}();
function dollyIn( dollyScale ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
scale /= dollyScale;
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
scope.enableZoom = false;
}
}
function dollyOut( dollyScale ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
scale *= dollyScale;
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
scope.enableZoom = false;
}
}
//
// event callbacks - update the object state
//
function handleMouseDownRotate( event ) {
//console.log( 'handleMouseDownRotate' );
rotateStart.set( event.clientX, event.clientY );
}
function handleMouseDownDolly( event ) {
//console.log( 'handleMouseDownDolly' );
dollyStart.set( event.clientX, event.clientY );
}
function handleMouseDownPan( event ) {
//console.log( 'handleMouseDownPan' );
panStart.set( event.clientX, event.clientY );
}
function handleMouseMoveRotate( event ) {
//console.log( 'handleMouseMoveRotate' );
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart );
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
scope.update();
}
function handleMouseMoveDolly( event ) {
//console.log( 'handleMouseMoveDolly' );
dollyEnd.set( event.clientX, event.clientY );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
dollyIn( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
dollyOut( getZoomScale() );
}
dollyStart.copy( dollyEnd );
scope.update();
}
function handleMouseMovePan( event ) {
//console.log( 'handleMouseMovePan' );
panEnd.set( event.clientX, event.clientY );
panDelta.subVectors( panEnd, panStart );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
}
function handleMouseUp( event ) {
// console.log( 'handleMouseUp' );
}
function handleMouseWheel( event ) {
// console.log( 'handleMouseWheel' );
if ( event.deltaY < 0 ) {
dollyOut( getZoomScale() );
} else if ( event.deltaY > 0 ) {
dollyIn( getZoomScale() );
}
scope.update();
}
function handleKeyDown( event ) {
//console.log( 'handleKeyDown' );
switch ( event.keyCode ) {
case scope.keys.UP:
pan( 0, scope.keyPanSpeed );
scope.update();
break;
case scope.keys.BOTTOM:
pan( 0, - scope.keyPanSpeed );
scope.update();
break;
case scope.keys.LEFT:
pan( scope.keyPanSpeed, 0 );
scope.update();
break;
case scope.keys.RIGHT:
pan( - scope.keyPanSpeed, 0 );
scope.update();
break;
}
}
function handleTouchStartRotate( event ) {
//console.log( 'handleTouchStartRotate' );
rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
}
function handleTouchStartDolly( event ) {
//console.log( 'handleTouchStartDolly' );
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
var distance = Math.sqrt( dx * dx + dy * dy );
dollyStart.set( 0, distance );
}
function handleTouchStartPan( event ) {
//console.log( 'handleTouchStartPan' );
panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
}
function handleTouchMoveRotate( event ) {
//console.log( 'handleTouchMoveRotate' );
rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
rotateDelta.subVectors( rotateEnd, rotateStart );
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
scope.update();
}
function handleTouchMoveDolly( event ) {
//console.log( 'handleTouchMoveDolly' );
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
var distance = Math.sqrt( dx * dx + dy * dy );
dollyEnd.set( 0, distance );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
dollyOut( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
dollyIn( getZoomScale() );
}
dollyStart.copy( dollyEnd );
scope.update();
}
function handleTouchMovePan( event ) {
//console.log( 'handleTouchMovePan' );
panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
panDelta.subVectors( panEnd, panStart );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
}
function handleTouchEnd( event ) {
//console.log( 'handleTouchEnd' );
}
//
// event handlers - FSM: listen for events and reset state
//
function onMouseDown( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
switch ( event.button ) {
case scope.mouseButtons.ORBIT:
if ( scope.enableRotate === false ) return;
handleMouseDownRotate( event );
state = STATE.ROTATE;
break;
case scope.mouseButtons.ZOOM:
if ( scope.enableZoom === false ) return;
handleMouseDownDolly( event );
state = STATE.DOLLY;
break;
case scope.mouseButtons.PAN:
if ( scope.enablePan === false ) return;
handleMouseDownPan( event );
state = STATE.PAN;
break;
}
if ( state !== STATE.NONE ) {
document.addEventListener( 'mousemove', onMouseMove, false );
document.addEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( startEvent );
}
}
function onMouseMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
switch ( state ) {
case STATE.ROTATE:
if ( scope.enableRotate === false ) return;
handleMouseMoveRotate( event );
break;
case STATE.DOLLY:
if ( scope.enableZoom === false ) return;
handleMouseMoveDolly( event );
break;
case STATE.PAN:
if ( scope.enablePan === false ) return;
handleMouseMovePan( event );
break;
}
}
function onMouseUp( event ) {
if ( scope.enabled === false ) return;
handleMouseUp( event );
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function onMouseWheel( event ) {
if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
event.preventDefault();
event.stopPropagation();
handleMouseWheel( event );
scope.dispatchEvent( startEvent ); // not sure why these are here...
scope.dispatchEvent( endEvent );
}
function onKeyDown( event ) {
if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
handleKeyDown( event );
}
function onTouchStart( event ) {
if ( scope.enabled === false ) return;
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.enableRotate === false ) return;
handleTouchStartRotate( event );
state = STATE.TOUCH_ROTATE;
break;
case 2: // two-fingered touch: dolly
if ( scope.enableZoom === false ) return;
handleTouchStartDolly( event );
state = STATE.TOUCH_DOLLY;
break;
case 3: // three-fingered touch: pan
if ( scope.enablePan === false ) return;
handleTouchStartPan( event );
state = STATE.TOUCH_PAN;
break;
default:
state = STATE.NONE;
}
if ( state !== STATE.NONE ) {
scope.dispatchEvent( startEvent );
}
}
function onTouchMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.enableRotate === false ) return;
if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
handleTouchMoveRotate( event );
break;
case 2: // two-fingered touch: dolly
if ( scope.enableZoom === false ) return;
if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
handleTouchMoveDolly( event );
break;
case 3: // three-fingered touch: pan
if ( scope.enablePan === false ) return;
if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
handleTouchMovePan( event );
break;
default:
state = STATE.NONE;
}
}
function onTouchEnd( event ) {
if ( scope.enabled === false ) return;
handleTouchEnd( event );
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function onContextMenu( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
}
//
scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
window.addEventListener( 'keydown', onKeyDown, false );
// force an update at start
this.update();
};
THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
Object.defineProperties( THREE.OrbitControls.prototype, {
center: {
get: function () {
console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
return this.target;
}
},
// backward compatibility
noZoom: {
get: function () {
console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
return ! this.enableZoom;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
this.enableZoom = ! value;
}
},
noRotate: {
get: function () {
console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
return ! this.enableRotate;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
this.enableRotate = ! value;
}
},
noPan: {
get: function () {
console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
return ! this.enablePan;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
this.enablePan = ! value;
}
},
noKeys: {
get: function () {
console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
return ! this.enableKeys;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
this.enableKeys = ! value;
}
},
staticMoving: {
get: function () {
console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
return ! this.enableDamping;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
this.enableDamping = ! value;
}
},
dynamicDampingFactor: {
get: function () {
console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
return this.dampingFactor;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
this.dampingFactor = value;
}
}
} );
================================================
FILE: README.md
================================================
================================================
FILE: obj/girl/obj.mtl
================================================
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# �������ļ�:23.03.2015 22:13:02
newmtl wire_154215229
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.6039 0.8431 0.8980
Kd 0.6039 0.8431 0.8980
Ks 0.3500 0.3500 0.3500
newmtl SkinHip_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl SkinTorso_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl PubicHair_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl SkinNeck_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl SkinHead_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka Cindy_MakeUpblue.jpg
map_Kd Cindy_MakeUpblue.jpg
newmtl SkinScalp_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyHeadMap_natural.jpg
map_Kd CindyHeadMap_natural.jpg
newmtl Lips_1
Ns 200.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka Cindy_Lipsvioletred.jpg
map_Kd Cindy_Lipsvioletred.jpg
newmtl EyeSocket_1
Ns 800.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka Cindy_MakeUpblue.jpg
map_Kd Cindy_MakeUpblue.jpg
newmtl InnerMouth_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyHeadMap_natural.jpg
map_Kd CindyHeadMap_natural.jpg
newmtl Nostrils_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyHeadMap_natural.jpg
map_Kd CindyHeadMap_natural.jpg
newmtl Lacrimal_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyHeadMap_natural.jpg
map_Kd CindyHeadMap_natural.jpg
newmtl Eyelashes_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.0588 0.0588 0.0588
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Eyebrows_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Teeth_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Gums_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.6078 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Tongue_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyHeadMap_natural.jpg
map_Kd CindyHeadMap_natural.jpg
newmtl LCornea_1
Ns 1000.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeLeft_darkblue.jpg
map_Kd EyeLeft_darkblue.jpg
newmtl LEyewhite_1
Ns 650.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeLeft_darkblue.jpg
map_Kd EyeLeft_darkblue.jpg
newmtl LPupil_1
Ns 1000.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeLeft_darkblue.jpg
map_Kd EyeLeft_darkblue.jpg
newmtl LIris_1
Ns 1000.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeLeft_darkblue.jpg
map_Kd EyeLeft_darkblue.jpg
newmtl RCornea_1
Ns 1000.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeRight_darkblue.jpg
map_Kd EyeRight_darkblue.jpg
newmtl REyewhite_1
Ns 650.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeRight_darkblue.jpg
map_Kd EyeRight_darkblue.jpg
newmtl RPupil_1
Ns 1000.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeRight_darkblue.jpg
map_Kd EyeRight_darkblue.jpg
newmtl RIris_1
Ns 1000.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka EyeRight_darkblue.jpg
map_Kd EyeRight_darkblue.jpg
newmtl Nipples_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl SkinForearm_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl SkinArm_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl SkinHand_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl Fingernails_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0118 0.0118 0.0118
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyNailsMap_violetred.jpg
map_Kd CindyNailsMap_violetred.jpg
newmtl SkinLeg_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl SkinFeet_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyBodyMap_natural.jpg
map_Kd CindyBodyMap_natural.jpg
newmtl Toenails_1
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0118 0.0118 0.0118
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka CindyNailsMap_violetred.jpg
map_Kd CindyNailsMap_violetred.jpg
newmtl Outer_2
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka ErikaHR_03.jpg
map_Kd ErikaHR_03.jpg
newmtl OuterBangs_2
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka ErikaHR_03.jpg
map_Kd ErikaHR_03.jpg
newmtl InnerBangs_2
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka ErikaHR_03.jpg
map_Kd ErikaHR_03.jpg
newmtl Middle_2
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka ErikaHR_03.jpg
map_Kd ErikaHR_03.jpg
newmtl Inner_2
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka ErikaHR_03.jpg
map_Kd ErikaHR_03.jpg
newmtl Scull_2
Ns 300.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka ErikaHR_03.jpg
map_Kd ErikaHR_03.jpg
================================================
FILE: obj/girl/obj.obj
================================================
[File too large to display: 16.0 MB]