topical media & game development
mobile-query-three-vendor-three.js-loaders-ctm-CTMLoader.js / js
Loader for CTM encoded models generated by OpenCTM tools:
http://openctm.sourceforge.net/
Uses js-openctm library by Juan Mellado
http://code.google.com/p/js-openctm/
author: alteredq / alteredqualia.com/
THREE.CTMLoader = function ( showStatus ) {
THREE.Loader.call( this, showStatus );
};
THREE.CTMLoader.prototype = Object.create( THREE.Loader.prototype );
// Load multiple CTM parts defined in JSON
THREE.CTMLoader.prototype.loadParts = function( url, callback, parameters ) {
var scope = this;
var xhr = new XMLHttpRequest();
var basePath = parameters.basePath ? parameters.basePath : this.extractUrlBase( url );
xhr.onreadystatechange = function() {
if ( xhr.readyState === 4 ) {
if ( xhr.status === 200 || xhr.status === 0 ) {
var jsonObject = JSON.parse( xhr.responseText );
var materials = [], geometries = [], counter = 0;
function callbackFinal( geometry ) {
counter += 1;
geometries.push( geometry );
if ( counter === jsonObject.offsets.length ) {
callback( geometries, materials );
}
}
// init materials
for ( var i = 0; i < jsonObject.materials.length; i ++ ) {
materials[ i ] = THREE.Loader.prototype.createMaterial( jsonObject.materials[ i ], basePath );
}
// load joined CTM file
var partUrl = basePath + jsonObject.data;
var parametersPart = { useWorker: parameters.useWorker, useBuffers: parameters.useBuffers, offsets: jsonObject.offsets };
scope.load( partUrl, callbackFinal, parametersPart );
}
}
}
xhr.open( "GET", url, true );
if ( xhr.overrideMimeType ) xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
xhr.setRequestHeader( "Content-Type", "text/plain" );
xhr.send( null );
};
// Load CTMLoader compressed models
// - parameters
// - url (required)
// - callback (required)
THREE.CTMLoader.prototype.load = function( url, callback, parameters ) {
var scope = this;
var offsets = parameters.offsets !== undefined ? parameters.offsets : [ 0 ];
var useBuffers = parameters.useBuffers !== undefined ? parameters.useBuffers : true;
var xhr = new XMLHttpRequest(),
callbackProgress = null;
var length = 0;
xhr.onreadystatechange = function() {
if ( xhr.readyState === 4 ) {
if ( xhr.status === 200 || xhr.status === 0 ) {
var binaryData = xhr.responseText;
//var s = Date.now();
if ( parameters.useWorker ) {
var worker = new Worker( "js/loaders/ctm/CTMWorker.js" );
worker.onmessage = function( event ) {
var files = event.data;
for ( var i = 0; i < files.length; i ++ ) {
var ctmFile = files[ i ];
if ( useBuffers ) {
scope.createModelBuffers( ctmFile, callback );
} else {
scope.createModelClassic( ctmFile, callback );
}
}
//var e = Date.now();
//console.log( "CTM data parse time [worker]: " + (e-s) + " ms" );
};
worker.postMessage( { "data": binaryData, "offsets": offsets } );
} else {
for ( var i = 0; i < offsets.length; i ++ ) {
var stream = new CTM.Stream( binaryData );
stream.offset = offsets[ i ];
var ctmFile = new CTM.File( stream );
if ( useBuffers ) {
scope.createModelBuffers( ctmFile, callback );
} else {
scope.createModelClassic( ctmFile, callback );
}
}
//var e = Date.now();
//console.log( "CTM data parse time [inline]: " + (e-s) + " ms" );
}
} else {
console.error( "Couldn't load [" + url + "] [" + xhr.status + "]" );
}
} else if ( xhr.readyState === 3 ) {
if ( callbackProgress ) {
if ( length === 0 ) {
length = xhr.getResponseHeader( "Content-Length" );
}
callbackProgress( { total: length, loaded: xhr.responseText.length } );
}
} else if ( xhr.readyState === 2 ) {
length = xhr.getResponseHeader( "Content-Length" );
}
}
xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
xhr.open( "GET", url, true );
xhr.send( null );
};
THREE.CTMLoader.prototype.createModelBuffers = function ( file, callback ) {
var Model = function ( ) {
var scope = this;
var reorderVertices = true;
scope.materials = [];
THREE.BufferGeometry.call( this );
// init GL buffers
var vertexIndexArray = file.body.indices,
vertexPositionArray = file.body.vertices,
vertexNormalArray = file.body.normals;
var vertexUvArray, vertexColorArray;
if ( file.body.uvMaps !== undefined && file.body.uvMaps.length > 0 ) {
vertexUvArray = file.body.uvMaps[ 0 ].uv;
}
if ( file.body.attrMaps !== undefined && file.body.attrMaps.length > 0 && file.body.attrMaps[ 0 ].name === "Color" ) {
vertexColorArray = file.body.attrMaps[ 0 ].attr;
}
// reorder vertices
// (needed for buffer splitting, to keep together face vertices)
if ( reorderVertices ) {
var newFaces = new Uint32Array( vertexIndexArray.length ),
newVertices = new Float32Array( vertexPositionArray.length );
var newNormals, newUvs, newColors;
if ( vertexNormalArray ) newNormals = new Float32Array( vertexNormalArray.length );
if ( vertexUvArray ) newUvs = new Float32Array( vertexUvArray.length );
if ( vertexColorArray ) newColors = new Float32Array( vertexColorArray.length );
var indexMap = {}, vertexCounter = 0;
function handleVertex( v ) {
if ( indexMap[ v ] === undefined ) {
indexMap[ v ] = vertexCounter;
var sx = v * 3,
sy = v * 3 + 1,
sz = v * 3 + 2,
dx = vertexCounter * 3,
dy = vertexCounter * 3 + 1,
dz = vertexCounter * 3 + 2;
newVertices[ dx ] = vertexPositionArray[ sx ];
newVertices[ dy ] = vertexPositionArray[ sy ];
newVertices[ dz ] = vertexPositionArray[ sz ];
if ( vertexNormalArray ) {
newNormals[ dx ] = vertexNormalArray[ sx ];
newNormals[ dy ] = vertexNormalArray[ sy ];
newNormals[ dz ] = vertexNormalArray[ sz ];
}
if ( vertexUvArray ) {
newUvs[ vertexCounter * 2 ] = vertexUvArray[ v * 2 ];
newUvs[ vertexCounter * 2 + 1 ] = vertexUvArray[ v * 2 + 1 ];
}
if ( vertexColorArray ) {
newColors[ vertexCounter * 4 ] = vertexColorArray[ v * 4 ];
newColors[ vertexCounter * 4 + 1 ] = vertexColorArray[ v * 4 + 1 ];
newColors[ vertexCounter * 4 + 2 ] = vertexColorArray[ v * 4 + 2 ];
newColors[ vertexCounter * 4 + 3 ] = vertexColorArray[ v * 4 + 3 ];
}
vertexCounter += 1;
}
}
var a, b, c;
for ( var i = 0; i < vertexIndexArray.length; i += 3 ) {
a = vertexIndexArray[ i ];
b = vertexIndexArray[ i + 1 ];
c = vertexIndexArray[ i + 2 ];
handleVertex( a );
handleVertex( b );
handleVertex( c );
newFaces[ i ] = indexMap[ a ];
newFaces[ i + 1 ] = indexMap[ b ];
newFaces[ i + 2 ] = indexMap[ c ];
}
vertexIndexArray = newFaces;
vertexPositionArray = newVertices;
if ( vertexNormalArray ) vertexNormalArray = newNormals;
if ( vertexUvArray ) vertexUvArray = newUvs;
if ( vertexColorArray ) vertexColorArray = newColors;
}
// compute offsets
scope.offsets = [];
var indices = vertexIndexArray;
var start = 0,
min = vertexPositionArray.length,
max = 0,
minPrev = min;
for ( var i = 0; i < indices.length; ) {
for ( var j = 0; j < 3; ++ j ) {
var idx = indices[ i ++ ];
if ( idx < min ) min = idx;
if ( idx > max ) max = idx;
}
if ( max - min > 65535 ) {
i -= 3;
for ( var k = start; k < i; ++ k ) {
indices[ k ] -= minPrev;
}
scope.offsets.push( { start: start, count: i - start, index: minPrev } );
start = i;
min = vertexPositionArray.length;
max = 0;
}
minPrev = min;
}
for ( var k = start; k < i; ++ k ) {
indices[ k ] -= minPrev;
}
scope.offsets.push( { start: start, count: i - start, index: minPrev } );
// recast CTM 32-bit indices as 16-bit WebGL indices
var vertexIndexArray16 = new Uint16Array( vertexIndexArray );
// attributes
var attributes = scope.attributes;
attributes[ "index" ] = { itemSize: 1, array: vertexIndexArray16, numItems: vertexIndexArray16.length };
attributes[ "position" ] = { itemSize: 3, array: vertexPositionArray, numItems: vertexPositionArray.length };
if ( vertexNormalArray !== undefined ) {
attributes[ "normal" ] = { itemSize: 3, array: vertexNormalArray, numItems: vertexNormalArray.length };
}
if ( vertexUvArray !== undefined ) {
attributes[ "uv" ] = { itemSize: 2, array: vertexUvArray, numItems: vertexUvArray.length };
}
if ( vertexColorArray !== undefined ) {
attributes[ "color" ] = { itemSize: 4, array: vertexColorArray, numItems: vertexColorArray.length };
}
}
Model.prototype = Object.create( THREE.BufferGeometry.prototype );
var geometry = new Model();
// compute vertex normals if not present in the CTM model
if ( geometry.attributes[ "normal" ] === undefined ) {
geometry.computeVertexNormals();
}
callback( geometry );
};
THREE.CTMLoader.prototype.createModelClassic = function ( file, callback ) {
var Model = function ( ) {
var scope = this;
scope.materials = [];
THREE.Geometry.call( this );
var normals = [],
uvs = [],
colors = [];
init_vertices( file.body.vertices );
if ( file.body.normals !== undefined )
init_normals( file.body.normals );
if ( file.body.uvMaps !== undefined && file.body.uvMaps.length > 0 )
init_uvs( file.body.uvMaps[ 0 ].uv );
if ( file.body.attrMaps !== undefined && file.body.attrMaps.length > 0 && file.body.attrMaps[ 0 ].name === "Color" )
init_colors( file.body.attrMaps[ 0 ].attr );
var hasNormals = normals.length > 0 ? true : false,
hasUvs = uvs.length > 0 ? true : false,
hasColors = colors.length > 0 ? true : false;
init_faces( file.body.indices );
this.computeCentroids();
this.computeFaceNormals();
//this.computeTangents();
function init_vertices( buffer ) {
var x, y, z, i, il = buffer.length;
for( i = 0; i < il; i += 3 ) {
x = buffer[ i ];
y = buffer[ i + 1 ];
z = buffer[ i + 2 ];
vertex( scope, x, y, z );
}
};
function init_normals( buffer ) {
var x, y, z, i, il = buffer.length;
for( i = 0; i < il; i += 3 ) {
x = buffer[ i ];
y = buffer[ i + 1 ];
z = buffer[ i + 2 ];
normals.push( x, y, z );
}
};
function init_colors( buffer ) {
var r, g, b, a, i, il = buffer.length;
for( i = 0; i < il; i += 4 ) {
r = buffer[ i ];
g = buffer[ i + 1 ];
b = buffer[ i + 2 ];
a = buffer[ i + 3 ];
var color = new THREE.Color();
color.setRGB( r, g, b );
colors.push( color );
}
};
function init_uvs( buffer ) {
var u, v, i, il = buffer.length;
for( i = 0; i < il; i += 2 ) {
u = buffer[ i ];
v = buffer[ i + 1 ];
uvs.push( u, v );
}
};
function init_faces( buffer ) {
var a, b, c,
u1, v1, u2, v2, u3, v3,
m, face,
i, il = buffer.length;
m = 0; // all faces defaulting to material 0
for( i = 0; i < il; i += 3 ) {
a = buffer[ i ];
b = buffer[ i + 1 ];
c = buffer[ i + 2 ];
if ( hasNormals ){
face = f3n( scope, normals, a, b, c, m, a, b, c );
} else {
face = f3( scope, a, b, c, m );
}
if ( hasColors ) {
face.vertexColors[ 0 ] = colors[ a ];
face.vertexColors[ 1 ] = colors[ b ];
face.vertexColors[ 2 ] = colors[ c ];
}
if ( hasUvs ) {
u1 = uvs[ a * 2 ];
v1 = uvs[ a * 2 + 1 ];
u2 = uvs[ b * 2 ];
v2 = uvs[ b * 2 + 1 ];
u3 = uvs[ c * 2 ];
v3 = uvs[ c * 2 + 1 ];
uv3( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3 );
}
}
}
};
function vertex ( scope, x, y, z ) {
scope.vertices.push( new THREE.Vector3( x, y, z ) );
};
function f3 ( scope, a, b, c, mi ) {
var face = new THREE.Face3( a, b, c, null, null, mi );
scope.faces.push( face );
return face;
};
function f3n ( scope, normals, a, b, c, mi, nai, nbi, nci ) {
var nax = normals[ nai * 3 ],
nay = normals[ nai * 3 + 1 ],
naz = normals[ nai * 3 + 2 ],
nbx = normals[ nbi * 3 ],
nby = normals[ nbi * 3 + 1 ],
nbz = normals[ nbi * 3 + 2 ],
ncx = normals[ nci * 3 ],
ncy = normals[ nci * 3 + 1 ],
ncz = normals[ nci * 3 + 2 ];
var na = new THREE.Vector3( nax, nay, naz ),
nb = new THREE.Vector3( nbx, nby, nbz ),
nc = new THREE.Vector3( ncx, ncy, ncz );
var face = new THREE.Face3( a, b, c, [ na, nb, nc ], null, mi );
scope.faces.push( face );
return face;
};
function uv3 ( where, u1, v1, u2, v2, u3, v3 ) {
var uv = [];
uv.push( new THREE.Vector2( u1, v1 ) );
uv.push( new THREE.Vector2( u2, v2 ) );
uv.push( new THREE.Vector2( u3, v3 ) );
where.push( uv );
};
Model.prototype = Object.create( THREE.Geometry.prototype );
callback( new Model() );
};
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.