Переглянути джерело

Merge remote-tracking branch 'mrdoob/dev' into dev

Daniel 9 роки тому
батько
коміт
9c0f3460b9

+ 66 - 23
build/three.js

@@ -14781,6 +14781,8 @@ THREE.XHRLoader.prototype = {
 
 	load: function ( url, onLoad, onProgress, onError ) {
 
+		if ( this.path !== undefined ) url = this.path + url;
+
 		var scope = this;
 
 		var cached = THREE.Cache.get( url );
@@ -14846,15 +14848,21 @@ THREE.XHRLoader.prototype = {
 
 	},
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	setResponseType: function ( value ) {
 
 		this.responseType = value;
 
 	},
 
-	setCrossOrigin: function ( value ) {
+	setPath: function ( value ) {
 
-		this.crossOrigin = value;
+		this.path = value;
 
 	},
 
@@ -14884,6 +14892,8 @@ THREE.ImageLoader.prototype = {
 
 	load: function ( url, onLoad, onProgress, onError ) {
 
+		if ( this.path !== undefined ) url = this.path + url;
+
 		var scope = this;
 
 		var cached = THREE.Cache.get( url );
@@ -14956,6 +14966,12 @@ THREE.ImageLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };
@@ -16468,6 +16484,7 @@ THREE.TextureLoader.prototype = {
 
 		var loader = new THREE.ImageLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
+		loader.setPath( this.path );
 		loader.load( url, function ( image ) {
 
 			texture.image = image;
@@ -16489,6 +16506,12 @@ THREE.TextureLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };
@@ -16515,6 +16538,7 @@ THREE.CubeTextureLoader.prototype = {
 
 		var loader = new THREE.ImageLoader();
 		loader.setCrossOrigin( this.crossOrigin );
+		loader.setPath( this.path );
 
 		var loaded = 0;
 
@@ -16552,6 +16576,12 @@ THREE.CubeTextureLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };
@@ -16688,42 +16718,43 @@ THREE.CompressedTextureLoader.prototype = {
 
 		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
+		loader.setPath( this.path );
 		loader.setResponseType( 'arraybuffer' );
 
-		if ( Array.isArray( url ) ) {
+		function loadTexture( i ) {
 
-			var loaded = 0;
+			loader.load( url[ i ], function ( buffer ) {
 
-			var loadTexture = function ( i ) {
+				var texDatas = scope._parser( buffer, true );
 
-				loader.load( url[ i ], function ( buffer ) {
+				images[ i ] = {
+					width: texDatas.width,
+					height: texDatas.height,
+					format: texDatas.format,
+					mipmaps: texDatas.mipmaps
+				};
 
-					var texDatas = scope._parser( buffer, true );
+				loaded += 1;
 
-					images[ i ] = {
-						width: texDatas.width,
-						height: texDatas.height,
-						format: texDatas.format,
-						mipmaps: texDatas.mipmaps
-					};
+				if ( loaded === 6 ) {
 
-					loaded += 1;
+					if ( texDatas.mipmapCount === 1 )
+						texture.minFilter = THREE.LinearFilter;
 
-					if ( loaded === 6 ) {
+					texture.format = texDatas.format;
+					texture.needsUpdate = true;
 
-						if ( texDatas.mipmapCount === 1 )
- 							texture.minFilter = THREE.LinearFilter;
+					if ( onLoad ) onLoad( texture );
 
-						texture.format = texDatas.format;
-						texture.needsUpdate = true;
+				}
 
-						if ( onLoad ) onLoad( texture );
+			}, onProgress, onError );
 
-					}
+		}
 
-				}, onProgress, onError );
+		if ( Array.isArray( url ) ) {
 
-			};
+			var loaded = 0;
 
 			for ( var i = 0, il = url.length; i < il; ++ i ) {
 
@@ -16789,6 +16820,12 @@ THREE.CompressedTextureLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };
@@ -26025,6 +26062,12 @@ THREE.WebGLGeometries = function ( gl, properties, info ) {
 		var geometry = event.target;
 		var buffergeometry = geometries[ geometry.id ];
 
+		if ( buffergeometry.index !== null ) {
+
+			deleteAttribute( buffergeometry.index );
+
+		}
+
 		deleteAttributes( buffergeometry.attributes );
 
 		geometry.removeEventListener( 'dispose', onGeometryDispose );

+ 12 - 11
build/three.min.js

@@ -333,10 +333,10 @@ case "mapNormal":l.normalMap=f(m,d.mapNormalRepeat,d.mapNormalOffset,d.mapNormal
 f(m,d.mapAlphaRepeat,d.mapAlphaOffset,d.mapAlphaWrap,d.mapAlphaAnisotropy);break;case "mapAlphaRepeat":case "mapAlphaOffset":case "mapAlphaWrap":case "mapAlphaAnisotropy":break;case "flipSided":l.side=THREE.BackSide;break;case "doubleSided":l.side=THREE.DoubleSide;break;case "transparency":console.warn("THREE.Loader.createMaterial: transparency has been renamed to opacity");l.opacity=m;break;case "opacity":case "transparent":case "depthTest":case "depthWrite":case "transparent":case "visible":case "wireframe":l[k]=
 m;break;case "vertexColors":!0===m&&(l.vertexColors=THREE.VertexColors);"face"===m&&(l.vertexColors=THREE.FaceColors);break;default:console.error("THREE.Loader.createMaterial: Unsupported",k,m)}}"MeshPhongMaterial"!==l.type&&delete l.specular;1>l.opacity&&(l.transparent=!0);c.setTextures(h);return c.parse(l)}}()};THREE.Loader.Handlers={handlers:[],add:function(a,b){this.handlers.push(a,b)},get:function(a){for(var b=this.handlers,c=0,d=b.length;c<d;c+=2){var e=b[c+1];if(b[c].test(a))return e}return null}};
 THREE.XHRLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
-THREE.XHRLoader.prototype={constructor:THREE.XHRLoader,load:function(a,b,c,d){var e=this,g=THREE.Cache.get(a);if(void 0!==g)return b&&setTimeout(function(){b(g)},0),g;var f=new XMLHttpRequest;f.open("GET",a,!0);f.addEventListener("load",function(c){c=c.target.response;THREE.Cache.add(a,c);b&&b(c);e.manager.itemEnd(a)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);f.addEventListener("error",function(b){d&&d(b);e.manager.itemError(a)},!1);void 0!==this.crossOrigin&&(f.crossOrigin=
-this.crossOrigin);void 0!==this.responseType&&(f.responseType=this.responseType);void 0!==this.withCredentials&&(f.withCredentials=this.withCredentials);f.send(null);e.manager.itemStart(a);return f},setResponseType:function(a){this.responseType=a},setCrossOrigin:function(a){this.crossOrigin=a},setWithCredentials:function(a){this.withCredentials=a}};THREE.ImageLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
-THREE.ImageLoader.prototype={constructor:THREE.ImageLoader,load:function(a,b,c,d){var e=this,g=THREE.Cache.get(a);if(void 0!==g)return e.manager.itemStart(a),b?setTimeout(function(){b(g);e.manager.itemEnd(a)},0):e.manager.itemEnd(a),g;var f=document.createElement("img");f.addEventListener("load",function(c){THREE.Cache.add(a,this);b&&b(this);e.manager.itemEnd(a)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);f.addEventListener("error",function(b){d&&d(b);e.manager.itemError(a)},
-!1);void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin);e.manager.itemStart(a);f.src=a;return f},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.JSONLoader=function(a){"boolean"===typeof a&&(console.warn("THREE.JSONLoader: showStatus parameter has been removed from constructor."),a=void 0);this.manager=void 0!==a?a:THREE.DefaultLoadingManager;this.withCredentials=!1};
+THREE.XHRLoader.prototype={constructor:THREE.XHRLoader,load:function(a,b,c,d){void 0!==this.path&&(a=this.path+a);var e=this,g=THREE.Cache.get(a);if(void 0!==g)return b&&setTimeout(function(){b(g)},0),g;var f=new XMLHttpRequest;f.open("GET",a,!0);f.addEventListener("load",function(c){c=c.target.response;THREE.Cache.add(a,c);b&&b(c);e.manager.itemEnd(a)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);f.addEventListener("error",function(b){d&&d(b);e.manager.itemError(a)},!1);void 0!==
+this.crossOrigin&&(f.crossOrigin=this.crossOrigin);void 0!==this.responseType&&(f.responseType=this.responseType);void 0!==this.withCredentials&&(f.withCredentials=this.withCredentials);f.send(null);e.manager.itemStart(a);return f},setCrossOrigin:function(a){this.crossOrigin=a},setResponseType:function(a){this.responseType=a},setPath:function(a){this.path=a},setWithCredentials:function(a){this.withCredentials=a}};THREE.ImageLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.ImageLoader.prototype={constructor:THREE.ImageLoader,load:function(a,b,c,d){void 0!==this.path&&(a=this.path+a);var e=this,g=THREE.Cache.get(a);if(void 0!==g)return e.manager.itemStart(a),b?setTimeout(function(){b(g);e.manager.itemEnd(a)},0):e.manager.itemEnd(a),g;var f=document.createElement("img");f.addEventListener("load",function(c){THREE.Cache.add(a,this);b&&b(this);e.manager.itemEnd(a)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);f.addEventListener("error",function(b){d&&
+d(b);e.manager.itemError(a)},!1);void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin);e.manager.itemStart(a);f.src=a;return f},setCrossOrigin:function(a){this.crossOrigin=a},setPath:function(a){this.path=a}};THREE.JSONLoader=function(a){"boolean"===typeof a&&(console.warn("THREE.JSONLoader: showStatus parameter has been removed from constructor."),a=void 0);this.manager=void 0!==a?a:THREE.DefaultLoadingManager;this.withCredentials=!1};
 THREE.JSONLoader.prototype={constructor:THREE.JSONLoader,get statusDomElement(){void 0===this._statusDomElement&&(this._statusDomElement=document.createElement("div"));console.warn("THREE.JSONLoader: .statusDomElement has been removed.");return this._statusDomElement},load:function(a,b,c,d){var e=this,g=this.texturePath&&"string"===typeof this.texturePath?this.texturePath:THREE.Loader.prototype.extractUrlBase(a);c=new THREE.XHRLoader(this.manager);c.setCrossOrigin(this.crossOrigin);c.setWithCredentials(this.withCredentials);
 c.load(a,function(c){c=JSON.parse(c);var d=c.metadata;if(void 0!==d&&(d=d.type,void 0!==d)){if("object"===d.toLowerCase()){console.error("THREE.JSONLoader: "+a+" should be loaded with THREE.ObjectLoader instead.");return}if("scene"===d.toLowerCase()){console.error("THREE.JSONLoader: "+a+" should be loaded with THREE.SceneLoader instead.");return}}c=e.parse(c,g);b(c.geometry,c.materials)})},setCrossOrigin:function(a){this.crossOrigin=a},setTexturePath:function(a){this.texturePath=a},parse:function(a,
 b){var c=new THREE.Geometry,d=void 0!==a.scale?1/a.scale:1;(function(b){var d,f,h,l,k,m,p,n,q,s,t,v,u,x=a.faces;m=a.vertices;var D=a.normals,w=a.colors,B=0;if(void 0!==a.uvs){for(d=0;d<a.uvs.length;d++)a.uvs[d].length&&B++;for(d=0;d<B;d++)c.faceVertexUvs[d]=[]}l=0;for(k=m.length;l<k;)d=new THREE.Vector3,d.x=m[l++]*b,d.y=m[l++]*b,d.z=m[l++]*b,c.vertices.push(d);l=0;for(k=x.length;l<k;)if(b=x[l++],q=b&1,h=b&2,d=b&8,p=b&16,s=b&32,m=b&64,b&=128,q){q=new THREE.Face3;q.a=x[l];q.b=x[l+1];q.c=x[l+3];t=new THREE.Face3;
@@ -370,15 +370,16 @@ c,d){function e(a){void 0===c[a]&&console.warn("THREE.ObjectLoader: Undefined ge
 break;case "DirectionalLight":f=new THREE.DirectionalLight(b.color,b.intensity);break;case "PointLight":f=new THREE.PointLight(b.color,b.intensity,b.distance,b.decay);break;case "SpotLight":f=new THREE.SpotLight(b.color,b.intensity,b.distance,b.angle,b.exponent,b.decay);break;case "HemisphereLight":f=new THREE.HemisphereLight(b.color,b.groundColor,b.intensity);break;case "Mesh":f=new THREE.Mesh(e(b.geometry),g(b.material));break;case "LOD":f=new THREE.LOD;break;case "Line":f=new THREE.Line(e(b.geometry),
 g(b.material),b.mode);break;case "PointCloud":case "Points":f=new THREE.Points(e(b.geometry),g(b.material));break;case "Sprite":f=new THREE.Sprite(g(b.material));break;case "Group":f=new THREE.Group;break;default:f=new THREE.Object3D}f.uuid=b.uuid;void 0!==b.name&&(f.name=b.name);void 0!==b.matrix?(a.fromArray(b.matrix),a.decompose(f.position,f.quaternion,f.scale)):(void 0!==b.position&&f.position.fromArray(b.position),void 0!==b.rotation&&f.rotation.fromArray(b.rotation),void 0!==b.scale&&f.scale.fromArray(b.scale));
 void 0!==b.castShadow&&(f.castShadow=b.castShadow);void 0!==b.receiveShadow&&(f.receiveShadow=b.receiveShadow);void 0!==b.visible&&(f.visible=b.visible);void 0!==b.userData&&(f.userData=b.userData);if(void 0!==b.children)for(var h in b.children)f.add(this.parseObject(b.children[h],c,d));if("LOD"===b.type){b=b.levels;for(var l=0;l<b.length;l++){var k=b[l];h=f.getObjectByProperty("uuid",k.object);void 0!==h&&f.addLevel(h,k.distance)}}return f}}()};
-THREE.TextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};THREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a,b,c,d){var e=new THREE.Texture,g=new THREE.ImageLoader(this.manager);g.setCrossOrigin(this.crossOrigin);g.load(a,function(a){e.image=a;e.needsUpdate=!0;void 0!==b&&b(e)},c,d);return e},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.CubeTextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
-THREE.CubeTextureLoader.prototype={constructor:THREE.CubeTextureLoader,load:function(a,b,c,d){function e(c){f.load(a[c],function(a){g.images[c]=a;h++;6===h&&(g.needsUpdate=!0,b&&b(g))},void 0,d)}var g=new THREE.CubeTexture([]),f=new THREE.ImageLoader;f.setCrossOrigin(this.crossOrigin);var h=0;for(c=0;c<a.length;++c)e(c);return g},setCrossOrigin:function(a){this.crossOrigin=a}};
+THREE.TextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};THREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a,b,c,d){var e=new THREE.Texture,g=new THREE.ImageLoader(this.manager);g.setCrossOrigin(this.crossOrigin);g.setPath(this.path);g.load(a,function(a){e.image=a;e.needsUpdate=!0;void 0!==b&&b(e)},c,d);return e},setCrossOrigin:function(a){this.crossOrigin=a},setPath:function(a){this.path=a}};
+THREE.CubeTextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.CubeTextureLoader.prototype={constructor:THREE.CubeTextureLoader,load:function(a,b,c,d){function e(c){f.load(a[c],function(a){g.images[c]=a;h++;6===h&&(g.needsUpdate=!0,b&&b(g))},void 0,d)}var g=new THREE.CubeTexture([]),f=new THREE.ImageLoader;f.setCrossOrigin(this.crossOrigin);f.setPath(this.path);var h=0;for(c=0;c<a.length;++c)e(c);return g},setCrossOrigin:function(a){this.crossOrigin=a},setPath:function(a){this.path=a}};
 THREE.DataTextureLoader=THREE.BinaryTextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager;this._parser=null};
 THREE.BinaryTextureLoader.prototype={constructor:THREE.BinaryTextureLoader,load:function(a,b,c,d){var e=this,g=new THREE.DataTexture,f=new THREE.XHRLoader(this.manager);f.setCrossOrigin(this.crossOrigin);f.setResponseType("arraybuffer");f.load(a,function(a){if(a=e._parser(a))void 0!==a.image?g.image=a.image:void 0!==a.data&&(g.image.width=a.width,g.image.height=a.height,g.image.data=a.data),g.wrapS=void 0!==a.wrapS?a.wrapS:THREE.ClampToEdgeWrapping,g.wrapT=void 0!==a.wrapT?a.wrapT:THREE.ClampToEdgeWrapping,
 g.magFilter=void 0!==a.magFilter?a.magFilter:THREE.LinearFilter,g.minFilter=void 0!==a.minFilter?a.minFilter:THREE.LinearMipMapLinearFilter,g.anisotropy=void 0!==a.anisotropy?a.anisotropy:1,void 0!==a.format&&(g.format=a.format),void 0!==a.type&&(g.type=a.type),void 0!==a.mipmaps&&(g.mipmaps=a.mipmaps),1===a.mipmapCount&&(g.minFilter=THREE.LinearFilter),g.needsUpdate=!0,b&&b(g,a)},c,d);return g},setCrossOrigin:function(a){this.crossOrigin=a}};
 THREE.CompressedTextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager;this._parser=null};
-THREE.CompressedTextureLoader.prototype={constructor:THREE.CompressedTextureLoader,load:function(a,b,c,d){var e=this,g=[],f=new THREE.CompressedTexture;f.image=g;var h=new THREE.XHRLoader(this.manager);h.setCrossOrigin(this.crossOrigin);h.setResponseType("arraybuffer");if(Array.isArray(a))for(var l=0,k=function(k){h.load(a[k],function(a){a=e._parser(a,!0);g[k]={width:a.width,height:a.height,format:a.format,mipmaps:a.mipmaps};l+=1;6===l&&(1===a.mipmapCount&&(f.minFilter=THREE.LinearFilter),f.format=
-a.format,f.needsUpdate=!0,b&&b(f))},c,d)},m=0,p=a.length;m<p;++m)k(m);else h.load(a,function(a){a=e._parser(a,!0);if(a.isCubemap)for(var c=a.mipmaps.length/a.mipmapCount,d=0;d<c;d++){g[d]={mipmaps:[]};for(var h=0;h<a.mipmapCount;h++)g[d].mipmaps.push(a.mipmaps[d*a.mipmapCount+h]),g[d].format=a.format,g[d].width=a.width,g[d].height=a.height}else f.image.width=a.width,f.image.height=a.height,f.mipmaps=a.mipmaps;1===a.mipmapCount&&(f.minFilter=THREE.LinearFilter);f.format=a.format;f.needsUpdate=!0;b&&
-b(f)},c,d);return f},setCrossOrigin:function(a){this.crossOrigin=a}};
+THREE.CompressedTextureLoader.prototype={constructor:THREE.CompressedTextureLoader,load:function(a,b,c,d){function e(e){l.load(a[e],function(a){a=g._parser(a,!0);f[e]={width:a.width,height:a.height,format:a.format,mipmaps:a.mipmaps};k+=1;6===k&&(1===a.mipmapCount&&(h.minFilter=THREE.LinearFilter),h.format=a.format,h.needsUpdate=!0,b&&b(h))},c,d)}var g=this,f=[],h=new THREE.CompressedTexture;h.image=f;var l=new THREE.XHRLoader(this.manager);l.setCrossOrigin(this.crossOrigin);l.setPath(this.path);l.setResponseType("arraybuffer");
+if(Array.isArray(a))for(var k=0,m=0,p=a.length;m<p;++m)e(m);else l.load(a,function(a){a=g._parser(a,!0);if(a.isCubemap)for(var c=a.mipmaps.length/a.mipmapCount,d=0;d<c;d++){f[d]={mipmaps:[]};for(var e=0;e<a.mipmapCount;e++)f[d].mipmaps.push(a.mipmaps[d*a.mipmapCount+e]),f[d].format=a.format,f[d].width=a.width,f[d].height=a.height}else h.image.width=a.width,h.image.height=a.height,h.mipmaps=a.mipmaps;1===a.mipmapCount&&(h.minFilter=THREE.LinearFilter);h.format=a.format;h.needsUpdate=!0;b&&b(h)},c,
+d);return h},setCrossOrigin:function(a){this.crossOrigin=a},setPath:function(a){this.path=a}};
 THREE.Material=function(){Object.defineProperty(this,"id",{value:THREE.MaterialIdCount++});this.uuid=THREE.Math.generateUUID();this.name="";this.type="Material";this.side=THREE.FrontSide;this.opacity=1;this.transparent=!1;this.blending=THREE.NormalBlending;this.blendSrc=THREE.SrcAlphaFactor;this.blendDst=THREE.OneMinusSrcAlphaFactor;this.blendEquation=THREE.AddEquation;this.blendEquationAlpha=this.blendDstAlpha=this.blendSrcAlpha=null;this.depthFunc=THREE.LessEqualDepth;this.colorWrite=this.depthWrite=
 this.depthTest=!0;this.precision=null;this.polygonOffset=!1;this.overdraw=this.alphaTest=this.polygonOffsetUnits=this.polygonOffsetFactor=0;this._needsUpdate=this.visible=!0};
 THREE.Material.prototype={constructor:THREE.Material,get needsUpdate(){return this._needsUpdate},set needsUpdate(a){!0===a&&this.update();this._needsUpdate=a},setValues:function(a){if(void 0!==a)for(var b in a){var c=a[b];if(void 0===c)console.warn("THREE.Material: '"+b+"' parameter is undefined.");else{var d=this[b];void 0===d?console.warn("THREE."+this.type+": '"+b+"' is not a property of this material."):d instanceof THREE.Color?d.set(c):d instanceof THREE.Vector3&&c instanceof THREE.Vector3?d.copy(c):
@@ -648,8 +649,8 @@ break;case "WEBGL_compressed_texture_pvrtc":d=a.getExtension("WEBGL_compressed_t
 THREE.WebGLCapabilities=function(a,b,c){function d(b){if("highp"===b){if(0<a.getShaderPrecisionFormat(a.VERTEX_SHADER,a.HIGH_FLOAT).precision&&0<a.getShaderPrecisionFormat(a.FRAGMENT_SHADER,a.HIGH_FLOAT).precision)return"highp";b="mediump"}return"mediump"===b&&0<a.getShaderPrecisionFormat(a.VERTEX_SHADER,a.MEDIUM_FLOAT).precision&&0<a.getShaderPrecisionFormat(a.FRAGMENT_SHADER,a.MEDIUM_FLOAT).precision?"mediump":"lowp"}this.getMaxPrecision=d;this.precision=void 0!==c.precision?c.precision:"highp";
 this.logarithmicDepthBuffer=void 0!==c.logarithmicDepthBuffer?c.logarithmicDepthBuffer:!1;this.maxTextures=a.getParameter(a.MAX_TEXTURE_IMAGE_UNITS);this.maxVertexTextures=a.getParameter(a.MAX_VERTEX_TEXTURE_IMAGE_UNITS);this.maxTextureSize=a.getParameter(a.MAX_TEXTURE_SIZE);this.maxCubemapSize=a.getParameter(a.MAX_CUBE_MAP_TEXTURE_SIZE);this.maxAttributes=a.getParameter(a.MAX_VERTEX_ATTRIBS);this.maxVertexUniforms=a.getParameter(a.MAX_VERTEX_UNIFORM_VECTORS);this.maxVaryings=a.getParameter(a.MAX_VARYING_VECTORS);
 this.maxFragmentUniforms=a.getParameter(a.MAX_FRAGMENT_UNIFORM_VECTORS);this.vertexTextures=0<this.maxVertexTextures;this.floatFragmentTextures=!!b.get("OES_texture_float");this.floatVertexTextures=this.vertexTextures&&this.floatFragmentTextures;c=d(this.precision);c!==this.precision&&(console.warn("THREE.WebGLRenderer:",this.precision,"not supported, using",c,"instead."),this.precision=c);this.logarithmicDepthBuffer&&(this.logarithmicDepthBuffer=!!b.get("EXT_frag_depth"))};
-THREE.WebGLGeometries=function(a,b,c){function d(a){a=a.target;var h=g[a.id].attributes,l;for(l in h)e(h[l]);a.removeEventListener("dispose",d);delete g[a.id];l=b.get(a);l.wireframe&&e(l.wireframe);c.memory.geometries--}function e(c){var d;d=c instanceof THREE.InterleavedBufferAttribute?b.get(c.data).__webglBuffer:b.get(c).__webglBuffer;void 0!==d&&(a.deleteBuffer(d),c instanceof THREE.InterleavedBufferAttribute?b.delete(c.data):b.delete(c))}var g={};this.get=function(a){var b=a.geometry;if(void 0!==
-g[b.id])return g[b.id];b.addEventListener("dispose",d);var e;b instanceof THREE.BufferGeometry?e=b:b instanceof THREE.Geometry&&(void 0===b._bufferGeometry&&(b._bufferGeometry=(new THREE.BufferGeometry).setFromObject(a)),e=b._bufferGeometry);g[b.id]=e;c.memory.geometries++;return e}};
+THREE.WebGLGeometries=function(a,b,c){function d(a){a=a.target;var h=g[a.id];null!==h.index&&e(h.index);var h=h.attributes,l;for(l in h)e(h[l]);a.removeEventListener("dispose",d);delete g[a.id];l=b.get(a);l.wireframe&&e(l.wireframe);c.memory.geometries--}function e(c){var d;d=c instanceof THREE.InterleavedBufferAttribute?b.get(c.data).__webglBuffer:b.get(c).__webglBuffer;void 0!==d&&(a.deleteBuffer(d),c instanceof THREE.InterleavedBufferAttribute?b.delete(c.data):b.delete(c))}var g={};this.get=function(a){var b=
+a.geometry;if(void 0!==g[b.id])return g[b.id];b.addEventListener("dispose",d);var e;b instanceof THREE.BufferGeometry?e=b:b instanceof THREE.Geometry&&(void 0===b._bufferGeometry&&(b._bufferGeometry=(new THREE.BufferGeometry).setFromObject(a)),e=b._bufferGeometry);g[b.id]=e;c.memory.geometries++;return e}};
 THREE.WebGLObjects=function(a,b,c){function d(c,d){var e=c instanceof THREE.InterleavedBufferAttribute?c.data:c,g=b.get(e);void 0===g.__webglBuffer?(g.__webglBuffer=a.createBuffer(),a.bindBuffer(d,g.__webglBuffer),a.bufferData(d,e.array,e.dynamic?a.DYNAMIC_DRAW:a.STATIC_DRAW),g.version=e.version):g.version!==e.version&&(a.bindBuffer(d,g.__webglBuffer),!1===e.dynamic||-1===e.updateRange.count?a.bufferSubData(d,0,e.array):0===e.updateRange.count?console.error("THREE.WebGLObjects.updateBuffer: dynamic THREE.BufferAttribute marked as needsUpdate but updateRange.count is 0, ensure you are using set methods or updating manually."):
 (a.bufferSubData(d,e.updateRange.offset*e.array.BYTES_PER_ELEMENT,e.array.subarray(e.updateRange.offset,e.updateRange.offset+e.updateRange.count)),e.updateRange.count=0),g.version=e.version)}function e(a,b,c){if(b>c){var d=b;b=c;c=d}d=a[b];return void 0===d?(a[b]=[c],!0):-1===d.indexOf(c)?(d.push(c),!0):!1}var g=new THREE.WebGLGeometries(a,b,c);this.getAttributeBuffer=function(a){return a instanceof THREE.InterleavedBufferAttribute?b.get(a.data).__webglBuffer:b.get(a).__webglBuffer};this.getWireframeAttribute=
 function(c){var g=b.get(c);if(void 0!==g.wireframe)return g.wireframe;var l=[],k=c.index,m=c.attributes;c=m.position;if(null!==k)for(var m={},k=k.array,p=0,n=k.length;p<n;p+=3){var q=k[p+0],s=k[p+1],t=k[p+2];e(m,q,s)&&l.push(q,s);e(m,s,t)&&l.push(s,t);e(m,t,q)&&l.push(t,q)}else for(k=m.position.array,p=0,n=k.length/3-1;p<n;p+=3)q=p+0,s=p+1,t=p+2,l.push(q,s,s,t,t,q);l=new THREE.BufferAttribute(new (65535<c.count?Uint32Array:Uint16Array)(l),1);d(l,a.ELEMENT_ARRAY_BUFFER);return g.wireframe=l};this.update=

+ 430 - 815
examples/js/controls/OrbitControls.js

@@ -7,1109 +7,724 @@
  */
 /*global THREE, console */
 
-( function () {
+// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
+// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
+// supported.
+//
+//    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 finter swipe
 
-	function OrbitConstraint ( object ) {
+THREE.OrbitControls = function ( object, domElement ) {
 
-		this.object = object;
+	this.object = object;
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
 
-		// "target" sets the location of focus, where the object orbits around
-		// and where it pans with respect to.
-		this.target = new THREE.Vector3();
+	// API
 
-		// Limits to how far you can dolly in and out ( PerspectiveCamera only )
-		this.minDistance = 0;
-		this.maxDistance = Infinity;
+	// Set to false to disable this control
+	this.enabled = true;
 
-		// Limits to how far you can zoom in and out ( OrthographicCamera only )
-		this.minZoom = 0;
-		this.maxZoom = Infinity;
+	// "target" sets the location of focus, where the control orbits around
+	// and where it pans with respect to.
+	this.target = new THREE.Vector3();
 
-		// 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
+	// center is old, deprecated; use "target" instead
+	this.center = this.target;
 
-		// 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
+	// This option actually enables dollying in and out; left as "zoom" for
+	// backwards compatibility
+	this.noZoom = false;
+	this.zoomSpeed = 1.0;
 
-		// 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;
+	// Limits to how far you can dolly in and out ( PerspectiveCamera only )
+	this.minDistance = 0;
+	this.maxDistance = Infinity;
 
-		////////////
-		// internals
+	// Limits to how far you can zoom in and out ( OrthographicCamera only )
+	this.minZoom = 0;
+	this.maxZoom = Infinity;
 
-		var scope = this;
+	// Set to true to disable this control
+	this.noRotate = false;
+	this.rotateSpeed = 1.0;
 
-		var EPS = 0.000001;
+	// Set to true to disable this control
+	this.noPan = false;
+	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
 
-		// Current position in spherical coordinate system.
-		var theta;
-		var phi;
+	// Set to true to automatically rotate around the target
+	this.autoRotate = false;
+	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
 
-		// Pending changes
-		var phiDelta = 0;
-		var thetaDelta = 0;
-		var scale = 1;
-		var panOffset = new THREE.Vector3();
-		var zoomChanged = false;
+	// 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
 
-		// API
+	// 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
 
-		this.getPolarAngle = function () {
+	// Set to true to disable use of the keys
+	this.noKeys = false;
 
-			return phi;
+	// 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 };
 
-		this.getAzimuthalAngle = function () {
+	////////////
+	// internals
 
-			return theta;
+	var scope = this;
 
-		};
+	var EPS = 0.000001;
 
-		this.rotateLeft = function ( angle ) {
+	var rotateStart = new THREE.Vector2();
+	var rotateEnd = new THREE.Vector2();
+	var rotateDelta = new THREE.Vector2();
 
-			thetaDelta -= angle;
+	var panStart = new THREE.Vector2();
+	var panEnd = new THREE.Vector2();
+	var panDelta = new THREE.Vector2();
+	var panOffset = new THREE.Vector3();
 
-		};
+	var offset = new THREE.Vector3();
 
-		this.rotateUp = function ( angle ) {
+	var dollyStart = new THREE.Vector2();
+	var dollyEnd = new THREE.Vector2();
+	var dollyDelta = new THREE.Vector2();
 
-			phiDelta -= angle;
+	var theta;
+	var phi;
+	var phiDelta = 0;
+	var thetaDelta = 0;
+	var scale = 1;
+	var pan = new THREE.Vector3();
 
-		};
+	var lastPosition = new THREE.Vector3();
+	var lastQuaternion = new THREE.Quaternion();
 
-		// pass in distance in world space to move left
-		this.panLeft = function() {
+	var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
 
-			var v = new THREE.Vector3();
+	var state = STATE.NONE;
 
-			return function panLeft ( distance ) {
+	// for reset
 
-				var te = this.object.matrix.elements;
+	this.target0 = this.target.clone();
+	this.position0 = this.object.position.clone();
+	this.zoom0 = this.object.zoom;
 
-				// get X column of matrix
-				v.set( te[ 0 ], te[ 1 ], te[ 2 ] );
-				v.multiplyScalar( - distance );
+	// so camera.up is the orbit axis
 
-				panOffset.add( v );
+	var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+	var quatInverse = quat.clone().inverse();
 
-			};
+	// events
 
-		}();
+	var changeEvent = { type: 'change' };
+	var startEvent = { type: 'start' };
+	var endEvent = { type: 'end' };
 
-		// pass in distance in world space to move up
-		this.panUp = function() {
+	this.rotateLeft = function ( angle ) {
 
-			var v = new THREE.Vector3();
+		if ( angle === undefined ) {
 
-			return function panUp ( distance ) {
+			angle = getAutoRotationAngle();
 
-				var te = this.object.matrix.elements;
-
-				// get Y column of matrix
-				v.set( te[ 4 ], te[ 5 ], te[ 6 ] );
-				v.multiplyScalar( distance );
-
-				panOffset.add( v );
-
-			};
-
-		}();
-
-		// pass in x,y of change desired in pixel space,
-		// right and down are positive
-		this.pan = function ( deltaX, deltaY, screenWidth, screenHeight ) {
-
-			if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
-				// perspective
-				var position = scope.object.position;
-				var offset = position.clone().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
-				scope.panLeft( 2 * deltaX * targetDistance / screenHeight );
-				scope.panUp( 2 * deltaY * targetDistance / screenHeight );
-
-			} else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
-				// orthographic
-				scope.panLeft( deltaX * ( scope.object.right - scope.object.left ) / screenWidth );
-				scope.panUp( deltaY * ( scope.object.top - scope.object.bottom ) / screenHeight );
-
-			} else {
-
-				// camera neither orthographic or perspective
-				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
-
-			}
-
-		};
-
-		this.dollyIn = function ( dollyScale ) {
-
-			if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
-				scale /= dollyScale;
-
-			} else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
-				scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) );
-				scope.object.updateProjectionMatrix();
-				zoomChanged = true;
-
-			} else {
-
-				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-
-			}
-
-		};
-
-		this.dollyOut = function ( dollyScale ) {
-
-			if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
-				scale *= dollyScale;
-
-			} else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
-				scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) );
-				scope.object.updateProjectionMatrix();
-				zoomChanged = true;
-
-			} else {
-
-				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-
-			}
-
-		};
-
-		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 () {
-
-				var position = this.object.position;
-
-				offset.copy( position ).sub( this.target );
-
-				// rotate offset to "y-axis-is-up" space
-				offset.applyQuaternion( quat );
-
-				// angle from z-axis around y-axis
-
-				theta = Math.atan2( offset.x, offset.z );
-
-				// angle from y-axis
-
-				phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
-
-				theta += thetaDelta;
-				phi += phiDelta;
-
-				// restrict theta to be between desired limits
-				theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
-
-				// restrict phi to be between desired limits
-				phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
-
-				// restrict phi to be betwee EPS and PI-EPS
-				phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
-
-				var radius = offset.length() * scale;
-
-				// restrict radius to be between desired limits
-				radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
-
-				// move target to panned location
-				this.target.add( panOffset );
-
-				offset.x = radius * Math.sin( phi ) * Math.sin( theta );
-				offset.y = radius * Math.cos( phi );
-				offset.z = radius * Math.sin( phi ) * Math.cos( theta );
-
-				// rotate offset back to "camera-up-vector-is-up" space
-				offset.applyQuaternion( quatInverse );
-
-				position.copy( this.target ).add( offset );
-
-				this.object.lookAt( this.target );
-
-				if ( this.enableDamping === true ) {
-
-					thetaDelta *= ( 1 - this.dampingFactor );
-					phiDelta *= ( 1 - this.dampingFactor );
-
-				} else {
-
-					thetaDelta = 0;
-					phiDelta = 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( this.object.position ) > EPS ||
-				    8 * ( 1 - lastQuaternion.dot( this.object.quaternion ) ) > EPS ) {
-
-					lastPosition.copy( this.object.position );
-					lastQuaternion.copy( this.object.quaternion );
-					zoomChanged = false;
-
-					return true;
-
-				}
-
-				return false;
-
-			};
+		}
 
-		}();
+		thetaDelta -= angle;
 
 	};
 
+	this.rotateUp = function ( angle ) {
 
-	// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
-	// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
-	// supported.
-	//
-	//    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 finter swipe
-
-	THREE.OrbitControls = function ( object, domElement ) {
-
-		var constraint = new OrbitConstraint( object );
-
-		this.domElement = ( domElement !== undefined ) ? domElement : document;
-
-		// API
-
-		Object.defineProperty( this, 'constraint', {
-
-			get: function() {
-
-				return constraint;
-
-			}
-
-		} );
-
-		this.getPolarAngle = function () {
-
-			return constraint.getPolarAngle();
-
-		};
-
-		this.getAzimuthalAngle = function () {
-
-			return constraint.getAzimuthalAngle();
+		if ( angle === undefined ) {
 
-		};
+			angle = getAutoRotationAngle();
 
-		// Set to false to disable this control
-		this.enabled = true;
+		}
 
-		// center is old, deprecated; use "target" instead
-		this.center = this.target;
+		phiDelta -= angle;
 
-		// 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;
+	// pass in distance in world space to move left
+	this.panLeft = function ( distance ) {
 
-		// Set to false to disable panning
-		this.enablePan = true;
-		this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
+		var te = this.object.matrix.elements;
 
-		// 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
+		// get X column of matrix
+		panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
+		panOffset.multiplyScalar( - distance );
 
-		// Set to false to disable use of the keys
-		this.enableKeys = true;
+		pan.add( panOffset );
 
-		// 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 };
+	// pass in distance in world space to move up
+	this.panUp = function ( distance ) {
 
-		////////////
-		// internals
+		var te = this.object.matrix.elements;
 
-		var scope = this;
+		// get Y column of matrix
+		panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
+		panOffset.multiplyScalar( distance );
 
-		var rotateStart = new THREE.Vector2();
-		var rotateEnd = new THREE.Vector2();
-		var rotateDelta = new THREE.Vector2();
+		pan.add( panOffset );
 
-		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();
+	// pass in x,y of change desired in pixel space,
+	// right and down are positive
+	this.pan = function ( deltaX, deltaY ) {
 
-		var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
+		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
 
-		var state = STATE.NONE;
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
 
-		// for reset
+			// perspective
+			var position = scope.object.position;
+			var offset = position.clone().sub( scope.target );
+			var targetDistance = offset.length();
 
-		this.target0 = this.target.clone();
-		this.position0 = this.object.position.clone();
-		this.zoom0 = this.object.zoom;
+			// half of the fov is center to top of screen
+			targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
 
-		// events
+			// we actually don't use screenWidth, since perspective camera is fixed to screen height
+			scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
+			scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
 
-		var changeEvent = { type: 'change' };
-		var startEvent = { type: 'start' };
-		var endEvent = { type: 'end' };
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
 
-		// pass in x,y of change desired in pixel space,
-		// right and down are positive
-		function pan( deltaX, deltaY ) {
+			// orthographic
+			scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
+			scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
 
-			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+		} else {
 
-			constraint.pan( deltaX, deltaY, element.clientWidth, element.clientHeight );
+			// camera neither orthographic or perspective
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
 
 		}
 
-		this.update = function () {
-
-			if ( this.autoRotate && state === STATE.NONE ) {
-
-				constraint.rotateLeft( getAutoRotationAngle() );
-
-			}
-
-			if ( constraint.update() === true ) {
-
-				this.dispatchEvent( changeEvent );
-
-			}
-
-		};
-
-		this.reset = function () {
-
-			state = STATE.NONE;
-
-			this.target.copy( this.target0 );
-			this.object.position.copy( this.position0 );
-			this.object.zoom = this.zoom0;
-
-			this.object.updateProjectionMatrix();
-			this.dispatchEvent( changeEvent );
-
-			this.update();
-
-		};
-
-		function getAutoRotationAngle() {
-
-			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+	};
 
-		}
+	this.dollyIn = function ( dollyScale ) {
 
-		function getZoomScale() {
+		if ( dollyScale === undefined ) {
 
-			return Math.pow( 0.95, scope.zoomSpeed );
+			dollyScale = getZoomScale();
 
 		}
 
-		function onMouseDown( event ) {
-
-			if ( scope.enabled === false ) return;
-
-			event.preventDefault();
-
-			if ( event.button === scope.mouseButtons.ORBIT ) {
-
-				if ( scope.enableRotate === false ) return;
-
-				state = STATE.ROTATE;
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
 
-				rotateStart.set( event.clientX, event.clientY );
+			scale /= dollyScale;
 
-			} else if ( event.button === scope.mouseButtons.ZOOM ) {
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
 
-				if ( scope.enableZoom === false ) return;
+			scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) );
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( changeEvent );
 
-				state = STATE.DOLLY;
+		} else {
 
-				dollyStart.set( event.clientX, event.clientY );
-
-			} else if ( event.button === scope.mouseButtons.PAN ) {
-
-				if ( scope.enablePan === false ) return;
-
-				state = STATE.PAN;
-
-				panStart.set( event.clientX, event.clientY );
-
-			}
-
-			if ( state !== STATE.NONE ) {
-
-				document.addEventListener( 'mousemove', onMouseMove, false );
-				document.addEventListener( 'mouseup', onMouseUp, false );
-				scope.dispatchEvent( startEvent );
-
-			}
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
 
 		}
 
-		function onMouseMove( event ) {
-
-			if ( scope.enabled === false ) return;
-
-			event.preventDefault();
-
-			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
-
-			if ( state === STATE.ROTATE ) {
-
-				if ( scope.enableRotate === false ) return;
-
-				rotateEnd.set( event.clientX, event.clientY );
-				rotateDelta.subVectors( rotateEnd, rotateStart );
-
-				// rotating across whole screen goes 360 degrees around
-				constraint.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
-				constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
-
-				rotateStart.copy( rotateEnd );
-
-			} else if ( state === STATE.DOLLY ) {
-
-				if ( scope.enableZoom === false ) return;
-
-				dollyEnd.set( event.clientX, event.clientY );
-				dollyDelta.subVectors( dollyEnd, dollyStart );
-
-				if ( dollyDelta.y > 0 ) {
-
-					constraint.dollyIn( getZoomScale() );
-
-				} else if ( dollyDelta.y < 0 ) {
-
-					constraint.dollyOut( getZoomScale() );
-
-				}
-
-				dollyStart.copy( dollyEnd );
-
-			} else if ( state === STATE.PAN ) {
-
-				if ( scope.enablePan === false ) return;
-
-				panEnd.set( event.clientX, event.clientY );
-				panDelta.subVectors( panEnd, panStart );
-
-				pan( panDelta.x, panDelta.y );
-
-				panStart.copy( panEnd );
-
-			}
-
-			if ( state !== STATE.NONE ) scope.update();
-
-		}
+	};
 
-		function onMouseUp( /* event */ ) {
+	this.dollyOut = function ( dollyScale ) {
 
-			if ( scope.enabled === false ) return;
+		if ( dollyScale === undefined ) {
 
-			document.removeEventListener( 'mousemove', onMouseMove, false );
-			document.removeEventListener( 'mouseup', onMouseUp, false );
-			scope.dispatchEvent( endEvent );
-			state = STATE.NONE;
+			dollyScale = getZoomScale();
 
 		}
 
-		function onMouseWheel( event ) {
-
-			if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
 
-			event.preventDefault();
-			event.stopPropagation();
+			scale *= dollyScale;
 
-			var delta = 0;
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
 
-			if ( event.wheelDelta !== undefined ) {
+			scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) );
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( changeEvent );
 
-				// WebKit / Opera / Explorer 9
+		} else {
 
-				delta = event.wheelDelta;
-
-			} else if ( event.detail !== undefined ) {
-
-				// Firefox
-
-				delta = - event.detail;
-
-			}
-
-			if ( delta > 0 ) {
-
-				constraint.dollyOut( getZoomScale() );
-
-			} else if ( delta < 0 ) {
-
-				constraint.dollyIn( getZoomScale() );
-
-			}
-
-			scope.update();
-			scope.dispatchEvent( startEvent );
-			scope.dispatchEvent( endEvent );
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
 
 		}
 
-		function onKeyDown( event ) {
+	};
 
-			if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+	this.update = function () {
 
-			switch ( event.keyCode ) {
+		var position = this.object.position;
 
-				case scope.keys.UP:
-					pan( 0, scope.keyPanSpeed );
-					scope.update();
-					break;
+		offset.copy( position ).sub( this.target );
 
-				case scope.keys.BOTTOM:
-					pan( 0, - scope.keyPanSpeed );
-					scope.update();
-					break;
+		// rotate offset to "y-axis-is-up" space
+		offset.applyQuaternion( quat );
 
-				case scope.keys.LEFT:
-					pan( scope.keyPanSpeed, 0 );
-					scope.update();
-					break;
+		// angle from z-axis around y-axis
 
-				case scope.keys.RIGHT:
-					pan( - scope.keyPanSpeed, 0 );
-					scope.update();
-					break;
+		theta = Math.atan2( offset.x, offset.z );
 
-			}
+		// angle from y-axis
 
-		}
+		phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
 
-		function touchstart( event ) {
+		if ( this.autoRotate && state === STATE.NONE ) {
 
-			if ( scope.enabled === false ) return;
+			this.rotateLeft( getAutoRotationAngle() );
 
-			switch ( event.touches.length ) {
+		}
 
-				case 1:	// one-fingered touch: rotate
+		theta += thetaDelta;
+		phi += phiDelta;
 
-					if ( scope.enableRotate === false ) return;
+		// restrict theta to be between desired limits
+		theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
 
-					state = STATE.TOUCH_ROTATE;
+		// restrict phi to be between desired limits
+		phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
 
-					rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-					break;
+		// restrict phi to be betwee EPS and PI-EPS
+		phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
 
-				case 2:	// two-fingered touch: dolly
+		var radius = offset.length() * scale;
 
-					if ( scope.enableZoom === false ) return;
+		// restrict radius to be between desired limits
+		radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
 
-					state = STATE.TOUCH_DOLLY;
+		// move target to panned location
+		this.target.add( pan );
 
-					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 );
-					break;
+		offset.x = radius * Math.sin( phi ) * Math.sin( theta );
+		offset.y = radius * Math.cos( phi );
+		offset.z = radius * Math.sin( phi ) * Math.cos( theta );
 
-				case 3: // three-fingered touch: pan
+		// rotate offset back to "camera-up-vector-is-up" space
+		offset.applyQuaternion( quatInverse );
 
-					if ( scope.enablePan === false ) return;
+		position.copy( this.target ).add( offset );
 
-					state = STATE.TOUCH_PAN;
+		this.object.lookAt( this.target );
 
-					panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-					break;
+		thetaDelta = 0;
+		phiDelta = 0;
+		scale = 1;
+		pan.set( 0, 0, 0 );
 
-				default:
+		// update condition is:
+		// min(camera displacement, camera rotation in radians)^2 > EPS
+		// using small-angle approximation cos(x/2) = 1 - x^2 / 8
 
-					state = STATE.NONE;
+		if ( lastPosition.distanceToSquared( this.object.position ) > EPS
+		    || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) {
 
-			}
+			this.dispatchEvent( changeEvent );
 
-			if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent );
+			lastPosition.copy( this.object.position );
+			lastQuaternion.copy (this.object.quaternion );
 
 		}
 
-		function touchmove( event ) {
-
-			if ( scope.enabled === false ) return;
-
-			event.preventDefault();
-			event.stopPropagation();
-
-			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
-
-			switch ( event.touches.length ) {
-
-				case 1: // one-fingered touch: rotate
-
-					if ( scope.enableRotate === false ) return;
-					if ( state !== STATE.TOUCH_ROTATE ) return;
-
-					rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-					rotateDelta.subVectors( rotateEnd, rotateStart );
+	};
 
-					// rotating across whole screen goes 360 degrees around
-					constraint.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
-					constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
 
-					rotateStart.copy( rotateEnd );
+	this.reset = function () {
 
-					scope.update();
-					break;
+		state = STATE.NONE;
 
-				case 2: // two-fingered touch: dolly
+		this.target.copy( this.target0 );
+		this.object.position.copy( this.position0 );
+		this.object.zoom = this.zoom0;
 
-					if ( scope.enableZoom === false ) return;
-					if ( state !== STATE.TOUCH_DOLLY ) return;
+		this.object.updateProjectionMatrix();
+		this.dispatchEvent( changeEvent );
 
-					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 );
+		this.update();
 
-					dollyEnd.set( 0, distance );
-					dollyDelta.subVectors( dollyEnd, dollyStart );
+	};
 
-					if ( dollyDelta.y > 0 ) {
+	this.getPolarAngle = function () {
 
-						constraint.dollyOut( getZoomScale() );
+		return phi;
 
-					} else if ( dollyDelta.y < 0 ) {
+	};
 
-						constraint.dollyIn( getZoomScale() );
+	this.getAzimuthalAngle = function () {
 
-					}
+		return theta
 
-					dollyStart.copy( dollyEnd );
+	};
 
-					scope.update();
-					break;
+	function getAutoRotationAngle() {
 
-				case 3: // three-fingered touch: pan
+		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
 
-					if ( scope.enablePan === false ) return;
-					if ( state !== STATE.TOUCH_PAN ) return;
+	}
 
-					panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-					panDelta.subVectors( panEnd, panStart );
+	function getZoomScale() {
 
-					pan( panDelta.x, panDelta.y );
+		return Math.pow( 0.95, scope.zoomSpeed );
 
-					panStart.copy( panEnd );
+	}
 
-					scope.update();
-					break;
+	function onMouseDown( event ) {
 
-				default:
+		if ( scope.enabled === false ) return;
+		event.preventDefault();
 
-					state = STATE.NONE;
+		if ( event.button === scope.mouseButtons.ORBIT ) {
+			if ( scope.noRotate === true ) return;
 
-			}
+			state = STATE.ROTATE;
 
-		}
+			rotateStart.set( event.clientX, event.clientY );
 
-		function touchend( /* event */ ) {
+		} else if ( event.button === scope.mouseButtons.ZOOM ) {
+			if ( scope.noZoom === true ) return;
 
-			if ( scope.enabled === false ) return;
+			state = STATE.DOLLY;
 
-			scope.dispatchEvent( endEvent );
-			state = STATE.NONE;
+			dollyStart.set( event.clientX, event.clientY );
 
-		}
+		} else if ( event.button === scope.mouseButtons.PAN ) {
+			if ( scope.noPan === true ) return;
 
-		function contextmenu( event ) {
+			state = STATE.PAN;
 
-			event.preventDefault();
+			panStart.set( event.clientX, event.clientY );
 
 		}
 
-		this.dispose = function() {
-
-			this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
-			this.domElement.removeEventListener( 'mousedown', onMouseDown, false );
-			this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
-			this.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
-
-			this.domElement.removeEventListener( 'touchstart', touchstart, false );
-			this.domElement.removeEventListener( 'touchend', touchend, false );
-			this.domElement.removeEventListener( 'touchmove', touchmove, false );
-
-			document.removeEventListener( 'mousemove', onMouseMove, false );
-			document.removeEventListener( 'mouseup', onMouseUp, false );
-
-			window.removeEventListener( 'keydown', onKeyDown, false );
-
+		if ( state !== STATE.NONE ) {
+			document.addEventListener( 'mousemove', onMouseMove, false );
+			document.addEventListener( 'mouseup', onMouseUp, false );
+			scope.dispatchEvent( startEvent );
 		}
 
-		this.domElement.addEventListener( 'contextmenu', contextmenu, false );
-
-		this.domElement.addEventListener( 'mousedown', onMouseDown, false );
-		this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
-		this.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
-
-		this.domElement.addEventListener( 'touchstart', touchstart, false );
-		this.domElement.addEventListener( 'touchend', touchend, false );
-		this.domElement.addEventListener( 'touchmove', touchmove, 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, {
-
-		object: {
-
-			get: function () {
+	}
 
-				return this.constraint.object;
+	function onMouseMove( event ) {
 
-			}
+		if ( scope.enabled === false ) return;
 
-		},
+		event.preventDefault();
 
-		target: {
+		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
 
-			get: function () {
+		if ( state === STATE.ROTATE ) {
 
-				return this.constraint.target;
+			if ( scope.noRotate === true ) return;
 
-			},
+			rotateEnd.set( event.clientX, event.clientY );
+			rotateDelta.subVectors( rotateEnd, rotateStart );
 
-			set: function ( value ) {
+			// rotating across whole screen goes 360 degrees around
+			scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
 
-				console.warn( 'THREE.OrbitControls: target is now immutable. Use target.set() instead.' );
-				this.constraint.target.copy( value );
+			// rotating up and down along whole screen attempts to go 360, but limited to 180
+			scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
 
-			}
+			rotateStart.copy( rotateEnd );
 
-		},
+		} else if ( state === STATE.DOLLY ) {
 
-		minDistance : {
+			if ( scope.noZoom === true ) return;
 
-			get: function () {
+			dollyEnd.set( event.clientX, event.clientY );
+			dollyDelta.subVectors( dollyEnd, dollyStart );
 
-				return this.constraint.minDistance;
+			if ( dollyDelta.y > 0 ) {
 
-			},
+				scope.dollyIn();
 
-			set: function ( value ) {
+			} else if ( dollyDelta.y < 0 ) {
 
-				this.constraint.minDistance = value;
+				scope.dollyOut();
 
 			}
 
-		},
-
-		maxDistance : {
-
-			get: function () {
-
-				return this.constraint.maxDistance;
+			dollyStart.copy( dollyEnd );
 
-			},
-
-			set: function ( value ) {
-
-				this.constraint.maxDistance = value;
-
-			}
+		} else if ( state === STATE.PAN ) {
 
-		},
-
-		minZoom : {
-
-			get: function () {
-
-				return this.constraint.minZoom;
-
-			},
-
-			set: function ( value ) {
-
-				this.constraint.minZoom = value;
-
-			}
+			if ( scope.noPan === true ) return;
 
-		},
+			panEnd.set( event.clientX, event.clientY );
+			panDelta.subVectors( panEnd, panStart );
 
-		maxZoom : {
+			scope.pan( panDelta.x, panDelta.y );
 
-			get: function () {
+			panStart.copy( panEnd );
 
-				return this.constraint.maxZoom;
-
-			},
+		}
 
-			set: function ( value ) {
+		if ( state !== STATE.NONE ) scope.update();
 
-				this.constraint.maxZoom = value;
+	}
 
-			}
+	function onMouseUp( /* event */ ) {
 
-		},
+		if ( scope.enabled === false ) return;
 
-		minPolarAngle : {
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
+		scope.dispatchEvent( endEvent );
+		state = STATE.NONE;
 
-			get: function () {
+	}
 
-				return this.constraint.minPolarAngle;
+	function onMouseWheel( event ) {
 
-			},
+		if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return;
 
-			set: function ( value ) {
+		event.preventDefault();
+		event.stopPropagation();
 
-				this.constraint.minPolarAngle = value;
-
-			}
+		var delta = 0;
 
-		},
+		if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
 
-		maxPolarAngle : {
+			delta = event.wheelDelta;
 
-			get: function () {
+		} else if ( event.detail !== undefined ) { // Firefox
 
-				return this.constraint.maxPolarAngle;
+			delta = - event.detail;
 
-			},
+		}
 
-			set: function ( value ) {
+		if ( delta > 0 ) {
 
-				this.constraint.maxPolarAngle = value;
+			scope.dollyOut();
 
-			}
+		} else if ( delta < 0 ) {
 
-		},
+			scope.dollyIn();
 
-		minAzimuthAngle : {
+		}
 
-			get: function () {
+		scope.update();
+		scope.dispatchEvent( startEvent );
+		scope.dispatchEvent( endEvent );
 
-				return this.constraint.minAzimuthAngle;
+	}
 
-			},
+	function onKeyDown( event ) {
 
-			set: function ( value ) {
+		if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
 
-				this.constraint.minAzimuthAngle = value;
+		switch ( event.keyCode ) {
 
-			}
+			case scope.keys.UP:
+				scope.pan( 0, scope.keyPanSpeed );
+				scope.update();
+				break;
 
-		},
+			case scope.keys.BOTTOM:
+				scope.pan( 0, - scope.keyPanSpeed );
+				scope.update();
+				break;
 
-		maxAzimuthAngle : {
+			case scope.keys.LEFT:
+				scope.pan( scope.keyPanSpeed, 0 );
+				scope.update();
+				break;
 
-			get: function () {
+			case scope.keys.RIGHT:
+				scope.pan( - scope.keyPanSpeed, 0 );
+				scope.update();
+				break;
 
-				return this.constraint.maxAzimuthAngle;
+		}
 
-			},
+	}
 
-			set: function ( value ) {
+	function touchstart( event ) {
 
-				this.constraint.maxAzimuthAngle = value;
+		if ( scope.enabled === false ) return;
 
-			}
+		switch ( event.touches.length ) {
 
-		},
+			case 1:	// one-fingered touch: rotate
 
-		enableDamping : {
+				if ( scope.noRotate === true ) return;
 
-			get: function () {
+				state = STATE.TOUCH_ROTATE;
 
-				return this.constraint.enableDamping;
+				rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
 
-			},
+			case 2:	// two-fingered touch: dolly
 
-			set: function ( value ) {
+				if ( scope.noZoom === true ) return;
 
-				this.constraint.enableDamping = value;
+				state = STATE.TOUCH_DOLLY;
 
-			}
+				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 );
+				break;
 
-		},
+			case 3: // three-fingered touch: pan
 
-		dampingFactor : {
+				if ( scope.noPan === true ) return;
 
-			get: function () {
+				state = STATE.TOUCH_PAN;
 
-				return this.constraint.dampingFactor;
+				panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
 
-			},
+			default:
 
-			set: function ( value ) {
+				state = STATE.NONE;
 
-				this.constraint.dampingFactor = value;
+		}
 
-			}
+		if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent );
 
-		},
+	}
 
-		// backward compatibility
+	function touchmove( event ) {
 
-		noZoom: {
+		if ( scope.enabled === false ) return;
 
-			get: function () {
+		event.preventDefault();
+		event.stopPropagation();
 
-				console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
-				return ! this.enableZoom;
+		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
 
-			},
+		switch ( event.touches.length ) {
 
-			set: function ( value ) {
+			case 1: // one-fingered touch: rotate
 
-				console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
-				this.enableZoom = ! value;
+				if ( scope.noRotate === true ) return;
+				if ( state !== STATE.TOUCH_ROTATE ) return;
 
-			}
+				rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				rotateDelta.subVectors( rotateEnd, rotateStart );
 
-		},
+				// rotating across whole screen goes 360 degrees around
+				scope.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
+				scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
 
-		noRotate: {
+				rotateStart.copy( rotateEnd );
 
-			get: function () {
+				scope.update();
+				break;
 
-				console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
-				return ! this.enableRotate;
+			case 2: // two-fingered touch: dolly
 
-			},
+				if ( scope.noZoom === true ) return;
+				if ( state !== STATE.TOUCH_DOLLY ) return;
 
-			set: function ( value ) {
+				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 );
 
-				console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
-				this.enableRotate = ! value;
+				dollyEnd.set( 0, distance );
+				dollyDelta.subVectors( dollyEnd, dollyStart );
 
-			}
+				if ( dollyDelta.y > 0 ) {
 
-		},
+					scope.dollyOut();
 
-		noPan: {
+				} else if ( dollyDelta.y < 0 ) {
 
-			get: function () {
+					scope.dollyIn();
 
-				console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
-				return ! this.enablePan;
+				}
 
-			},
+				dollyStart.copy( dollyEnd );
 
-			set: function ( value ) {
+				scope.update();
+				break;
 
-				console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
-				this.enablePan = ! value;
+			case 3: // three-fingered touch: pan
 
-			}
+				if ( scope.noPan === true ) return;
+				if ( state !== STATE.TOUCH_PAN ) return;
 
-		},
+				panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				panDelta.subVectors( panEnd, panStart );
 
-		noKeys: {
+				scope.pan( panDelta.x, panDelta.y );
 
-			get: function () {
+				panStart.copy( panEnd );
 
-				console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
-				return ! this.enableKeys;
+				scope.update();
+				break;
 
-			},
+			default:
 
-			set: function ( value ) {
+				state = STATE.NONE;
 
-				console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
-				this.enableKeys = ! value;
+		}
 
-			}
+	}
 
-		},
+	function touchend( /* event */ ) {
 
-		staticMoving : {
+		if ( scope.enabled === false ) return;
 
-			get: function () {
+		scope.dispatchEvent( endEvent );
+		state = STATE.NONE;
 
-				console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
-				return ! this.constraint.enableDamping;
+	}
 
-			},
+	function contextmenu( event ) {
 
-			set: function ( value ) {
+		event.preventDefault();
 
-				console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
-				this.constraint.enableDamping = ! value;
+	}
 
-			}
+	this.dispose = function() {
 
-		},
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+		this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
+		this.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
 
-		dynamicDampingFactor : {
+		this.domElement.removeEventListener( 'touchstart', touchstart, false );
+		this.domElement.removeEventListener( 'touchend', touchend, false );
+		this.domElement.removeEventListener( 'touchmove', touchmove, false );
 
-			get: function () {
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
 
-				console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
-				return this.constraint.dampingFactor;
+		window.removeEventListener( 'keydown', onKeyDown, false );
 
-			},
+	}
 
-			set: function ( value ) {
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousedown', onMouseDown, false );
+	this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
+	this.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
 
-				console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
-				this.constraint.dampingFactor = value;
+	this.domElement.addEventListener( 'touchstart', touchstart, false );
+	this.domElement.addEventListener( 'touchend', touchend, false );
+	this.domElement.addEventListener( 'touchmove', touchmove, 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;

+ 245 - 0
examples/webgl_materials_phong_variations.html

@@ -0,0 +1,245 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+					body {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+
+				background-color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="http://threejs.org" target="_blank">three.js</a> - Phong Material Variantions by <a href="http://clara.io/" target="_blank">Ben Houston</a>.</div>
+
+
+		<script src="../build/three.min.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/geometries/TextGeometry.js"></script>
+		<script src="js/utils/FontUtils.js"></script>
+		<script src="fonts/gentilis_regular.typeface.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+
+			var camera, scene, renderer, controls, objects = [];
+			var particleLight;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( 0.0, 400, 400 * 3.5 );
+
+				scene = new THREE.Scene();
+
+				// Materials
+
+				var imgTexture = THREE.ImageUtils.loadTexture( "textures/planets/moon_1024.jpg" );
+				imgTexture.wrapS = imgTexture.wrapT = THREE.RepeatWrapping;
+				imgTexture.anisotropy = 16;
+				imgTexture = null;
+
+				var shininess = 50, specular = 0x333333, bumpScale = 1, shading = THREE.SmoothShading;
+
+				var materials = [];
+
+				var path = "textures/cube/SwedishRoyalCastle/";
+				var format = '.jpg';
+				var urls = [
+						path + 'px' + format, path + 'nx' + format,
+						path + 'py' + format, path + 'ny' + format,
+						path + 'pz' + format, path + 'nz' + format
+					];
+
+				var reflectionCube = THREE.ImageUtils.loadTextureCube( urls );
+				reflectionCube.format = THREE.RGBFormat;
+				reflectionCube = null;
+
+				var cubeWidth = 400;
+				var numberOfSphersPerSide = 5;
+				var sphereRadius = ( cubeWidth / numberOfSphersPerSide ) * 0.8 * 0.5;
+				var stepSize = 1.0 / numberOfSphersPerSide;
+
+				var geometry = new THREE.SphereBufferGeometry( sphereRadius, 32, 16 );
+
+	
+				for( var alpha = 0; alpha <= 1.0; alpha += stepSize ) {
+
+					var specularShininess = Math.pow( 2, alpha * 10 );
+
+					for( var beta = 0; beta <= 1.0; beta += stepSize ) {
+
+						var specularColor = new THREE.Color( beta * 0.2, beta * 0.2, beta * 0.2 );
+
+						for( var gamma = 0; gamma <= 1.0; gamma += stepSize ) {
+
+							// basic monochromatic energy preservation
+							var diffuseColor = new THREE.Color( 0, 0, gamma ).multiplyScalar( 1 - beta * 0.2 );
+
+							var material = new THREE.MeshPhongMaterial( { map: imgTexture, bumpMap: imgTexture, bumpScale: bumpScale, color: diffuseColor, specular: specularColor, shininess: specularShininess, shading: THREE.SmoothShading, envMap: reflectionCube,  } )
+
+							var mesh = new THREE.Mesh( geometry, material );
+
+							mesh.position.x = alpha * 400 - 200;
+							mesh.position.y = beta * 400 - 200;
+							mesh.position.z = gamma * 400 - 200;
+
+							objects.push( mesh );
+
+							scene.add( mesh );
+						}
+					}
+				}
+	
+	
+				function addLabel( name, location ) {
+					var textGeo = new THREE.TextGeometry( name, {
+
+						size: 20,
+						height: 5,
+						curveSegments: 10,
+
+						font: 'gentilis',
+						weight: 'normal',
+						style: 'normal',
+
+						material: 0,
+						extrudeMaterial: 1
+
+					});
+
+					var textMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff } );
+					var textMesh = new THREE.Mesh( textGeo, textMaterial );
+					textMesh.position.copy( location );
+					scene.add( textMesh );
+				}
+
+				addLabel( "-shininess", new THREE.Vector3( -350, 0, 0 ) );
+				addLabel( "+shininess", new THREE.Vector3( 350, 0, 0 ) );
+
+				addLabel( "-specular", new THREE.Vector3( 0, -300, 0 ) );
+				addLabel( "+specular", new THREE.Vector3( 0, 300, 0 ) );
+
+				addLabel( "-diffuse", new THREE.Vector3( 0, 0, -300 ) );
+				addLabel( "+diffuse", new THREE.Vector3( 0, 0, 300 ) );
+
+				particleLight = new THREE.Mesh( new THREE.SphereBufferGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xffffff } ) );
+				scene.add( particleLight );
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0x222222 ) );
+
+				var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
+				directionalLight.position.set( 1, 1, 1 ).normalize();
+				scene.add( directionalLight );
+
+				var pointLight = new THREE.PointLight( 0xffffff, 2, 800 );
+				particleLight.add( pointLight );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setClearColor( 0x0a0a0a );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.sortObjects = true;
+
+				container.appendChild( renderer.domElement );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+
+				//
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+
+				container.appendChild( stats.domElement );
+
+				controls = new THREE.OrbitControls( camera );
+				controls.target.set( 0, 0, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var timer = Date.now() * 0.00025;
+
+				//camera.position.x = Math.cos( timer ) * 800;
+				//camera.position.z = Math.sin( timer ) * 800;
+
+				camera.lookAt( scene.position );
+
+				for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+					var object = objects[ i ];
+
+					object.rotation.y += 0.005;
+
+				}
+
+				particleLight.position.x = Math.sin( timer * 7 ) * 300;
+				particleLight.position.y = Math.cos( timer * 5 ) * 400;
+				particleLight.position.z = Math.cos( timer * 3 ) * 300;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 242 - 0
examples/webgl_materials_physical_variations.html

@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+					body {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+
+				background-color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="http://threejs.org" target="_blank">three.js</a> - Physical Material Variantions by <a href="http://clara.io/" target="_blank">Ben Houston</a>.</div>
+
+		<script src="../build/three.min.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/geometries/TextGeometry.js"></script>
+		<script src="js/utils/FontUtils.js"></script>
+		<script src="fonts/gentilis_regular.typeface.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+
+			var camera, scene, renderer, controls, objects = [];
+			var particleLight;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( 0.0, 400, 400 * 3.5 );
+
+				scene = new THREE.Scene();
+
+				// Materials
+
+				var imgTexture = THREE.ImageUtils.loadTexture( "textures/planets/moon_1024.jpg" );
+				imgTexture.wrapS = imgTexture.wrapT = THREE.RepeatWrapping;
+				imgTexture.anisotropy = 16;
+				imgTexture = null;
+
+				var shininess = 50, specular = 0x333333, bumpScale = 1, shading = THREE.SmoothShading;
+
+				var materials = [];
+
+				var path = "textures/cube/SwedishRoyalCastle/";
+				var format = '.jpg';
+				var urls = [
+						path + 'px' + format, path + 'nx' + format,
+						path + 'py' + format, path + 'ny' + format,
+						path + 'pz' + format, path + 'nz' + format
+					];
+
+				var reflectionCube = THREE.ImageUtils.loadTextureCube( urls );
+				reflectionCube.format = THREE.RGBFormat;
+
+				var cubeWidth = 400;
+				var numberOfSphersPerSide = 5;
+				var sphereRadius = ( cubeWidth / numberOfSphersPerSide ) * 0.8 * 0.5;
+				var stepSize = 1.0 / numberOfSphersPerSide;
+
+				var geometry = new THREE.SphereBufferGeometry( sphereRadius, 32, 16 );
+
+	
+				for( var alpha = 0; alpha <= 1.0; alpha += stepSize ) {
+
+					var roughness = 1.0 - alpha;
+
+					for( var beta = 0; beta <= 1.0; beta += stepSize ) {
+
+						var metalness = beta;
+
+						for( var gamma = 0; gamma <= 1.0; gamma += stepSize ) {
+
+							// basic monochromatic energy preservation
+							var diffuseColor = new THREE.Color( gamma, 0, 0 ).multiplyScalar( 1 - 0.08 );
+
+							var material = new THREE.MeshPhysicalMaterial( { map: imgTexture, bumpMap: imgTexture, bumpScale: bumpScale, color: diffuseColor, metalness: metalness, roughness: roughness, shading: THREE.SmoothShading, envMap: reflectionCube } )
+
+							var mesh = new THREE.Mesh( geometry, material );
+
+							mesh.position.x = alpha * 400 - 200;
+							mesh.position.y = beta * 400 - 200;
+							mesh.position.z = gamma * 400 - 200;
+
+							objects.push( mesh );
+
+							scene.add( mesh );
+						}
+					}
+				}
+	
+				function addLabel( name, location ) {
+					var textGeo = new THREE.TextGeometry( name, {
+
+						size: 20,
+						height: 5,
+						curveSegments: 10,
+
+						font: 'gentilis',
+						weight: 'normal',
+						style: 'normal',
+
+						material: 0,
+						extrudeMaterial: 1
+
+					});
+
+					var textMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff } );
+					var textMesh = new THREE.Mesh( textGeo, textMaterial );
+					textMesh.position.copy( location );
+					scene.add( textMesh );
+				}
+
+				addLabel( "+roughness", new THREE.Vector3( -350, 0, 0 ) );
+				addLabel( "-roughness", new THREE.Vector3( 350, 0, 0 ) );
+
+				addLabel( "-metalness", new THREE.Vector3( 0, -300, 0 ) );
+				addLabel( "+metalness", new THREE.Vector3( 0, 300, 0 ) );
+
+				addLabel( "-diffuse", new THREE.Vector3( 0, 0, -300 ) );
+				addLabel( "+diffuse", new THREE.Vector3( 0, 0, 300 ) );
+
+				particleLight = new THREE.Mesh( new THREE.SphereBufferGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xffffff } ) );
+				scene.add( particleLight );
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0x222222 ) );
+
+				var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
+				directionalLight.position.set( 1, 1, 1 ).normalize();
+				scene.add( directionalLight );
+
+				var pointLight = new THREE.PointLight( 0xffffff, 2, 800 );
+				particleLight.add( pointLight );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setClearColor( 0x0a0a0a );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.sortObjects = true;
+
+				container.appendChild( renderer.domElement );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+
+				//
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+
+				container.appendChild( stats.domElement );
+
+				controls = new THREE.OrbitControls( camera );
+				controls.target.set( 0, 0, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var timer = Date.now() * 0.00025;
+
+				//camera.position.x = Math.cos( timer ) * 800;
+				//camera.position.z = Math.sin( timer ) * 800;
+
+				camera.lookAt( scene.position );
+
+				for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+					var object = objects[ i ];
+
+					object.rotation.y += 0.005;
+
+				}
+
+				particleLight.position.x = Math.sin( timer * 7 ) * 300;
+				particleLight.position.y = Math.cos( timer * 5 ) * 400;
+				particleLight.position.z = Math.cos( timer * 3 ) * 300;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 28 - 21
src/loaders/CompressedTextureLoader.js

@@ -29,42 +29,43 @@ THREE.CompressedTextureLoader.prototype = {
 
 		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
+		loader.setPath( this.path );
 		loader.setResponseType( 'arraybuffer' );
 
-		if ( Array.isArray( url ) ) {
+		function loadTexture( i ) {
 
-			var loaded = 0;
+			loader.load( url[ i ], function ( buffer ) {
 
-			var loadTexture = function ( i ) {
+				var texDatas = scope._parser( buffer, true );
 
-				loader.load( url[ i ], function ( buffer ) {
+				images[ i ] = {
+					width: texDatas.width,
+					height: texDatas.height,
+					format: texDatas.format,
+					mipmaps: texDatas.mipmaps
+				};
 
-					var texDatas = scope._parser( buffer, true );
+				loaded += 1;
 
-					images[ i ] = {
-						width: texDatas.width,
-						height: texDatas.height,
-						format: texDatas.format,
-						mipmaps: texDatas.mipmaps
-					};
+				if ( loaded === 6 ) {
 
-					loaded += 1;
+					if ( texDatas.mipmapCount === 1 )
+						texture.minFilter = THREE.LinearFilter;
 
-					if ( loaded === 6 ) {
+					texture.format = texDatas.format;
+					texture.needsUpdate = true;
 
-						if ( texDatas.mipmapCount === 1 )
- 							texture.minFilter = THREE.LinearFilter;
+					if ( onLoad ) onLoad( texture );
 
-						texture.format = texDatas.format;
-						texture.needsUpdate = true;
+				}
 
-						if ( onLoad ) onLoad( texture );
+			}, onProgress, onError );
 
-					}
+		}
 
-				}, onProgress, onError );
+		if ( Array.isArray( url ) ) {
 
-			};
+			var loaded = 0;
 
 			for ( var i = 0, il = url.length; i < il; ++ i ) {
 
@@ -130,6 +131,12 @@ THREE.CompressedTextureLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };

+ 7 - 0
src/loaders/CubeTextureLoader.js

@@ -18,6 +18,7 @@ THREE.CubeTextureLoader.prototype = {
 
 		var loader = new THREE.ImageLoader();
 		loader.setCrossOrigin( this.crossOrigin );
+		loader.setPath( this.path );
 
 		var loaded = 0;
 
@@ -55,6 +56,12 @@ THREE.CubeTextureLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };

+ 8 - 0
src/loaders/ImageLoader.js

@@ -14,6 +14,8 @@ THREE.ImageLoader.prototype = {
 
 	load: function ( url, onLoad, onProgress, onError ) {
 
+		if ( this.path !== undefined ) url = this.path + url;
+
 		var scope = this;
 
 		var cached = THREE.Cache.get( url );
@@ -86,6 +88,12 @@ THREE.ImageLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };

+ 7 - 0
src/loaders/TextureLoader.js

@@ -18,6 +18,7 @@ THREE.TextureLoader.prototype = {
 
 		var loader = new THREE.ImageLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
+		loader.setPath( this.path );
 		loader.load( url, function ( image ) {
 
 			texture.image = image;
@@ -39,6 +40,12 @@ THREE.TextureLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+
 	}
 
 };

+ 10 - 2
src/loaders/XHRLoader.js

@@ -14,6 +14,8 @@ THREE.XHRLoader.prototype = {
 
 	load: function ( url, onLoad, onProgress, onError ) {
 
+		if ( this.path !== undefined ) url = this.path + url;
+
 		var scope = this;
 
 		var cached = THREE.Cache.get( url );
@@ -79,15 +81,21 @@ THREE.XHRLoader.prototype = {
 
 	},
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	setResponseType: function ( value ) {
 
 		this.responseType = value;
 
 	},
 
-	setCrossOrigin: function ( value ) {
+	setPath: function ( value ) {
 
-		this.crossOrigin = value;
+		this.path = value;
 
 	},