text: /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   */
  
  THREE.ImageUtils = {
  
  	crossOrigin: 'anonymous',
  
  	loadTexture: function ( url, mapping, onLoad, onError ) {
  
  		var image = new Image();
  		var texture = new THREE.Texture( image, mapping );
  
  		var loader = new THREE.ImageLoader();
  		loader.crossOrigin = this.crossOrigin;
  		loader.load( url, function ( image ) {
  
  			texture.image = image;
  			texture.needsUpdate = true;
  
  			if ( onLoad ) onLoad( texture );
  
  		} );
  
  		texture.sourceFile = url;
  
  		return texture;
  
  	},
  
  	loadCompressedTexture: function ( url, mapping, onLoad, onError ) {
  
  		var texture = new THREE.CompressedTexture();
  		texture.mapping = mapping;
  
  		var request = new XMLHttpRequest();
  
  		request.onload = function () {
  
  			var buffer = request.response;
  			var dds = THREE.ImageUtils.parseDDS( buffer, true );
  
  			texture.format = dds.format;
  
  			texture.mipmaps = dds.mipmaps;
  			texture.image.width = dds.width;
  			texture.image.height = dds.height;
  
  			// gl.generateMipmap fails for compressed textures
  			// mipmaps must be embedded in the DDS file
  			// or texture filters must not use mipmapping
  
  			texture.generateMipmaps = false;
  
  			texture.needsUpdate = true;
  
  			if ( onLoad ) onLoad( texture );
  
  		}
  
  		request.onerror = onError;
  
  		request.open( 'GET', url, true );
  		request.responseType = "arraybuffer";
  		request.send( null );
  
  		return texture;
  
  	},
  
  	loadTextureCube: function ( array, mapping, onLoad, onError ) {
  
  		var images = [];
  		images.loadCount = 0;
  
  		var texture = new THREE.Texture();
  		texture.image = images;
  		if ( mapping !== undefined ) texture.mapping = mapping;
  
  		// no flipping needed for cube textures
  
  		texture.flipY = false;
  
  		for ( var i = 0, il = array.length; i < il; ++ i ) {
  
  			var cubeImage = new Image();
  			images[ i ] = cubeImage;
  
  			cubeImage.onload = function () {
  
  				images.loadCount += 1;
  
  				if ( images.loadCount === 6 ) {
  
  					texture.needsUpdate = true;
  					if ( onLoad ) onLoad( texture );
  
  				}
  
  			};
  
  			cubeImage.onerror = onError;
  
  			cubeImage.crossOrigin = this.crossOrigin;
  			cubeImage.src = array[ i ];
  
  		}
  
  		return texture;
  
  	},
  
  	loadCompressedTextureCube: function ( array, mapping, onLoad, onError ) {
  
  		var images = [];
  		images.loadCount = 0;
  
  		var texture = new THREE.CompressedTexture();
  		texture.image = images;
  		if ( mapping !== undefined ) texture.mapping = mapping;
  
  		// no flipping for cube textures
  		// (also flipping doesn't work for compressed textures )
  
  		texture.flipY = false;
  
  		// can't generate mipmaps for compressed textures
  		// mips must be embedded in DDS files
  
  		texture.generateMipmaps = false;
  
  		var generateCubeFaceCallback = function ( rq, img ) {
  
  			return function () {
  
  				var buffer = rq.response;
  				var dds = THREE.ImageUtils.parseDDS( buffer, true );
  
  				img.format = dds.format;
  
  				img.mipmaps = dds.mipmaps;
  				img.width = dds.width;
  				img.height = dds.height;
  
  				images.loadCount += 1;
  
  				if ( images.loadCount === 6 ) {
  
  					texture.format = dds.format;
  					texture.needsUpdate = true;
  					if ( onLoad ) onLoad( texture );
  
  				}
  
  			}
  
  		}
  
  		// compressed cubemap textures as 6 separate DDS files
  
  		if ( array instanceof Array ) {
  
  			for ( var i = 0, il = array.length; i < il; ++ i ) {
  
  				var cubeImage = {};
  				images[ i ] = cubeImage;
  
  				var request = new XMLHttpRequest();
  
  				request.onload = generateCubeFaceCallback( request, cubeImage );
  				request.onerror = onError;
  
  				var url = array[ i ];
  
  				request.open( 'GET', url, true );
  				request.responseType = "arraybuffer";
  				request.send( null );
  
  			}
  
  		// compressed cubemap texture stored in a single DDS file
  
  		} else {
  
  			var url = array;
  			var request = new XMLHttpRequest();
  
  			request.onload = function( ) {
  
  				var buffer = request.response;
  				var dds = THREE.ImageUtils.parseDDS( buffer, true );
  
  				if ( dds.isCubemap ) {
  
  					var faces = dds.mipmaps.length / dds.mipmapCount;
  
  					for ( var f = 0; f < faces; f ++ ) {
  
  						images[ f ] = { mipmaps : [] };
  
  						for ( var i = 0; i < dds.mipmapCount; i ++ ) {
  
  							images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] );
  							images[ f ].format = dds.format;
  							images[ f ].width = dds.width;
  							images[ f ].height = dds.height;
  
  						}
  
  					}
  
  					texture.format = dds.format;
  					texture.needsUpdate = true;
  					if ( onLoad ) onLoad( texture );
  
  				}
  
  			}
  
  			request.onerror = onError;
  
  			request.open( 'GET', url, true );
  			request.responseType = "arraybuffer";
  			request.send( null );
  
  		}
  
  		return texture;
  
  	},
  
  	parseDDS: function ( buffer, loadMipmaps ) {
  
  		var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
  
  		// Adapted from @toji's DDS utils
  		//	https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
  
  		// All values and structures referenced from:
  		// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
  
  		var DDS_MAGIC = 0x20534444;
  
  		var DDSD_CAPS = 0x1,
  			DDSD_HEIGHT = 0x2,
  			DDSD_WIDTH = 0x4,
  			DDSD_PITCH = 0x8,
  			DDSD_PIXELFORMAT = 0x1000,
  			DDSD_MIPMAPCOUNT = 0x20000,
  			DDSD_LINEARSIZE = 0x80000,
  			DDSD_DEPTH = 0x800000;
  
  		var DDSCAPS_COMPLEX = 0x8,
  			DDSCAPS_MIPMAP = 0x400000,
  			DDSCAPS_TEXTURE = 0x1000;
  
  		var DDSCAPS2_CUBEMAP = 0x200,
  			DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
  			DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
  			DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
  			DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
  			DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
  			DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
  			DDSCAPS2_VOLUME = 0x200000;
  
  		var DDPF_ALPHAPIXELS = 0x1,
  			DDPF_ALPHA = 0x2,
  			DDPF_FOURCC = 0x4,
  			DDPF_RGB = 0x40,
  			DDPF_YUV = 0x200,
  			DDPF_LUMINANCE = 0x20000;
  
  		function fourCCToInt32( value ) {
  
  			return value.charCodeAt(0) +
  				(value.charCodeAt(1) << 8) +
  				(value.charCodeAt(2) << 16) +
  				(value.charCodeAt(3) << 24);
  
  		}
  
  		function int32ToFourCC( value ) {
  
  			return String.fromCharCode(
  				value & 0xff,
  				(value >> 8) & 0xff,
  				(value >> 16) & 0xff,
  				(value >> 24) & 0xff
  			);
  		}
  
  		var FOURCC_DXT1 = fourCCToInt32("DXT1");
  		var FOURCC_DXT3 = fourCCToInt32("DXT3");
  		var FOURCC_DXT5 = fourCCToInt32("DXT5");
  
  		var headerLengthInt = 31; // The header length in 32 bit ints
  
  		// Offsets into the header array
  
  		var off_magic = 0;
  
  		var off_size = 1;
  		var off_flags = 2;
  		var off_height = 3;
  		var off_width = 4;
  
  		var off_mipmapCount = 7;
  
  		var off_pfFlags = 20;
  		var off_pfFourCC = 21;
  
  		var off_caps = 27;
  		var off_caps2 = 28;
  		var off_caps3 = 29;
  		var off_caps4 = 30;
  
  		// Parse header
  
  		var header = new Int32Array( buffer, 0, headerLengthInt );
  
  		if ( header[ off_magic ] !== DDS_MAGIC ) {
  
  			console.error( "ImageUtils.parseDDS(): Invalid magic number in DDS header" );
  			return dds;
  
  		}
  
  		if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
  
  			console.error( "ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code" );
  			return dds;
  
  		}
  
  		var blockBytes;
  
  		var fourCC = header[ off_pfFourCC ];
  
  		switch ( fourCC ) {
  
  			case FOURCC_DXT1:
  
  				blockBytes = 8;
  				dds.format = THREE.RGB_S3TC_DXT1_Format;
  				break;
  
  			case FOURCC_DXT3:
  
  				blockBytes = 16;
  				dds.format = THREE.RGBA_S3TC_DXT3_Format;
  				break;
  
  			case FOURCC_DXT5:
  
  				blockBytes = 16;
  				dds.format = THREE.RGBA_S3TC_DXT5_Format;
  				break;
  
  			default:
  
  				console.error( "ImageUtils.parseDDS(): Unsupported FourCC code: ", int32ToFourCC( fourCC ) );
  				return dds;
  
  		}
  
  		dds.mipmapCount = 1;
  
  		if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
  
  			dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
  
  		}
  
  		//TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc.
  
  		dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false;
  
  		dds.width = header[ off_width ];
  		dds.height = header[ off_height ];
  
  		var dataOffset = header[ off_size ] + 4;
  
  		// Extract mipmaps buffers
  
  		var width = dds.width;
  		var height = dds.height;
  
  		var faces = dds.isCubemap ? 6 : 1;
  
  		for ( var face = 0; face < faces; face ++ ) {
  
  			for ( var i = 0; i < dds.mipmapCount; i ++ ) {
  
  				var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
  				var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
  
  				var mipmap = { "data": byteArray, "width": width, "height": height };
  				dds.mipmaps.push( mipmap );
  
  				dataOffset += dataLength;
  
  				width = Math.max( width * 0.5, 1 );
  				height = Math.max( height * 0.5, 1 );
  
  			}
  
  			width = dds.width;
  			height = dds.height;
  
  		}
  
  		return dds;
  
  	},
  
  	getNormalMap: function ( image, depth ) {
  
  		// Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/
  
  		var cross = function ( a, b ) {
  
  			return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ];
  
  		}
  
  		var subtract = function ( a, b ) {
  
  			return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ];
  
  		}
  
  		var normalize = function ( a ) {
  
  			var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] );
  			return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ];
  
  		}
  
  		depth = depth | 1;
  
  		var width = image.width;
  		var height = image.height;
  
  		var canvas = document.createElement( 'canvas' );
  		canvas.width = width;
  		canvas.height = height;
  
  		var context = canvas.getContext( '2d' );
  		context.drawImage( image, 0, 0 );
  
  		var data = context.getImageData( 0, 0, width, height ).data;
  		var imageData = context.createImageData( width, height );
  		var output = imageData.data;
  
  		for ( var x = 0; x < width; x ++ ) {
  
  			for ( var y = 0; y < height; y ++ ) {
  
  				var ly = y - 1 < 0 ? 0 : y - 1;
  				var uy = y + 1 > height - 1 ? height - 1 : y + 1;
  				var lx = x - 1 < 0 ? 0 : x - 1;
  				var ux = x + 1 > width - 1 ? width - 1 : x + 1;
  
  				var points = [];
  				var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ];
  				points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] );
  				points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] );
  				points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] );
  				points.push( [  1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] );
  				points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] );
  				points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] );
  				points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] );
  				points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] );
  
  				var normals = [];
  				var num_points = points.length;
  
  				for ( var i = 0; i < num_points; i ++ ) {
  
  					var v1 = points[ i ];
  					var v2 = points[ ( i + 1 ) % num_points ];
  					v1 = subtract( v1, origin );
  					v2 = subtract( v2, origin );
  					normals.push( normalize( cross( v1, v2 ) ) );
  
  				}
  
  				var normal = [ 0, 0, 0 ];
  
  				for ( var i = 0; i < normals.length; i ++ ) {
  
  					normal[ 0 ] += normals[ i ][ 0 ];
  					normal[ 1 ] += normals[ i ][ 1 ];
  					normal[ 2 ] += normals[ i ][ 2 ];
  
  				}
  
  				normal[ 0 ] /= normals.length;
  				normal[ 1 ] /= normals.length;
  				normal[ 2 ] /= normals.length;
  
  				var idx = ( y * width + x ) * 4;
  
  				output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0;
  				output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0;
  				output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0;
  				output[ idx + 3 ] = 255;
  
  			}
  
  		}
  
  		context.putImageData( imageData, 0, 0 );
  
  		return canvas;
  
  	},
  
  	generateDataTexture: function ( width, height, color ) {
  
  		var size = width * height;
  		var data = new Uint8Array( 3 * size );
  
  		var r = Math.floor( color.r * 255 );
  		var g = Math.floor( color.g * 255 );
  		var b = Math.floor( color.b * 255 );
  
  		for ( var i = 0; i < size; i ++ ) {
  
  			data[ i * 3 ] 	  = r;
  			data[ i * 3 + 1 ] = g;
  			data[ i * 3 + 2 ] = b;
  
  		}
  
  		var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
  		texture.needsUpdate = true;
  
  		return texture;
  
  	}
  
  };
  