Browse Source

Updated examples builds.

Mr.doob 4 years ago
parent
commit
361490339a

+ 1 - 1
examples/js/animation/MMDAnimationHelper.js

@@ -44,7 +44,7 @@
 				cameraAnimation: true
 				cameraAnimation: true
 			};
 			};
 
 
-			this.onBeforePhysics = function ( ) {}; // experimental
+			this.onBeforePhysics = function () {}; // experimental
 
 
 
 
 			this.sharedPhysics = false;
 			this.sharedPhysics = false;

+ 2 - 2
examples/js/controls/OrbitControls.js

@@ -514,7 +514,7 @@
 
 
 			}
 			}
 
 
-			function handleMouseUp( ) { // no-op
+			function handleMouseUp() { // no-op
 			}
 			}
 
 
 			function handleMouseWheel( event ) {
 			function handleMouseWheel( event ) {
@@ -698,7 +698,7 @@
 
 
 			}
 			}
 
 
-			function handleTouchEnd( ) { // no-op
+			function handleTouchEnd() { // no-op
 			} //
 			} //
 			// event handlers - FSM: listen for events and reset state
 			// event handlers - FSM: listen for events and reset state
 			//
 			//

+ 1 - 2
examples/js/controls/TrackballControls.js

@@ -117,8 +117,7 @@
 				const vector = new THREE.Vector2();
 				const vector = new THREE.Vector2();
 				return function getMouseOnCircle( pageX, pageY ) {
 				return function getMouseOnCircle( pageX, pageY ) {
 
 
-					vector.set( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ), ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width // screen.width intentional
-					);
+					vector.set( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ), ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width );
 					return vector;
 					return vector;
 
 
 				};
 				};

+ 2 - 2
examples/js/controls/experimental/CameraControls.js

@@ -510,7 +510,7 @@
 
 
 		}
 		}
 
 
-		function handleMouseUp( ) { // no-op
+		function handleMouseUp() { // no-op
 		}
 		}
 
 
 		function handleMouseWheel( event ) {
 		function handleMouseWheel( event ) {
@@ -691,7 +691,7 @@
 
 
 		}
 		}
 
 
-		function handleTouchEnd( ) { // no-op
+		function handleTouchEnd() { // no-op
 		} //
 		} //
 		// event handlers - FSM: listen for events and reset state
 		// event handlers - FSM: listen for events and reset state
 		//
 		//

+ 287 - 264
examples/js/curves/NURBSUtils.js

@@ -10,444 +10,467 @@
  *	NURBS Utils
  *	NURBS Utils
  **************************************************************/
  **************************************************************/
 
 
-	class NURBSUtils {
+	/*
+Finds knot vector span.
 
 
-		/*
-  Finds knot vector span.
-  	p : degree
-  u : parametric value
-  U : knot vector
-  	returns the span
-  */
-		static findSpan( p, u, U ) {
+p : degree
+u : parametric value
+U : knot vector
 
 
-			const n = U.length - p - 1;
+returns the span
+*/
 
 
-			if ( u >= U[ n ] ) {
+	function findSpan( p, u, U ) {
 
 
-				return n - 1;
+		const n = U.length - p - 1;
 
 
-			}
+		if ( u >= U[ n ] ) {
 
 
-			if ( u <= U[ p ] ) {
+			return n - 1;
 
 
-				return p;
+		}
 
 
-			}
+		if ( u <= U[ p ] ) {
 
 
-			let low = p;
-			let high = n;
-			let mid = Math.floor( ( low + high ) / 2 );
+			return p;
 
 
-			while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
+		}
 
 
-				if ( u < U[ mid ] ) {
+		let low = p;
+		let high = n;
+		let mid = Math.floor( ( low + high ) / 2 );
 
 
-					high = mid;
+		while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
 
 
-				} else {
+			if ( u < U[ mid ] ) {
 
 
-					low = mid;
+				high = mid;
 
 
-				}
+			} else {
 
 
-				mid = Math.floor( ( low + high ) / 2 );
+				low = mid;
 
 
 			}
 			}
 
 
-			return mid;
+			mid = Math.floor( ( low + high ) / 2 );
 
 
 		}
 		}
-		/*
-  Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
-  	span : span in which u lies
-  u    : parametric point
-  p    : degree
-  U    : knot vector
-  	returns array[p+1] with basis functions values.
-  */
 
 
+		return mid;
 
 
-		static calcBasisFunctions( span, u, p, U ) {
+	}
+	/*
+Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
 
 
-			const N = [];
-			const left = [];
-			const right = [];
-			N[ 0 ] = 1.0;
+span : span in which u lies
+u    : parametric point
+p    : degree
+U    : knot vector
 
 
-			for ( let j = 1; j <= p; ++ j ) {
+returns array[p+1] with basis functions values.
+*/
 
 
-				left[ j ] = u - U[ span + 1 - j ];
-				right[ j ] = U[ span + j ] - u;
-				let saved = 0.0;
 
 
-				for ( let r = 0; r < j; ++ r ) {
+	function calcBasisFunctions( span, u, p, U ) {
 
 
-					const rv = right[ r + 1 ];
-					const lv = left[ j - r ];
-					const temp = N[ r ] / ( rv + lv );
-					N[ r ] = saved + rv * temp;
-					saved = lv * temp;
+		const N = [];
+		const left = [];
+		const right = [];
+		N[ 0 ] = 1.0;
 
 
-				}
+		for ( let j = 1; j <= p; ++ j ) {
+
+			left[ j ] = u - U[ span + 1 - j ];
+			right[ j ] = U[ span + j ] - u;
+			let saved = 0.0;
+
+			for ( let r = 0; r < j; ++ r ) {
 
 
-				N[ j ] = saved;
+				const rv = right[ r + 1 ];
+				const lv = left[ j - r ];
+				const temp = N[ r ] / ( rv + lv );
+				N[ r ] = saved + rv * temp;
+				saved = lv * temp;
 
 
 			}
 			}
 
 
-			return N;
+			N[ j ] = saved;
 
 
 		}
 		}
-		/*
-  Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
-  	p : degree of B-Spline
-  U : knot vector
-  P : control points (x, y, z, w)
-  u : parametric point
-  	returns point for given u
-  */
 
 
+		return N;
 
 
-		static calcBSplinePoint( p, U, P, u ) {
+	}
+	/*
+Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
 
 
-			const span = this.findSpan( p, u, U );
-			const N = this.calcBasisFunctions( span, u, p, U );
-			const C = new THREE.Vector4( 0, 0, 0, 0 );
+p : degree of B-Spline
+U : knot vector
+P : control points (x, y, z, w)
+u : parametric point
 
 
-			for ( let j = 0; j <= p; ++ j ) {
+returns point for given u
+*/
 
 
-				const point = P[ span - p + j ];
-				const Nj = N[ j ];
-				const wNj = point.w * Nj;
-				C.x += point.x * wNj;
-				C.y += point.y * wNj;
-				C.z += point.z * wNj;
-				C.w += point.w * Nj;
 
 
-			}
+	function calcBSplinePoint( p, U, P, u ) {
+
+		const span = this.findSpan( p, u, U );
+		const N = this.calcBasisFunctions( span, u, p, U );
+		const C = new THREE.Vector4( 0, 0, 0, 0 );
+
+		for ( let j = 0; j <= p; ++ j ) {
 
 
-			return C;
+			const point = P[ span - p + j ];
+			const Nj = N[ j ];
+			const wNj = point.w * Nj;
+			C.x += point.x * wNj;
+			C.y += point.y * wNj;
+			C.z += point.z * wNj;
+			C.w += point.w * Nj;
 
 
 		}
 		}
-		/*
-  Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
-  	span : span in which u lies
-  u    : parametric point
-  p    : degree
-  n    : number of derivatives to calculate
-  U    : knot vector
-  	returns array[n+1][p+1] with basis functions derivatives
-  */
 
 
+		return C;
 
 
-		static calcBasisFunctionDerivatives( span, u, p, n, U ) {
+	}
+	/*
+Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
 
 
-			const zeroArr = [];
+span : span in which u lies
+u    : parametric point
+p    : degree
+n    : number of derivatives to calculate
+U    : knot vector
 
 
-			for ( let i = 0; i <= p; ++ i ) zeroArr[ i ] = 0.0;
+returns array[n+1][p+1] with basis functions derivatives
+*/
 
 
-			const ders = [];
 
 
-			for ( let i = 0; i <= n; ++ i ) ders[ i ] = zeroArr.slice( 0 );
+	function calcBasisFunctionDerivatives( span, u, p, n, U ) {
 
 
-			const ndu = [];
+		const zeroArr = [];
 
 
-			for ( let i = 0; i <= p; ++ i ) ndu[ i ] = zeroArr.slice( 0 );
+		for ( let i = 0; i <= p; ++ i ) zeroArr[ i ] = 0.0;
 
 
-			ndu[ 0 ][ 0 ] = 1.0;
-			const left = zeroArr.slice( 0 );
-			const right = zeroArr.slice( 0 );
+		const ders = [];
 
 
-			for ( let j = 1; j <= p; ++ j ) {
+		for ( let i = 0; i <= n; ++ i ) ders[ i ] = zeroArr.slice( 0 );
 
 
-				left[ j ] = u - U[ span + 1 - j ];
-				right[ j ] = U[ span + j ] - u;
-				let saved = 0.0;
+		const ndu = [];
 
 
-				for ( let r = 0; r < j; ++ r ) {
+		for ( let i = 0; i <= p; ++ i ) ndu[ i ] = zeroArr.slice( 0 );
 
 
-					const rv = right[ r + 1 ];
-					const lv = left[ j - r ];
-					ndu[ j ][ r ] = rv + lv;
-					const temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
-					ndu[ r ][ j ] = saved + rv * temp;
-					saved = lv * temp;
+		ndu[ 0 ][ 0 ] = 1.0;
+		const left = zeroArr.slice( 0 );
+		const right = zeroArr.slice( 0 );
 
 
-				}
+		for ( let j = 1; j <= p; ++ j ) {
+
+			left[ j ] = u - U[ span + 1 - j ];
+			right[ j ] = U[ span + j ] - u;
+			let saved = 0.0;
 
 
-				ndu[ j ][ j ] = saved;
+			for ( let r = 0; r < j; ++ r ) {
+
+				const rv = right[ r + 1 ];
+				const lv = left[ j - r ];
+				ndu[ j ][ r ] = rv + lv;
+				const temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
+				ndu[ r ][ j ] = saved + rv * temp;
+				saved = lv * temp;
 
 
 			}
 			}
 
 
-			for ( let j = 0; j <= p; ++ j ) {
+			ndu[ j ][ j ] = saved;
 
 
-				ders[ 0 ][ j ] = ndu[ j ][ p ];
+		}
 
 
-			}
+		for ( let j = 0; j <= p; ++ j ) {
 
 
-			for ( let r = 0; r <= p; ++ r ) {
+			ders[ 0 ][ j ] = ndu[ j ][ p ];
 
 
-				let s1 = 0;
-				let s2 = 1;
-				const a = [];
+		}
 
 
-				for ( let i = 0; i <= p; ++ i ) {
+		for ( let r = 0; r <= p; ++ r ) {
 
 
-					a[ i ] = zeroArr.slice( 0 );
+			let s1 = 0;
+			let s2 = 1;
+			const a = [];
 
 
-				}
+			for ( let i = 0; i <= p; ++ i ) {
 
 
-				a[ 0 ][ 0 ] = 1.0;
+				a[ i ] = zeroArr.slice( 0 );
 
 
-				for ( let k = 1; k <= n; ++ k ) {
+			}
 
 
-					let d = 0.0;
-					const rk = r - k;
-					const pk = p - k;
+			a[ 0 ][ 0 ] = 1.0;
 
 
-					if ( r >= k ) {
+			for ( let k = 1; k <= n; ++ k ) {
 
 
-						a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
-						d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
+				let d = 0.0;
+				const rk = r - k;
+				const pk = p - k;
 
 
-					}
+				if ( r >= k ) {
 
 
-					const j1 = rk >= - 1 ? 1 : - rk;
-					const j2 = r - 1 <= pk ? k - 1 : p - r;
+					a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
+					d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
 
 
-					for ( let j = j1; j <= j2; ++ j ) {
+				}
 
 
-						a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
-						d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
+				const j1 = rk >= - 1 ? 1 : - rk;
+				const j2 = r - 1 <= pk ? k - 1 : p - r;
 
 
-					}
+				for ( let j = j1; j <= j2; ++ j ) {
 
 
-					if ( r <= pk ) {
+					a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
+					d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
 
 
-						a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
-						d += a[ s2 ][ k ] * ndu[ r ][ pk ];
+				}
 
 
-					}
+				if ( r <= pk ) {
 
 
-					ders[ k ][ r ] = d;
-					const j = s1;
-					s1 = s2;
-					s2 = j;
+					a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
+					d += a[ s2 ][ k ] * ndu[ r ][ pk ];
 
 
 				}
 				}
 
 
-			}
+				ders[ k ][ r ] = d;
+				const j = s1;
+				s1 = s2;
+				s2 = j;
 
 
-			let r = p;
+			}
 
 
-			for ( let k = 1; k <= n; ++ k ) {
+		}
 
 
-				for ( let j = 0; j <= p; ++ j ) {
+		let r = p;
 
 
-					ders[ k ][ j ] *= r;
+		for ( let k = 1; k <= n; ++ k ) {
 
 
-				}
+			for ( let j = 0; j <= p; ++ j ) {
 
 
-				r *= p - k;
+				ders[ k ][ j ] *= r;
 
 
 			}
 			}
 
 
-			return ders;
+			r *= p - k;
 
 
 		}
 		}
-		/*
-  	Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
-  		p  : degree
-  	U  : knot vector
-  	P  : control points
-  	u  : Parametric points
-  	nd : number of derivatives
-  		returns array[d+1] with derivatives
-  	*/
 
 
+		return ders;
 
 
-		static calcBSplineDerivatives( p, U, P, u, nd ) {
+	}
+	/*
+	Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
 
 
-			const du = nd < p ? nd : p;
-			const CK = [];
-			const span = this.findSpan( p, u, U );
-			const nders = this.calcBasisFunctionDerivatives( span, u, p, du, U );
-			const Pw = [];
+	p  : degree
+	U  : knot vector
+	P  : control points
+	u  : Parametric points
+	nd : number of derivatives
 
 
-			for ( let i = 0; i < P.length; ++ i ) {
+	returns array[d+1] with derivatives
+	*/
 
 
-				const point = P[ i ].clone();
-				const w = point.w;
-				point.x *= w;
-				point.y *= w;
-				point.z *= w;
-				Pw[ i ] = point;
 
 
-			}
+	function calcBSplineDerivatives( p, U, P, u, nd ) {
 
 
-			for ( let k = 0; k <= du; ++ k ) {
+		const du = nd < p ? nd : p;
+		const CK = [];
+		const span = this.findSpan( p, u, U );
+		const nders = this.calcBasisFunctionDerivatives( span, u, p, du, U );
+		const Pw = [];
 
 
-				const point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
+		for ( let i = 0; i < P.length; ++ i ) {
 
 
-				for ( let j = 1; j <= p; ++ j ) {
+			const point = P[ i ].clone();
+			const w = point.w;
+			point.x *= w;
+			point.y *= w;
+			point.z *= w;
+			Pw[ i ] = point;
 
 
-					point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
+		}
 
 
-				}
+		for ( let k = 0; k <= du; ++ k ) {
 
 
-				CK[ k ] = point;
+			const point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
+
+			for ( let j = 1; j <= p; ++ j ) {
+
+				point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
 
 
 			}
 			}
 
 
-			for ( let k = du + 1; k <= nd + 1; ++ k ) {
+			CK[ k ] = point;
 
 
-				CK[ k ] = new THREE.Vector4( 0, 0, 0 );
+		}
 
 
-			}
+		for ( let k = du + 1; k <= nd + 1; ++ k ) {
 
 
-			return CK;
+			CK[ k ] = new THREE.Vector4( 0, 0, 0 );
 
 
 		}
 		}
-		/*
-  Calculate "K over I"
-  	returns k!/(i!(k-i)!)
-  */
 
 
+		return CK;
 
 
-		static calcKoverI( k, i ) {
+	}
+	/*
+Calculate "K over I"
 
 
-			let nom = 1;
+returns k!/(i!(k-i)!)
+*/
 
 
-			for ( let j = 2; j <= k; ++ j ) {
 
 
-				nom *= j;
+	function calcKoverI( k, i ) {
 
 
-			}
+		let nom = 1;
 
 
-			let denom = 1;
+		for ( let j = 2; j <= k; ++ j ) {
 
 
-			for ( let j = 2; j <= i; ++ j ) {
+			nom *= j;
 
 
-				denom *= j;
+		}
 
 
-			}
+		let denom = 1;
 
 
-			for ( let j = 2; j <= k - i; ++ j ) {
+		for ( let j = 2; j <= i; ++ j ) {
 
 
-				denom *= j;
+			denom *= j;
 
 
-			}
+		}
+
+		for ( let j = 2; j <= k - i; ++ j ) {
 
 
-			return nom / denom;
+			denom *= j;
 
 
 		}
 		}
-		/*
-  Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
-  	Pders : result of function calcBSplineDerivatives
-  	returns array with derivatives for rational curve.
-  */
 
 
+		return nom / denom;
 
 
-		static calcRationalCurveDerivatives( Pders ) {
+	}
+	/*
+Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
 
 
-			const nd = Pders.length;
-			const Aders = [];
-			const wders = [];
+Pders : result of function calcBSplineDerivatives
 
 
-			for ( let i = 0; i < nd; ++ i ) {
+returns array with derivatives for rational curve.
+*/
 
 
-				const point = Pders[ i ];
-				Aders[ i ] = new THREE.Vector3( point.x, point.y, point.z );
-				wders[ i ] = point.w;
 
 
-			}
+	function calcRationalCurveDerivatives( Pders ) {
 
 
-			const CK = [];
+		const nd = Pders.length;
+		const Aders = [];
+		const wders = [];
 
 
-			for ( let k = 0; k < nd; ++ k ) {
+		for ( let i = 0; i < nd; ++ i ) {
 
 
-				const v = Aders[ k ].clone();
+			const point = Pders[ i ];
+			Aders[ i ] = new THREE.Vector3( point.x, point.y, point.z );
+			wders[ i ] = point.w;
 
 
-				for ( let i = 1; i <= k; ++ i ) {
+		}
 
 
-					v.sub( CK[ k - i ].clone().multiplyScalar( this.calcKoverI( k, i ) * wders[ i ] ) );
+		const CK = [];
 
 
-				}
+		for ( let k = 0; k < nd; ++ k ) {
+
+			const v = Aders[ k ].clone();
 
 
-				CK[ k ] = v.divideScalar( wders[ 0 ] );
+			for ( let i = 1; i <= k; ++ i ) {
+
+				v.sub( CK[ k - i ].clone().multiplyScalar( this.calcKoverI( k, i ) * wders[ i ] ) );
 
 
 			}
 			}
 
 
-			return CK;
+			CK[ k ] = v.divideScalar( wders[ 0 ] );
 
 
 		}
 		}
-		/*
-  Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
-  	p  : degree
-  U  : knot vector
-  P  : control points in homogeneous space
-  u  : parametric points
-  nd : number of derivatives
-  	returns array with derivatives.
-  */
 
 
+		return CK;
 
 
-		static calcNURBSDerivatives( p, U, P, u, nd ) {
+	}
+	/*
+Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
 
 
-			const Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
-			return this.calcRationalCurveDerivatives( Pders );
+p  : degree
+U  : knot vector
+P  : control points in homogeneous space
+u  : parametric points
+nd : number of derivatives
 
 
-		}
-		/*
-  Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
-  	p1, p2 : degrees of B-Spline surface
-  U1, U2 : knot vectors
-  P      : control points (x, y, z, w)
-  u, v   : parametric values
-  	returns point for given (u, v)
-  */
+returns array with derivatives.
+*/
 
 
 
 
-		static calcSurfacePoint( p, q, U, V, P, u, v, target ) {
+	function calcNURBSDerivatives( p, U, P, u, nd ) {
 
 
-			const uspan = this.findSpan( p, u, U );
-			const vspan = this.findSpan( q, v, V );
-			const Nu = this.calcBasisFunctions( uspan, u, p, U );
-			const Nv = this.calcBasisFunctions( vspan, v, q, V );
-			const temp = [];
+		const Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
+		return this.calcRationalCurveDerivatives( Pders );
 
 
-			for ( let l = 0; l <= q; ++ l ) {
+	}
+	/*
+Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
 
 
-				temp[ l ] = new THREE.Vector4( 0, 0, 0, 0 );
+p1, p2 : degrees of B-Spline surface
+U1, U2 : knot vectors
+P      : control points (x, y, z, w)
+u, v   : parametric values
 
 
-				for ( let k = 0; k <= p; ++ k ) {
+returns point for given (u, v)
+*/
 
 
-					const point = P[ uspan - p + k ][ vspan - q + l ].clone();
-					const w = point.w;
-					point.x *= w;
-					point.y *= w;
-					point.z *= w;
-					temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
 
 
-				}
+	function calcSurfacePoint( p, q, U, V, P, u, v, target ) {
 
 
-			}
+		const uspan = this.findSpan( p, u, U );
+		const vspan = this.findSpan( q, v, V );
+		const Nu = this.calcBasisFunctions( uspan, u, p, U );
+		const Nv = this.calcBasisFunctions( vspan, v, q, V );
+		const temp = [];
 
 
-			const Sw = new THREE.Vector4( 0, 0, 0, 0 );
+		for ( let l = 0; l <= q; ++ l ) {
 
 
-			for ( let l = 0; l <= q; ++ l ) {
+			temp[ l ] = new THREE.Vector4( 0, 0, 0, 0 );
 
 
-				Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
+			for ( let k = 0; k <= p; ++ k ) {
+
+				const point = P[ uspan - p + k ][ vspan - q + l ].clone();
+				const w = point.w;
+				point.x *= w;
+				point.y *= w;
+				point.z *= w;
+				temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
 
 
 			}
 			}
 
 
-			Sw.divideScalar( Sw.w );
-			target.set( Sw.x, Sw.y, Sw.z );
+		}
+
+		const Sw = new THREE.Vector4( 0, 0, 0, 0 );
+
+		for ( let l = 0; l <= q; ++ l ) {
+
+			Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
 
 
 		}
 		}
 
 
+		Sw.divideScalar( Sw.w );
+		target.set( Sw.x, Sw.y, Sw.z );
+
 	}
 	}
 
 
-	THREE.NURBSUtils = NURBSUtils;
+	THREE.NURBSUtils = {};
+	THREE.NURBSUtils.calcBSplineDerivatives = calcBSplineDerivatives;
+	THREE.NURBSUtils.calcBSplinePoint = calcBSplinePoint;
+	THREE.NURBSUtils.calcBasisFunctionDerivatives = calcBasisFunctionDerivatives;
+	THREE.NURBSUtils.calcBasisFunctions = calcBasisFunctions;
+	THREE.NURBSUtils.calcKoverI = calcKoverI;
+	THREE.NURBSUtils.calcNURBSDerivatives = calcNURBSDerivatives;
+	THREE.NURBSUtils.calcRationalCurveDerivatives = calcRationalCurveDerivatives;
+	THREE.NURBSUtils.calcSurfacePoint = calcSurfacePoint;
+	THREE.NURBSUtils.findSpan = findSpan;
 
 
 } )();
 } )();

+ 1 - 1
examples/js/geometries/LightningStrike.js

@@ -452,7 +452,7 @@
 
 
 		}
 		}
 
 
-		addNewSubray( ) {
+		addNewSubray() {
 
 
 			return this.subrays[ this.numSubrays ++ ];
 			return this.subrays[ this.numSubrays ++ ];
 
 

+ 1 - 11
examples/js/loaders/FBXLoader.js

@@ -636,17 +636,7 @@
 
 
 			}
 			}
 
 
-			const texture = textureMap.get( id );
-
-			if ( texture !== undefined && texture.image !== undefined ) {
-
-				return texture;
-
-			} else {
-
-				return undefined;
-
-			}
+			return textureMap.get( id );
 
 
 		} // Parse nodes in FBXTree.Objects.Deformer
 		} // Parse nodes in FBXTree.Objects.Deformer
 		// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
 		// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here

+ 1 - 1
examples/js/loaders/GLTFLoader.js

@@ -2707,7 +2707,7 @@
 
 
 		}
 		}
 
 
-		getMaterialType( ) {
+		getMaterialType() {
 
 
 			return THREE.MeshStandardMaterial;
 			return THREE.MeshStandardMaterial;
 
 

+ 260 - 299
examples/js/loaders/LDrawLoader.js

@@ -361,6 +361,18 @@
 
 
 	}
 	}
 
 
+	function isPartType( type ) {
+
+		return type === 'Part';
+
+	}
+
+	function isModelType( type ) {
+
+		return type === 'Model' || type === 'Unofficial_Model';
+
+	}
+
 	function isPrimitiveType( type ) {
 	function isPrimitiveType( type ) {
 
 
 		return /primitive/i.test( type ) || type === 'Subpart';
 		return /primitive/i.test( type ) || type === 'Subpart';
@@ -447,6 +459,116 @@
 
 
 	}
 	}
 
 
+	class LDrawFileCache {
+
+		constructor( loader ) {
+
+			this.cache = {};
+			this.loader = loader;
+
+		}
+
+		setData( key, contents ) {
+
+			this.cache[ key.toLowerCase() ] = contents;
+
+		}
+
+		async loadData( fileName ) {
+
+			const key = fileName.toLowerCase();
+
+			if ( key in this.cache ) {
+
+				return this.cache[ key ];
+
+			}
+
+			this.cache[ fileName ] = new Promise( async ( resolve, reject ) => {
+
+				let triedLowerCase = false;
+				let locationState = FILE_LOCATION_AS_IS;
+
+				while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
+
+					let subobjectURL = fileName;
+
+					switch ( locationState ) {
+
+						case FILE_LOCATION_AS_IS:
+							locationState = locationState + 1;
+							break;
+
+						case FILE_LOCATION_TRY_PARTS:
+							subobjectURL = 'parts/' + subobjectURL;
+							locationState = locationState + 1;
+							break;
+
+						case FILE_LOCATION_TRY_P:
+							subobjectURL = 'p/' + subobjectURL;
+							locationState = locationState + 1;
+							break;
+
+						case FILE_LOCATION_TRY_MODELS:
+							subobjectURL = 'models/' + subobjectURL;
+							locationState = locationState + 1;
+							break;
+
+						case FILE_LOCATION_TRY_RELATIVE:
+							subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
+							locationState = locationState + 1;
+							break;
+
+						case FILE_LOCATION_TRY_ABSOLUTE:
+							if ( triedLowerCase ) {
+
+								// Try absolute path
+								locationState = FILE_LOCATION_NOT_FOUND;
+
+							} else {
+
+								// Next attempt is lower case
+								fileName = fileName.toLowerCase();
+								subobjectURL = fileName;
+								triedLowerCase = true;
+								locationState = FILE_LOCATION_AS_IS;
+
+							}
+
+							break;
+
+					}
+
+					const loader = this.loader;
+					const fileLoader = new THREE.FileLoader( loader.manager );
+					fileLoader.setPath( loader.partsLibraryPath );
+					fileLoader.setRequestHeader( loader.requestHeader );
+					fileLoader.setWithCredentials( loader.withCredentials );
+
+					try {
+
+						const text = await fileLoader.loadAsync( subobjectURL );
+						this.setData( fileName, text );
+						resolve( text );
+						return;
+
+					} catch {
+
+						continue;
+
+					}
+
+				}
+
+				reject();
+
+			} );
+			return this.cache[ fileName ];
+
+		}
+
+	}
+
 	function sortByMaterial( a, b ) {
 	function sortByMaterial( a, b ) {
 
 
 		if ( a.colourCode === b.colourCode ) {
 		if ( a.colourCode === b.colourCode ) {
@@ -652,19 +774,15 @@
 
 
 		constructor( manager ) {
 		constructor( manager ) {
 
 
-			super( manager ); // This is a stack of 'parse scopes' with one level per subobject loaded file.
-			// Each level contains a material lib and also other runtime variables passed between parent and child subobjects
-			// When searching for a material code, the stack is read from top of the stack to bottom
-			// Each material library is an object map keyed by colour codes.
-
-			this.parseScopesStack = null; // Array of THREE.Material
+			super( manager ); // Array of THREE.Material
 
 
 			this.materials = []; // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
 			this.materials = []; // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
 			// This also allows to handle the embedded text files ("0 FILE" lines)
 			// This also allows to handle the embedded text files ("0 FILE" lines)
 
 
-			this.subobjectCache = {}; // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
+			this.cache = new LDrawFileCache( this ); // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
 
 
-			this.fileMap = null; // Add default main triangle and line edge materials (used in piecess that can be coloured with a main color)
+			this.fileMap = null;
+			this.rootParseScope = this.newParseScopeLevel(); // Add default main triangle and line edge materials (used in pieces that can be coloured with a main color)
 
 
 			this.setMaterials( [ this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ), this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ] ); // If this flag is set to true, each subobject will be a Object.
 			this.setMaterials( [ this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ), this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ] ); // If this flag is set to true, each subobject will be a Object.
 			// If not (the default), only one object which contains all the merged primitives will be created.
 			// If not (the default), only one object which contains all the merged primitives will be created.
@@ -721,14 +839,17 @@
 
 
 			}
 			}
 
 
-			const scope = this;
 			const fileLoader = new THREE.FileLoader( this.manager );
 			const fileLoader = new THREE.FileLoader( this.manager );
 			fileLoader.setPath( this.path );
 			fileLoader.setPath( this.path );
 			fileLoader.setRequestHeader( this.requestHeader );
 			fileLoader.setRequestHeader( this.requestHeader );
 			fileLoader.setWithCredentials( this.withCredentials );
 			fileLoader.setWithCredentials( this.withCredentials );
-			fileLoader.load( url, function ( text ) {
+			fileLoader.load( url, text => {
+
+				this.processObject( text, null, url, this.rootParseScope ).then( function ( result ) {
+
+					onLoad( result.groupObject );
 
 
-				scope.processObject( text, onLoad, null, url );
+				} );
 
 
 			}, onProgress, onError );
 			}, onProgress, onError );
 
 
@@ -737,16 +858,19 @@
 		parse( text, path, onLoad ) {
 		parse( text, path, onLoad ) {
 
 
 			// Async parse.  This function calls onParse with the parsed THREE.Object3D as parameter
 			// Async parse.  This function calls onParse with the parsed THREE.Object3D as parameter
-			this.processObject( text, onLoad, null, path );
+			this.processObject( text, null, path, this.rootParseScope ).then( function ( result ) {
+
+				onLoad( result.groupObject );
+
+			} );
 
 
 		}
 		}
 
 
 		setMaterials( materials ) {
 		setMaterials( materials ) {
 
 
 			// Clears parse scopes stack, adds new scope with material library
 			// Clears parse scopes stack, adds new scope with material library
-			this.parseScopesStack = [];
-			this.newParseScopeLevel( materials );
-			this.getCurrentParseScope().isFromParse = false;
+			this.rootParseScope = this.newParseScopeLevel( materials );
+			this.rootParseScope.isFromParse = false;
 			this.materials = materials;
 			this.materials = materials;
 			return this;
 			return this;
 
 
@@ -759,7 +883,7 @@
 
 
 		}
 		}
 
 
-		newParseScopeLevel( materials ) {
+		newParseScopeLevel( materials = null, parentScope = null ) {
 
 
 			// Adds a new scope level, assign materials to it and returns it
 			// Adds a new scope level, assign materials to it and returns it
 			const matLib = {};
 			const matLib = {};
@@ -775,8 +899,8 @@
 
 
 			}
 			}
 
 
-			const topParseScope = this.getCurrentParseScope();
 			const newParseScope = {
 			const newParseScope = {
+				parentScope: parentScope,
 				lib: matLib,
 				lib: matLib,
 				url: null,
 				url: null,
 				// Subobjects
 				// Subobjects
@@ -788,8 +912,8 @@
 				keywords: null,
 				keywords: null,
 				// Current subobject
 				// Current subobject
 				currentFileName: null,
 				currentFileName: null,
-				mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
-				mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
+				mainColourCode: parentScope ? parentScope.mainColourCode : '16',
+				mainEdgeColourCode: parentScope ? parentScope.mainEdgeColourCode : '24',
 				currentMatrix: new THREE.Matrix4(),
 				currentMatrix: new THREE.Matrix4(),
 				matrix: new THREE.Matrix4(),
 				matrix: new THREE.Matrix4(),
 				// If false, it is a root material scope previous to parse
 				// If false, it is a root material scope previous to parse
@@ -801,22 +925,14 @@
 				// If true, this object is the start of a construction step
 				// If true, this object is the start of a construction step
 				startingConstructionStep: false
 				startingConstructionStep: false
 			};
 			};
-			this.parseScopesStack.push( newParseScope );
 			return newParseScope;
 			return newParseScope;
 
 
 		}
 		}
 
 
-		removeScopeLevel() {
-
-			this.parseScopesStack.pop();
-			return this;
-
-		}
-
-		addMaterial( material ) {
+		addMaterial( material, parseScope ) {
 
 
 			// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
 			// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
-			const matLib = this.getCurrentParseScope().lib;
+			const matLib = parseScope.lib;
 
 
 			if ( ! matLib[ material.userData.code ] ) {
 			if ( ! matLib[ material.userData.code ] ) {
 
 
@@ -829,7 +945,7 @@
 
 
 		}
 		}
 
 
-		getMaterial( colourCode ) {
+		getMaterial( colourCode, parseScope = this.rootParseScope ) {
 
 
 			// Given a colour code search its material in the parse scopes stack
 			// Given a colour code search its material in the parse scopes stack
 			if ( colourCode.startsWith( '0x2' ) ) {
 			if ( colourCode.startsWith( '0x2' ) ) {
@@ -840,42 +956,22 @@
 
 
 			}
 			}
 
 
-			for ( let i = this.parseScopesStack.length - 1; i >= 0; i -- ) {
+			while ( parseScope ) {
 
 
-				const material = this.parseScopesStack[ i ].lib[ colourCode ];
+				const material = parseScope.lib[ colourCode ];
 
 
 				if ( material ) {
 				if ( material ) {
 
 
 					return material;
 					return material;
 
 
-				}
-
-			} // Material was not found
-
-
-			return null;
-
-		}
-
-		getParentParseScope() {
-
-			if ( this.parseScopesStack.length > 1 ) {
-
-				return this.parseScopesStack[ this.parseScopesStack.length - 2 ];
-
-			}
-
-			return null;
-
-		}
+				} else {
 
 
-		getCurrentParseScope() {
+					parseScope = parseScope.parentScope;
 
 
-			if ( this.parseScopesStack.length > 0 ) {
+				}
 
 
-				return this.parseScopesStack[ this.parseScopesStack.length - 1 ];
+			} // Material was not found
 
 
-			}
 
 
 			return null;
 			return null;
 
 
@@ -883,7 +979,7 @@
 
 
 		parseColourMetaDirective( lineParser ) {
 		parseColourMetaDirective( lineParser ) {
 
 
-			// Parses a colour definition and returns a THREE.Material or null if error
+			// Parses a colour definition and returns a THREE.Material
 			let code = null; // Triangle and line colours
 			let code = null; // Triangle and line colours
 
 
 			let colour = 0xFF00FF;
 			let colour = 0xFF00FF;
@@ -1142,14 +1238,14 @@
 		} //
 		} //
 
 
 
 
-		objectParse( text ) {
+		objectParse( text, parseScope ) {
 
 
 			// Retrieve data from the parent parse scope
 			// Retrieve data from the parent parse scope
-			const parentParseScope = this.getParentParseScope(); // Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
+			const currentParseScope = parseScope;
+			const parentParseScope = currentParseScope.parentScope; // Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
 
 
-			const mainColourCode = parentParseScope.mainColourCode;
-			const mainEdgeColourCode = parentParseScope.mainEdgeColourCode;
-			const currentParseScope = this.getCurrentParseScope(); // Parse result variables
+			const mainColourCode = currentParseScope.mainColourCode;
+			const mainEdgeColourCode = currentParseScope.mainEdgeColourCode; // Parse result variables
 
 
 			let faces;
 			let faces;
 			let lineSegments;
 			let lineSegments;
@@ -1195,7 +1291,7 @@
 
 
 				}
 				}
 
 
-				const material = scope.getMaterial( colourCode );
+				const material = scope.getMaterial( colourCode, currentParseScope );
 
 
 				if ( ! material ) {
 				if ( ! material ) {
 
 
@@ -1232,7 +1328,7 @@
 					if ( line.startsWith( '0 FILE ' ) ) {
 					if ( line.startsWith( '0 FILE ' ) ) {
 
 
 						// Save previous embedded file in the cache
 						// Save previous embedded file in the cache
-						this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText; // New embedded text file
+						this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText ); // New embedded text file
 
 
 						currentEmbeddedFileName = line.substring( 7 );
 						currentEmbeddedFileName = line.substring( 7 );
 						currentEmbeddedText = '';
 						currentEmbeddedText = '';
@@ -1310,7 +1406,7 @@
 
 
 									if ( material ) {
 									if ( material ) {
 
 
-										this.addMaterial( material );
+										this.addMaterial( material, parseScope );
 
 
 									} else {
 									} else {
 
 
@@ -1453,10 +1549,6 @@
 							material: material,
 							material: material,
 							matrix: matrix,
 							matrix: matrix,
 							fileName: fileName,
 							fileName: fileName,
-							originalFileName: fileName,
-							locationState: FILE_LOCATION_AS_IS,
-							url: null,
-							triedLowerCase: false,
 							inverted: bfcInverted !== currentParseScope.inverted,
 							inverted: bfcInverted !== currentParseScope.inverted,
 							startingConstructionStep: startingConstructionStep
 							startingConstructionStep: startingConstructionStep
 						} );
 						} );
@@ -1608,7 +1700,7 @@
 
 
 			if ( parsingEmbeddedFiles ) {
 			if ( parsingEmbeddedFiles ) {
 
 
-				this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
+				this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
 
 
 			}
 			}
 
 
@@ -1643,323 +1735,192 @@
 
 
 		}
 		}
 
 
-		processObject( text, onProcessed, subobject, url ) {
-
-			const scope = this;
-			const parseScope = scope.newParseScopeLevel();
-			parseScope.url = url;
-			const parentParseScope = scope.getParentParseScope(); // Set current matrix
-
-			if ( subobject ) {
-
-				parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
-				parseScope.matrix.copy( subobject.matrix );
-				parseScope.inverted = subobject.inverted;
-				parseScope.startingConstructionStep = subobject.startingConstructionStep;
-
-			} // Add to cache
+		finalizeObject( subobjectParseScope ) {
 
 
+			const parentParseScope = subobjectParseScope.parentScope; // Smooth the normals if this is a part or if this is a case where the subpart
+			// is added directly into the parent model (meaning it will never get smoothed by
+			// being added to a part)
 
 
-			let currentFileName = parentParseScope.currentFileName;
+			const doSmooth = isPartType( subobjectParseScope.type ) || ! isPartType( subobjectParseScope.type ) && ! isModelType( subobjectParseScope.type ) && isModelType( subobjectParseScope.parentScope.type );
 
 
-			if ( currentFileName !== null ) {
+			if ( this.smoothNormals && doSmooth ) {
 
 
-				currentFileName = parentParseScope.currentFileName.toLowerCase();
+				smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments );
 
 
 			}
 			}
 
 
-			if ( scope.subobjectCache[ currentFileName ] === undefined ) {
-
-				scope.subobjectCache[ currentFileName ] = text;
-
-			} // Parse the object (returns a THREE.Group)
-
-
-			scope.objectParse( text );
-			let finishedCount = 0;
-			onSubobjectFinish();
+			const isRoot = ! parentParseScope.isFromParse;
 
 
-			function onSubobjectFinish() {
+			if ( this.separateObjects && ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
 
 
-				finishedCount ++;
+				const objGroup = subobjectParseScope.groupObject;
 
 
-				if ( finishedCount === parseScope.subobjects.length + 1 ) {
+				if ( subobjectParseScope.faces.length > 0 ) {
 
 
-					finalizeObject();
-
-				} else {
-
-					// Once the previous subobject has finished we can start processing the next one in the list.
-					// The subobject processing shares scope in processing so it's important that they be loaded serially
-					// to avoid race conditions.
-					// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
-					// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
-					// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
-					const subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
-					Promise.resolve().then( function () {
-
-						loadSubobject( subobject );
-
-					} );
-					parseScope.subobjectIndex ++;
+					objGroup.add( createObject( subobjectParseScope.faces, 3, false, subobjectParseScope.totalFaces ) );
 
 
 				}
 				}
 
 
-			}
-
-			function finalizeObject() {
-
-				if ( scope.smoothNormals && parseScope.type === 'Part' ) {
+				if ( subobjectParseScope.lineSegments.length > 0 ) {
 
 
-					smoothNormals( parseScope.faces, parseScope.lineSegments );
+					objGroup.add( createObject( subobjectParseScope.lineSegments, 2 ) );
 
 
 				}
 				}
 
 
-				const isRoot = ! parentParseScope.isFromParse;
-
-				if ( scope.separateObjects && ! isPrimitiveType( parseScope.type ) || isRoot ) {
-
-					const objGroup = parseScope.groupObject;
-
-					if ( parseScope.faces.length > 0 ) {
+				if ( subobjectParseScope.conditionalSegments.length > 0 ) {
 
 
-						objGroup.add( createObject( parseScope.faces, 3, false, parseScope.totalFaces ) );
+					objGroup.add( createObject( subobjectParseScope.conditionalSegments, 2, true ) );
 
 
-					}
-
-					if ( parseScope.lineSegments.length > 0 ) {
-
-						objGroup.add( createObject( parseScope.lineSegments, 2 ) );
-
-					}
-
-					if ( parseScope.conditionalSegments.length > 0 ) {
-
-						objGroup.add( createObject( parseScope.conditionalSegments, 2, true ) );
-
-					}
-
-					if ( parentParseScope.groupObject ) {
-
-						objGroup.name = parseScope.fileName;
-						objGroup.userData.category = parseScope.category;
-						objGroup.userData.keywords = parseScope.keywords;
-						parseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
-						parentParseScope.groupObject.add( objGroup );
+				}
 
 
-					}
+				if ( parentParseScope.groupObject ) {
 
 
-				} else {
+					objGroup.name = subobjectParseScope.fileName;
+					objGroup.userData.category = subobjectParseScope.category;
+					objGroup.userData.keywords = subobjectParseScope.keywords;
+					subobjectParseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
+					parentParseScope.groupObject.add( objGroup );
 
 
-					const separateObjects = scope.separateObjects;
-					const parentLineSegments = parentParseScope.lineSegments;
-					const parentConditionalSegments = parentParseScope.conditionalSegments;
-					const parentFaces = parentParseScope.faces;
-					const lineSegments = parseScope.lineSegments;
-					const conditionalSegments = parseScope.conditionalSegments;
-					const faces = parseScope.faces;
+				}
 
 
-					for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
+			} else {
 
 
-						const ls = lineSegments[ i ];
+				const separateObjects = this.separateObjects;
+				const parentLineSegments = parentParseScope.lineSegments;
+				const parentConditionalSegments = parentParseScope.conditionalSegments;
+				const parentFaces = parentParseScope.faces;
+				const lineSegments = subobjectParseScope.lineSegments;
+				const conditionalSegments = subobjectParseScope.conditionalSegments;
+				const faces = subobjectParseScope.faces;
 
 
-						if ( separateObjects ) {
+				for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
 
 
-							const vertices = ls.vertices;
-							vertices[ 0 ].applyMatrix4( parseScope.matrix );
-							vertices[ 1 ].applyMatrix4( parseScope.matrix );
+					const ls = lineSegments[ i ];
 
 
-						}
+					if ( separateObjects ) {
 
 
-						parentLineSegments.push( ls );
+						const vertices = ls.vertices;
+						vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
+						vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
 
 
 					}
 					}
 
 
-					for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
+					parentLineSegments.push( ls );
 
 
-						const os = conditionalSegments[ i ];
+				}
 
 
-						if ( separateObjects ) {
+				for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
 
 
-							const vertices = os.vertices;
-							const controlPoints = os.controlPoints;
-							vertices[ 0 ].applyMatrix4( parseScope.matrix );
-							vertices[ 1 ].applyMatrix4( parseScope.matrix );
-							controlPoints[ 0 ].applyMatrix4( parseScope.matrix );
-							controlPoints[ 1 ].applyMatrix4( parseScope.matrix );
+					const os = conditionalSegments[ i ];
 
 
-						}
+					if ( separateObjects ) {
 
 
-						parentConditionalSegments.push( os );
+						const vertices = os.vertices;
+						const controlPoints = os.controlPoints;
+						vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
+						vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
+						controlPoints[ 0 ].applyMatrix4( subobjectParseScope.matrix );
+						controlPoints[ 1 ].applyMatrix4( subobjectParseScope.matrix );
 
 
 					}
 					}
 
 
-					for ( let i = 0, l = faces.length; i < l; i ++ ) {
+					parentConditionalSegments.push( os );
 
 
-						const tri = faces[ i ];
+				}
 
 
-						if ( separateObjects ) {
+				for ( let i = 0, l = faces.length; i < l; i ++ ) {
 
 
-							const vertices = tri.vertices;
+					const tri = faces[ i ];
 
 
-							for ( let i = 0, l = vertices.length; i < l; i ++ ) {
+					if ( separateObjects ) {
 
 
-								vertices[ i ] = vertices[ i ].clone().applyMatrix4( parseScope.matrix );
+						const vertices = tri.vertices;
 
 
-							}
+						for ( let i = 0, l = vertices.length; i < l; i ++ ) {
 
 
-							_tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
+							vertices[ i ] = vertices[ i ].clone().applyMatrix4( subobjectParseScope.matrix );
 
 
-							_tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
+						}
 
 
-							tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
+						_tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
 
 
-						}
+						_tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
 
 
-						parentFaces.push( tri );
+						tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
 
 
 					}
 					}
 
 
-					parentParseScope.totalFaces += parseScope.totalFaces;
+					parentFaces.push( tri );
 
 
 				}
 				}
 
 
-				scope.removeScopeLevel(); // If it is root object, compute construction steps
-
-				if ( ! parentParseScope.isFromParse ) {
+				parentParseScope.totalFaces += subobjectParseScope.totalFaces;
 
 
-					scope.computeConstructionSteps( parseScope.groupObject );
-
-				}
-
-				if ( onProcessed ) {
+			}
 
 
-					onProcessed( parseScope.groupObject );
+		}
 
 
-				}
+		async processObject( text, subobject, url, parentScope ) {
 
 
-			}
+			const scope = this;
+			const parseScope = this.newParseScopeLevel( null, parentScope );
+			parseScope.url = url;
+			const parentParseScope = parseScope.parentScope; // Set current matrix
 
 
-			function loadSubobject( subobject ) {
+			if ( subobject ) {
 
 
+				parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
+				parseScope.matrix.copy( subobject.matrix );
+				parseScope.inverted = subobject.inverted;
+				parseScope.startingConstructionStep = subobject.startingConstructionStep;
 				parseScope.mainColourCode = subobject.material.userData.code;
 				parseScope.mainColourCode = subobject.material.userData.code;
 				parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
 				parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
-				parseScope.currentFileName = subobject.originalFileName; // If subobject was cached previously, use the cached one
-
-				const cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
-
-				if ( cached ) {
-
-					scope.processObject( cached, function ( subobjectGroup ) {
-
-						onSubobjectLoaded( subobjectGroup, subobject );
-						onSubobjectFinish();
-
-					}, subobject, url );
-					return;
-
-				} // Adjust file name to locate the subobject file path in standard locations (always under directory scope.path)
-				// Update also subobject.locationState for the next try if this load fails.
-
-
-				let subobjectURL = subobject.fileName;
-				let newLocationState = FILE_LOCATION_NOT_FOUND;
+				parseScope.fileName = subobject.fileName;
 
 
-				switch ( subobject.locationState ) {
+			} // Parse the object
 
 
-					case FILE_LOCATION_AS_IS:
-						newLocationState = subobject.locationState + 1;
-						break;
-
-					case FILE_LOCATION_TRY_PARTS:
-						subobjectURL = 'parts/' + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
-
-					case FILE_LOCATION_TRY_P:
-						subobjectURL = 'p/' + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
-
-					case FILE_LOCATION_TRY_MODELS:
-						subobjectURL = 'models/' + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
-
-					case FILE_LOCATION_TRY_RELATIVE:
-						subobjectURL = url.substring( 0, url.lastIndexOf( '/' ) + 1 ) + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
-
-					case FILE_LOCATION_TRY_ABSOLUTE:
-						if ( subobject.triedLowerCase ) {
-
-							// Try absolute path
-							newLocationState = FILE_LOCATION_NOT_FOUND;
-
-						} else {
-
-							// Next attempt is lower case
-							subobject.fileName = subobject.fileName.toLowerCase();
-							subobjectURL = subobject.fileName;
-							subobject.triedLowerCase = true;
-							newLocationState = FILE_LOCATION_AS_IS;
 
 
-						}
+			this.objectParse( text, parseScope );
+			const subobjects = parseScope.subobjects;
+			const promises = [];
 
 
-						break;
+			for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
 
 
-					case FILE_LOCATION_NOT_FOUND:
-						// All location possibilities have been tried, give up loading this object
-						console.warn( 'LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.' );
-						return;
+				promises.push( loadSubobject( parseScope.subobjects[ i ] ) );
 
 
-				}
+			} // Kick off of the downloads in parallel but process all the subobjects
+			// in order so all the assembly instructions are correct
 
 
-				subobject.locationState = newLocationState;
-				subobject.url = subobjectURL; // Load the subobject
-				// Use another file loader here so we can keep track of the subobject information
-				// and use it when processing the next model.
 
 
-				const fileLoader = new THREE.FileLoader( scope.manager );
-				fileLoader.setPath( scope.partsLibraryPath );
-				fileLoader.setRequestHeader( scope.requestHeader );
-				fileLoader.setWithCredentials( scope.withCredentials );
-				fileLoader.load( subobjectURL, function ( text ) {
+			const subobjectScopes = await Promise.all( promises );
 
 
-					scope.processObject( text, function ( subobjectGroup ) {
+			for ( let i = 0, l = subobjectScopes.length; i < l; i ++ ) {
 
 
-						onSubobjectLoaded( subobjectGroup, subobject );
-						onSubobjectFinish();
+				this.finalizeObject( subobjectScopes[ i ] );
 
 
-					}, subobject, url );
+			} // If it is root object then finalize this object and compute construction steps
 
 
-				}, undefined, function ( err ) {
 
 
-					onSubobjectError( err, subobject );
+			if ( ! parentParseScope.isFromParse ) {
 
 
-				}, subobject );
+				this.finalizeObject( parseScope );
+				this.computeConstructionSteps( parseScope.groupObject );
 
 
 			}
 			}
 
 
-			function onSubobjectLoaded( subobjectGroup, subobject ) {
+			return parseScope;
 
 
-				if ( subobjectGroup === null ) {
-
-					// Try to reload
-					loadSubobject( subobject );
-					return;
+			function loadSubobject( subobject ) {
 
 
-				}
+				return scope.cache.loadData( subobject.fileName ).then( function ( text ) {
 
 
-				scope.fileMap[ subobject.originalFileName ] = subobject.url;
+					return scope.processObject( text, subobject, url, parseScope );
 
 
-			}
+				} ).catch( function () {
 
 
-			function onSubobjectError( err, subobject ) {
+					console.warn( 'LDrawLoader: Subobject "' + subobject.fileName + '" could not be found.' );
 
 
-				// Retry download from a different default possible location
-				loadSubobject( subobject );
+				} );
 
 
 			}
 			}
 
 

+ 1 - 2
examples/js/loaders/RGBELoader.js

@@ -220,8 +220,7 @@
 					const scanline_width = w;
 					const scanline_width = w;
 
 
 					if ( // run length encoding is not allowed so read flat
 					if ( // run length encoding is not allowed so read flat
-						scanline_width < 8 || scanline_width > 0x7fff || // this file is not run length encoded
-      2 !== buffer[ 0 ] || 2 !== buffer[ 1 ] || buffer[ 2 ] & 0x80 ) {
+						scanline_width < 8 || scanline_width > 0x7fff || 2 !== buffer[ 0 ] || 2 !== buffer[ 1 ] || buffer[ 2 ] & 0x80 ) {
 
 
 						// return the flat buffer
 						// return the flat buffer
 						return new Uint8Array( buffer );
 						return new Uint8Array( buffer );

+ 13 - 13
examples/js/misc/ProgressiveLightMap.js

@@ -25,22 +25,22 @@
 			this.res = res;
 			this.res = res;
 			this.lightMapContainers = [];
 			this.lightMapContainers = [];
 			this.compiled = false;
 			this.compiled = false;
-			this.scene = new THREE.Scene();
+			this.scene = new THREE.THREE.THREE.Scene();
 			this.scene.background = null;
 			this.scene.background = null;
-			this.tinyTarget = new THREE.WebGLRenderTarget( 1, 1 );
+			this.tinyTarget = new THREE.THREE.THREE.WebGLRenderTarget( 1, 1 );
 			this.buffer1Active = false;
 			this.buffer1Active = false;
 			this.firstUpdate = true;
 			this.firstUpdate = true;
 			this.warned = false; // Create the Progressive LightMap Texture
 			this.warned = false; // Create the Progressive LightMap Texture
 
 
-			const format = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? THREE.HalfFloatType : THREE.FloatType;
-			this.progressiveLightMap1 = new THREE.WebGLRenderTarget( this.res, this.res, {
+			const format = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? THREE.THREE.THREE.HalfFloatType : THREE.THREE.THREE.FloatType;
+			this.progressiveLightMap1 = new THREE.THREE.THREE.WebGLRenderTarget( this.res, this.res, {
 				type: format
 				type: format
 			} );
 			} );
-			this.progressiveLightMap2 = new THREE.WebGLRenderTarget( this.res, this.res, {
+			this.progressiveLightMap2 = new THREE.THREE.THREE.WebGLRenderTarget( this.res, this.res, {
 				type: format
 				type: format
 			} ); // Inject some spicy new logic into a standard phong material
 			} ); // Inject some spicy new logic into a standard phong material
 
 
-			this.uvMat = new THREE.MeshPhongMaterial();
+			this.uvMat = new THREE.THREE.THREE.MeshPhongMaterial();
 			this.uvMat.uniforms = {};
 			this.uvMat.uniforms = {};
 
 
 			this.uvMat.onBeforeCompile = shader => {
 			this.uvMat.onBeforeCompile = shader => {
@@ -243,12 +243,12 @@
 
 
 			if ( this.labelMesh == null ) {
 			if ( this.labelMesh == null ) {
 
 
-				this.labelMaterial = new THREE.MeshBasicMaterial( {
+				this.labelMaterial = new THREE.THREE.THREE.MeshBasicMaterial( {
 					map: this.progressiveLightMap1.texture,
 					map: this.progressiveLightMap1.texture,
-					side: THREE.DoubleSide
+					side: THREE.THREE.THREE.DoubleSide
 				} );
 				} );
-				this.labelPlane = new THREE.PlaneGeometry( 100, 100 );
-				this.labelMesh = new THREE.Mesh( this.labelPlane, this.labelMaterial );
+				this.labelPlane = new THREE.THREE.THREE.PlaneGeometry( 100, 100 );
+				this.labelMesh = new THREE.THREE.THREE.Mesh( this.labelPlane, this.labelMaterial );
 				this.labelMesh.position.y = 250;
 				this.labelMesh.position.y = 250;
 				this.lightMapContainers[ 0 ].object.parent.add( this.labelMesh );
 				this.lightMapContainers[ 0 ].object.parent.add( this.labelMesh );
 
 
@@ -272,7 +272,7 @@
 
 
 		_initializeBlurPlane( res, lightMap = null ) {
 		_initializeBlurPlane( res, lightMap = null ) {
 
 
-			const blurMaterial = new THREE.MeshBasicMaterial();
+			const blurMaterial = new THREE.THREE.THREE.MeshBasicMaterial();
 			blurMaterial.uniforms = {
 			blurMaterial.uniforms = {
 				previousShadowMap: {
 				previousShadowMap: {
 					value: null
 					value: null
@@ -315,7 +315,7 @@
 
 
 			};
 			};
 
 
-			this.blurringPlane = new THREE.Mesh( new THREE.PlaneBufferGeometry( 1, 1 ), blurMaterial );
+			this.blurringPlane = new THREE.THREE.THREE.Mesh( new THREE.THREE.THREE.PlaneBufferGeometry( 1, 1 ), blurMaterial );
 			this.blurringPlane.name = 'Blurring Plane';
 			this.blurringPlane.name = 'Blurring Plane';
 			this.blurringPlane.frustumCulled = false;
 			this.blurringPlane.frustumCulled = false;
 			this.blurringPlane.renderOrder = 0;
 			this.blurringPlane.renderOrder = 0;
@@ -326,6 +326,6 @@
 
 
 	}
 	}
 
 
-	THREE.ProgressiveLightMap = ProgressiveLightMap;
+	THREE.THREE.THREE.ProgressiveLightMap = ProgressiveLightMap;
 
 
 } )();
 } )();

+ 2 - 2
examples/js/postprocessing/EffectComposer.js

@@ -223,9 +223,9 @@
 
 
 		}
 		}
 
 
-		setSize( ) {}
+		setSize() {}
 
 
-		render( ) {
+		render() {
 
 
 			console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
 			console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
 
 

+ 2 - 2
examples/js/postprocessing/Pass.js

@@ -15,9 +15,9 @@
 
 
 		}
 		}
 
 
-		setSize( ) {}
+		setSize() {}
 
 
-		render( ) {
+		render() {
 
 
 			console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
 			console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
 
 

+ 0 - 7
examples/js/shaders/MMDToonShader.js

@@ -17,13 +17,6 @@
 	const lights_mmd_toon_pars_fragment = `
 	const lights_mmd_toon_pars_fragment = `
 varying vec3 vViewPosition;
 varying vec3 vViewPosition;
 
 
-#ifndef FLAT_SHADED
-
-	varying vec3 vNormal;
-
-#endif
-
-
 struct BlinnPhongMaterial {
 struct BlinnPhongMaterial {
 
 
 	vec3 diffuseColor;
 	vec3 diffuseColor;

+ 484 - 480
examples/js/utils/BufferGeometryUtils.js

@@ -1,445 +1,441 @@
 ( function () {
 ( function () {
 
 
-	class BufferGeometryUtils {
+	function computeTangents( geometry ) {
 
 
-		static computeTangents( geometry ) {
+		geometry.computeTangents();
+		console.warn( 'THREE.BufferGeometryUtils: .computeTangents() has been removed. Use THREE.BufferGeometry.computeTangents() instead.' );
 
 
-			geometry.computeTangents();
-			console.warn( 'THREE.BufferGeometryUtils: .computeTangents() has been removed. Use THREE.BufferGeometry.computeTangents() instead.' );
-
-		}
-		/**
-   * @param  {Array<BufferGeometry>} geometries
-   * @param  {Boolean} useGroups
-   * @return {BufferGeometry}
-   */
-
-
-		static mergeBufferGeometries( geometries, useGroups = false ) {
+	}
+	/**
+	 * @param  {Array<BufferGeometry>} geometries
+	 * @param  {Boolean} useGroups
+	 * @return {BufferGeometry}
+	 */
 
 
-			const isIndexed = geometries[ 0 ].index !== null;
-			const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
-			const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
-			const attributes = {};
-			const morphAttributes = {};
-			const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;
-			const mergedGeometry = new THREE.BufferGeometry();
-			let offset = 0;
 
 
-			for ( let i = 0; i < geometries.length; ++ i ) {
+	function mergeBufferGeometries( geometries, useGroups = false ) {
 
 
-				const geometry = geometries[ i ];
-				let attributesCount = 0; // ensure that all geometries are indexed, or none
+		const isIndexed = geometries[ 0 ].index !== null;
+		const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
+		const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
+		const attributes = {};
+		const morphAttributes = {};
+		const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;
+		const mergedGeometry = new THREE.BufferGeometry();
+		let offset = 0;
 
 
-				if ( isIndexed !== ( geometry.index !== null ) ) {
+		for ( let i = 0; i < geometries.length; ++ i ) {
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' );
-					return null;
+			const geometry = geometries[ i ];
+			let attributesCount = 0; // ensure that all geometries are indexed, or none
 
 
-				} // gather attributes, exit early if they're different
+			if ( isIndexed !== ( geometry.index !== null ) ) {
 
 
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' );
+				return null;
 
 
-				for ( const name in geometry.attributes ) {
+			} // gather attributes, exit early if they're different
 
 
-					if ( ! attributesUsed.has( name ) ) {
 
 
-						console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' );
-						return null;
+			for ( const name in geometry.attributes ) {
 
 
-					}
+				if ( ! attributesUsed.has( name ) ) {
 
 
-					if ( attributes[ name ] === undefined ) attributes[ name ] = [];
-					attributes[ name ].push( geometry.attributes[ name ] );
-					attributesCount ++;
+					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' );
+					return null;
 
 
-				} // ensure geometries have the same number of attributes
+				}
 
 
+				if ( attributes[ name ] === undefined ) attributes[ name ] = [];
+				attributes[ name ].push( geometry.attributes[ name ] );
+				attributesCount ++;
 
 
-				if ( attributesCount !== attributesUsed.size ) {
+			} // ensure geometries have the same number of attributes
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );
-					return null;
 
 
-				} // gather morph attributes, exit early if they're different
+			if ( attributesCount !== attributesUsed.size ) {
 
 
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );
+				return null;
 
 
-				if ( morphTargetsRelative !== geometry.morphTargetsRelative ) {
+			} // gather morph attributes, exit early if they're different
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );
-					return null;
 
 
-				}
+			if ( morphTargetsRelative !== geometry.morphTargetsRelative ) {
 
 
-				for ( const name in geometry.morphAttributes ) {
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );
+				return null;
 
 
-					if ( ! morphAttributesUsed.has( name ) ) {
+			}
 
 
-						console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '.  .morphAttributes must be consistent throughout all geometries.' );
-						return null;
+			for ( const name in geometry.morphAttributes ) {
 
 
-					}
+				if ( ! morphAttributesUsed.has( name ) ) {
 
 
-					if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
-					morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
+					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '.  .morphAttributes must be consistent throughout all geometries.' );
+					return null;
 
 
-				} // gather .userData
+				}
 
 
+				if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
+				morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
 
 
-				mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || [];
-				mergedGeometry.userData.mergedUserData.push( geometry.userData );
+			} // gather .userData
 
 
-				if ( useGroups ) {
 
 
-					let count;
+			mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || [];
+			mergedGeometry.userData.mergedUserData.push( geometry.userData );
 
 
-					if ( isIndexed ) {
+			if ( useGroups ) {
 
 
-						count = geometry.index.count;
+				let count;
 
 
-					} else if ( geometry.attributes.position !== undefined ) {
+				if ( isIndexed ) {
 
 
-						count = geometry.attributes.position.count;
+					count = geometry.index.count;
 
 
-					} else {
+				} else if ( geometry.attributes.position !== undefined ) {
 
 
-						console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );
-						return null;
+					count = geometry.attributes.position.count;
 
 
-					}
+				} else {
 
 
-					mergedGeometry.addGroup( offset, count, i );
-					offset += count;
+					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );
+					return null;
 
 
 				}
 				}
 
 
-			} // merge indices
+				mergedGeometry.addGroup( offset, count, i );
+				offset += count;
 
 
+			}
 
 
-			if ( isIndexed ) {
+		} // merge indices
 
 
-				let indexOffset = 0;
-				const mergedIndex = [];
 
 
-				for ( let i = 0; i < geometries.length; ++ i ) {
+		if ( isIndexed ) {
 
 
-					const index = geometries[ i ].index;
+			let indexOffset = 0;
+			const mergedIndex = [];
 
 
-					for ( let j = 0; j < index.count; ++ j ) {
+			for ( let i = 0; i < geometries.length; ++ i ) {
 
 
-						mergedIndex.push( index.getX( j ) + indexOffset );
+				const index = geometries[ i ].index;
 
 
-					}
+				for ( let j = 0; j < index.count; ++ j ) {
 
 
-					indexOffset += geometries[ i ].attributes.position.count;
+					mergedIndex.push( index.getX( j ) + indexOffset );
 
 
 				}
 				}
 
 
-				mergedGeometry.setIndex( mergedIndex );
+				indexOffset += geometries[ i ].attributes.position.count;
 
 
-			} // merge attributes
+			}
 
 
+			mergedGeometry.setIndex( mergedIndex );
 
 
-			for ( const name in attributes ) {
+		} // merge attributes
 
 
-				const mergedAttribute = this.mergeBufferAttributes( attributes[ name ] );
 
 
-				if ( ! mergedAttribute ) {
+		for ( const name in attributes ) {
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed while trying to merge the ' + name + ' attribute.' );
-					return null;
+			const mergedAttribute = mergeBufferAttributes( attributes[ name ] );
 
 
-				}
+			if ( ! mergedAttribute ) {
 
 
-				mergedGeometry.setAttribute( name, mergedAttribute );
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed while trying to merge the ' + name + ' attribute.' );
+				return null;
 
 
-			} // merge morph attributes
+			}
 
 
+			mergedGeometry.setAttribute( name, mergedAttribute );
 
 
-			for ( const name in morphAttributes ) {
+		} // merge morph attributes
 
 
-				const numMorphTargets = morphAttributes[ name ][ 0 ].length;
-				if ( numMorphTargets === 0 ) break;
-				mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
-				mergedGeometry.morphAttributes[ name ] = [];
 
 
-				for ( let i = 0; i < numMorphTargets; ++ i ) {
+		for ( const name in morphAttributes ) {
 
 
-					const morphAttributesToMerge = [];
+			const numMorphTargets = morphAttributes[ name ][ 0 ].length;
+			if ( numMorphTargets === 0 ) break;
+			mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
+			mergedGeometry.morphAttributes[ name ] = [];
 
 
-					for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) {
+			for ( let i = 0; i < numMorphTargets; ++ i ) {
 
 
-						morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
+				const morphAttributesToMerge = [];
 
 
-					}
+				for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) {
 
 
-					const mergedMorphAttribute = this.mergeBufferAttributes( morphAttributesToMerge );
+					morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
 
 
-					if ( ! mergedMorphAttribute ) {
+				}
 
 
-						console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );
-						return null;
+				const mergedMorphAttribute = mergeBufferAttributes( morphAttributesToMerge );
 
 
-					}
+				if ( ! mergedMorphAttribute ) {
 
 
-					mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
+					console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );
+					return null;
 
 
 				}
 				}
 
 
-			}
+				mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
 
 
-			return mergedGeometry;
+			}
 
 
 		}
 		}
-		/**
-   * @param {Array<BufferAttribute>} attributes
-   * @return {BufferAttribute}
-   */
 
 
+		return mergedGeometry;
 
 
-		static mergeBufferAttributes( attributes ) {
+	}
+	/**
+ * @param {Array<BufferAttribute>} attributes
+ * @return {BufferAttribute}
+ */
 
 
-			let TypedArray;
-			let itemSize;
-			let normalized;
-			let arrayLength = 0;
 
 
-			for ( let i = 0; i < attributes.length; ++ i ) {
+	function mergeBufferAttributes( attributes ) {
 
 
-				const attribute = attributes[ i ];
+		let TypedArray;
+		let itemSize;
+		let normalized;
+		let arrayLength = 0;
 
 
-				if ( attribute.isInterleavedBufferAttribute ) {
+		for ( let i = 0; i < attributes.length; ++ i ) {
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. InterleavedBufferAttributes are not supported.' );
-					return null;
+			const attribute = attributes[ i ];
 
 
-				}
+			if ( attribute.isInterleavedBufferAttribute ) {
 
 
-				if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. InterleavedBufferAttributes are not supported.' );
+				return null;
 
 
-				if ( TypedArray !== attribute.array.constructor ) {
+			}
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. THREE.BufferAttribute.array must be of consistent array types across matching attributes.' );
-					return null;
+			if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
 
 
-				}
+			if ( TypedArray !== attribute.array.constructor ) {
 
 
-				if ( itemSize === undefined ) itemSize = attribute.itemSize;
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. THREE.BufferAttribute.array must be of consistent array types across matching attributes.' );
+				return null;
 
 
-				if ( itemSize !== attribute.itemSize ) {
+			}
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. THREE.BufferAttribute.itemSize must be consistent across matching attributes.' );
-					return null;
+			if ( itemSize === undefined ) itemSize = attribute.itemSize;
 
 
-				}
+			if ( itemSize !== attribute.itemSize ) {
 
 
-				if ( normalized === undefined ) normalized = attribute.normalized;
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. THREE.BufferAttribute.itemSize must be consistent across matching attributes.' );
+				return null;
 
 
-				if ( normalized !== attribute.normalized ) {
+			}
 
 
-					console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. THREE.BufferAttribute.normalized must be consistent across matching attributes.' );
-					return null;
+			if ( normalized === undefined ) normalized = attribute.normalized;
 
 
-				}
+			if ( normalized !== attribute.normalized ) {
 
 
-				arrayLength += attribute.array.length;
+				console.error( 'THREE.BufferGeometryUtils: .mergeBufferAttributes() failed. THREE.BufferAttribute.normalized must be consistent across matching attributes.' );
+				return null;
 
 
 			}
 			}
 
 
-			const array = new TypedArray( arrayLength );
-			let offset = 0;
+			arrayLength += attribute.array.length;
 
 
-			for ( let i = 0; i < attributes.length; ++ i ) {
+		}
 
 
-				array.set( attributes[ i ].array, offset );
-				offset += attributes[ i ].array.length;
+		const array = new TypedArray( arrayLength );
+		let offset = 0;
 
 
-			}
+		for ( let i = 0; i < attributes.length; ++ i ) {
 
 
-			return new THREE.BufferAttribute( array, itemSize, normalized );
+			array.set( attributes[ i ].array, offset );
+			offset += attributes[ i ].array.length;
 
 
 		}
 		}
-		/**
-   * @param {Array<BufferAttribute>} attributes
-   * @return {Array<InterleavedBufferAttribute>}
-   */
 
 
+		return new THREE.BufferAttribute( array, itemSize, normalized );
 
 
-		static interleaveAttributes( attributes ) {
+	}
+	/**
+ * @param {Array<BufferAttribute>} attributes
+ * @return {Array<InterleavedBufferAttribute>}
+ */
 
 
-			// Interleaves the provided attributes into an THREE.InterleavedBuffer and returns
-			// a set of InterleavedBufferAttributes for each attribute
-			let TypedArray;
-			let arrayLength = 0;
-			let stride = 0; // calculate the the length and type of the interleavedBuffer
 
 
-			for ( let i = 0, l = attributes.length; i < l; ++ i ) {
+	function interleaveAttributes( attributes ) {
 
 
-				const attribute = attributes[ i ];
-				if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
+		// Interleaves the provided attributes into an THREE.InterleavedBuffer and returns
+		// a set of InterleavedBufferAttributes for each attribute
+		let TypedArray;
+		let arrayLength = 0;
+		let stride = 0; // calculate the the length and type of the interleavedBuffer
 
 
-				if ( TypedArray !== attribute.array.constructor ) {
+		for ( let i = 0, l = attributes.length; i < l; ++ i ) {
 
 
-					console.error( 'AttributeBuffers of different types cannot be interleaved' );
-					return null;
+			const attribute = attributes[ i ];
+			if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
 
 
-				}
+			if ( TypedArray !== attribute.array.constructor ) {
 
 
-				arrayLength += attribute.array.length;
-				stride += attribute.itemSize;
+				console.error( 'AttributeBuffers of different types cannot be interleaved' );
+				return null;
 
 
-			} // Create the set of buffer attributes
+			}
 
 
+			arrayLength += attribute.array.length;
+			stride += attribute.itemSize;
 
 
-			const interleavedBuffer = new THREE.InterleavedBuffer( new TypedArray( arrayLength ), stride );
-			let offset = 0;
-			const res = [];
-			const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
-			const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
+		} // Create the set of buffer attributes
 
 
-			for ( let j = 0, l = attributes.length; j < l; j ++ ) {
 
 
-				const attribute = attributes[ j ];
-				const itemSize = attribute.itemSize;
-				const count = attribute.count;
-				const iba = new THREE.InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
-				res.push( iba );
-				offset += itemSize; // Move the data for each attribute into the new interleavedBuffer
-				// at the appropriate offset
+		const interleavedBuffer = new THREE.InterleavedBuffer( new TypedArray( arrayLength ), stride );
+		let offset = 0;
+		const res = [];
+		const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
+		const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
 
 
-				for ( let c = 0; c < count; c ++ ) {
+		for ( let j = 0, l = attributes.length; j < l; j ++ ) {
 
 
-					for ( let k = 0; k < itemSize; k ++ ) {
+			const attribute = attributes[ j ];
+			const itemSize = attribute.itemSize;
+			const count = attribute.count;
+			const iba = new THREE.InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
+			res.push( iba );
+			offset += itemSize; // Move the data for each attribute into the new interleavedBuffer
+			// at the appropriate offset
 
 
-						iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );
+			for ( let c = 0; c < count; c ++ ) {
 
 
-					}
+				for ( let k = 0; k < itemSize; k ++ ) {
+
+					iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );
 
 
 				}
 				}
 
 
 			}
 			}
 
 
-			return res;
-
 		}
 		}
-		/**
-   * @param {Array<BufferGeometry>} geometry
-   * @return {number}
-   */
 
 
+		return res;
 
 
-		static estimateBytesUsed( geometry ) {
+	}
+	/**
+ * @param {Array<BufferGeometry>} geometry
+ * @return {number}
+ */
 
 
-			// Return the estimated memory used by this geometry in bytes
-			// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
-			// for InterleavedBufferAttributes.
-			let mem = 0;
 
 
-			for ( const name in geometry.attributes ) {
+	function estimateBytesUsed( geometry ) {
 
 
-				const attr = geometry.getAttribute( name );
-				mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;
+		// Return the estimated memory used by this geometry in bytes
+		// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
+		// for InterleavedBufferAttributes.
+		let mem = 0;
 
 
-			}
+		for ( const name in geometry.attributes ) {
 
 
-			const indices = geometry.getIndex();
-			mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
-			return mem;
+			const attr = geometry.getAttribute( name );
+			mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;
 
 
 		}
 		}
-		/**
-   * @param {BufferGeometry} geometry
-   * @param {number} tolerance
-   * @return {BufferGeometry>}
-   */
 
 
+		const indices = geometry.getIndex();
+		mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
+		return mem;
 
 
-		static mergeVertices( geometry, tolerance = 1e-4 ) {
+	}
+	/**
+ * @param {BufferGeometry} geometry
+ * @param {number} tolerance
+ * @return {BufferGeometry>}
+ */
 
 
-			tolerance = Math.max( tolerance, Number.EPSILON ); // Generate an index buffer if the geometry doesn't have one, or optimize it
-			// if it's already available.
 
 
-			const hashToIndex = {};
-			const indices = geometry.getIndex();
-			const positions = geometry.getAttribute( 'position' );
-			const vertexCount = indices ? indices.count : positions.count; // next value for triangle indices
+	function mergeVertices( geometry, tolerance = 1e-4 ) {
 
 
-			let nextIndex = 0; // attributes and new attribute arrays
+		tolerance = Math.max( tolerance, Number.EPSILON ); // Generate an index buffer if the geometry doesn't have one, or optimize it
+		// if it's already available.
 
 
-			const attributeNames = Object.keys( geometry.attributes );
-			const attrArrays = {};
-			const morphAttrsArrays = {};
-			const newIndices = [];
-			const getters = [ 'getX', 'getY', 'getZ', 'getW' ]; // initialize the arrays
+		const hashToIndex = {};
+		const indices = geometry.getIndex();
+		const positions = geometry.getAttribute( 'position' );
+		const vertexCount = indices ? indices.count : positions.count; // next value for triangle indices
 
 
-			for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
+		let nextIndex = 0; // attributes and new attribute arrays
 
 
-				const name = attributeNames[ i ];
-				attrArrays[ name ] = [];
-				const morphAttr = geometry.morphAttributes[ name ];
+		const attributeNames = Object.keys( geometry.attributes );
+		const attrArrays = {};
+		const morphAttrsArrays = {};
+		const newIndices = [];
+		const getters = [ 'getX', 'getY', 'getZ', 'getW' ]; // initialize the arrays
 
 
-				if ( morphAttr ) {
+		for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
 
 
-					morphAttrsArrays[ name ] = new Array( morphAttr.length ).fill().map( () => [] );
+			const name = attributeNames[ i ];
+			attrArrays[ name ] = [];
+			const morphAttr = geometry.morphAttributes[ name ];
 
 
-				}
+			if ( morphAttr ) {
 
 
-			} // convert the error tolerance to an amount of decimal places to truncate to
+				morphAttrsArrays[ name ] = new Array( morphAttr.length ).fill().map( () => [] );
 
 
+			}
 
 
-			const decimalShift = Math.log10( 1 / tolerance );
-			const shiftMultiplier = Math.pow( 10, decimalShift );
+		} // convert the error tolerance to an amount of decimal places to truncate to
 
 
-			for ( let i = 0; i < vertexCount; i ++ ) {
 
 
-				const index = indices ? indices.getX( i ) : i; // Generate a hash for the vertex attributes at the current index 'i'
+		const decimalShift = Math.log10( 1 / tolerance );
+		const shiftMultiplier = Math.pow( 10, decimalShift );
 
 
-				let hash = '';
+		for ( let i = 0; i < vertexCount; i ++ ) {
 
 
-				for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
+			const index = indices ? indices.getX( i ) : i; // Generate a hash for the vertex attributes at the current index 'i'
 
 
-					const name = attributeNames[ j ];
-					const attribute = geometry.getAttribute( name );
-					const itemSize = attribute.itemSize;
+			let hash = '';
 
 
-					for ( let k = 0; k < itemSize; k ++ ) {
+			for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
 
 
-						// double tilde truncates the decimal value
-						hash += `${~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier )},`;
+				const name = attributeNames[ j ];
+				const attribute = geometry.getAttribute( name );
+				const itemSize = attribute.itemSize;
 
 
-					}
+				for ( let k = 0; k < itemSize; k ++ ) {
 
 
-				} // Add another reference to the vertex if it's already
-				// used by another index
+					// double tilde truncates the decimal value
+					hash += `${~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier )},`;
 
 
+				}
 
 
-				if ( hash in hashToIndex ) {
+			} // Add another reference to the vertex if it's already
+			// used by another index
 
 
-					newIndices.push( hashToIndex[ hash ] );
 
 
-				} else {
+			if ( hash in hashToIndex ) {
 
 
-					// copy data to the new index in the attribute arrays
-					for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
+				newIndices.push( hashToIndex[ hash ] );
 
 
-						const name = attributeNames[ j ];
-						const attribute = geometry.getAttribute( name );
-						const morphAttr = geometry.morphAttributes[ name ];
-						const itemSize = attribute.itemSize;
-						const newarray = attrArrays[ name ];
-						const newMorphArrays = morphAttrsArrays[ name ];
+			} else {
 
 
-						for ( let k = 0; k < itemSize; k ++ ) {
+				// copy data to the new index in the attribute arrays
+				for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
 
 
-							const getterFunc = getters[ k ];
-							newarray.push( attribute[ getterFunc ]( index ) );
+					const name = attributeNames[ j ];
+					const attribute = geometry.getAttribute( name );
+					const morphAttr = geometry.morphAttributes[ name ];
+					const itemSize = attribute.itemSize;
+					const newarray = attrArrays[ name ];
+					const newMorphArrays = morphAttrsArrays[ name ];
 
 
-							if ( morphAttr ) {
+					for ( let k = 0; k < itemSize; k ++ ) {
 
 
-								for ( let m = 0, ml = morphAttr.length; m < ml; m ++ ) {
+						const getterFunc = getters[ k ];
+						newarray.push( attribute[ getterFunc ]( index ) );
 
 
-									newMorphArrays[ m ].push( morphAttr[ m ][ getterFunc ]( index ) );
+						if ( morphAttr ) {
 
 
-								}
+							for ( let m = 0, ml = morphAttr.length; m < ml; m ++ ) {
+
+								newMorphArrays[ m ].push( morphAttr[ m ][ getterFunc ]( index ) );
 
 
 							}
 							}
 
 
@@ -447,384 +443,392 @@
 
 
 					}
 					}
 
 
-					hashToIndex[ hash ] = nextIndex;
-					newIndices.push( nextIndex );
-					nextIndex ++;
-
 				}
 				}
 
 
-			} // Generate typed arrays from new attribute arrays and update
-			// the attributeBuffers
+				hashToIndex[ hash ] = nextIndex;
+				newIndices.push( nextIndex );
+				nextIndex ++;
 
 
+			}
 
 
-			const result = geometry.clone();
+		} // Generate typed arrays from new attribute arrays and update
+		// the attributeBuffers
 
 
-			for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
 
 
-				const name = attributeNames[ i ];
-				const oldAttribute = geometry.getAttribute( name );
-				const buffer = new oldAttribute.array.constructor( attrArrays[ name ] );
-				const attribute = new THREE.BufferAttribute( buffer, oldAttribute.itemSize, oldAttribute.normalized );
-				result.setAttribute( name, attribute ); // Update the attribute arrays
+		const result = geometry.clone();
 
 
-				if ( name in morphAttrsArrays ) {
+		for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
 
 
-					for ( let j = 0; j < morphAttrsArrays[ name ].length; j ++ ) {
+			const name = attributeNames[ i ];
+			const oldAttribute = geometry.getAttribute( name );
+			const buffer = new oldAttribute.array.constructor( attrArrays[ name ] );
+			const attribute = new THREE.BufferAttribute( buffer, oldAttribute.itemSize, oldAttribute.normalized );
+			result.setAttribute( name, attribute ); // Update the attribute arrays
 
 
-						const oldMorphAttribute = geometry.morphAttributes[ name ][ j ];
-						const buffer = new oldMorphAttribute.array.constructor( morphAttrsArrays[ name ][ j ] );
-						const morphAttribute = new THREE.BufferAttribute( buffer, oldMorphAttribute.itemSize, oldMorphAttribute.normalized );
-						result.morphAttributes[ name ][ j ] = morphAttribute;
+			if ( name in morphAttrsArrays ) {
 
 
-					}
+				for ( let j = 0; j < morphAttrsArrays[ name ].length; j ++ ) {
+
+					const oldMorphAttribute = geometry.morphAttributes[ name ][ j ];
+					const buffer = new oldMorphAttribute.array.constructor( morphAttrsArrays[ name ][ j ] );
+					const morphAttribute = new THREE.BufferAttribute( buffer, oldMorphAttribute.itemSize, oldMorphAttribute.normalized );
+					result.morphAttributes[ name ][ j ] = morphAttribute;
 
 
 				}
 				}
 
 
-			} // indices
+			}
 
 
+		} // indices
 
 
-			result.setIndex( newIndices );
-			return result;
 
 
-		}
-		/**
-   * @param {BufferGeometry} geometry
-   * @param {number} drawMode
-   * @return {BufferGeometry>}
-   */
+		result.setIndex( newIndices );
+		return result;
 
 
+	}
+	/**
+ * @param {BufferGeometry} geometry
+ * @param {number} drawMode
+ * @return {BufferGeometry>}
+ */
 
 
-		static toTrianglesDrawMode( geometry, drawMode ) {
 
 
-			if ( drawMode === THREE.TrianglesDrawMode ) {
+	function toTrianglesDrawMode( geometry, drawMode ) {
 
 
-				console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
-				return geometry;
+		if ( drawMode === THREE.TrianglesDrawMode ) {
 
 
-			}
+			console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
+			return geometry;
 
 
-			if ( drawMode === THREE.TriangleFanDrawMode || drawMode === THREE.TriangleStripDrawMode ) {
+		}
 
 
-				let index = geometry.getIndex(); // generate index if not present
+		if ( drawMode === THREE.TriangleFanDrawMode || drawMode === THREE.TriangleStripDrawMode ) {
 
 
-				if ( index === null ) {
+			let index = geometry.getIndex(); // generate index if not present
 
 
-					const indices = [];
-					const position = geometry.getAttribute( 'position' );
+			if ( index === null ) {
 
 
-					if ( position !== undefined ) {
+				const indices = [];
+				const position = geometry.getAttribute( 'position' );
 
 
-						for ( let i = 0; i < position.count; i ++ ) {
+				if ( position !== undefined ) {
 
 
-							indices.push( i );
+					for ( let i = 0; i < position.count; i ++ ) {
 
 
-						}
+						indices.push( i );
 
 
-						geometry.setIndex( indices );
-						index = geometry.getIndex();
+					}
 
 
-					} else {
+					geometry.setIndex( indices );
+					index = geometry.getIndex();
 
 
-						console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
-						return geometry;
+				} else {
 
 
-					}
+					console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
+					return geometry;
 
 
-				} //
+				}
 
 
+			} //
 
 
-				const numberOfTriangles = index.count - 2;
-				const newIndices = [];
 
 
-				if ( drawMode === THREE.TriangleFanDrawMode ) {
+			const numberOfTriangles = index.count - 2;
+			const newIndices = [];
 
 
-					// gl.TRIANGLE_FAN
-					for ( let i = 1; i <= numberOfTriangles; i ++ ) {
+			if ( drawMode === THREE.TriangleFanDrawMode ) {
 
 
-						newIndices.push( index.getX( 0 ) );
-						newIndices.push( index.getX( i ) );
-						newIndices.push( index.getX( i + 1 ) );
+				// gl.TRIANGLE_FAN
+				for ( let i = 1; i <= numberOfTriangles; i ++ ) {
 
 
-					}
+					newIndices.push( index.getX( 0 ) );
+					newIndices.push( index.getX( i ) );
+					newIndices.push( index.getX( i + 1 ) );
 
 
-				} else {
+				}
 
 
-					// gl.TRIANGLE_STRIP
-					for ( let i = 0; i < numberOfTriangles; i ++ ) {
+			} else {
 
 
-						if ( i % 2 === 0 ) {
+				// gl.TRIANGLE_STRIP
+				for ( let i = 0; i < numberOfTriangles; i ++ ) {
 
 
-							newIndices.push( index.getX( i ) );
-							newIndices.push( index.getX( i + 1 ) );
-							newIndices.push( index.getX( i + 2 ) );
+					if ( i % 2 === 0 ) {
 
 
-						} else {
+						newIndices.push( index.getX( i ) );
+						newIndices.push( index.getX( i + 1 ) );
+						newIndices.push( index.getX( i + 2 ) );
 
 
-							newIndices.push( index.getX( i + 2 ) );
-							newIndices.push( index.getX( i + 1 ) );
-							newIndices.push( index.getX( i ) );
+					} else {
 
 
-						}
+						newIndices.push( index.getX( i + 2 ) );
+						newIndices.push( index.getX( i + 1 ) );
+						newIndices.push( index.getX( i ) );
 
 
 					}
 					}
 
 
 				}
 				}
 
 
-				if ( newIndices.length / 3 !== numberOfTriangles ) {
+			}
 
 
-					console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
+			if ( newIndices.length / 3 !== numberOfTriangles ) {
 
 
-				} // build final geometry
+				console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
 
 
+			} // build final geometry
 
 
-				const newGeometry = geometry.clone();
-				newGeometry.setIndex( newIndices );
-				newGeometry.clearGroups();
-				return newGeometry;
 
 
-			} else {
+			const newGeometry = geometry.clone();
+			newGeometry.setIndex( newIndices );
+			newGeometry.clearGroups();
+			return newGeometry;
 
 
-				console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:', drawMode );
-				return geometry;
+		} else {
 
 
-			}
+			console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:', drawMode );
+			return geometry;
 
 
 		}
 		}
-		/**
-   * Calculates the morphed attributes of a morphed/skinned THREE.BufferGeometry.
-   * Helpful for Raytracing or Decals.
-   * @param {Mesh | Line | Points} object An instance of Mesh, Line or Points.
-   * @return {Object} An Object with original position/normal attributes and morphed ones.
-   */
 
 
+	}
+	/**
+ * Calculates the morphed attributes of a morphed/skinned THREE.BufferGeometry.
+ * Helpful for Raytracing or Decals.
+ * @param {Mesh | Line | Points} object An instance of Mesh, Line or Points.
+ * @return {Object} An Object with original position/normal attributes and morphed ones.
+ */
 
 
-		static computeMorphedAttributes( object ) {
 
 
-			if ( object.geometry.isBufferGeometry !== true ) {
+	function computeMorphedAttributes( object ) {
 
 
-				console.error( 'THREE.BufferGeometryUtils: Geometry is not of type THREE.BufferGeometry.' );
-				return null;
+		if ( object.geometry.isBufferGeometry !== true ) {
 
 
-			}
+			console.error( 'THREE.BufferGeometryUtils: Geometry is not of type THREE.BufferGeometry.' );
+			return null;
 
 
-			const _vA = new THREE.Vector3();
+		}
 
 
-			const _vB = new THREE.Vector3();
+		const _vA = new THREE.Vector3();
 
 
-			const _vC = new THREE.Vector3();
+		const _vB = new THREE.Vector3();
 
 
-			const _tempA = new THREE.Vector3();
+		const _vC = new THREE.Vector3();
 
 
-			const _tempB = new THREE.Vector3();
+		const _tempA = new THREE.Vector3();
 
 
-			const _tempC = new THREE.Vector3();
+		const _tempB = new THREE.Vector3();
 
 
-			const _morphA = new THREE.Vector3();
+		const _tempC = new THREE.Vector3();
 
 
-			const _morphB = new THREE.Vector3();
+		const _morphA = new THREE.Vector3();
 
 
-			const _morphC = new THREE.Vector3();
+		const _morphB = new THREE.Vector3();
 
 
-			function _calculateMorphedAttributeData( object, material, attribute, morphAttribute, morphTargetsRelative, a, b, c, modifiedAttributeArray ) {
+		const _morphC = new THREE.Vector3();
 
 
-				_vA.fromBufferAttribute( attribute, a );
+		function _calculateMorphedAttributeData( object, material, attribute, morphAttribute, morphTargetsRelative, a, b, c, modifiedAttributeArray ) {
 
 
-				_vB.fromBufferAttribute( attribute, b );
+			_vA.fromBufferAttribute( attribute, a );
 
 
-				_vC.fromBufferAttribute( attribute, c );
+			_vB.fromBufferAttribute( attribute, b );
 
 
-				const morphInfluences = object.morphTargetInfluences;
+			_vC.fromBufferAttribute( attribute, c );
 
 
-				if ( material.morphTargets && morphAttribute && morphInfluences ) {
+			const morphInfluences = object.morphTargetInfluences;
 
 
-					_morphA.set( 0, 0, 0 );
+			if ( material.morphTargets && morphAttribute && morphInfluences ) {
 
 
-					_morphB.set( 0, 0, 0 );
+				_morphA.set( 0, 0, 0 );
 
 
-					_morphC.set( 0, 0, 0 );
+				_morphB.set( 0, 0, 0 );
 
 
-					for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
+				_morphC.set( 0, 0, 0 );
 
 
-						const influence = morphInfluences[ i ];
-						const morph = morphAttribute[ i ];
-						if ( influence === 0 ) continue;
+				for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
 
 
-						_tempA.fromBufferAttribute( morph, a );
+					const influence = morphInfluences[ i ];
+					const morph = morphAttribute[ i ];
+					if ( influence === 0 ) continue;
 
 
-						_tempB.fromBufferAttribute( morph, b );
+					_tempA.fromBufferAttribute( morph, a );
 
 
-						_tempC.fromBufferAttribute( morph, c );
+					_tempB.fromBufferAttribute( morph, b );
 
 
-						if ( morphTargetsRelative ) {
+					_tempC.fromBufferAttribute( morph, c );
 
 
-							_morphA.addScaledVector( _tempA, influence );
+					if ( morphTargetsRelative ) {
 
 
-							_morphB.addScaledVector( _tempB, influence );
+						_morphA.addScaledVector( _tempA, influence );
 
 
-							_morphC.addScaledVector( _tempC, influence );
+						_morphB.addScaledVector( _tempB, influence );
 
 
-						} else {
+						_morphC.addScaledVector( _tempC, influence );
 
 
-							_morphA.addScaledVector( _tempA.sub( _vA ), influence );
+					} else {
 
 
-							_morphB.addScaledVector( _tempB.sub( _vB ), influence );
+						_morphA.addScaledVector( _tempA.sub( _vA ), influence );
 
 
-							_morphC.addScaledVector( _tempC.sub( _vC ), influence );
+						_morphB.addScaledVector( _tempB.sub( _vB ), influence );
 
 
-						}
+						_morphC.addScaledVector( _tempC.sub( _vC ), influence );
 
 
 					}
 					}
 
 
-					_vA.add( _morphA );
-
-					_vB.add( _morphB );
+				}
 
 
-					_vC.add( _morphC );
+				_vA.add( _morphA );
 
 
-				}
+				_vB.add( _morphB );
 
 
-				if ( object.isSkinnedMesh ) {
+				_vC.add( _morphC );
 
 
-					object.boneTransform( a, _vA );
-					object.boneTransform( b, _vB );
-					object.boneTransform( c, _vC );
+			}
 
 
-				}
+			if ( object.isSkinnedMesh ) {
 
 
-				modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
-				modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
-				modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
-				modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
-				modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
-				modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
-				modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
-				modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
-				modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
+				object.boneTransform( a, _vA );
+				object.boneTransform( b, _vB );
+				object.boneTransform( c, _vC );
 
 
 			}
 			}
 
 
-			const geometry = object.geometry;
-			const material = object.material;
-			let a, b, c;
-			const index = geometry.index;
-			const positionAttribute = geometry.attributes.position;
-			const morphPosition = geometry.morphAttributes.position;
-			const morphTargetsRelative = geometry.morphTargetsRelative;
-			const normalAttribute = geometry.attributes.normal;
-			const morphNormal = geometry.morphAttributes.position;
-			const groups = geometry.groups;
-			const drawRange = geometry.drawRange;
-			let i, j, il, jl;
-			let group, groupMaterial;
-			let start, end;
-			const modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize );
-			const modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize );
+			modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
+			modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
+			modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
+			modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
+			modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
+			modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
+			modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
+			modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
+			modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
 
 
-			if ( index !== null ) {
+		}
 
 
-				// indexed buffer geometry
-				if ( Array.isArray( material ) ) {
+		const geometry = object.geometry;
+		const material = object.material;
+		let a, b, c;
+		const index = geometry.index;
+		const positionAttribute = geometry.attributes.position;
+		const morphPosition = geometry.morphAttributes.position;
+		const morphTargetsRelative = geometry.morphTargetsRelative;
+		const normalAttribute = geometry.attributes.normal;
+		const morphNormal = geometry.morphAttributes.position;
+		const groups = geometry.groups;
+		const drawRange = geometry.drawRange;
+		let i, j, il, jl;
+		let group, groupMaterial;
+		let start, end;
+		const modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize );
+		const modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize );
 
 
-					for ( i = 0, il = groups.length; i < il; i ++ ) {
+		if ( index !== null ) {
 
 
-						group = groups[ i ];
-						groupMaterial = material[ group.materialIndex ];
-						start = Math.max( group.start, drawRange.start );
-						end = Math.min( group.start + group.count, drawRange.start + drawRange.count );
+			// indexed buffer geometry
+			if ( Array.isArray( material ) ) {
 
 
-						for ( j = start, jl = end; j < jl; j += 3 ) {
+				for ( i = 0, il = groups.length; i < il; i ++ ) {
 
 
-							a = index.getX( j );
-							b = index.getX( j + 1 );
-							c = index.getX( j + 2 );
+					group = groups[ i ];
+					groupMaterial = material[ group.materialIndex ];
+					start = Math.max( group.start, drawRange.start );
+					end = Math.min( group.start + group.count, drawRange.start + drawRange.count );
 
 
-							_calculateMorphedAttributeData( object, groupMaterial, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
+					for ( j = start, jl = end; j < jl; j += 3 ) {
 
 
-							_calculateMorphedAttributeData( object, groupMaterial, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
+						a = index.getX( j );
+						b = index.getX( j + 1 );
+						c = index.getX( j + 2 );
 
 
-						}
+						_calculateMorphedAttributeData( object, groupMaterial, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
+
+						_calculateMorphedAttributeData( object, groupMaterial, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
 
 
 					}
 					}
 
 
-				} else {
+				}
 
 
-					start = Math.max( 0, drawRange.start );
-					end = Math.min( index.count, drawRange.start + drawRange.count );
+			} else {
 
 
-					for ( i = start, il = end; i < il; i += 3 ) {
+				start = Math.max( 0, drawRange.start );
+				end = Math.min( index.count, drawRange.start + drawRange.count );
 
 
-						a = index.getX( i );
-						b = index.getX( i + 1 );
-						c = index.getX( i + 2 );
+				for ( i = start, il = end; i < il; i += 3 ) {
 
 
-						_calculateMorphedAttributeData( object, material, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
+					a = index.getX( i );
+					b = index.getX( i + 1 );
+					c = index.getX( i + 2 );
 
 
-						_calculateMorphedAttributeData( object, material, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
+					_calculateMorphedAttributeData( object, material, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
 
 
-					}
+					_calculateMorphedAttributeData( object, material, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
 
 
 				}
 				}
 
 
-			} else if ( positionAttribute !== undefined ) {
+			}
 
 
-				// non-indexed buffer geometry
-				if ( Array.isArray( material ) ) {
+		} else if ( positionAttribute !== undefined ) {
 
 
-					for ( i = 0, il = groups.length; i < il; i ++ ) {
+			// non-indexed buffer geometry
+			if ( Array.isArray( material ) ) {
 
 
-						group = groups[ i ];
-						groupMaterial = material[ group.materialIndex ];
-						start = Math.max( group.start, drawRange.start );
-						end = Math.min( group.start + group.count, drawRange.start + drawRange.count );
+				for ( i = 0, il = groups.length; i < il; i ++ ) {
 
 
-						for ( j = start, jl = end; j < jl; j += 3 ) {
+					group = groups[ i ];
+					groupMaterial = material[ group.materialIndex ];
+					start = Math.max( group.start, drawRange.start );
+					end = Math.min( group.start + group.count, drawRange.start + drawRange.count );
 
 
-							a = j;
-							b = j + 1;
-							c = j + 2;
+					for ( j = start, jl = end; j < jl; j += 3 ) {
 
 
-							_calculateMorphedAttributeData( object, groupMaterial, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
+						a = j;
+						b = j + 1;
+						c = j + 2;
 
 
-							_calculateMorphedAttributeData( object, groupMaterial, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
+						_calculateMorphedAttributeData( object, groupMaterial, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
 
 
-						}
+						_calculateMorphedAttributeData( object, groupMaterial, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
 
 
 					}
 					}
 
 
-				} else {
+				}
 
 
-					start = Math.max( 0, drawRange.start );
-					end = Math.min( positionAttribute.count, drawRange.start + drawRange.count );
+			} else {
 
 
-					for ( i = start, il = end; i < il; i += 3 ) {
+				start = Math.max( 0, drawRange.start );
+				end = Math.min( positionAttribute.count, drawRange.start + drawRange.count );
 
 
-						a = i;
-						b = i + 1;
-						c = i + 2;
+				for ( i = start, il = end; i < il; i += 3 ) {
 
 
-						_calculateMorphedAttributeData( object, material, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
+					a = i;
+					b = i + 1;
+					c = i + 2;
 
 
-						_calculateMorphedAttributeData( object, material, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
+					_calculateMorphedAttributeData( object, material, positionAttribute, morphPosition, morphTargetsRelative, a, b, c, modifiedPosition );
 
 
-					}
+					_calculateMorphedAttributeData( object, material, normalAttribute, morphNormal, morphTargetsRelative, a, b, c, modifiedNormal );
 
 
 				}
 				}
 
 
 			}
 			}
 
 
-			const morphedPositionAttribute = new THREE.Float32BufferAttribute( modifiedPosition, 3 );
-			const morphedNormalAttribute = new THREE.Float32BufferAttribute( modifiedNormal, 3 );
-			return {
-				positionAttribute: positionAttribute,
-				normalAttribute: normalAttribute,
-				morphedPositionAttribute: morphedPositionAttribute,
-				morphedNormalAttribute: morphedNormalAttribute
-			};
-
 		}
 		}
 
 
+		const morphedPositionAttribute = new THREE.Float32BufferAttribute( modifiedPosition, 3 );
+		const morphedNormalAttribute = new THREE.Float32BufferAttribute( modifiedNormal, 3 );
+		return {
+			positionAttribute: positionAttribute,
+			normalAttribute: normalAttribute,
+			morphedPositionAttribute: morphedPositionAttribute,
+			morphedNormalAttribute: morphedNormalAttribute
+		};
+
 	}
 	}
 
 
-	THREE.BufferGeometryUtils = BufferGeometryUtils;
+	THREE.BufferGeometryUtils = {};
+	THREE.BufferGeometryUtils.computeMorphedAttributes = computeMorphedAttributes;
+	THREE.BufferGeometryUtils.computeTangents = computeTangents;
+	THREE.BufferGeometryUtils.estimateBytesUsed = estimateBytesUsed;
+	THREE.BufferGeometryUtils.interleaveAttributes = interleaveAttributes;
+	THREE.BufferGeometryUtils.mergeBufferAttributes = mergeBufferAttributes;
+	THREE.BufferGeometryUtils.mergeBufferGeometries = mergeBufferGeometries;
+	THREE.BufferGeometryUtils.mergeVertices = mergeVertices;
+	THREE.BufferGeometryUtils.toTrianglesDrawMode = toTrianglesDrawMode;
 
 
 } )();
 } )();

+ 37 - 39
examples/js/utils/CameraUtils.js

@@ -16,73 +16,71 @@
 		// temporary vector
 		// temporary vector
 		_quat = /*@__PURE__*/new THREE.Quaternion(); // temporary quaternion
 		_quat = /*@__PURE__*/new THREE.Quaternion(); // temporary quaternion
 
 
+	/** Set a PerspectiveCamera's projectionMatrix and quaternion
+ * to exactly frame the corners of an arbitrary rectangle.
+ * NOTE: This function ignores the standard parameters;
+ * do not call updateProjectionMatrix() after this!
+ * @param {Vector3} bottomLeftCorner
+ * @param {Vector3} bottomRightCorner
+ * @param {Vector3} topLeftCorner
+ * @param {boolean} estimateViewFrustum */
 
 
-	class CameraUtils {
 
 
-		/** Set a PerspectiveCamera's projectionMatrix and quaternion
-   * to exactly frame the corners of an arbitrary rectangle.
-   * NOTE: This function ignores the standard parameters;
-   * do not call updateProjectionMatrix() after this!
-   * @param {Vector3} bottomLeftCorner
-   * @param {Vector3} bottomRightCorner
-   * @param {Vector3} topLeftCorner
-   * @param {boolean} estimateViewFrustum */
-		static frameCorners( camera, bottomLeftCorner, bottomRightCorner, topLeftCorner, estimateViewFrustum = false ) {
+	function frameCorners( camera, bottomLeftCorner, bottomRightCorner, topLeftCorner, estimateViewFrustum = false ) {
 
 
-			const pa = bottomLeftCorner,
-				pb = bottomRightCorner,
-				pc = topLeftCorner;
-			const pe = camera.position; // eye position
+		const pa = bottomLeftCorner,
+			pb = bottomRightCorner,
+			pc = topLeftCorner;
+		const pe = camera.position; // eye position
 
 
-			const n = camera.near; // distance of near clipping plane
+		const n = camera.near; // distance of near clipping plane
 
 
-			const f = camera.far; //distance of far clipping plane
+		const f = camera.far; //distance of far clipping plane
 
 
-			_vr.copy( pb ).sub( pa ).normalize();
+		_vr.copy( pb ).sub( pa ).normalize();
 
 
-			_vu.copy( pc ).sub( pa ).normalize();
+		_vu.copy( pc ).sub( pa ).normalize();
 
 
-			_vn.crossVectors( _vr, _vu ).normalize();
+		_vn.crossVectors( _vr, _vu ).normalize();
 
 
-			_va.copy( pa ).sub( pe ); // from pe to pa
+		_va.copy( pa ).sub( pe ); // from pe to pa
 
 
 
 
-			_vb.copy( pb ).sub( pe ); // from pe to pb
+		_vb.copy( pb ).sub( pe ); // from pe to pb
 
 
 
 
-			_vc.copy( pc ).sub( pe ); // from pe to pc
+		_vc.copy( pc ).sub( pe ); // from pe to pc
 
 
 
 
-			const d = - _va.dot( _vn ); // distance from eye to screen
+		const d = - _va.dot( _vn ); // distance from eye to screen
 
 
-			const l = _vr.dot( _va ) * n / d; // distance to left screen edge
+		const l = _vr.dot( _va ) * n / d; // distance to left screen edge
 
 
-			const r = _vr.dot( _vb ) * n / d; // distance to right screen edge
+		const r = _vr.dot( _vb ) * n / d; // distance to right screen edge
 
 
-			const b = _vu.dot( _va ) * n / d; // distance to bottom screen edge
+		const b = _vu.dot( _va ) * n / d; // distance to bottom screen edge
 
 
-			const t = _vu.dot( _vc ) * n / d; // distance to top screen edge
-			// Set the camera rotation to match the focal plane to the corners' plane
+		const t = _vu.dot( _vc ) * n / d; // distance to top screen edge
+		// Set the camera rotation to match the focal plane to the corners' plane
 
 
-			_quat.setFromUnitVectors( _vec.set( 0, 1, 0 ), _vu );
+		_quat.setFromUnitVectors( _vec.set( 0, 1, 0 ), _vu );
 
 
-			camera.quaternion.setFromUnitVectors( _vec.set( 0, 0, 1 ).applyQuaternion( _quat ), _vn ).multiply( _quat ); // Set the off-axis projection matrix to match the corners
+		camera.quaternion.setFromUnitVectors( _vec.set( 0, 0, 1 ).applyQuaternion( _quat ), _vn ).multiply( _quat ); // Set the off-axis projection matrix to match the corners
 
 
-			camera.projectionMatrix.set( 2.0 * n / ( r - l ), 0.0, ( r + l ) / ( r - l ), 0.0, 0.0, 2.0 * n / ( t - b ), ( t + b ) / ( t - b ), 0.0, 0.0, 0.0, ( f + n ) / ( n - f ), 2.0 * f * n / ( n - f ), 0.0, 0.0, - 1.0, 0.0 );
-			camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); // FoV estimation to fix frustum culling
+		camera.projectionMatrix.set( 2.0 * n / ( r - l ), 0.0, ( r + l ) / ( r - l ), 0.0, 0.0, 2.0 * n / ( t - b ), ( t + b ) / ( t - b ), 0.0, 0.0, 0.0, ( f + n ) / ( n - f ), 2.0 * f * n / ( n - f ), 0.0, 0.0, - 1.0, 0.0 );
+		camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); // FoV estimation to fix frustum culling
 
 
-			if ( estimateViewFrustum ) {
+		if ( estimateViewFrustum ) {
 
 
-				// Set fieldOfView to a conservative estimate
-				// to make frustum tall/wide enough to encompass it
-				camera.fov = THREE.MathUtils.RAD2DEG / Math.min( 1.0, camera.aspect ) * Math.atan( ( _vec.copy( pb ).sub( pa ).length() + _vec.copy( pc ).sub( pa ).length() ) / _va.length() );
-
-			}
+			// Set fieldOfView to a conservative estimate
+			// to make frustum tall/wide enough to encompass it
+			camera.fov = THREE.MathUtils.RAD2DEG / Math.min( 1.0, camera.aspect ) * Math.atan( ( _vec.copy( pb ).sub( pa ).length() + _vec.copy( pc ).sub( pa ).length() ) / _va.length() );
 
 
 		}
 		}
 
 
 	}
 	}
 
 
-	THREE.CameraUtils = CameraUtils;
+	THREE.CameraUtils = {};
+	THREE.CameraUtils.frameCorners = frameCorners;
 
 
 } )();
 } )();

+ 343 - 468
examples/js/utils/GeometryCompressionUtils.js

@@ -5,695 +5,570 @@
  *
  *
  * @link https://github.com/tsherif/mesh-quantization-example
  * @link https://github.com/tsherif/mesh-quantization-example
  *
  *
+ */
+	/**
+ * Make the input mesh.geometry's normal attribute encoded and compressed by 3 different methods.
+ * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the normal data.
+ *
+ * @param {THREE.Mesh} mesh
+ * @param {String} encodeMethod		"DEFAULT" || "OCT1Byte" || "OCT2Byte" || "ANGLES"
+ *
  */
  */
 
 
-	class GeometryCompressionUtils {
-
-		/**
-  	 * Make the input mesh.geometry's normal attribute encoded and compressed by 3 different methods.
-  	 * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the normal data.
-  	 *
-  	 * @param {THREE.Mesh} mesh
-  	 * @param {String} encodeMethod		"DEFAULT" || "OCT1Byte" || "OCT2Byte" || "ANGLES"
-  	 *
-  	 */
-		static compressNormals( mesh, encodeMethod ) {
-
-			if ( ! mesh.geometry ) {
-
-				console.error( 'Mesh must contain geometry. ' );
-
-			}
-
-			const normal = mesh.geometry.attributes.normal;
-
-			if ( ! normal ) {
-
-				console.error( 'Geometry must contain normal attribute. ' );
-
-			}
-
-			if ( normal.isPacked ) return;
-
-			if ( normal.itemSize != 3 ) {
-
-				console.error( 'normal.itemSize is not 3, which cannot be encoded. ' );
+	function compressNormals( mesh, encodeMethod ) {
 
 
-			}
+		if ( ! mesh.geometry ) {
 
 
-			const array = normal.array;
-			const count = normal.count;
-			let result;
+			console.error( 'Mesh must contain geometry. ' );
 
 
-			if ( encodeMethod == 'DEFAULT' ) {
+		}
 
 
-				// TODO: Add 1 byte to the result, making the encoded length to be 4 bytes.
-				result = new Uint8Array( count * 3 );
+		const normal = mesh.geometry.attributes.normal;
 
 
-				for ( let idx = 0; idx < array.length; idx += 3 ) {
+		if ( ! normal ) {
 
 
-					const encoded = EncodingFuncs.defaultEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
-					result[ idx + 0 ] = encoded[ 0 ];
-					result[ idx + 1 ] = encoded[ 1 ];
-					result[ idx + 2 ] = encoded[ 2 ];
+			console.error( 'Geometry must contain normal attribute. ' );
 
 
-				}
+		}
 
 
-				mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 3, true ) );
-				mesh.geometry.attributes.normal.bytes = result.length * 1;
+		if ( normal.isPacked ) return;
 
 
-			} else if ( encodeMethod == 'OCT1Byte' ) {
+		if ( normal.itemSize != 3 ) {
 
 
-				/**
-      * It is not recommended to use 1-byte octahedron normals encoding unless you want to extremely reduce the memory usage
-      * As it makes vertex data not aligned to a 4 byte boundary which may harm some WebGL implementations and sometimes the normal distortion is visible
-      * Please refer to @zeux 's comments in https://github.com/mrdoob/three.js/pull/18208
-      */
-				result = new Int8Array( count * 2 );
+			console.error( 'normal.itemSize is not 3, which cannot be encoded. ' );
 
 
-				for ( let idx = 0; idx < array.length; idx += 3 ) {
+		}
 
 
-					const encoded = EncodingFuncs.octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
-					result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
-					result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
+		const array = normal.array;
+		const count = normal.count;
+		let result;
 
 
-				}
+		if ( encodeMethod == 'DEFAULT' ) {
 
 
-				mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 2, true ) );
-				mesh.geometry.attributes.normal.bytes = result.length * 1;
+			// TODO: Add 1 byte to the result, making the encoded length to be 4 bytes.
+			result = new Uint8Array( count * 3 );
 
 
-			} else if ( encodeMethod == 'OCT2Byte' ) {
+			for ( let idx = 0; idx < array.length; idx += 3 ) {
 
 
-				result = new Int16Array( count * 2 );
+				const encoded = defaultEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
+				result[ idx + 0 ] = encoded[ 0 ];
+				result[ idx + 1 ] = encoded[ 1 ];
+				result[ idx + 2 ] = encoded[ 2 ];
 
 
-				for ( let idx = 0; idx < array.length; idx += 3 ) {
+			}
 
 
-					const encoded = EncodingFuncs.octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 2 );
-					result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
-					result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
+			mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 3, true ) );
+			mesh.geometry.attributes.normal.bytes = result.length * 1;
 
 
-				}
+		} else if ( encodeMethod == 'OCT1Byte' ) {
 
 
-				mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 2, true ) );
-				mesh.geometry.attributes.normal.bytes = result.length * 2;
+			/**
+    * It is not recommended to use 1-byte octahedron normals encoding unless you want to extremely reduce the memory usage
+    * As it makes vertex data not aligned to a 4 byte boundary which may harm some WebGL implementations and sometimes the normal distortion is visible
+    * Please refer to @zeux 's comments in https://github.com/mrdoob/three.js/pull/18208
+    */
+			result = new Int8Array( count * 2 );
 
 
-			} else if ( encodeMethod == 'ANGLES' ) {
+			for ( let idx = 0; idx < array.length; idx += 3 ) {
 
 
-				result = new Uint16Array( count * 2 );
+				const encoded = octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
+				result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
+				result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
 
 
-				for ( let idx = 0; idx < array.length; idx += 3 ) {
+			}
 
 
-					const encoded = EncodingFuncs.anglesEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ] );
-					result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
-					result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
+			mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 2, true ) );
+			mesh.geometry.attributes.normal.bytes = result.length * 1;
 
 
-				}
+		} else if ( encodeMethod == 'OCT2Byte' ) {
 
 
-				mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 2, true ) );
-				mesh.geometry.attributes.normal.bytes = result.length * 2;
+			result = new Int16Array( count * 2 );
 
 
-			} else {
+			for ( let idx = 0; idx < array.length; idx += 3 ) {
 
 
-				console.error( 'Unrecognized encoding method, should be `DEFAULT` or `ANGLES` or `OCT`. ' );
+				const encoded = octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 2 );
+				result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
+				result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
 
 
 			}
 			}
 
 
-			mesh.geometry.attributes.normal.needsUpdate = true;
-			mesh.geometry.attributes.normal.isPacked = true;
-			mesh.geometry.attributes.normal.packingMethod = encodeMethod; // modify material
-
-			if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+			mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 2, true ) );
+			mesh.geometry.attributes.normal.bytes = result.length * 2;
 
 
-				mesh.material = new PackedPhongMaterial().copy( mesh.material );
+		} else if ( encodeMethod == 'ANGLES' ) {
 
 
-			}
+			result = new Uint16Array( count * 2 );
 
 
-			if ( encodeMethod == 'ANGLES' ) {
+			for ( let idx = 0; idx < array.length; idx += 3 ) {
 
 
-				mesh.material.defines.USE_PACKED_NORMAL = 0;
+				const encoded = anglesEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ] );
+				result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
+				result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
 
 
 			}
 			}
 
 
-			if ( encodeMethod == 'OCT1Byte' ) {
+			mesh.geometry.setAttribute( 'normal', new THREE.BufferAttribute( result, 2, true ) );
+			mesh.geometry.attributes.normal.bytes = result.length * 2;
 
 
-				mesh.material.defines.USE_PACKED_NORMAL = 1;
+		} else {
 
 
-			}
+			console.error( 'Unrecognized encoding method, should be `DEFAULT` or `ANGLES` or `OCT`. ' );
 
 
-			if ( encodeMethod == 'OCT2Byte' ) {
+		}
 
 
-				mesh.material.defines.USE_PACKED_NORMAL = 1;
+		mesh.geometry.attributes.normal.needsUpdate = true;
+		mesh.geometry.attributes.normal.isPacked = true;
+		mesh.geometry.attributes.normal.packingMethod = encodeMethod; // modify material
 
 
-			}
+		if ( ! ( mesh.material instanceof THREE.PackedPhongMaterial ) ) {
 
 
-			if ( encodeMethod == 'DEFAULT' ) {
+			mesh.material = new THREE.PackedPhongMaterial().copy( mesh.material );
 
 
-				mesh.material.defines.USE_PACKED_NORMAL = 2;
+		}
 
 
-			}
+		if ( encodeMethod == 'ANGLES' ) {
 
 
-		}
-		/**
-  	 * Make the input mesh.geometry's position attribute encoded and compressed.
-  	 * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the position data.
-  	 *
-  	 * @param {THREE.Mesh} mesh
-  	 *
-  	 */
+			mesh.material.defines.USE_PACKED_NORMAL = 0;
 
 
+		}
 
 
-		static compressPositions( mesh ) {
+		if ( encodeMethod == 'OCT1Byte' ) {
 
 
-			if ( ! mesh.geometry ) {
+			mesh.material.defines.USE_PACKED_NORMAL = 1;
 
 
-				console.error( 'Mesh must contain geometry. ' );
+		}
 
 
-			}
+		if ( encodeMethod == 'OCT2Byte' ) {
 
 
-			const position = mesh.geometry.attributes.position;
+			mesh.material.defines.USE_PACKED_NORMAL = 1;
 
 
-			if ( ! position ) {
+		}
 
 
-				console.error( 'Geometry must contain position attribute. ' );
+		if ( encodeMethod == 'DEFAULT' ) {
 
 
-			}
+			mesh.material.defines.USE_PACKED_NORMAL = 2;
 
 
-			if ( position.isPacked ) return;
+		}
 
 
-			if ( position.itemSize != 3 ) {
+	}
+	/**
+	 * Make the input mesh.geometry's position attribute encoded and compressed.
+	 * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the position data.
+	 *
+	 * @param {THREE.Mesh} mesh
+	 *
+	 */
 
 
-				console.error( 'position.itemSize is not 3, which cannot be packed. ' );
 
 
-			}
+	function compressPositions( mesh ) {
 
 
-			const array = position.array;
-			const encodingBytes = 2;
-			const result = EncodingFuncs.quantizedEncode( array, encodingBytes );
-			const quantized = result.quantized;
-			const decodeMat = result.decodeMat; // IMPORTANT: calculate original geometry bounding info first, before updating packed positions
+		if ( ! mesh.geometry ) {
 
 
-			if ( mesh.geometry.boundingBox == null ) mesh.geometry.computeBoundingBox();
-			if ( mesh.geometry.boundingSphere == null ) mesh.geometry.computeBoundingSphere();
-			mesh.geometry.setAttribute( 'position', new THREE.BufferAttribute( quantized, 3 ) );
-			mesh.geometry.attributes.position.isPacked = true;
-			mesh.geometry.attributes.position.needsUpdate = true;
-			mesh.geometry.attributes.position.bytes = quantized.length * encodingBytes; // modify material
+			console.error( 'Mesh must contain geometry. ' );
 
 
-			if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+		}
 
 
-				mesh.material = new PackedPhongMaterial().copy( mesh.material );
+		const position = mesh.geometry.attributes.position;
 
 
-			}
+		if ( ! position ) {
 
 
-			mesh.material.defines.USE_PACKED_POSITION = 0;
-			mesh.material.uniforms.quantizeMatPos.value = decodeMat;
-			mesh.material.uniforms.quantizeMatPos.needsUpdate = true;
+			console.error( 'Geometry must contain position attribute. ' );
 
 
 		}
 		}
-		/**
-  	 * Make the input mesh.geometry's uv attribute encoded and compressed.
-  	 * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the uv data.
-  	 *
-  	 * @param {THREE.Mesh} mesh
-  	 *
-  	 */
 
 
+		if ( position.isPacked ) return;
 
 
-		static compressUvs( mesh ) {
+		if ( position.itemSize != 3 ) {
 
 
-			if ( ! mesh.geometry ) {
+			console.error( 'position.itemSize is not 3, which cannot be packed. ' );
 
 
-				console.error( 'Mesh must contain geometry property. ' );
-
-			}
+		}
 
 
-			const uvs = mesh.geometry.attributes.uv;
+		const array = position.array;
+		const encodingBytes = 2;
+		const result = quantizedEncode( array, encodingBytes );
+		const quantized = result.quantized;
+		const decodeMat = result.decodeMat; // IMPORTANT: calculate original geometry bounding info first, before updating packed positions
 
 
-			if ( ! uvs ) {
+		if ( mesh.geometry.boundingBox == null ) mesh.geometry.computeBoundingBox();
+		if ( mesh.geometry.boundingSphere == null ) mesh.geometry.computeBoundingSphere();
+		mesh.geometry.setAttribute( 'position', new THREE.BufferAttribute( quantized, 3 ) );
+		mesh.geometry.attributes.position.isPacked = true;
+		mesh.geometry.attributes.position.needsUpdate = true;
+		mesh.geometry.attributes.position.bytes = quantized.length * encodingBytes; // modify material
 
 
-				console.error( 'Geometry must contain uv attribute. ' );
+		if ( ! ( mesh.material instanceof THREE.PackedPhongMaterial ) ) {
 
 
-			}
+			mesh.material = new THREE.PackedPhongMaterial().copy( mesh.material );
 
 
-			if ( uvs.isPacked ) return;
-			const range = {
-				min: Infinity,
-				max: - Infinity
-			};
-			const array = uvs.array;
+		}
 
 
-			for ( let i = 0; i < array.length; i ++ ) {
+		mesh.material.defines.USE_PACKED_POSITION = 0;
+		mesh.material.uniforms.quantizeMatPos.value = decodeMat;
+		mesh.material.uniforms.quantizeMatPos.needsUpdate = true;
 
 
-				range.min = Math.min( range.min, array[ i ] );
-				range.max = Math.max( range.max, array[ i ] );
+	}
+	/**
+ * Make the input mesh.geometry's uv attribute encoded and compressed.
+ * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the uv data.
+ *
+ * @param {THREE.Mesh} mesh
+ *
+ */
 
 
-			}
 
 
-			let result;
+	function compressUvs( mesh ) {
 
 
-			if ( range.min >= - 1.0 && range.max <= 1.0 ) {
+		if ( ! mesh.geometry ) {
 
 
-				// use default encoding method
-				result = new Uint16Array( array.length );
+			console.error( 'Mesh must contain geometry property. ' );
 
 
-				for ( let i = 0; i < array.length; i += 2 ) {
+		}
 
 
-					const encoded = EncodingFuncs.defaultEncode( array[ i ], array[ i + 1 ], 0, 2 );
-					result[ i ] = encoded[ 0 ];
-					result[ i + 1 ] = encoded[ 1 ];
+		const uvs = mesh.geometry.attributes.uv;
 
 
-				}
+		if ( ! uvs ) {
 
 
-				mesh.geometry.setAttribute( 'uv', new THREE.BufferAttribute( result, 2, true ) );
-				mesh.geometry.attributes.uv.isPacked = true;
-				mesh.geometry.attributes.uv.needsUpdate = true;
-				mesh.geometry.attributes.uv.bytes = result.length * 2;
+			console.error( 'Geometry must contain uv attribute. ' );
 
 
-				if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+		}
 
 
-					mesh.material = new PackedPhongMaterial().copy( mesh.material );
+		if ( uvs.isPacked ) return;
+		const range = {
+			min: Infinity,
+			max: - Infinity
+		};
+		const array = uvs.array;
 
 
-				}
+		for ( let i = 0; i < array.length; i ++ ) {
 
 
-				mesh.material.defines.USE_PACKED_UV = 0;
+			range.min = Math.min( range.min, array[ i ] );
+			range.max = Math.max( range.max, array[ i ] );
 
 
-			} else {
+		}
 
 
-				// use quantized encoding method
-				result = EncodingFuncs.quantizedEncodeUV( array, 2 );
-				mesh.geometry.setAttribute( 'uv', new THREE.BufferAttribute( result.quantized, 2 ) );
-				mesh.geometry.attributes.uv.isPacked = true;
-				mesh.geometry.attributes.uv.needsUpdate = true;
-				mesh.geometry.attributes.uv.bytes = result.quantized.length * 2;
+		let result;
 
 
-				if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+		if ( range.min >= - 1.0 && range.max <= 1.0 ) {
 
 
-					mesh.material = new PackedPhongMaterial().copy( mesh.material );
+			// use default encoding method
+			result = new Uint16Array( array.length );
 
 
-				}
+			for ( let i = 0; i < array.length; i += 2 ) {
 
 
-				mesh.material.defines.USE_PACKED_UV = 1;
-				mesh.material.uniforms.quantizeMatUV.value = result.decodeMat;
-				mesh.material.uniforms.quantizeMatUV.needsUpdate = true;
+				const encoded = defaultEncode( array[ i ], array[ i + 1 ], 0, 2 );
+				result[ i ] = encoded[ 0 ];
+				result[ i + 1 ] = encoded[ 1 ];
 
 
 			}
 			}
 
 
-		}
-
-	}
+			mesh.geometry.setAttribute( 'uv', new THREE.BufferAttribute( result, 2, true ) );
+			mesh.geometry.attributes.uv.isPacked = true;
+			mesh.geometry.attributes.uv.needsUpdate = true;
+			mesh.geometry.attributes.uv.bytes = result.length * 2;
 
 
-	class EncodingFuncs {
+			if ( ! ( mesh.material instanceof THREE.PackedPhongMaterial ) ) {
 
 
-		static defaultEncode( x, y, z, bytes ) {
+				mesh.material = new THREE.PackedPhongMaterial().copy( mesh.material );
 
 
-			if ( bytes == 1 ) {
+			}
 
 
-				const tmpx = Math.round( ( x + 1 ) * 0.5 * 255 );
-				const tmpy = Math.round( ( y + 1 ) * 0.5 * 255 );
-				const tmpz = Math.round( ( z + 1 ) * 0.5 * 255 );
-				return new Uint8Array( [ tmpx, tmpy, tmpz ] );
+			mesh.material.defines.USE_PACKED_UV = 0;
 
 
-			} else if ( bytes == 2 ) {
+		} else {
 
 
-				const tmpx = Math.round( ( x + 1 ) * 0.5 * 65535 );
-				const tmpy = Math.round( ( y + 1 ) * 0.5 * 65535 );
-				const tmpz = Math.round( ( z + 1 ) * 0.5 * 65535 );
-				return new Uint16Array( [ tmpx, tmpy, tmpz ] );
+			// use quantized encoding method
+			result = quantizedEncodeUV( array, 2 );
+			mesh.geometry.setAttribute( 'uv', new THREE.BufferAttribute( result.quantized, 2 ) );
+			mesh.geometry.attributes.uv.isPacked = true;
+			mesh.geometry.attributes.uv.needsUpdate = true;
+			mesh.geometry.attributes.uv.bytes = result.quantized.length * 2;
 
 
-			} else {
+			if ( ! ( mesh.material instanceof THREE.PackedPhongMaterial ) ) {
 
 
-				console.error( 'number of bytes must be 1 or 2' );
+				mesh.material = new THREE.PackedPhongMaterial().copy( mesh.material );
 
 
 			}
 			}
 
 
-		}
-
-		static defaultDecode( array, bytes ) {
+			mesh.material.defines.USE_PACKED_UV = 1;
+			mesh.material.uniforms.quantizeMatUV.value = result.decodeMat;
+			mesh.material.uniforms.quantizeMatUV.needsUpdate = true;
 
 
-			if ( bytes == 1 ) {
+		}
 
 
-				return [ array[ 0 ] / 255 * 2.0 - 1.0, array[ 1 ] / 255 * 2.0 - 1.0, array[ 2 ] / 255 * 2.0 - 1.0 ];
+	} // Encoding functions
 
 
-			} else if ( bytes == 2 ) {
 
 
-				return [ array[ 0 ] / 65535 * 2.0 - 1.0, array[ 1 ] / 65535 * 2.0 - 1.0, array[ 2 ] / 65535 * 2.0 - 1.0 ];
+	function defaultEncode( x, y, z, bytes ) {
 
 
-			} else {
+		if ( bytes == 1 ) {
 
 
-				console.error( 'number of bytes must be 1 or 2' );
+			const tmpx = Math.round( ( x + 1 ) * 0.5 * 255 );
+			const tmpy = Math.round( ( y + 1 ) * 0.5 * 255 );
+			const tmpz = Math.round( ( z + 1 ) * 0.5 * 255 );
+			return new Uint8Array( [ tmpx, tmpy, tmpz ] );
 
 
-			}
+		} else if ( bytes == 2 ) {
 
 
-		} // for `Angles` encoding
+			const tmpx = Math.round( ( x + 1 ) * 0.5 * 65535 );
+			const tmpy = Math.round( ( y + 1 ) * 0.5 * 65535 );
+			const tmpz = Math.round( ( z + 1 ) * 0.5 * 65535 );
+			return new Uint16Array( [ tmpx, tmpy, tmpz ] );
 
 
+		} else {
 
 
-		static anglesEncode( x, y, z ) {
+			console.error( 'number of bytes must be 1 or 2' );
 
 
-			const normal0 = parseInt( 0.5 * ( 1.0 + Math.atan2( y, x ) / Math.PI ) * 65535 );
-			const normal1 = parseInt( 0.5 * ( 1.0 + z ) * 65535 );
-			return new Uint16Array( [ normal0, normal1 ] );
+		}
 
 
-		} // for `Octahedron` encoding
+	} // for `Angles` encoding
 
 
 
 
-		static octEncodeBest( x, y, z, bytes ) {
+	function anglesEncode( x, y, z ) {
 
 
-			let oct, dec, best, currentCos, bestCos; // Test various combinations of ceil and floor
-			// to minimize rounding errors
+		const normal0 = parseInt( 0.5 * ( 1.0 + Math.atan2( y, x ) / Math.PI ) * 65535 );
+		const normal1 = parseInt( 0.5 * ( 1.0 + z ) * 65535 );
+		return new Uint16Array( [ normal0, normal1 ] );
 
 
-			best = oct = octEncodeVec3( x, y, z, 'floor', 'floor' );
-			dec = octDecodeVec2( oct );
-			bestCos = dot( x, y, z, dec );
-			oct = octEncodeVec3( x, y, z, 'ceil', 'floor' );
-			dec = octDecodeVec2( oct );
-			currentCos = dot( x, y, z, dec );
+	} // for `Octahedron` encoding
 
 
-			if ( currentCos > bestCos ) {
 
 
-				best = oct;
-				bestCos = currentCos;
+	function octEncodeBest( x, y, z, bytes ) {
 
 
-			}
+		let oct, dec, best, currentCos, bestCos; // Test various combinations of ceil and floor
+		// to minimize rounding errors
 
 
-			oct = octEncodeVec3( x, y, z, 'floor', 'ceil' );
-			dec = octDecodeVec2( oct );
-			currentCos = dot( x, y, z, dec );
+		best = oct = octEncodeVec3( x, y, z, 'floor', 'floor' );
+		dec = octDecodeVec2( oct );
+		bestCos = dot( x, y, z, dec );
+		oct = octEncodeVec3( x, y, z, 'ceil', 'floor' );
+		dec = octDecodeVec2( oct );
+		currentCos = dot( x, y, z, dec );
 
 
-			if ( currentCos > bestCos ) {
+		if ( currentCos > bestCos ) {
 
 
-				best = oct;
-				bestCos = currentCos;
+			best = oct;
+			bestCos = currentCos;
 
 
-			}
+		}
 
 
-			oct = octEncodeVec3( x, y, z, 'ceil', 'ceil' );
-			dec = octDecodeVec2( oct );
-			currentCos = dot( x, y, z, dec );
+		oct = octEncodeVec3( x, y, z, 'floor', 'ceil' );
+		dec = octDecodeVec2( oct );
+		currentCos = dot( x, y, z, dec );
 
 
-			if ( currentCos > bestCos ) {
+		if ( currentCos > bestCos ) {
 
 
-				best = oct;
+			best = oct;
+			bestCos = currentCos;
 
 
-			}
+		}
 
 
-			return best;
+		oct = octEncodeVec3( x, y, z, 'ceil', 'ceil' );
+		dec = octDecodeVec2( oct );
+		currentCos = dot( x, y, z, dec );
 
 
-			function octEncodeVec3( x0, y0, z0, xfunc, yfunc ) {
+		if ( currentCos > bestCos ) {
 
 
-				let x = x0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
-				let y = y0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
+			best = oct;
 
 
-				if ( z < 0 ) {
+		}
 
 
-					const tempx = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
-					const tempy = ( 1 - Math.abs( x ) ) * ( y >= 0 ? 1 : - 1 );
-					x = tempx;
-					y = tempy;
-					let diff = 1 - Math.abs( x ) - Math.abs( y );
+		return best;
 
 
-					if ( diff > 0 ) {
+		function octEncodeVec3( x0, y0, z0, xfunc, yfunc ) {
 
 
-						diff += 0.001;
-						x += x > 0 ? diff / 2 : - diff / 2;
-						y += y > 0 ? diff / 2 : - diff / 2;
+			let x = x0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
+			let y = y0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
 
 
-					}
+			if ( z < 0 ) {
 
 
-				}
+				const tempx = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
+				const tempy = ( 1 - Math.abs( x ) ) * ( y >= 0 ? 1 : - 1 );
+				x = tempx;
+				y = tempy;
+				let diff = 1 - Math.abs( x ) - Math.abs( y );
 
 
-				if ( bytes == 1 ) {
+				if ( diff > 0 ) {
 
 
-					return new Int8Array( [ Math[ xfunc ]( x * 127.5 + ( x < 0 ? 1 : 0 ) ), Math[ yfunc ]( y * 127.5 + ( y < 0 ? 1 : 0 ) ) ] );
+					diff += 0.001;
+					x += x > 0 ? diff / 2 : - diff / 2;
+					y += y > 0 ? diff / 2 : - diff / 2;
 
 
 				}
 				}
 
 
-				if ( bytes == 2 ) {
+			}
 
 
-					return new Int16Array( [ Math[ xfunc ]( x * 32767.5 + ( x < 0 ? 1 : 0 ) ), Math[ yfunc ]( y * 32767.5 + ( y < 0 ? 1 : 0 ) ) ] );
+			if ( bytes == 1 ) {
 
 
-				}
+				return new Int8Array( [ Math[ xfunc ]( x * 127.5 + ( x < 0 ? 1 : 0 ) ), Math[ yfunc ]( y * 127.5 + ( y < 0 ? 1 : 0 ) ) ] );
 
 
 			}
 			}
 
 
-			function octDecodeVec2( oct ) {
+			if ( bytes == 2 ) {
 
 
-				let x = oct[ 0 ];
-				let y = oct[ 1 ];
+				return new Int16Array( [ Math[ xfunc ]( x * 32767.5 + ( x < 0 ? 1 : 0 ) ), Math[ yfunc ]( y * 32767.5 + ( y < 0 ? 1 : 0 ) ) ] );
 
 
-				if ( bytes == 1 ) {
-
-					x /= x < 0 ? 127 : 128;
-					y /= y < 0 ? 127 : 128;
+			}
 
 
-				} else if ( bytes == 2 ) {
+		}
 
 
-					x /= x < 0 ? 32767 : 32768;
-					y /= y < 0 ? 32767 : 32768;
+		function octDecodeVec2( oct ) {
 
 
-				}
+			let x = oct[ 0 ];
+			let y = oct[ 1 ];
 
 
-				const z = 1 - Math.abs( x ) - Math.abs( y );
-
-				if ( z < 0 ) {
+			if ( bytes == 1 ) {
 
 
-					const tmpx = x;
-					x = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
-					y = ( 1 - Math.abs( tmpx ) ) * ( y >= 0 ? 1 : - 1 );
+				x /= x < 0 ? 127 : 128;
+				y /= y < 0 ? 127 : 128;
 
 
-				}
+			} else if ( bytes == 2 ) {
 
 
-				const length = Math.sqrt( x * x + y * y + z * z );
-				return [ x / length, y / length, z / length ];
+				x /= x < 0 ? 32767 : 32768;
+				y /= y < 0 ? 32767 : 32768;
 
 
 			}
 			}
 
 
-			function dot( x, y, z, vec3 ) {
+			const z = 1 - Math.abs( x ) - Math.abs( y );
 
 
-				return x * vec3[ 0 ] + y * vec3[ 1 ] + z * vec3[ 2 ];
+			if ( z < 0 ) {
 
 
-			}
+				const tmpx = x;
+				x = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
+				y = ( 1 - Math.abs( tmpx ) ) * ( y >= 0 ? 1 : - 1 );
 
 
-		}
+			}
 
 
-		static quantizedEncode( array, bytes ) {
+			const length = Math.sqrt( x * x + y * y + z * z );
+			return [ x / length, y / length, z / length ];
 
 
-			let quantized, segments;
+		}
 
 
-			if ( bytes == 1 ) {
+		function dot( x, y, z, vec3 ) {
 
 
-				quantized = new Uint8Array( array.length );
-				segments = 255;
+			return x * vec3[ 0 ] + y * vec3[ 1 ] + z * vec3[ 2 ];
 
 
-			} else if ( bytes == 2 ) {
+		}
 
 
-				quantized = new Uint16Array( array.length );
-				segments = 65535;
+	}
 
 
-			} else {
+	function quantizedEncode( array, bytes ) {
 
 
-				console.error( 'number of bytes error! ' );
+		let quantized, segments;
 
 
-			}
+		if ( bytes == 1 ) {
 
 
-			const decodeMat = new THREE.Matrix4();
-			const min = new Float32Array( 3 );
-			const max = new Float32Array( 3 );
-			min[ 0 ] = min[ 1 ] = min[ 2 ] = Number.MAX_VALUE;
-			max[ 0 ] = max[ 1 ] = max[ 2 ] = - Number.MAX_VALUE;
+			quantized = new Uint8Array( array.length );
+			segments = 255;
 
 
-			for ( let i = 0; i < array.length; i += 3 ) {
+		} else if ( bytes == 2 ) {
 
 
-				min[ 0 ] = Math.min( min[ 0 ], array[ i + 0 ] );
-				min[ 1 ] = Math.min( min[ 1 ], array[ i + 1 ] );
-				min[ 2 ] = Math.min( min[ 2 ], array[ i + 2 ] );
-				max[ 0 ] = Math.max( max[ 0 ], array[ i + 0 ] );
-				max[ 1 ] = Math.max( max[ 1 ], array[ i + 1 ] );
-				max[ 2 ] = Math.max( max[ 2 ], array[ i + 2 ] );
+			quantized = new Uint16Array( array.length );
+			segments = 65535;
 
 
-			}
+		} else {
 
 
-			decodeMat.scale( new THREE.Vector3( ( max[ 0 ] - min[ 0 ] ) / segments, ( max[ 1 ] - min[ 1 ] ) / segments, ( max[ 2 ] - min[ 2 ] ) / segments ) );
-			decodeMat.elements[ 12 ] = min[ 0 ];
-			decodeMat.elements[ 13 ] = min[ 1 ];
-			decodeMat.elements[ 14 ] = min[ 2 ];
-			decodeMat.transpose();
-			const multiplier = new Float32Array( [ max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0, max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0, max[ 2 ] !== min[ 2 ] ? segments / ( max[ 2 ] - min[ 2 ] ) : 0 ] );
+			console.error( 'number of bytes error! ' );
 
 
-			for ( let i = 0; i < array.length; i += 3 ) {
+		}
 
 
-				quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
-				quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
-				quantized[ i + 2 ] = Math.floor( ( array[ i + 2 ] - min[ 2 ] ) * multiplier[ 2 ] );
+		const decodeMat = new THREE.Matrix4();
+		const min = new Float32Array( 3 );
+		const max = new Float32Array( 3 );
+		min[ 0 ] = min[ 1 ] = min[ 2 ] = Number.MAX_VALUE;
+		max[ 0 ] = max[ 1 ] = max[ 2 ] = - Number.MAX_VALUE;
 
 
-			}
+		for ( let i = 0; i < array.length; i += 3 ) {
 
 
-			return {
-				quantized: quantized,
-				decodeMat: decodeMat
-			};
+			min[ 0 ] = Math.min( min[ 0 ], array[ i + 0 ] );
+			min[ 1 ] = Math.min( min[ 1 ], array[ i + 1 ] );
+			min[ 2 ] = Math.min( min[ 2 ], array[ i + 2 ] );
+			max[ 0 ] = Math.max( max[ 0 ], array[ i + 0 ] );
+			max[ 1 ] = Math.max( max[ 1 ], array[ i + 1 ] );
+			max[ 2 ] = Math.max( max[ 2 ], array[ i + 2 ] );
 
 
 		}
 		}
 
 
-		static quantizedEncodeUV( array, bytes ) {
+		decodeMat.scale( new THREE.Vector3( ( max[ 0 ] - min[ 0 ] ) / segments, ( max[ 1 ] - min[ 1 ] ) / segments, ( max[ 2 ] - min[ 2 ] ) / segments ) );
+		decodeMat.elements[ 12 ] = min[ 0 ];
+		decodeMat.elements[ 13 ] = min[ 1 ];
+		decodeMat.elements[ 14 ] = min[ 2 ];
+		decodeMat.transpose();
+		const multiplier = new Float32Array( [ max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0, max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0, max[ 2 ] !== min[ 2 ] ? segments / ( max[ 2 ] - min[ 2 ] ) : 0 ] );
 
 
-			let quantized, segments;
+		for ( let i = 0; i < array.length; i += 3 ) {
 
 
-			if ( bytes == 1 ) {
+			quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
+			quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
+			quantized[ i + 2 ] = Math.floor( ( array[ i + 2 ] - min[ 2 ] ) * multiplier[ 2 ] );
 
 
-				quantized = new Uint8Array( array.length );
-				segments = 255;
+		}
 
 
-			} else if ( bytes == 2 ) {
+		return {
+			quantized: quantized,
+			decodeMat: decodeMat
+		};
 
 
-				quantized = new Uint16Array( array.length );
-				segments = 65535;
+	}
 
 
-			} else {
+	function quantizedEncodeUV( array, bytes ) {
 
 
-				console.error( 'number of bytes error! ' );
+		let quantized, segments;
 
 
-			}
+		if ( bytes == 1 ) {
 
 
-			const decodeMat = new THREE.Matrix3();
-			const min = new Float32Array( 2 );
-			const max = new Float32Array( 2 );
-			min[ 0 ] = min[ 1 ] = Number.MAX_VALUE;
-			max[ 0 ] = max[ 1 ] = - Number.MAX_VALUE;
+			quantized = new Uint8Array( array.length );
+			segments = 255;
 
 
-			for ( let i = 0; i < array.length; i += 2 ) {
+		} else if ( bytes == 2 ) {
 
 
-				min[ 0 ] = Math.min( min[ 0 ], array[ i + 0 ] );
-				min[ 1 ] = Math.min( min[ 1 ], array[ i + 1 ] );
-				max[ 0 ] = Math.max( max[ 0 ], array[ i + 0 ] );
-				max[ 1 ] = Math.max( max[ 1 ], array[ i + 1 ] );
+			quantized = new Uint16Array( array.length );
+			segments = 65535;
 
 
-			}
+		} else {
 
 
-			decodeMat.scale( ( max[ 0 ] - min[ 0 ] ) / segments, ( max[ 1 ] - min[ 1 ] ) / segments );
-			decodeMat.elements[ 6 ] = min[ 0 ];
-			decodeMat.elements[ 7 ] = min[ 1 ];
-			decodeMat.transpose();
-			const multiplier = new Float32Array( [ max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0, max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0 ] );
+			console.error( 'number of bytes error! ' );
 
 
-			for ( let i = 0; i < array.length; i += 2 ) {
+		}
 
 
-				quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
-				quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
+		const decodeMat = new THREE.Matrix3();
+		const min = new Float32Array( 2 );
+		const max = new Float32Array( 2 );
+		min[ 0 ] = min[ 1 ] = Number.MAX_VALUE;
+		max[ 0 ] = max[ 1 ] = - Number.MAX_VALUE;
 
 
-			}
+		for ( let i = 0; i < array.length; i += 2 ) {
 
 
-			return {
-				quantized: quantized,
-				decodeMat: decodeMat
-			};
+			min[ 0 ] = Math.min( min[ 0 ], array[ i + 0 ] );
+			min[ 1 ] = Math.min( min[ 1 ], array[ i + 1 ] );
+			max[ 0 ] = Math.max( max[ 0 ], array[ i + 0 ] );
+			max[ 1 ] = Math.max( max[ 1 ], array[ i + 1 ] );
 
 
 		}
 		}
 
 
-	}
-	/**
- * `PackedPhongMaterial` inherited from THREE.MeshPhongMaterial
- *
- * @param {Object} parameters
- */
-
+		decodeMat.scale( ( max[ 0 ] - min[ 0 ] ) / segments, ( max[ 1 ] - min[ 1 ] ) / segments );
+		decodeMat.elements[ 6 ] = min[ 0 ];
+		decodeMat.elements[ 7 ] = min[ 1 ];
+		decodeMat.transpose();
+		const multiplier = new Float32Array( [ max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0, max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0 ] );
 
 
-	class PackedPhongMaterial extends THREE.MeshPhongMaterial {
+		for ( let i = 0; i < array.length; i += 2 ) {
 
 
-		constructor( parameters ) {
-
-			super();
-			this.defines = {};
-			this.type = 'PackedPhongMaterial';
-			this.uniforms = THREE.UniformsUtils.merge( [ THREE.ShaderLib.phong.uniforms, {
-				quantizeMatPos: {
-					value: null
-				},
-				quantizeMatUV: {
-					value: null
-				}
-			} ] );
-			this.vertexShader = [ '#define PHONG', 'varying vec3 vViewPosition;', '#ifndef FLAT_SHADED', 'varying vec3 vNormal;', '#endif', THREE.ShaderChunk.common, THREE.ShaderChunk.uv_pars_vertex, THREE.ShaderChunk.uv2_pars_vertex, THREE.ShaderChunk.displacementmap_pars_vertex, THREE.ShaderChunk.envmap_pars_vertex, THREE.ShaderChunk.color_pars_vertex, THREE.ShaderChunk.fog_pars_vertex, THREE.ShaderChunk.morphtarget_pars_vertex, THREE.ShaderChunk.skinning_pars_vertex, THREE.ShaderChunk.shadowmap_pars_vertex, THREE.ShaderChunk.logdepthbuf_pars_vertex, THREE.ShaderChunk.clipping_planes_pars_vertex, `#ifdef USE_PACKED_NORMAL
-					#if USE_PACKED_NORMAL == 0
-						vec3 decodeNormal(vec3 packedNormal)
-						{
-							float x = packedNormal.x * 2.0 - 1.0;
-							float y = packedNormal.y * 2.0 - 1.0;
-							vec2 scth = vec2(sin(x * PI), cos(x * PI));
-							vec2 scphi = vec2(sqrt(1.0 - y * y), y);
-							return normalize( vec3(scth.y * scphi.x, scth.x * scphi.x, scphi.y) );
-						}
-					#endif
-
-					#if USE_PACKED_NORMAL == 1
-						vec3 decodeNormal(vec3 packedNormal)
-						{
-							vec3 v = vec3(packedNormal.xy, 1.0 - abs(packedNormal.x) - abs(packedNormal.y));
-							if (v.z < 0.0)
-							{
-								v.xy = (1.0 - abs(v.yx)) * vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0);
-							}
-							return normalize(v);
-						}
-					#endif
-
-					#if USE_PACKED_NORMAL == 2
-						vec3 decodeNormal(vec3 packedNormal)
-						{
-							vec3 v = (packedNormal * 2.0) - 1.0;
-							return normalize(v);
-						}
-					#endif
-				#endif`, `#ifdef USE_PACKED_POSITION
-					#if USE_PACKED_POSITION == 0
-						uniform mat4 quantizeMatPos;
-					#endif
-				#endif`, `#ifdef USE_PACKED_UV
-					#if USE_PACKED_UV == 1
-						uniform mat3 quantizeMatUV;
-					#endif
-				#endif`, `#ifdef USE_PACKED_UV
-					#if USE_PACKED_UV == 0
-						vec2 decodeUV(vec2 packedUV)
-						{
-							vec2 uv = (packedUV * 2.0) - 1.0;
-							return uv;
-						}
-					#endif
-
-					#if USE_PACKED_UV == 1
-						vec2 decodeUV(vec2 packedUV)
-						{
-							vec2 uv = ( vec3(packedUV, 1.0) * quantizeMatUV ).xy;
-							return uv;
-						}
-					#endif
-				#endif`, 'void main() {', THREE.ShaderChunk.uv_vertex, `#ifdef USE_UV
-					#ifdef USE_PACKED_UV
-						vUv = decodeUV(vUv);
-					#endif
-				#endif`, THREE.ShaderChunk.uv2_vertex, THREE.ShaderChunk.color_vertex, THREE.ShaderChunk.beginnormal_vertex, `#ifdef USE_PACKED_NORMAL
-					objectNormal = decodeNormal(objectNormal);
-				#endif
-
-				#ifdef USE_TANGENT
-					vec3 objectTangent = vec3( tangent.xyz );
-				#endif
-				`, THREE.ShaderChunk.morphnormal_vertex, THREE.ShaderChunk.skinbase_vertex, THREE.ShaderChunk.skinnormal_vertex, THREE.ShaderChunk.defaultnormal_vertex, '#ifndef FLAT_SHADED', '	vNormal = normalize( transformedNormal );', '#endif', THREE.ShaderChunk.begin_vertex, `#ifdef USE_PACKED_POSITION
-					#if USE_PACKED_POSITION == 0
-						transformed = ( vec4(transformed, 1.0) * quantizeMatPos ).xyz;
-					#endif
-				#endif`, THREE.ShaderChunk.morphtarget_vertex, THREE.ShaderChunk.skinning_vertex, THREE.ShaderChunk.displacementmap_vertex, THREE.ShaderChunk.project_vertex, THREE.ShaderChunk.logdepthbuf_vertex, THREE.ShaderChunk.clipping_planes_vertex, 'vViewPosition = - mvPosition.xyz;', THREE.ShaderChunk.worldpos_vertex, THREE.ShaderChunk.envmap_vertex, THREE.ShaderChunk.shadowmap_vertex, THREE.ShaderChunk.fog_vertex, '}' ].join( '\n' ); // Use the original THREE.MeshPhongMaterial's fragmentShader.
-
-			this.fragmentShader = [ '#define PHONG', 'uniform vec3 diffuse;', 'uniform vec3 emissive;', 'uniform vec3 specular;', 'uniform float shininess;', 'uniform float opacity;', THREE.ShaderChunk.common, THREE.ShaderChunk.packing, THREE.ShaderChunk.dithering_pars_fragment, THREE.ShaderChunk.color_pars_fragment, THREE.ShaderChunk.uv_pars_fragment, THREE.ShaderChunk.uv2_pars_fragment, THREE.ShaderChunk.map_pars_fragment, THREE.ShaderChunk.alphamap_pars_fragment, THREE.ShaderChunk.aomap_pars_fragment, THREE.ShaderChunk.lightmap_pars_fragment, THREE.ShaderChunk.emissivemap_pars_fragment, THREE.ShaderChunk.envmap_common_pars_fragment, THREE.ShaderChunk.envmap_pars_fragment, THREE.ShaderChunk.cube_uv_reflection_fragment, THREE.ShaderChunk.fog_pars_fragment, THREE.ShaderChunk.bsdfs, THREE.ShaderChunk.lights_pars_begin, THREE.ShaderChunk.lights_phong_pars_fragment, THREE.ShaderChunk.shadowmap_pars_fragment, THREE.ShaderChunk.bumpmap_pars_fragment, THREE.ShaderChunk.normalmap_pars_fragment, THREE.ShaderChunk.specularmap_pars_fragment, THREE.ShaderChunk.logdepthbuf_pars_fragment, THREE.ShaderChunk.clipping_planes_pars_fragment, 'void main() {', THREE.ShaderChunk.clipping_planes_fragment, 'vec4 diffuseColor = vec4( diffuse, opacity );', 'ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );', 'vec3 totalEmissiveRadiance = emissive;', THREE.ShaderChunk.logdepthbuf_fragment, THREE.ShaderChunk.map_fragment, THREE.ShaderChunk.color_fragment, THREE.ShaderChunk.alphamap_fragment, THREE.ShaderChunk.alphatest_fragment, THREE.ShaderChunk.specularmap_fragment, THREE.ShaderChunk.normal_fragment_begin, THREE.ShaderChunk.normal_fragment_maps, THREE.ShaderChunk.emissivemap_fragment, // accumulation
-				THREE.ShaderChunk.lights_phong_fragment, THREE.ShaderChunk.lights_fragment_begin, THREE.ShaderChunk.lights_fragment_maps, THREE.ShaderChunk.lights_fragment_end, // modulation
-				THREE.ShaderChunk.aomap_fragment, 'vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;', THREE.ShaderChunk.envmap_fragment, 'gl_FragColor = vec4( outgoingLight, diffuseColor.a );', THREE.ShaderChunk.tonemapping_fragment, THREE.ShaderChunk.encodings_fragment, THREE.ShaderChunk.fog_fragment, THREE.ShaderChunk.premultiplied_alpha_fragment, THREE.ShaderChunk.dithering_fragment, '}' ].join( '\n' );
-			this.setValues( parameters );
+			quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
+			quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
 
 
 		}
 		}
 
 
+		return {
+			quantized: quantized,
+			decodeMat: decodeMat
+		};
+
 	}
 	}
 
 
-	THREE.GeometryCompressionUtils = GeometryCompressionUtils;
-	THREE.PackedPhongMaterial = PackedPhongMaterial;
+	THREE.GeometryCompressionUtils = {};
+	THREE.GeometryCompressionUtils.compressNormals = compressNormals;
+	THREE.GeometryCompressionUtils.compressPositions = compressPositions;
+	THREE.GeometryCompressionUtils.compressUvs = compressUvs;
 
 
 } )();
 } )();

+ 134 - 134
examples/js/utils/GeometryUtils.js

@@ -1,189 +1,189 @@
 ( function () {
 ( function () {
 
 
-	class GeometryUtils {
+	/**
+ * Generates 2D-Coordinates in a very fast way.
+ *
+ * Based on work by:
+ * @link http://www.openprocessing.org/sketch/15493
+ *
+ * @param center     Center of Hilbert curve.
+ * @param size       Total width of Hilbert curve.
+ * @param iterations Number of subdivisions.
+ * @param v0         Corner index -X, -Z.
+ * @param v1         Corner index -X, +Z.
+ * @param v2         Corner index +X, +Z.
+ * @param v3         Corner index +X, -Z.
+ */
 
 
-		/**
-   * Generates 2D-Coordinates in a very fast way.
-   *
-   * Based on work by:
-   * @link http://www.openprocessing.org/sketch/15493
-   *
-   * @param center     Center of Hilbert curve.
-   * @param size       Total width of Hilbert curve.
-   * @param iterations Number of subdivisions.
-   * @param v0         Corner index -X, -Z.
-   * @param v1         Corner index -X, +Z.
-   * @param v2         Corner index +X, +Z.
-   * @param v3         Corner index +X, -Z.
-   */
-		static hilbert2D( center = new THREE.Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3 ) {
+	function hilbert2D( center = new THREE.Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3 ) {
 
 
-			const half = size / 2;
-			const vec_s = [ new THREE.Vector3( center.x - half, center.y, center.z - half ), new THREE.Vector3( center.x - half, center.y, center.z + half ), new THREE.Vector3( center.x + half, center.y, center.z + half ), new THREE.Vector3( center.x + half, center.y, center.z - half ) ];
-			const vec = [ vec_s[ v0 ], vec_s[ v1 ], vec_s[ v2 ], vec_s[ v3 ] ]; // Recurse iterations
+		const half = size / 2;
+		const vec_s = [ new THREE.Vector3( center.x - half, center.y, center.z - half ), new THREE.Vector3( center.x - half, center.y, center.z + half ), new THREE.Vector3( center.x + half, center.y, center.z + half ), new THREE.Vector3( center.x + half, center.y, center.z - half ) ];
+		const vec = [ vec_s[ v0 ], vec_s[ v1 ], vec_s[ v2 ], vec_s[ v3 ] ]; // Recurse iterations
 
 
-			if ( 0 <= -- iterations ) {
+		if ( 0 <= -- iterations ) {
 
 
-				const tmp = [];
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 ) ); // Return recursive call
+			const tmp = [];
+			Array.prototype.push.apply( tmp, hilbert2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ) );
+			Array.prototype.push.apply( tmp, hilbert2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ) );
+			Array.prototype.push.apply( tmp, hilbert2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ) );
+			Array.prototype.push.apply( tmp, hilbert2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 ) ); // Return recursive call
 
 
-				return tmp;
+			return tmp;
 
 
-			} // Return complete Hilbert Curve.
+		} // Return complete Hilbert Curve.
 
 
 
 
-			return vec;
+		return vec;
 
 
-		}
-		/**
-   * Generates 3D-Coordinates in a very fast way.
-   *
-   * Based on work by:
-   * @link http://www.openprocessing.org/visuals/?visualID=15599
-   *
-   * @param center     Center of Hilbert curve.
-   * @param size       Total width of Hilbert curve.
-   * @param iterations Number of subdivisions.
-   * @param v0         Corner index -X, +Y, -Z.
-   * @param v1         Corner index -X, +Y, +Z.
-   * @param v2         Corner index -X, -Y, +Z.
-   * @param v3         Corner index -X, -Y, -Z.
-   * @param v4         Corner index +X, -Y, -Z.
-   * @param v5         Corner index +X, -Y, +Z.
-   * @param v6         Corner index +X, +Y, +Z.
-   * @param v7         Corner index +X, +Y, -Z.
-   */
-
-
-		static hilbert3D( center = new THREE.Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6, v7 = 7 ) {
-
-			// Default Vars
-			const half = size / 2;
-			const vec_s = [ new THREE.Vector3( center.x - half, center.y + half, center.z - half ), new THREE.Vector3( center.x - half, center.y + half, center.z + half ), new THREE.Vector3( center.x - half, center.y - half, center.z + half ), new THREE.Vector3( center.x - half, center.y - half, center.z - half ), new THREE.Vector3( center.x + half, center.y - half, center.z - half ), new THREE.Vector3( center.x + half, center.y - half, center.z + half ), new THREE.Vector3( center.x + half, center.y + half, center.z + half ), new THREE.Vector3( center.x + half, center.y + half, center.z - half ) ];
-			const vec = [ vec_s[ v0 ], vec_s[ v1 ], vec_s[ v2 ], vec_s[ v3 ], vec_s[ v4 ], vec_s[ v5 ], vec_s[ v6 ], vec_s[ v7 ] ]; // Recurse iterations
-
-			if ( -- iterations >= 0 ) {
-
-				const tmp = [];
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
-				Array.prototype.push.apply( tmp, GeometryUtils.hilbert3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 ) ); // Return recursive call
-
-				return tmp;
-
-			} // Return complete Hilbert Curve.
-
-
-			return vec;
-
-		}
-		/**
-   * Generates a Gosper curve (lying in the XY plane)
-   *
-   * https://gist.github.com/nitaku/6521802
-   *
-   * @param size The size of a single gosper island.
-   */
+	}
+	/**
+ * Generates 3D-Coordinates in a very fast way.
+ *
+ * Based on work by:
+ * @link http://www.openprocessing.org/visuals/?visualID=15599
+ *
+ * @param center     Center of Hilbert curve.
+ * @param size       Total width of Hilbert curve.
+ * @param iterations Number of subdivisions.
+ * @param v0         Corner index -X, +Y, -Z.
+ * @param v1         Corner index -X, +Y, +Z.
+ * @param v2         Corner index -X, -Y, +Z.
+ * @param v3         Corner index -X, -Y, -Z.
+ * @param v4         Corner index +X, -Y, -Z.
+ * @param v5         Corner index +X, -Y, +Z.
+ * @param v6         Corner index +X, +Y, +Z.
+ * @param v7         Corner index +X, +Y, -Z.
+ */
+
+
+	function hilbert3D( center = new THREE.Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6, v7 = 7 ) {
+
+		// Default Vars
+		const half = size / 2;
+		const vec_s = [ new THREE.Vector3( center.x - half, center.y + half, center.z - half ), new THREE.Vector3( center.x - half, center.y + half, center.z + half ), new THREE.Vector3( center.x - half, center.y - half, center.z + half ), new THREE.Vector3( center.x - half, center.y - half, center.z - half ), new THREE.Vector3( center.x + half, center.y - half, center.z - half ), new THREE.Vector3( center.x + half, center.y - half, center.z + half ), new THREE.Vector3( center.x + half, center.y + half, center.z + half ), new THREE.Vector3( center.x + half, center.y + half, center.z - half ) ];
+		const vec = [ vec_s[ v0 ], vec_s[ v1 ], vec_s[ v2 ], vec_s[ v3 ], vec_s[ v4 ], vec_s[ v5 ], vec_s[ v6 ], vec_s[ v7 ] ]; // Recurse iterations
+
+		if ( -- iterations >= 0 ) {
+
+			const tmp = [];
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
+			Array.prototype.push.apply( tmp, hilbert3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 ) ); // Return recursive call
+
+			return tmp;
+
+		} // Return complete Hilbert Curve.
+
+
+		return vec;
 
 
+	}
+	/**
+ * Generates a Gosper curve (lying in the XY plane)
+ *
+ * https://gist.github.com/nitaku/6521802
+ *
+ * @param size The size of a single gosper island.
+ */
 
 
-		static gosper( size = 1 ) {
 
 
-			function fractalize( config ) {
+	function gosper( size = 1 ) {
 
 
-				let output;
-				let input = config.axiom;
+		function fractalize( config ) {
 
 
-				for ( let i = 0, il = config.steps; 0 <= il ? i < il : i > il; 0 <= il ? i ++ : i -- ) {
+			let output;
+			let input = config.axiom;
 
 
-					output = '';
+			for ( let i = 0, il = config.steps; 0 <= il ? i < il : i > il; 0 <= il ? i ++ : i -- ) {
 
 
-					for ( let j = 0, jl = input.length; j < jl; j ++ ) {
+				output = '';
 
 
-						const char = input[ j ];
+				for ( let j = 0, jl = input.length; j < jl; j ++ ) {
 
 
-						if ( char in config.rules ) {
+					const char = input[ j ];
 
 
-							output += config.rules[ char ];
+					if ( char in config.rules ) {
 
 
-						} else {
+						output += config.rules[ char ];
 
 
-							output += char;
+					} else {
 
 
-						}
+						output += char;
 
 
 					}
 					}
 
 
-					input = output;
-
 				}
 				}
 
 
-				return output;
+				input = output;
 
 
 			}
 			}
 
 
-			function toPoints( config ) {
+			return output;
 
 
-				let currX = 0,
-					currY = 0;
-				let angle = 0;
-				const path = [ 0, 0, 0 ];
-				const fractal = config.fractal;
+		}
 
 
-				for ( let i = 0, l = fractal.length; i < l; i ++ ) {
+		function toPoints( config ) {
 
 
-					const char = fractal[ i ];
+			let currX = 0,
+				currY = 0;
+			let angle = 0;
+			const path = [ 0, 0, 0 ];
+			const fractal = config.fractal;
 
 
-					if ( char === '+' ) {
+			for ( let i = 0, l = fractal.length; i < l; i ++ ) {
 
 
-						angle += config.angle;
+				const char = fractal[ i ];
 
 
-					} else if ( char === '-' ) {
+				if ( char === '+' ) {
 
 
-						angle -= config.angle;
+					angle += config.angle;
 
 
-					} else if ( char === 'F' ) {
+				} else if ( char === '-' ) {
 
 
-						currX += config.size * Math.cos( angle );
-						currY += - config.size * Math.sin( angle );
-						path.push( currX, currY, 0 );
+					angle -= config.angle;
 
 
-					}
+				} else if ( char === 'F' ) {
+
+					currX += config.size * Math.cos( angle );
+					currY += - config.size * Math.sin( angle );
+					path.push( currX, currY, 0 );
 
 
 				}
 				}
 
 
-				return path;
+			}
 
 
-			} //
+			return path;
 
 
+		} //
 
 
-			const gosper = fractalize( {
-				axiom: 'A',
-				steps: 4,
-				rules: {
-					A: 'A+BF++BF-FA--FAFA-BF+',
-					B: '-FA+BFBF++BF+FA--FA-B'
-				}
-			} );
-			const points = toPoints( {
-				fractal: gosper,
-				size: size,
-				angle: Math.PI / 3 // 60 degrees
 
 
-			} );
-			return points;
+		const gosper = fractalize( {
+			axiom: 'A',
+			steps: 4,
+			rules: {
+				A: 'A+BF++BF-FA--FAFA-BF+',
+				B: '-FA+BFBF++BF+FA--FA-B'
+			}
+		} );
+		const points = toPoints( {
+			fractal: gosper,
+			size: size,
+			angle: Math.PI / 3 // 60 degrees
 
 
-		}
+		} );
+		return points;
 
 
 	}
 	}
 
 
-	THREE.GeometryUtils = GeometryUtils;
+	THREE.GeometryUtils = {};
+	THREE.GeometryUtils.gosper = gosper;
+	THREE.GeometryUtils.hilbert2D = hilbert2D;
+	THREE.GeometryUtils.hilbert3D = hilbert3D;
 
 
 } )();
 } )();

+ 107 - 0
examples/js/utils/PackedPhongMaterial.js

@@ -0,0 +1,107 @@
+( function () {
+
+	/**
+ * `PackedPhongMaterial` inherited from THREE.MeshPhongMaterial
+ *
+ * @param {Object} parameters
+ */
+
+	class PackedPhongMaterial extends THREE.MeshPhongMaterial {
+
+		constructor( parameters ) {
+
+			super();
+			this.defines = {};
+			this.type = 'PackedPhongMaterial';
+			this.uniforms = THREE.UniformsUtils.merge( [ THREE.ShaderLib.phong.uniforms, {
+				quantizeMatPos: {
+					value: null
+				},
+				quantizeMatUV: {
+					value: null
+				}
+			} ] );
+			this.vertexShader = [ '#define PHONG', 'varying vec3 vViewPosition;', '#ifndef FLAT_SHADED', 'varying vec3 vNormal;', '#endif', THREE.ShaderChunk.common, THREE.ShaderChunk.uv_pars_vertex, THREE.ShaderChunk.uv2_pars_vertex, THREE.ShaderChunk.displacementmap_pars_vertex, THREE.ShaderChunk.envmap_pars_vertex, THREE.ShaderChunk.color_pars_vertex, THREE.ShaderChunk.fog_pars_vertex, THREE.ShaderChunk.morphtarget_pars_vertex, THREE.ShaderChunk.skinning_pars_vertex, THREE.ShaderChunk.shadowmap_pars_vertex, THREE.ShaderChunk.logdepthbuf_pars_vertex, THREE.ShaderChunk.clipping_planes_pars_vertex, `#ifdef USE_PACKED_NORMAL
+					#if USE_PACKED_NORMAL == 0
+						vec3 decodeNormal(vec3 packedNormal)
+						{
+							float x = packedNormal.x * 2.0 - 1.0;
+							float y = packedNormal.y * 2.0 - 1.0;
+							vec2 scth = vec2(sin(x * PI), cos(x * PI));
+							vec2 scphi = vec2(sqrt(1.0 - y * y), y);
+							return normalize( vec3(scth.y * scphi.x, scth.x * scphi.x, scphi.y) );
+						}
+					#endif
+
+					#if USE_PACKED_NORMAL == 1
+						vec3 decodeNormal(vec3 packedNormal)
+						{
+							vec3 v = vec3(packedNormal.xy, 1.0 - abs(packedNormal.x) - abs(packedNormal.y));
+							if (v.z < 0.0)
+							{
+								v.xy = (1.0 - abs(v.yx)) * vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0);
+							}
+							return normalize(v);
+						}
+					#endif
+
+					#if USE_PACKED_NORMAL == 2
+						vec3 decodeNormal(vec3 packedNormal)
+						{
+							vec3 v = (packedNormal * 2.0) - 1.0;
+							return normalize(v);
+						}
+					#endif
+				#endif`, `#ifdef USE_PACKED_POSITION
+					#if USE_PACKED_POSITION == 0
+						uniform mat4 quantizeMatPos;
+					#endif
+				#endif`, `#ifdef USE_PACKED_UV
+					#if USE_PACKED_UV == 1
+						uniform mat3 quantizeMatUV;
+					#endif
+				#endif`, `#ifdef USE_PACKED_UV
+					#if USE_PACKED_UV == 0
+						vec2 decodeUV(vec2 packedUV)
+						{
+							vec2 uv = (packedUV * 2.0) - 1.0;
+							return uv;
+						}
+					#endif
+
+					#if USE_PACKED_UV == 1
+						vec2 decodeUV(vec2 packedUV)
+						{
+							vec2 uv = ( vec3(packedUV, 1.0) * quantizeMatUV ).xy;
+							return uv;
+						}
+					#endif
+				#endif`, 'void main() {', THREE.ShaderChunk.uv_vertex, `#ifdef USE_UV
+					#ifdef USE_PACKED_UV
+						vUv = decodeUV(vUv);
+					#endif
+				#endif`, THREE.ShaderChunk.uv2_vertex, THREE.ShaderChunk.color_vertex, THREE.ShaderChunk.beginnormal_vertex, `#ifdef USE_PACKED_NORMAL
+					objectNormal = decodeNormal(objectNormal);
+				#endif
+
+				#ifdef USE_TANGENT
+					vec3 objectTangent = vec3( tangent.xyz );
+				#endif
+				`, THREE.ShaderChunk.morphnormal_vertex, THREE.ShaderChunk.skinbase_vertex, THREE.ShaderChunk.skinnormal_vertex, THREE.ShaderChunk.defaultnormal_vertex, '#ifndef FLAT_SHADED', '	vNormal = normalize( transformedNormal );', '#endif', THREE.ShaderChunk.begin_vertex, `#ifdef USE_PACKED_POSITION
+					#if USE_PACKED_POSITION == 0
+						transformed = ( vec4(transformed, 1.0) * quantizeMatPos ).xyz;
+					#endif
+				#endif`, THREE.ShaderChunk.morphtarget_vertex, THREE.ShaderChunk.skinning_vertex, THREE.ShaderChunk.displacementmap_vertex, THREE.ShaderChunk.project_vertex, THREE.ShaderChunk.logdepthbuf_vertex, THREE.ShaderChunk.clipping_planes_vertex, 'vViewPosition = - mvPosition.xyz;', THREE.ShaderChunk.worldpos_vertex, THREE.ShaderChunk.envmap_vertex, THREE.ShaderChunk.shadowmap_vertex, THREE.ShaderChunk.fog_vertex, '}' ].join( '\n' ); // Use the original THREE.MeshPhongMaterial's fragmentShader.
+
+			this.fragmentShader = [ '#define PHONG', 'uniform vec3 diffuse;', 'uniform vec3 emissive;', 'uniform vec3 specular;', 'uniform float shininess;', 'uniform float opacity;', THREE.ShaderChunk.common, THREE.ShaderChunk.packing, THREE.ShaderChunk.dithering_pars_fragment, THREE.ShaderChunk.color_pars_fragment, THREE.ShaderChunk.uv_pars_fragment, THREE.ShaderChunk.uv2_pars_fragment, THREE.ShaderChunk.map_pars_fragment, THREE.ShaderChunk.alphamap_pars_fragment, THREE.ShaderChunk.aomap_pars_fragment, THREE.ShaderChunk.lightmap_pars_fragment, THREE.ShaderChunk.emissivemap_pars_fragment, THREE.ShaderChunk.envmap_common_pars_fragment, THREE.ShaderChunk.envmap_pars_fragment, THREE.ShaderChunk.cube_uv_reflection_fragment, THREE.ShaderChunk.fog_pars_fragment, THREE.ShaderChunk.bsdfs, THREE.ShaderChunk.lights_pars_begin, THREE.ShaderChunk.lights_phong_pars_fragment, THREE.ShaderChunk.shadowmap_pars_fragment, THREE.ShaderChunk.bumpmap_pars_fragment, THREE.ShaderChunk.normalmap_pars_fragment, THREE.ShaderChunk.specularmap_pars_fragment, THREE.ShaderChunk.logdepthbuf_pars_fragment, THREE.ShaderChunk.clipping_planes_pars_fragment, 'void main() {', THREE.ShaderChunk.clipping_planes_fragment, 'vec4 diffuseColor = vec4( diffuse, opacity );', 'ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );', 'vec3 totalEmissiveRadiance = emissive;', THREE.ShaderChunk.logdepthbuf_fragment, THREE.ShaderChunk.map_fragment, THREE.ShaderChunk.color_fragment, THREE.ShaderChunk.alphamap_fragment, THREE.ShaderChunk.alphatest_fragment, THREE.ShaderChunk.specularmap_fragment, THREE.ShaderChunk.normal_fragment_begin, THREE.ShaderChunk.normal_fragment_maps, THREE.ShaderChunk.emissivemap_fragment, // accumulation
+				THREE.ShaderChunk.lights_phong_fragment, THREE.ShaderChunk.lights_fragment_begin, THREE.ShaderChunk.lights_fragment_maps, THREE.ShaderChunk.lights_fragment_end, // modulation
+				THREE.ShaderChunk.aomap_fragment, 'vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;', THREE.ShaderChunk.envmap_fragment, 'gl_FragColor = vec4( outgoingLight, diffuseColor.a );', THREE.ShaderChunk.tonemapping_fragment, THREE.ShaderChunk.encodings_fragment, THREE.ShaderChunk.fog_fragment, THREE.ShaderChunk.premultiplied_alpha_fragment, THREE.ShaderChunk.dithering_fragment, '}' ].join( '\n' );
+			this.setValues( parameters );
+
+		}
+
+	}
+
+	THREE.PackedPhongMaterial = PackedPhongMaterial;
+
+} )();

+ 34 - 34
examples/js/utils/SceneUtils.js

@@ -1,60 +1,60 @@
 ( function () {
 ( function () {
 
 
-	class SceneUtils {
+	function createMeshesFromInstancedMesh( instancedMesh ) {
 
 
-		static createMeshesFromInstancedMesh( instancedMesh ) {
+		const group = new THREE.Group();
+		const count = instancedMesh.count;
+		const geometry = instancedMesh.geometry;
+		const material = instancedMesh.material;
 
 
-			const group = new THREE.Group();
-			const count = instancedMesh.count;
-			const geometry = instancedMesh.geometry;
-			const material = instancedMesh.material;
+		for ( let i = 0; i < count; i ++ ) {
 
 
-			for ( let i = 0; i < count; i ++ ) {
-
-				const mesh = new THREE.Mesh( geometry, material );
-				instancedMesh.getMatrixAt( i, mesh.matrix );
-				mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
-				group.add( mesh );
-
-			}
-
-			group.copy( instancedMesh );
-			group.updateMatrixWorld(); // ensure correct world matrices of meshes
-
-			return group;
+			const mesh = new THREE.Mesh( geometry, material );
+			instancedMesh.getMatrixAt( i, mesh.matrix );
+			mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
+			group.add( mesh );
 
 
 		}
 		}
 
 
-		static createMultiMaterialObject( geometry, materials ) {
+		group.copy( instancedMesh );
+		group.updateMatrixWorld(); // ensure correct world matrices of meshes
+
+		return group;
 
 
-			const group = new THREE.Group();
+	}
 
 
-			for ( let i = 0, l = materials.length; i < l; i ++ ) {
+	function createMultiMaterialObject( geometry, materials ) {
 
 
-				group.add( new THREE.Mesh( geometry, materials[ i ] ) );
+		const group = new THREE.Group();
 
 
-			}
+		for ( let i = 0, l = materials.length; i < l; i ++ ) {
 
 
-			return group;
+			group.add( new THREE.Mesh( geometry, materials[ i ] ) );
 
 
 		}
 		}
 
 
-		static detach( child, parent, scene ) {
+		return group;
 
 
-			console.warn( 'THREE.SceneUtils: detach() has been deprecated. Use scene.attach( child ) instead.' );
-			scene.attach( child );
+	}
 
 
-		}
+	function detach( child, parent, scene ) {
 
 
-		static attach( child, scene, parent ) {
+		console.warn( 'THREE.SceneUtils: detach() has been deprecated. Use scene.attach( child ) instead.' );
+		scene.attach( child );
 
 
-			console.warn( 'THREE.SceneUtils: attach() has been deprecated. Use parent.attach( child ) instead.' );
-			parent.attach( child );
+	}
 
 
-		}
+	function attach( child, scene, parent ) {
+
+		console.warn( 'THREE.SceneUtils: attach() has been deprecated. Use parent.attach( child ) instead.' );
+		parent.attach( child );
 
 
 	}
 	}
 
 
-	THREE.SceneUtils = SceneUtils;
+	THREE.SceneUtils = {};
+	THREE.SceneUtils.attach = attach;
+	THREE.SceneUtils.createMeshesFromInstancedMesh = createMeshesFromInstancedMesh;
+	THREE.SceneUtils.createMultiMaterialObject = createMultiMaterialObject;
+	THREE.SceneUtils.detach = detach;
 
 
 } )();
 } )();

+ 290 - 283
examples/js/utils/SkeletonUtils.js

@@ -1,485 +1,481 @@
 ( function () {
 ( function () {
 
 
-	class SkeletonUtils {
-
-		static retarget( target, source, options = {} ) {
-
-			const pos = new THREE.Vector3(),
-				quat = new THREE.Quaternion(),
-				scale = new THREE.Vector3(),
-				bindBoneMatrix = new THREE.Matrix4(),
-				relativeMatrix = new THREE.Matrix4(),
-				globalMatrix = new THREE.Matrix4();
-			options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
-			options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
-			options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false;
-			options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
-			options.hip = options.hip !== undefined ? options.hip : 'hip';
-			options.names = options.names || {};
-			const sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
-				bones = target.isObject3D ? target.skeleton.bones : this.getBones( target );
-			let bindBones, bone, name, boneTo, bonesPosition; // reset bones
-
-			if ( target.isObject3D ) {
-
-				target.skeleton.pose();
+	function retarget( target, source, options = {} ) {
+
+		const pos = new THREE.Vector3(),
+			quat = new THREE.Quaternion(),
+			scale = new THREE.Vector3(),
+			bindBoneMatrix = new THREE.Matrix4(),
+			relativeMatrix = new THREE.Matrix4(),
+			globalMatrix = new THREE.Matrix4();
+		options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
+		options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
+		options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false;
+		options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
+		options.hip = options.hip !== undefined ? options.hip : 'hip';
+		options.names = options.names || {};
+		const sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
+			bones = target.isObject3D ? target.skeleton.bones : this.getBones( target );
+		let bindBones, bone, name, boneTo, bonesPosition; // reset bones
+
+		if ( target.isObject3D ) {
 
 
-			} else {
+			target.skeleton.pose();
 
 
-				options.useTargetMatrix = true;
-				options.preserveMatrix = false;
+		} else {
 
 
-			}
+			options.useTargetMatrix = true;
+			options.preserveMatrix = false;
 
 
-			if ( options.preservePosition ) {
+		}
 
 
-				bonesPosition = [];
+		if ( options.preservePosition ) {
 
 
-				for ( let i = 0; i < bones.length; i ++ ) {
+			bonesPosition = [];
 
 
-					bonesPosition.push( bones[ i ].position.clone() );
+			for ( let i = 0; i < bones.length; i ++ ) {
 
 
-				}
+				bonesPosition.push( bones[ i ].position.clone() );
 
 
 			}
 			}
 
 
-			if ( options.preserveMatrix ) {
+		}
 
 
-				// reset matrix
-				target.updateMatrixWorld();
-				target.matrixWorld.identity(); // reset children matrix
+		if ( options.preserveMatrix ) {
 
 
-				for ( let i = 0; i < target.children.length; ++ i ) {
+			// reset matrix
+			target.updateMatrixWorld();
+			target.matrixWorld.identity(); // reset children matrix
 
 
-					target.children[ i ].updateMatrixWorld( true );
+			for ( let i = 0; i < target.children.length; ++ i ) {
 
 
-				}
+				target.children[ i ].updateMatrixWorld( true );
 
 
 			}
 			}
 
 
-			if ( options.offsets ) {
-
-				bindBones = [];
+		}
 
 
-				for ( let i = 0; i < bones.length; ++ i ) {
+		if ( options.offsets ) {
 
 
-					bone = bones[ i ];
-					name = options.names[ bone.name ] || bone.name;
+			bindBones = [];
 
 
-					if ( options.offsets && options.offsets[ name ] ) {
+			for ( let i = 0; i < bones.length; ++ i ) {
 
 
-						bone.matrix.multiply( options.offsets[ name ] );
-						bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
-						bone.updateMatrixWorld();
+				bone = bones[ i ];
+				name = options.names[ bone.name ] || bone.name;
 
 
-					}
+				if ( options.offsets && options.offsets[ name ] ) {
 
 
-					bindBones.push( bone.matrixWorld.clone() );
+					bone.matrix.multiply( options.offsets[ name ] );
+					bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+					bone.updateMatrixWorld();
 
 
 				}
 				}
 
 
-			}
+				bindBones.push( bone.matrixWorld.clone() );
 
 
-			for ( let i = 0; i < bones.length; ++ i ) {
+			}
 
 
-				bone = bones[ i ];
-				name = options.names[ bone.name ] || bone.name;
-				boneTo = this.getBoneByName( name, sourceBones );
-				globalMatrix.copy( bone.matrixWorld );
+		}
 
 
-				if ( boneTo ) {
+		for ( let i = 0; i < bones.length; ++ i ) {
 
 
-					boneTo.updateMatrixWorld();
+			bone = bones[ i ];
+			name = options.names[ bone.name ] || bone.name;
+			boneTo = this.getBoneByName( name, sourceBones );
+			globalMatrix.copy( bone.matrixWorld );
 
 
-					if ( options.useTargetMatrix ) {
+			if ( boneTo ) {
 
 
-						relativeMatrix.copy( boneTo.matrixWorld );
+				boneTo.updateMatrixWorld();
 
 
-					} else {
+				if ( options.useTargetMatrix ) {
 
 
-						relativeMatrix.copy( target.matrixWorld ).invert();
-						relativeMatrix.multiply( boneTo.matrixWorld );
+					relativeMatrix.copy( boneTo.matrixWorld );
 
 
-					} // ignore scale to extract rotation
+				} else {
 
 
+					relativeMatrix.copy( target.matrixWorld ).invert();
+					relativeMatrix.multiply( boneTo.matrixWorld );
 
 
-					scale.setFromMatrixScale( relativeMatrix );
-					relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) ); // apply to global matrix
+				} // ignore scale to extract rotation
 
 
-					globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
 
 
-					if ( target.isObject3D ) {
+				scale.setFromMatrixScale( relativeMatrix );
+				relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) ); // apply to global matrix
 
 
-						const boneIndex = bones.indexOf( bone ),
-							wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert();
-						globalMatrix.multiply( wBindMatrix );
+				globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
 
 
-					}
+				if ( target.isObject3D ) {
 
 
-					globalMatrix.copyPosition( relativeMatrix );
+					const boneIndex = bones.indexOf( bone ),
+						wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert();
+					globalMatrix.multiply( wBindMatrix );
 
 
 				}
 				}
 
 
-				if ( bone.parent && bone.parent.isBone ) {
+				globalMatrix.copyPosition( relativeMatrix );
 
 
-					bone.matrix.copy( bone.parent.matrixWorld ).invert();
-					bone.matrix.multiply( globalMatrix );
+			}
 
 
-				} else {
+			if ( bone.parent && bone.parent.isBone ) {
 
 
-					bone.matrix.copy( globalMatrix );
+				bone.matrix.copy( bone.parent.matrixWorld ).invert();
+				bone.matrix.multiply( globalMatrix );
 
 
-				}
+			} else {
 
 
-				if ( options.preserveHipPosition && name === options.hip ) {
+				bone.matrix.copy( globalMatrix );
 
 
-					bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
+			}
 
 
-				}
+			if ( options.preserveHipPosition && name === options.hip ) {
 
 
-				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
-				bone.updateMatrixWorld();
+				bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
 
 
 			}
 			}
 
 
-			if ( options.preservePosition ) {
+			bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+			bone.updateMatrixWorld();
 
 
-				for ( let i = 0; i < bones.length; ++ i ) {
+		}
 
 
-					bone = bones[ i ];
-					name = options.names[ bone.name ] || bone.name;
+		if ( options.preservePosition ) {
 
 
-					if ( name !== options.hip ) {
+			for ( let i = 0; i < bones.length; ++ i ) {
+
+				bone = bones[ i ];
+				name = options.names[ bone.name ] || bone.name;
 
 
-						bone.position.copy( bonesPosition[ i ] );
+				if ( name !== options.hip ) {
 
 
-					}
+					bone.position.copy( bonesPosition[ i ] );
 
 
 				}
 				}
 
 
 			}
 			}
 
 
-			if ( options.preserveMatrix ) {
+		}
 
 
-				// restore matrix
-				target.updateMatrixWorld( true );
+		if ( options.preserveMatrix ) {
 
 
-			}
+			// restore matrix
+			target.updateMatrixWorld( true );
 
 
 		}
 		}
 
 
-		static retargetClip( target, source, clip, options = {} ) {
+	}
 
 
-			options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
-			options.fps = options.fps !== undefined ? options.fps : 30;
-			options.names = options.names || [];
+	function retargetClip( target, source, clip, options = {} ) {
 
 
-			if ( ! source.isObject3D ) {
+		options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
+		options.fps = options.fps !== undefined ? options.fps : 30;
+		options.names = options.names || [];
 
 
-				source = this.getHelperFromSkeleton( source );
+		if ( ! source.isObject3D ) {
 
 
-			}
+			source = this.getHelperFromSkeleton( source );
 
 
-			const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
-				delta = 1 / options.fps,
-				convertedTracks = [],
-				mixer = new THREE.AnimationMixer( source ),
-				bones = this.getBones( target.skeleton ),
-				boneDatas = [];
-			let positionOffset, bone, boneTo, boneData, name;
-			mixer.clipAction( clip ).play();
-			mixer.update( 0 );
-			source.updateMatrixWorld();
-
-			for ( let i = 0; i < numFrames; ++ i ) {
+		}
 
 
-				const time = i * delta;
-				this.retarget( target, source, options );
+		const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
+			delta = 1 / options.fps,
+			convertedTracks = [],
+			mixer = new THREE.AnimationMixer( source ),
+			bones = this.getBones( target.skeleton ),
+			boneDatas = [];
+		let positionOffset, bone, boneTo, boneData, name;
+		mixer.clipAction( clip ).play();
+		mixer.update( 0 );
+		source.updateMatrixWorld();
 
 
-				for ( let j = 0; j < bones.length; ++ j ) {
+		for ( let i = 0; i < numFrames; ++ i ) {
 
 
-					name = options.names[ bones[ j ].name ] || bones[ j ].name;
-					boneTo = this.getBoneByName( name, source.skeleton );
+			const time = i * delta;
+			this.retarget( target, source, options );
 
 
-					if ( boneTo ) {
+			for ( let j = 0; j < bones.length; ++ j ) {
 
 
-						bone = bones[ j ];
-						boneData = boneDatas[ j ] = boneDatas[ j ] || {
-							bone: bone
-						};
+				name = options.names[ bones[ j ].name ] || bones[ j ].name;
+				boneTo = this.getBoneByName( name, source.skeleton );
 
 
-						if ( options.hip === name ) {
+				if ( boneTo ) {
 
 
-							if ( ! boneData.pos ) {
+					bone = bones[ j ];
+					boneData = boneDatas[ j ] = boneDatas[ j ] || {
+						bone: bone
+					};
 
 
-								boneData.pos = {
-									times: new Float32Array( numFrames ),
-									values: new Float32Array( numFrames * 3 )
-								};
+					if ( options.hip === name ) {
 
 
-							}
+						if ( ! boneData.pos ) {
 
 
-							if ( options.useFirstFramePosition ) {
+							boneData.pos = {
+								times: new Float32Array( numFrames ),
+								values: new Float32Array( numFrames * 3 )
+							};
 
 
-								if ( i === 0 ) {
+						}
 
 
-									positionOffset = bone.position.clone();
+						if ( options.useFirstFramePosition ) {
 
 
-								}
+							if ( i === 0 ) {
 
 
-								bone.position.sub( positionOffset );
+								positionOffset = bone.position.clone();
 
 
 							}
 							}
 
 
-							boneData.pos.times[ i ] = time;
-							bone.position.toArray( boneData.pos.values, i * 3 );
+							bone.position.sub( positionOffset );
 
 
 						}
 						}
 
 
-						if ( ! boneData.quat ) {
+						boneData.pos.times[ i ] = time;
+						bone.position.toArray( boneData.pos.values, i * 3 );
 
 
-							boneData.quat = {
-								times: new Float32Array( numFrames ),
-								values: new Float32Array( numFrames * 4 )
-							};
+					}
 
 
-						}
+					if ( ! boneData.quat ) {
 
 
-						boneData.quat.times[ i ] = time;
-						bone.quaternion.toArray( boneData.quat.values, i * 4 );
+						boneData.quat = {
+							times: new Float32Array( numFrames ),
+							values: new Float32Array( numFrames * 4 )
+						};
 
 
 					}
 					}
 
 
-				}
+					boneData.quat.times[ i ] = time;
+					bone.quaternion.toArray( boneData.quat.values, i * 4 );
 
 
-				mixer.update( delta );
-				source.updateMatrixWorld();
+				}
 
 
 			}
 			}
 
 
-			for ( let i = 0; i < boneDatas.length; ++ i ) {
+			mixer.update( delta );
+			source.updateMatrixWorld();
 
 
-				boneData = boneDatas[ i ];
+		}
 
 
-				if ( boneData ) {
+		for ( let i = 0; i < boneDatas.length; ++ i ) {
 
 
-					if ( boneData.pos ) {
+			boneData = boneDatas[ i ];
 
 
-						convertedTracks.push( new THREE.VectorKeyframeTrack( '.bones[' + boneData.bone.name + '].position', boneData.pos.times, boneData.pos.values ) );
+			if ( boneData ) {
 
 
-					}
+				if ( boneData.pos ) {
 
 
-					convertedTracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + boneData.bone.name + '].quaternion', boneData.quat.times, boneData.quat.values ) );
+					convertedTracks.push( new THREE.VectorKeyframeTrack( '.bones[' + boneData.bone.name + '].position', boneData.pos.times, boneData.pos.values ) );
 
 
 				}
 				}
 
 
-			}
+				convertedTracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + boneData.bone.name + '].quaternion', boneData.quat.times, boneData.quat.values ) );
 
 
-			mixer.uncacheAction( clip );
-			return new THREE.AnimationClip( clip.name, - 1, convertedTracks );
+			}
 
 
 		}
 		}
 
 
-		static getHelperFromSkeleton( skeleton ) {
-
-			const source = new THREE.SkeletonHelper( skeleton.bones[ 0 ] );
-			source.skeleton = skeleton;
-			return source;
+		mixer.uncacheAction( clip );
+		return new THREE.AnimationClip( clip.name, - 1, convertedTracks );
 
 
-		}
+	}
 
 
-		static getSkeletonOffsets( target, source, options = {} ) {
+	function getHelperFromSkeleton( skeleton ) {
 
 
-			const targetParentPos = new THREE.Vector3(),
-				targetPos = new THREE.Vector3(),
-				sourceParentPos = new THREE.Vector3(),
-				sourcePos = new THREE.Vector3(),
-				targetDir = new THREE.Vector2(),
-				sourceDir = new THREE.Vector2();
-			options.hip = options.hip !== undefined ? options.hip : 'hip';
-			options.names = options.names || {};
+		const source = new THREE.SkeletonHelper( skeleton.bones[ 0 ] );
+		source.skeleton = skeleton;
+		return source;
 
 
-			if ( ! source.isObject3D ) {
+	}
 
 
-				source = this.getHelperFromSkeleton( source );
+	function getSkeletonOffsets( target, source, options = {} ) {
 
 
-			}
+		const targetParentPos = new THREE.Vector3(),
+			targetPos = new THREE.Vector3(),
+			sourceParentPos = new THREE.Vector3(),
+			sourcePos = new THREE.Vector3(),
+			targetDir = new THREE.Vector2(),
+			sourceDir = new THREE.Vector2();
+		options.hip = options.hip !== undefined ? options.hip : 'hip';
+		options.names = options.names || {};
 
 
-			const nameKeys = Object.keys( options.names ),
-				nameValues = Object.values( options.names ),
-				sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
-				bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
-				offsets = [];
-			let bone, boneTo, name, i;
-			target.skeleton.pose();
+		if ( ! source.isObject3D ) {
 
 
-			for ( i = 0; i < bones.length; ++ i ) {
+			source = this.getHelperFromSkeleton( source );
 
 
-				bone = bones[ i ];
-				name = options.names[ bone.name ] || bone.name;
-				boneTo = this.getBoneByName( name, sourceBones );
-
-				if ( boneTo && name !== options.hip ) {
-
-					const boneParent = this.getNearestBone( bone.parent, nameKeys ),
-						boneToParent = this.getNearestBone( boneTo.parent, nameValues );
-					boneParent.updateMatrixWorld();
-					boneToParent.updateMatrixWorld();
-					targetParentPos.setFromMatrixPosition( boneParent.matrixWorld );
-					targetPos.setFromMatrixPosition( bone.matrixWorld );
-					sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
-					sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
-					targetDir.subVectors( new THREE.Vector2( targetPos.x, targetPos.y ), new THREE.Vector2( targetParentPos.x, targetParentPos.y ) ).normalize();
-					sourceDir.subVectors( new THREE.Vector2( sourcePos.x, sourcePos.y ), new THREE.Vector2( sourceParentPos.x, sourceParentPos.y ) ).normalize();
-					const laterialAngle = targetDir.angle() - sourceDir.angle();
-					const offset = new THREE.Matrix4().makeRotationFromEuler( new THREE.Euler( 0, 0, laterialAngle ) );
-					bone.matrix.multiply( offset );
-					bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
-					bone.updateMatrixWorld();
-					offsets[ name ] = offset;
+		}
 
 
-				}
+		const nameKeys = Object.keys( options.names ),
+			nameValues = Object.values( options.names ),
+			sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
+			bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
+			offsets = [];
+		let bone, boneTo, name, i;
+		target.skeleton.pose();
+
+		for ( i = 0; i < bones.length; ++ i ) {
+
+			bone = bones[ i ];
+			name = options.names[ bone.name ] || bone.name;
+			boneTo = this.getBoneByName( name, sourceBones );
+
+			if ( boneTo && name !== options.hip ) {
+
+				const boneParent = this.getNearestBone( bone.parent, nameKeys ),
+					boneToParent = this.getNearestBone( boneTo.parent, nameValues );
+				boneParent.updateMatrixWorld();
+				boneToParent.updateMatrixWorld();
+				targetParentPos.setFromMatrixPosition( boneParent.matrixWorld );
+				targetPos.setFromMatrixPosition( bone.matrixWorld );
+				sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
+				sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
+				targetDir.subVectors( new THREE.Vector2( targetPos.x, targetPos.y ), new THREE.Vector2( targetParentPos.x, targetParentPos.y ) ).normalize();
+				sourceDir.subVectors( new THREE.Vector2( sourcePos.x, sourcePos.y ), new THREE.Vector2( sourceParentPos.x, sourceParentPos.y ) ).normalize();
+				const laterialAngle = targetDir.angle() - sourceDir.angle();
+				const offset = new THREE.Matrix4().makeRotationFromEuler( new THREE.Euler( 0, 0, laterialAngle ) );
+				bone.matrix.multiply( offset );
+				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+				bone.updateMatrixWorld();
+				offsets[ name ] = offset;
 
 
 			}
 			}
 
 
-			return offsets;
-
 		}
 		}
 
 
-		static renameBones( skeleton, names ) {
+		return offsets;
 
 
-			const bones = this.getBones( skeleton );
+	}
 
 
-			for ( let i = 0; i < bones.length; ++ i ) {
+	function renameBones( skeleton, names ) {
 
 
-				const bone = bones[ i ];
+		const bones = this.getBones( skeleton );
 
 
-				if ( names[ bone.name ] ) {
+		for ( let i = 0; i < bones.length; ++ i ) {
 
 
-					bone.name = names[ bone.name ];
+			const bone = bones[ i ];
 
 
-				}
+			if ( names[ bone.name ] ) {
 
 
-			}
+				bone.name = names[ bone.name ];
 
 
-			return this;
+			}
 
 
 		}
 		}
 
 
-		static getBones( skeleton ) {
+		return this;
 
 
-			return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
+	}
 
 
-		}
+	function getBones( skeleton ) {
 
 
-		static getBoneByName( name, skeleton ) {
+		return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
 
 
-			for ( let i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
+	}
 
 
-				if ( name === bones[ i ].name ) return bones[ i ];
+	function getBoneByName( name, skeleton ) {
 
 
-			}
+		for ( let i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
 
 
-		}
+			if ( name === bones[ i ].name ) return bones[ i ];
 
 
-		static getNearestBone( bone, names ) {
+		}
 
 
-			while ( bone.isBone ) {
+	}
 
 
-				if ( names.indexOf( bone.name ) !== - 1 ) {
+	function getNearestBone( bone, names ) {
 
 
-					return bone;
+		while ( bone.isBone ) {
 
 
-				}
+			if ( names.indexOf( bone.name ) !== - 1 ) {
 
 
-				bone = bone.parent;
+				return bone;
 
 
 			}
 			}
 
 
+			bone = bone.parent;
+
 		}
 		}
 
 
-		static findBoneTrackData( name, tracks ) {
+	}
 
 
-			const regexp = /\[(.*)\]\.(.*)/,
-				result = {
-					name: name
-				};
+	function findBoneTrackData( name, tracks ) {
 
 
-			for ( let i = 0; i < tracks.length; ++ i ) {
+		const regexp = /\[(.*)\]\.(.*)/,
+			result = {
+				name: name
+			};
 
 
-				// 1 is track name
-				// 2 is track type
-				const trackData = regexp.exec( tracks[ i ].name );
+		for ( let i = 0; i < tracks.length; ++ i ) {
 
 
-				if ( trackData && name === trackData[ 1 ] ) {
+			// 1 is track name
+			// 2 is track type
+			const trackData = regexp.exec( tracks[ i ].name );
 
 
-					result[ trackData[ 2 ] ] = i;
+			if ( trackData && name === trackData[ 1 ] ) {
 
 
-				}
+				result[ trackData[ 2 ] ] = i;
 
 
 			}
 			}
 
 
-			return result;
-
 		}
 		}
 
 
-		static getEqualsBonesNames( skeleton, targetSkeleton ) {
+		return result;
 
 
-			const sourceBones = this.getBones( skeleton ),
-				targetBones = this.getBones( targetSkeleton ),
-				bones = [];
+	}
 
 
-			search: for ( let i = 0; i < sourceBones.length; i ++ ) {
+	function getEqualsBonesNames( skeleton, targetSkeleton ) {
 
 
-				const boneName = sourceBones[ i ].name;
+		const sourceBones = this.getBones( skeleton ),
+			targetBones = this.getBones( targetSkeleton ),
+			bones = [];
 
 
-				for ( let j = 0; j < targetBones.length; j ++ ) {
+		search: for ( let i = 0; i < sourceBones.length; i ++ ) {
 
 
-					if ( boneName === targetBones[ j ].name ) {
+			const boneName = sourceBones[ i ].name;
 
 
-						bones.push( boneName );
-						continue search;
+			for ( let j = 0; j < targetBones.length; j ++ ) {
 
 
-					}
+				if ( boneName === targetBones[ j ].name ) {
+
+					bones.push( boneName );
+					continue search;
 
 
 				}
 				}
 
 
 			}
 			}
 
 
-			return bones;
-
 		}
 		}
 
 
-		static clone( source ) {
+		return bones;
+
+	}
 
 
-			const sourceLookup = new Map();
-			const cloneLookup = new Map();
-			const clone = source.clone();
-			parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
+	function clone( source ) {
 
 
-				sourceLookup.set( clonedNode, sourceNode );
-				cloneLookup.set( sourceNode, clonedNode );
+		const sourceLookup = new Map();
+		const cloneLookup = new Map();
+		const clone = source.clone();
+		parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
 
 
-			} );
-			clone.traverse( function ( node ) {
+			sourceLookup.set( clonedNode, sourceNode );
+			cloneLookup.set( sourceNode, clonedNode );
 
 
-				if ( ! node.isSkinnedMesh ) return;
-				const clonedMesh = node;
-				const sourceMesh = sourceLookup.get( node );
-				const sourceBones = sourceMesh.skeleton.bones;
-				clonedMesh.skeleton = sourceMesh.skeleton.clone();
-				clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
-				clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
+		} );
+		clone.traverse( function ( node ) {
 
 
-					return cloneLookup.get( bone );
+			if ( ! node.isSkinnedMesh ) return;
+			const clonedMesh = node;
+			const sourceMesh = sourceLookup.get( node );
+			const sourceBones = sourceMesh.skeleton.bones;
+			clonedMesh.skeleton = sourceMesh.skeleton.clone();
+			clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
+			clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
 
 
-				} );
-				clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
+				return cloneLookup.get( bone );
 
 
 			} );
 			} );
-			return clone;
+			clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
 
 
-		}
+		} );
+		return clone;
 
 
 	}
 	}
 
 
@@ -495,6 +491,17 @@
 
 
 	}
 	}
 
 
-	THREE.SkeletonUtils = SkeletonUtils;
+	THREE.SkeletonUtils = {};
+	THREE.SkeletonUtils.clone = clone;
+	THREE.SkeletonUtils.findBoneTrackData = findBoneTrackData;
+	THREE.SkeletonUtils.getBoneByName = getBoneByName;
+	THREE.SkeletonUtils.getBones = getBones;
+	THREE.SkeletonUtils.getEqualsBonesNames = getEqualsBonesNames;
+	THREE.SkeletonUtils.getHelperFromSkeleton = getHelperFromSkeleton;
+	THREE.SkeletonUtils.getNearestBone = getNearestBone;
+	THREE.SkeletonUtils.getSkeletonOffsets = getSkeletonOffsets;
+	THREE.SkeletonUtils.renameBones = renameBones;
+	THREE.SkeletonUtils.retarget = retarget;
+	THREE.SkeletonUtils.retargetClip = retargetClip;
 
 
 } )();
 } )();