Răsfoiți Sursa

Examples: convert Utils files to esmodules (#22284)

* NURBSUtils: convert to esmodules

* CameraUtils: convert to esmodules

* SceneUtils: convert to esmodules

* GeometryCompressionUtils: convert to esmodules

* GeometryUtils: convert to esmodules

* SkeletonUtils: convert to esmodules

* NURBSUtils: update imports

* CameraUtils: update imports

* GeometryCompressionUtils: update imports

* GeometryUtils: update imports

* SkeletonUtils: update imports
Marco Fugaro 4 ani în urmă
părinte
comite
6276d11bdf

+ 1 - 1
examples/jsm/curves/NURBSCurve.js

@@ -3,7 +3,7 @@ import {
 	Vector3,
 	Vector4
 } from '../../../build/three.module.js';
-import { NURBSUtils } from '../curves/NURBSUtils.js';
+import * as NURBSUtils from '../curves/NURBSUtils.js';
 
 /**
  * NURBS curve object

+ 1 - 1
examples/jsm/curves/NURBSSurface.js

@@ -1,7 +1,7 @@
 import {
 	Vector4
 } from '../../../build/three.module.js';
-import { NURBSUtils } from '../curves/NURBSUtils.js';
+import * as NURBSUtils from '../curves/NURBSUtils.js';
 
 /**
  * NURBS surface object

+ 281 - 273
examples/jsm/curves/NURBSUtils.js

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

+ 44 - 47
examples/jsm/utils/CameraUtils.js

@@ -13,64 +13,61 @@ const _va = /*@__PURE__*/ new Vector3(), // from pe to pa
 	_vec = /*@__PURE__*/ new Vector3(), // temporary vector
 	_quat = /*@__PURE__*/ new Quaternion(); // temporary quaternion
 
-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 ) {
+/** 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 */
+function frameCorners( camera, bottomLeftCorner, bottomRightCorner, topLeftCorner, estimateViewFrustum = false ) {
 
-		const pa = bottomLeftCorner, pb = bottomRightCorner, pc = topLeftCorner;
-		const pe = camera.position; // eye position
-		const n = camera.near; // distance of near clipping plane
-		const f = camera.far; //distance of far clipping plane
+	const pa = bottomLeftCorner, pb = bottomRightCorner, pc = topLeftCorner;
+	const pe = camera.position; // eye position
+	const n = camera.near; // distance of near clipping plane
+	const f = camera.far; //distance of far clipping plane
 
-		_vr.copy( pb ).sub( pa ).normalize();
-		_vu.copy( pc ).sub( pa ).normalize();
-		_vn.crossVectors( _vr, _vu ).normalize();
+	_vr.copy( pb ).sub( pa ).normalize();
+	_vu.copy( pc ).sub( pa ).normalize();
+	_vn.crossVectors( _vr, _vu ).normalize();
 
-		_va.copy( pa ).sub( pe ); // from pe to pa
-		_vb.copy( pb ).sub( pe ); // from pe to pb
-		_vc.copy( pc ).sub( pe ); // from pe to pc
+	_va.copy( pa ).sub( pe ); // from pe to pa
+	_vb.copy( pb ).sub( pe ); // from pe to pb
+	_vc.copy( pc ).sub( pe ); // from pe to pc
 
-		const d = - _va.dot( _vn );	// distance from eye to screen
-		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 b = _vu.dot( _va ) * n / d; // distance to bottom screen edge
-		const t = _vu.dot( _vc ) * n / d; // distance to top screen edge
+	const d = - _va.dot( _vn );	// distance from eye to screen
+	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 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
-		_quat.setFromUnitVectors( _vec.set( 0, 1, 0 ), _vu );
-		camera.quaternion.setFromUnitVectors( _vec.set( 0, 0, 1 ).applyQuaternion( _quat ), _vn ).multiply( _quat );
+	// Set the camera rotation to match the focal plane to the corners' plane
+	_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.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();
+	// 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
-		if ( estimateViewFrustum ) {
+	// FoV estimation to fix frustum culling
+	if ( estimateViewFrustum ) {
 
-			// Set fieldOfView to a conservative estimate
-			// to make frustum tall/wide enough to encompass it
-			camera.fov =
-				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 =
+			MathUtils.RAD2DEG / Math.min( 1.0, camera.aspect ) *
+			Math.atan( ( _vec.copy( pb ).sub( pa ).length() +
+							( _vec.copy( pc ).sub( pa ).length() ) ) / _va.length() );
 
 	}
 
 }
 
-export { CameraUtils };
+export { frameCorners };

+ 391 - 641
examples/jsm/utils/GeometryCompressionUtils.js

@@ -9,907 +9,657 @@ import {
 	BufferAttribute,
 	Matrix3,
 	Matrix4,
-	MeshPhongMaterial,
-	ShaderChunk,
-	ShaderLib,
-	UniformsUtils,
 	Vector3
 } from '../../../build/three.module.js';
+import { PackedPhongMaterial } from './PackedPhongMaterial.js';
 
-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;
+/**
+ * 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"
+ *
+ */
+function compressNormals( mesh, encodeMethod ) {
 
-		if ( ! normal ) {
+	if ( ! mesh.geometry ) {
 
-			console.error( 'Geometry must contain normal attribute. ' );
+		console.error( 'Mesh must contain geometry. ' );
 
-		}
+	}
 
-		if ( normal.isPacked ) return;
+	const normal = mesh.geometry.attributes.normal;
 
-		if ( normal.itemSize != 3 ) {
+	if ( ! normal ) {
 
-			console.error( 'normal.itemSize is not 3, which cannot be encoded. ' );
+		console.error( 'Geometry must contain normal attribute. ' );
 
-		}
+	}
 
-		const array = normal.array;
-		const count = normal.count;
+	if ( normal.isPacked ) return;
 
-		let result;
-		if ( encodeMethod == 'DEFAULT' ) {
+	if ( normal.itemSize != 3 ) {
 
-			// TODO: Add 1 byte to the result, making the encoded length to be 4 bytes.
-			result = new Uint8Array( count * 3 );
+		console.error( 'normal.itemSize is not 3, which cannot be encoded. ' );
 
-			for ( let idx = 0; idx < array.length; idx += 3 ) {
+	}
 
-				const encoded = EncodingFuncs.defaultEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
+	const array = normal.array;
+	const count = normal.count;
 
-				result[ idx + 0 ] = encoded[ 0 ];
-				result[ idx + 1 ] = encoded[ 1 ];
-				result[ idx + 2 ] = encoded[ 2 ];
+	let result;
+	if ( encodeMethod == 'DEFAULT' ) {
 
-			}
+		// TODO: Add 1 byte to the result, making the encoded length to be 4 bytes.
+		result = new Uint8Array( count * 3 );
 
-			mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 3, true ) );
-			mesh.geometry.attributes.normal.bytes = result.length * 1;
+		for ( let idx = 0; idx < array.length; idx += 3 ) {
 
-		} else if ( encodeMethod == 'OCT1Byte' ) {
+			const encoded = defaultEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
 
-			/**
-			* 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[ idx + 0 ] = encoded[ 0 ];
+			result[ idx + 1 ] = encoded[ 1 ];
+			result[ idx + 2 ] = encoded[ 2 ];
 
-			result = new Int8Array( count * 2 );
+		}
 
-			for ( let idx = 0; idx < array.length; idx += 3 ) {
+		mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 3, true ) );
+		mesh.geometry.attributes.normal.bytes = result.length * 1;
 
-				const encoded = EncodingFuncs.octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
+	} else if ( encodeMethod == 'OCT1Byte' ) {
 
-				result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
-				result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
+		/**
+		* 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 );
 
-			mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
-			mesh.geometry.attributes.normal.bytes = result.length * 1;
+		for ( let idx = 0; idx < array.length; idx += 3 ) {
 
-		} else if ( encodeMethod == 'OCT2Byte' ) {
+			const encoded = octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
 
-			result = new Int16Array( count * 2 );
+			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.octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 2 );
+		mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
+		mesh.geometry.attributes.normal.bytes = result.length * 1;
 
-				result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
-				result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
+	} else if ( encodeMethod == 'OCT2Byte' ) {
 
-			}
+		result = new Int16Array( count * 2 );
 
-			mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
-			mesh.geometry.attributes.normal.bytes = result.length * 2;
+		for ( let idx = 0; idx < array.length; idx += 3 ) {
 
-		} else if ( encodeMethod == 'ANGLES' ) {
+			const encoded = octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 2 );
 
-			result = new Uint16Array( count * 2 );
+			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 ] );
+		mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
+		mesh.geometry.attributes.normal.bytes = result.length * 2;
 
-				result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
-				result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
+	} else if ( encodeMethod == 'ANGLES' ) {
 
-			}
+		result = new Uint16Array( count * 2 );
 
-			mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
-			mesh.geometry.attributes.normal.bytes = result.length * 2;
+		for ( let idx = 0; idx < array.length; idx += 3 ) {
 
-		} else {
+			const encoded = anglesEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ] );
 
-			console.error( 'Unrecognized encoding method, should be `DEFAULT` or `ANGLES` or `OCT`. ' );
+			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;
+		mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
+		mesh.geometry.attributes.normal.bytes = result.length * 2;
 
-		// modify material
-		if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+	} else {
 
-			mesh.material = new PackedPhongMaterial().copy( mesh.material );
+		console.error( 'Unrecognized encoding method, should be `DEFAULT` or `ANGLES` or `OCT`. ' );
 
-		}
+	}
 
-		if ( encodeMethod == 'ANGLES' ) {
+	mesh.geometry.attributes.normal.needsUpdate = true;
+	mesh.geometry.attributes.normal.isPacked = true;
+	mesh.geometry.attributes.normal.packingMethod = encodeMethod;
 
-			mesh.material.defines.USE_PACKED_NORMAL = 0;
+	// modify material
+	if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
 
-		}
+		mesh.material = new PackedPhongMaterial().copy( mesh.material );
 
-		if ( encodeMethod == 'OCT1Byte' ) {
+	}
 
-			mesh.material.defines.USE_PACKED_NORMAL = 1;
+	if ( encodeMethod == 'ANGLES' ) {
 
-		}
+		mesh.material.defines.USE_PACKED_NORMAL = 0;
 
-		if ( encodeMethod == 'OCT2Byte' ) {
+	}
 
-			mesh.material.defines.USE_PACKED_NORMAL = 1;
+	if ( encodeMethod == 'OCT1Byte' ) {
 
-		}
+		mesh.material.defines.USE_PACKED_NORMAL = 1;
 
-		if ( encodeMethod == 'DEFAULT' ) {
+	}
 
-			mesh.material.defines.USE_PACKED_NORMAL = 2;
+	if ( encodeMethod == 'OCT2Byte' ) {
 
-		}
+		mesh.material.defines.USE_PACKED_NORMAL = 1;
 
 	}
 
+	if ( encodeMethod == 'DEFAULT' ) {
 
-	/**
-		 * 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
-		 *
-		 */
-	static compressPositions( mesh ) {
+		mesh.material.defines.USE_PACKED_NORMAL = 2;
 
-		if ( ! mesh.geometry ) {
+	}
 
-			console.error( 'Mesh must contain geometry. ' );
+}
 
-		}
 
-		const position = mesh.geometry.attributes.position;
+/**
+	 * 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
+	 *
+	 */
+function compressPositions( mesh ) {
 
-		if ( ! position ) {
+	if ( ! mesh.geometry ) {
 
-			console.error( 'Geometry must contain position attribute. ' );
+		console.error( 'Mesh must contain geometry. ' );
 
-		}
+	}
 
-		if ( position.isPacked ) return;
+	const position = mesh.geometry.attributes.position;
 
-		if ( position.itemSize != 3 ) {
+	if ( ! position ) {
 
-			console.error( 'position.itemSize is not 3, which cannot be packed. ' );
+		console.error( 'Geometry must contain position attribute. ' );
 
-		}
+	}
 
-		const array = position.array;
-		const encodingBytes = 2;
+	if ( position.isPacked ) return;
 
-		const result = EncodingFuncs.quantizedEncode( array, encodingBytes );
+	if ( position.itemSize != 3 ) {
 
-		const quantized = result.quantized;
-		const decodeMat = result.decodeMat;
+		console.error( 'position.itemSize is not 3, which cannot be packed. ' );
 
-		// IMPORTANT: calculate original geometry bounding info first, before updating packed positions
-		if ( mesh.geometry.boundingBox == null ) mesh.geometry.computeBoundingBox();
-		if ( mesh.geometry.boundingSphere == null ) mesh.geometry.computeBoundingSphere();
+	}
 
-		mesh.geometry.setAttribute( 'position', new BufferAttribute( quantized, 3 ) );
-		mesh.geometry.attributes.position.isPacked = true;
-		mesh.geometry.attributes.position.needsUpdate = true;
-		mesh.geometry.attributes.position.bytes = quantized.length * encodingBytes;
+	const array = position.array;
+	const encodingBytes = 2;
 
-		// modify material
-		if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+	const result = quantizedEncode( array, encodingBytes );
 
-			mesh.material = new PackedPhongMaterial().copy( mesh.material );
+	const quantized = result.quantized;
+	const decodeMat = result.decodeMat;
 
-		}
+	// IMPORTANT: calculate original geometry bounding info first, before updating packed positions
+	if ( mesh.geometry.boundingBox == null ) mesh.geometry.computeBoundingBox();
+	if ( mesh.geometry.boundingSphere == null ) mesh.geometry.computeBoundingSphere();
 
-		mesh.material.defines.USE_PACKED_POSITION = 0;
+	mesh.geometry.setAttribute( 'position', new BufferAttribute( quantized, 3 ) );
+	mesh.geometry.attributes.position.isPacked = true;
+	mesh.geometry.attributes.position.needsUpdate = true;
+	mesh.geometry.attributes.position.bytes = quantized.length * encodingBytes;
 
-		mesh.material.uniforms.quantizeMatPos.value = decodeMat;
-		mesh.material.uniforms.quantizeMatPos.needsUpdate = true;
+	// modify material
+	if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+
+		mesh.material = new PackedPhongMaterial().copy( mesh.material );
 
 	}
 
-	/**
-		 * 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
-		 *
-		 */
-	static compressUvs( mesh ) {
+	mesh.material.defines.USE_PACKED_POSITION = 0;
 
-		if ( ! mesh.geometry ) {
+	mesh.material.uniforms.quantizeMatPos.value = decodeMat;
+	mesh.material.uniforms.quantizeMatPos.needsUpdate = true;
 
-			console.error( 'Mesh must contain geometry property. ' );
+}
 
-		}
+/**
+ * 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
+ *
+ */
+function compressUvs( mesh ) {
 
-		const uvs = mesh.geometry.attributes.uv;
+	if ( ! mesh.geometry ) {
 
-		if ( ! uvs ) {
+		console.error( 'Mesh must contain geometry property. ' );
 
-			console.error( 'Geometry must contain uv attribute. ' );
+	}
 
-		}
+	const uvs = mesh.geometry.attributes.uv;
 
-		if ( uvs.isPacked ) return;
+	if ( ! uvs ) {
 
-		const range = { min: Infinity, max: - Infinity };
+		console.error( 'Geometry must contain uv attribute. ' );
 
-		const array = uvs.array;
+	}
 
-		for ( let i = 0; i < array.length; i ++ ) {
+	if ( uvs.isPacked ) return;
 
-			range.min = Math.min( range.min, array[ i ] );
-			range.max = Math.max( range.max, array[ i ] );
+	const range = { min: Infinity, max: - Infinity };
 
-		}
+	const array = uvs.array;
 
-		let result;
+	for ( let i = 0; i < array.length; i ++ ) {
 
-		if ( range.min >= - 1.0 && range.max <= 1.0 ) {
+		range.min = Math.min( range.min, array[ i ] );
+		range.max = Math.max( range.max, array[ i ] );
 
-			// use default encoding method
-			result = new Uint16Array( array.length );
+	}
 
-			for ( let i = 0; i < array.length; i += 2 ) {
+	let result;
 
-				const encoded = EncodingFuncs.defaultEncode( array[ i ], array[ i + 1 ], 0, 2 );
+	if ( range.min >= - 1.0 && range.max <= 1.0 ) {
 
-				result[ i ] = encoded[ 0 ];
-				result[ i + 1 ] = encoded[ 1 ];
+		// use default encoding method
+		result = new Uint16Array( array.length );
 
-			}
+		for ( let i = 0; i < array.length; i += 2 ) {
 
-			mesh.geometry.setAttribute( 'uv', new BufferAttribute( result, 2, true ) );
-			mesh.geometry.attributes.uv.isPacked = true;
-			mesh.geometry.attributes.uv.needsUpdate = true;
-			mesh.geometry.attributes.uv.bytes = result.length * 2;
+			const encoded = defaultEncode( array[ i ], array[ i + 1 ], 0, 2 );
 
-			if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+			result[ i ] = encoded[ 0 ];
+			result[ i + 1 ] = encoded[ 1 ];
 
-				mesh.material = new PackedPhongMaterial().copy( mesh.material );
+		}
 
-			}
+		mesh.geometry.setAttribute( 'uv', new BufferAttribute( result, 2, true ) );
+		mesh.geometry.attributes.uv.isPacked = true;
+		mesh.geometry.attributes.uv.needsUpdate = true;
+		mesh.geometry.attributes.uv.bytes = result.length * 2;
 
-			mesh.material.defines.USE_PACKED_UV = 0;
+		if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
 
-		} else {
+			mesh.material = new PackedPhongMaterial().copy( mesh.material );
 
-			// use quantized encoding method
-			result = EncodingFuncs.quantizedEncodeUV( array, 2 );
+		}
 
-			mesh.geometry.setAttribute( 'uv', new 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;
+		mesh.material.defines.USE_PACKED_UV = 0;
 
-			if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
+	} else {
 
-				mesh.material = new PackedPhongMaterial().copy( mesh.material );
+		// use quantized encoding method
+		result = quantizedEncodeUV( array, 2 );
 
-			}
+		mesh.geometry.setAttribute( 'uv', new 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;
 
-			mesh.material.defines.USE_PACKED_UV = 1;
+		if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
 
-			mesh.material.uniforms.quantizeMatUV.value = result.decodeMat;
-			mesh.material.uniforms.quantizeMatUV.needsUpdate = true;
+			mesh.material = new PackedPhongMaterial().copy( mesh.material );
 
 		}
 
+		mesh.material.defines.USE_PACKED_UV = 1;
+
+		mesh.material.uniforms.quantizeMatUV.value = result.decodeMat;
+		mesh.material.uniforms.quantizeMatUV.needsUpdate = true;
+
 	}
 
 }
 
-class EncodingFuncs {
 
-	static defaultEncode( x, y, z, bytes ) {
+// Encoding functions
 
-		if ( bytes == 1 ) {
+function defaultEncode( x, y, z, bytes ) {
 
-			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 ] );
+	if ( bytes == 1 ) {
 
-		} else if ( bytes == 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 ] );
 
-			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 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 ] );
 
-			console.error( 'number of bytes must be 1 or 2' );
+	} else {
 
-		}
+		console.error( 'number of bytes must be 1 or 2' );
 
 	}
 
-	static defaultDecode( array, bytes ) {
+}
 
-		if ( bytes == 1 ) {
+function defaultDecode( array, bytes ) {
 
-			return [
-				( ( array[ 0 ] / 255 ) * 2.0 ) - 1.0,
-				( ( array[ 1 ] / 255 ) * 2.0 ) - 1.0,
-				( ( array[ 2 ] / 255 ) * 2.0 ) - 1.0,
-			];
+	if ( bytes == 1 ) {
 
-		} else if ( bytes == 2 ) {
+		return [
+			( ( array[ 0 ] / 255 ) * 2.0 ) - 1.0,
+			( ( array[ 1 ] / 255 ) * 2.0 ) - 1.0,
+			( ( array[ 2 ] / 255 ) * 2.0 ) - 1.0,
+		];
 
-			return [
-				( ( array[ 0 ] / 65535 ) * 2.0 ) - 1.0,
-				( ( array[ 1 ] / 65535 ) * 2.0 ) - 1.0,
-				( ( array[ 2 ] / 65535 ) * 2.0 ) - 1.0,
-			];
+	} else if ( bytes == 2 ) {
 
-		} else {
+		return [
+			( ( array[ 0 ] / 65535 ) * 2.0 ) - 1.0,
+			( ( array[ 1 ] / 65535 ) * 2.0 ) - 1.0,
+			( ( array[ 2 ] / 65535 ) * 2.0 ) - 1.0,
+		];
 
-			console.error( 'number of bytes must be 1 or 2' );
+	} else {
 
-		}
+		console.error( 'number of bytes must be 1 or 2' );
 
 	}
 
-	// for `Angles` encoding
-	static anglesEncode( x, y, z ) {
+}
 
-		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 `Angles` encoding
+function anglesEncode( x, y, z ) {
 
-	}
+	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
-	static octEncodeBest( x, y, z, bytes ) {
+}
 
-		let oct, dec, best, currentCos, bestCos;
+// for `Octahedron` encoding
+function octEncodeBest( x, y, z, bytes ) {
 
-		// Test various combinations of ceil and floor
-		// to minimize rounding errors
-		best = oct = octEncodeVec3( x, y, z, 'floor', 'floor' );
-		dec = octDecodeVec2( oct );
-		bestCos = dot( x, y, z, dec );
+	let oct, dec, best, currentCos, bestCos;
 
-		oct = octEncodeVec3( x, y, z, 'ceil', 'floor' );
-		dec = octDecodeVec2( oct );
-		currentCos = dot( x, y, z, dec );
+	// Test various combinations of ceil and floor
+	// to minimize rounding errors
+	best = oct = octEncodeVec3( x, y, z, 'floor', 'floor' );
+	dec = octDecodeVec2( oct );
+	bestCos = dot( x, y, z, dec );
 
-		if ( currentCos > bestCos ) {
+	oct = octEncodeVec3( x, y, z, 'ceil', 'floor' );
+	dec = octDecodeVec2( oct );
+	currentCos = dot( x, y, z, dec );
 
-			best = oct;
-			bestCos = currentCos;
+	if ( currentCos > bestCos ) {
 
-		}
+		best = oct;
+		bestCos = currentCos;
 
-		oct = octEncodeVec3( x, y, z, 'floor', 'ceil' );
-		dec = octDecodeVec2( oct );
-		currentCos = dot( x, y, z, dec );
+	}
 
-		if ( currentCos > bestCos ) {
+	oct = octEncodeVec3( x, y, z, 'floor', 'ceil' );
+	dec = octDecodeVec2( oct );
+	currentCos = dot( x, y, z, dec );
 
-			best = oct;
-			bestCos = currentCos;
+	if ( currentCos > bestCos ) {
 
-		}
+		best = oct;
+		bestCos = currentCos;
 
-		oct = octEncodeVec3( x, y, z, 'ceil', 'ceil' );
-		dec = octDecodeVec2( oct );
-		currentCos = dot( x, y, z, dec );
+	}
 
-		if ( currentCos > bestCos ) {
+	oct = octEncodeVec3( x, y, z, 'ceil', 'ceil' );
+	dec = octDecodeVec2( oct );
+	currentCos = dot( x, y, z, dec );
 
-			best = oct;
+	if ( currentCos > bestCos ) {
 
-		}
+		best = oct;
 
-		return best;
+	}
 
-		function octEncodeVec3( x0, y0, z0, xfunc, yfunc ) {
+	return best;
 
-			let x = x0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
-			let y = y0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
+	function octEncodeVec3( x0, y0, z0, xfunc, yfunc ) {
 
-			if ( z < 0 ) {
+		let x = x0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
+		let y = y0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
 
-				const tempx = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
-				const tempy = ( 1 - Math.abs( x ) ) * ( y >= 0 ? 1 : - 1 );
+		if ( z < 0 ) {
 
-				x = tempx;
-				y = tempy;
+			const tempx = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
+			const tempy = ( 1 - Math.abs( x ) ) * ( y >= 0 ? 1 : - 1 );
 
-				let diff = 1 - Math.abs( x ) - Math.abs( y );
-				if ( diff > 0 ) {
+			x = tempx;
+			y = tempy;
 
-					diff += 0.001;
-					x += x > 0 ? diff / 2 : - diff / 2;
-					y += y > 0 ? diff / 2 : - diff / 2;
+			let diff = 1 - Math.abs( x ) - Math.abs( y );
+			if ( diff > 0 ) {
 
-				}
+				diff += 0.001;
+				x += x > 0 ? diff / 2 : - diff / 2;
+				y += y > 0 ? diff / 2 : - diff / 2;
 
 			}
 
-			if ( bytes == 1 ) {
-
-				return new Int8Array( [
-					Math[ xfunc ]( x * 127.5 + ( x < 0 ? 1 : 0 ) ),
-					Math[ yfunc ]( y * 127.5 + ( y < 0 ? 1 : 0 ) )
-				] );
+		}
 
-			}
+		if ( bytes == 1 ) {
 
-			if ( bytes == 2 ) {
+			return new Int8Array( [
+				Math[ xfunc ]( x * 127.5 + ( x < 0 ? 1 : 0 ) ),
+				Math[ yfunc ]( y * 127.5 + ( y < 0 ? 1 : 0 ) )
+			] );
 
-				return new Int16Array( [
-					Math[ xfunc ]( x * 32767.5 + ( x < 0 ? 1 : 0 ) ),
-					Math[ yfunc ]( y * 32767.5 + ( y < 0 ? 1 : 0 ) )
-				] );
+		}
 
-			}
+		if ( bytes == 2 ) {
 
+			return new Int16Array( [
+				Math[ xfunc ]( x * 32767.5 + ( x < 0 ? 1 : 0 ) ),
+				Math[ yfunc ]( y * 32767.5 + ( y < 0 ? 1 : 0 ) )
+			] );
 
 		}
 
-		function octDecodeVec2( oct ) {
-
-			let x = oct[ 0 ];
-			let y = oct[ 1 ];
 
-			if ( bytes == 1 ) {
+	}
 
-				x /= x < 0 ? 127 : 128;
-				y /= y < 0 ? 127 : 128;
+	function octDecodeVec2( oct ) {
 
-			} else if ( bytes == 2 ) {
+		let x = oct[ 0 ];
+		let y = oct[ 1 ];
 
-				x /= x < 0 ? 32767 : 32768;
-				y /= y < 0 ? 32767 : 32768;
+		if ( bytes == 1 ) {
 
-			}
+			x /= x < 0 ? 127 : 128;
+			y /= y < 0 ? 127 : 128;
 
+		} else if ( bytes == 2 ) {
 
-			const z = 1 - Math.abs( x ) - Math.abs( y );
+			x /= x < 0 ? 32767 : 32768;
+			y /= y < 0 ? 32767 : 32768;
 
-			if ( z < 0 ) {
+		}
 
-				const tmpx = x;
-				x = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
-				y = ( 1 - Math.abs( tmpx ) ) * ( y >= 0 ? 1 : - 1 );
 
-			}
+		const z = 1 - Math.abs( x ) - Math.abs( y );
 
-			const length = Math.sqrt( x * x + y * y + z * z );
+		if ( z < 0 ) {
 
-			return [
-				x / length,
-				y / length,
-				z / length
-			];
+			const tmpx = x;
+			x = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
+			y = ( 1 - Math.abs( tmpx ) ) * ( y >= 0 ? 1 : - 1 );
 
 		}
 
-		function dot( x, y, z, vec3 ) {
+		const length = Math.sqrt( x * x + y * y + z * z );
 
-			return x * vec3[ 0 ] + y * vec3[ 1 ] + z * vec3[ 2 ];
-
-		}
+		return [
+			x / length,
+			y / length,
+			z / length
+		];
 
 	}
 
-	static quantizedEncode( array, bytes ) {
-
-		let quantized, segments;
-
-		if ( bytes == 1 ) {
-
-			quantized = new Uint8Array( array.length );
-			segments = 255;
-
-		} else if ( bytes == 2 ) {
-
-			quantized = new Uint16Array( array.length );
-			segments = 65535;
+	function dot( x, y, z, vec3 ) {
 
-		} else {
+		return x * vec3[ 0 ] + y * vec3[ 1 ] + z * vec3[ 2 ];
 
-			console.error( 'number of bytes error! ' );
+	}
 
-		}
+}
 
-		const decodeMat = new Matrix4();
+function quantizedEncode( array, bytes ) {
 
-		const min = new Float32Array( 3 );
-		const max = new Float32Array( 3 );
+	let quantized, segments;
 
-		min[ 0 ] = min[ 1 ] = min[ 2 ] = Number.MAX_VALUE;
-		max[ 0 ] = max[ 1 ] = max[ 2 ] = - Number.MAX_VALUE;
+	if ( bytes == 1 ) {
 
-		for ( let i = 0; i < array.length; i += 3 ) {
+		quantized = new Uint8Array( array.length );
+		segments = 255;
 
-			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 ] );
+	} else if ( bytes == 2 ) {
 
-		}
+		quantized = new Uint16Array( array.length );
+		segments = 65535;
 
-		decodeMat.scale( new Vector3(
-			( max[ 0 ] - min[ 0 ] ) / segments,
-			( max[ 1 ] - min[ 1 ] ) / segments,
-			( max[ 2 ] - min[ 2 ] ) / segments
-		) );
+	} else {
 
-		decodeMat.elements[ 12 ] = min[ 0 ];
-		decodeMat.elements[ 13 ] = min[ 1 ];
-		decodeMat.elements[ 14 ] = min[ 2 ];
-
-		decodeMat.transpose();
+		console.error( 'number of bytes error! ' );
 
+	}
 
-		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
-		] );
+	const decodeMat = new Matrix4();
 
-		for ( let i = 0; i < array.length; i += 3 ) {
+	const min = new Float32Array( 3 );
+	const max = new Float32Array( 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 ] );
+	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 Vector3(
+		( max[ 0 ] - min[ 0 ] ) / segments,
+		( max[ 1 ] - min[ 1 ] ) / segments,
+		( max[ 2 ] - min[ 2 ] ) / segments
+	) );
 
-		let quantized, segments;
+	decodeMat.elements[ 12 ] = min[ 0 ];
+	decodeMat.elements[ 13 ] = min[ 1 ];
+	decodeMat.elements[ 14 ] = min[ 2 ];
 
-		if ( bytes == 1 ) {
+	decodeMat.transpose();
 
-			quantized = new Uint8Array( array.length );
-			segments = 255;
 
-		} else if ( bytes == 2 ) {
+	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
+	] );
 
-			quantized = new Uint16Array( array.length );
-			segments = 65535;
+	for ( let i = 0; i < array.length; i += 3 ) {
 
-		} else {
+		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 ] );
 
-			console.error( 'number of bytes error! ' );
+	}
 
-		}
+	return {
+		quantized: quantized,
+		decodeMat: decodeMat
+	};
 
-		const decodeMat = new Matrix3();
+}
 
-		const min = new Float32Array( 2 );
-		const max = new Float32Array( 2 );
+function quantizedEncodeUV( array, bytes ) {
 
-		min[ 0 ] = min[ 1 ] = Number.MAX_VALUE;
-		max[ 0 ] = max[ 1 ] = - Number.MAX_VALUE;
+	let quantized, segments;
 
-		for ( let i = 0; i < array.length; i += 2 ) {
+	if ( bytes == 1 ) {
 
-			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 Uint8Array( array.length );
+		segments = 255;
 
-		}
+	} else if ( bytes == 2 ) {
 
-		decodeMat.scale(
-			( max[ 0 ] - min[ 0 ] ) / segments,
-			( max[ 1 ] - min[ 1 ] ) / segments
-		);
+		quantized = new Uint16Array( array.length );
+		segments = 65535;
 
-		decodeMat.elements[ 6 ] = min[ 0 ];
-		decodeMat.elements[ 7 ] = min[ 1 ];
+	} else {
 
-		decodeMat.transpose();
+		console.error( 'number of bytes error! ' );
 
-		const multiplier = new Float32Array( [
-			max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0,
-			max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0
-		] );
+	}
 
-		for ( let i = 0; i < array.length; i += 2 ) {
+	const decodeMat = new Matrix3();
 
-			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 min = new Float32Array( 2 );
+	const max = new Float32Array( 2 );
 
-		}
+	min[ 0 ] = min[ 1 ] = Number.MAX_VALUE;
+	max[ 0 ] = max[ 1 ] = - Number.MAX_VALUE;
 
-		return {
-			quantized: quantized,
-			decodeMat: decodeMat
-		};
+	for ( let i = 0; i < array.length; i += 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 ] );
 
-}
+	}
 
+	decodeMat.scale(
+		( max[ 0 ] - min[ 0 ] ) / segments,
+		( max[ 1 ] - min[ 1 ] ) / segments
+	);
 
+	decodeMat.elements[ 6 ] = min[ 0 ];
+	decodeMat.elements[ 7 ] = min[ 1 ];
 
-/**
- * `PackedPhongMaterial` inherited from THREE.MeshPhongMaterial
- *
- * @param {Object} parameters
- */
-class PackedPhongMaterial extends MeshPhongMaterial {
+	decodeMat.transpose();
 
-	constructor( parameters ) {
+	const multiplier = new Float32Array( [
+		max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0,
+		max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0
+	] );
 
-		super();
+	for ( let i = 0; i < array.length; i += 2 ) {
 
-		this.defines = {};
-		this.type = 'PackedPhongMaterial';
-		this.uniforms = UniformsUtils.merge( [
+		quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
+		quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
 
-			ShaderLib.phong.uniforms,
+	}
 
-			{
-				quantizeMatPos: { value: null },
-				quantizeMatUV: { value: null }
-			}
+	return {
+		quantized: quantized,
+		decodeMat: decodeMat
+	};
 
-		] );
-
-		this.vertexShader = [
-			'#define PHONG',
-
-			'varying vec3 vViewPosition;',
-
-			'#ifndef FLAT_SHADED',
-			'varying vec3 vNormal;',
-			'#endif',
-
-			ShaderChunk.common,
-			ShaderChunk.uv_pars_vertex,
-			ShaderChunk.uv2_pars_vertex,
-			ShaderChunk.displacementmap_pars_vertex,
-			ShaderChunk.envmap_pars_vertex,
-			ShaderChunk.color_pars_vertex,
-			ShaderChunk.fog_pars_vertex,
-			ShaderChunk.morphtarget_pars_vertex,
-			ShaderChunk.skinning_pars_vertex,
-			ShaderChunk.shadowmap_pars_vertex,
-			ShaderChunk.logdepthbuf_pars_vertex,
-			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() {',
-
-			ShaderChunk.uv_vertex,
-
-			`#ifdef USE_UV
-					#ifdef USE_PACKED_UV
-						vUv = decodeUV(vUv);
-					#endif
-				#endif`,
-
-			ShaderChunk.uv2_vertex,
-			ShaderChunk.color_vertex,
-			ShaderChunk.beginnormal_vertex,
-
-			`#ifdef USE_PACKED_NORMAL
-					objectNormal = decodeNormal(objectNormal);
-				#endif
-
-				#ifdef USE_TANGENT
-					vec3 objectTangent = vec3( tangent.xyz );
-				#endif
-				`,
-
-			ShaderChunk.morphnormal_vertex,
-			ShaderChunk.skinbase_vertex,
-			ShaderChunk.skinnormal_vertex,
-			ShaderChunk.defaultnormal_vertex,
-
-			'#ifndef FLAT_SHADED',
-			'	vNormal = normalize( transformedNormal );',
-			'#endif',
-
-			ShaderChunk.begin_vertex,
-
-			`#ifdef USE_PACKED_POSITION
-					#if USE_PACKED_POSITION == 0
-						transformed = ( vec4(transformed, 1.0) * quantizeMatPos ).xyz;
-					#endif
-				#endif`,
-
-			ShaderChunk.morphtarget_vertex,
-			ShaderChunk.skinning_vertex,
-			ShaderChunk.displacementmap_vertex,
-			ShaderChunk.project_vertex,
-			ShaderChunk.logdepthbuf_vertex,
-			ShaderChunk.clipping_planes_vertex,
-
-			'vViewPosition = - mvPosition.xyz;',
-
-			ShaderChunk.worldpos_vertex,
-			ShaderChunk.envmap_vertex,
-			ShaderChunk.shadowmap_vertex,
-			ShaderChunk.fog_vertex,
-
-			'}',
-		].join( '\n' );
-
-		// Use the original MeshPhongMaterial's fragmentShader.
-		this.fragmentShader = [
-			'#define PHONG',
-
-			'uniform vec3 diffuse;',
-			'uniform vec3 emissive;',
-			'uniform vec3 specular;',
-			'uniform float shininess;',
-			'uniform float opacity;',
-
-			ShaderChunk.common,
-			ShaderChunk.packing,
-			ShaderChunk.dithering_pars_fragment,
-			ShaderChunk.color_pars_fragment,
-			ShaderChunk.uv_pars_fragment,
-			ShaderChunk.uv2_pars_fragment,
-			ShaderChunk.map_pars_fragment,
-			ShaderChunk.alphamap_pars_fragment,
-			ShaderChunk.aomap_pars_fragment,
-			ShaderChunk.lightmap_pars_fragment,
-			ShaderChunk.emissivemap_pars_fragment,
-			ShaderChunk.envmap_common_pars_fragment,
-			ShaderChunk.envmap_pars_fragment,
-			ShaderChunk.cube_uv_reflection_fragment,
-			ShaderChunk.fog_pars_fragment,
-			ShaderChunk.bsdfs,
-			ShaderChunk.lights_pars_begin,
-			ShaderChunk.lights_phong_pars_fragment,
-			ShaderChunk.shadowmap_pars_fragment,
-			ShaderChunk.bumpmap_pars_fragment,
-			ShaderChunk.normalmap_pars_fragment,
-			ShaderChunk.specularmap_pars_fragment,
-			ShaderChunk.logdepthbuf_pars_fragment,
-			ShaderChunk.clipping_planes_pars_fragment,
-
-			'void main() {',
-
-			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;',
-
-			ShaderChunk.logdepthbuf_fragment,
-			ShaderChunk.map_fragment,
-			ShaderChunk.color_fragment,
-			ShaderChunk.alphamap_fragment,
-			ShaderChunk.alphatest_fragment,
-			ShaderChunk.specularmap_fragment,
-			ShaderChunk.normal_fragment_begin,
-			ShaderChunk.normal_fragment_maps,
-			ShaderChunk.emissivemap_fragment,
-
-			// accumulation
-			ShaderChunk.lights_phong_fragment,
-			ShaderChunk.lights_fragment_begin,
-			ShaderChunk.lights_fragment_maps,
-			ShaderChunk.lights_fragment_end,
-
-			// modulation
-			ShaderChunk.aomap_fragment,
-
-			'vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;',
-
-			ShaderChunk.envmap_fragment,
-
-			'gl_FragColor = vec4( outgoingLight, diffuseColor.a );',
-
-			ShaderChunk.tonemapping_fragment,
-			ShaderChunk.encodings_fragment,
-			ShaderChunk.fog_fragment,
-			ShaderChunk.premultiplied_alpha_fragment,
-			ShaderChunk.dithering_fragment,
-			'}',
-		].join( '\n' );
-
-		this.setValues( parameters );
+}
 
-	}
 
-}
 
-export { GeometryCompressionUtils, PackedPhongMaterial };
+export {
+	compressNormals,
+	compressPositions,
+	compressUvs,
+};

+ 175 - 172
examples/jsm/utils/GeometryUtils.js

@@ -2,225 +2,228 @@ import {
 	Vector3
 } from '../../../build/three.module.js';
 
-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.
-	 */
-	static hilbert2D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3 ) {
-
-		const half = size / 2;
-
-		const vec_s = [
-			new Vector3( center.x - half, center.y, center.z - half ),
-			new Vector3( center.x - half, center.y, center.z + half ),
-			new Vector3( center.x + half, center.y, center.z + half ),
-			new 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 ) {
-
-			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
-			return tmp;
 
-		}
-
-		// Return complete Hilbert Curve.
-		return vec;
+/**
+ * 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.
+ */
+function hilbert2D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3 ) {
+
+	const half = size / 2;
+
+	const vec_s = [
+		new Vector3( center.x - half, center.y, center.z - half ),
+		new Vector3( center.x - half, center.y, center.z + half ),
+		new Vector3( center.x + half, center.y, center.z + half ),
+		new 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 ) {
+
+		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;
 
 	}
 
-	/**
-	 * 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 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 Vector3( center.x - half, center.y + half, center.z - half ),
-			new Vector3( center.x - half, center.y + half, center.z + half ),
-			new Vector3( center.x - half, center.y - half, center.z + half ),
-			new Vector3( center.x - half, center.y - half, center.z - half ),
-			new Vector3( center.x + half, center.y - half, center.z - half ),
-			new Vector3( center.x + half, center.y - half, center.z + half ),
-			new Vector3( center.x + half, center.y + half, center.z + half ),
-			new 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;
 
-		}
+}
 
-		// Return complete Hilbert Curve.
-		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.
+ */
+function hilbert3D( center = new 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 Vector3( center.x - half, center.y + half, center.z - half ),
+		new Vector3( center.x - half, center.y + half, center.z + half ),
+		new Vector3( center.x - half, center.y - half, center.z + half ),
+		new Vector3( center.x - half, center.y - half, center.z - half ),
+		new Vector3( center.x + half, center.y - half, center.z - half ),
+		new Vector3( center.x + half, center.y - half, center.z + half ),
+		new Vector3( center.x + half, center.y + half, center.z + half ),
+		new 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;
 
 	}
 
-	/**
-	 * 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 ) {
+	// Return complete Hilbert Curve.
+	return vec;
 
-		function fractalize( config ) {
+}
 
-			let output;
-			let input = config.axiom;
+/**
+ * Generates a Gosper curve (lying in the XY plane)
+ *
+ * https://gist.github.com/nitaku/6521802
+ *
+ * @param size The size of a single gosper island.
+ */
+function gosper( size = 1 ) {
 
-			for ( let i = 0, il = config.steps; 0 <= il ? i < il : i > il; 0 <= il ? i ++ : i -- ) {
+	function fractalize( config ) {
 
-				output = '';
+		let output;
+		let input = config.axiom;
 
-				for ( let j = 0, jl = input.length; j < jl; j ++ ) {
+		for ( let i = 0, il = config.steps; 0 <= il ? i < il : i > il; 0 <= il ? i ++ : i -- ) {
 
-					const char = input[ j ];
+			output = '';
 
-					if ( char in config.rules ) {
+			for ( let j = 0, jl = input.length; j < jl; j ++ ) {
 
-						output += config.rules[ char ];
+				const char = input[ j ];
 
-					} else {
+				if ( char in config.rules ) {
 
-						output += char;
+					output += config.rules[ 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;
 
 }
 
-export { GeometryUtils };
+
+
+export {
+	hilbert2D,
+	hilbert3D,
+	gosper,
+};

+ 257 - 0
examples/jsm/utils/PackedPhongMaterial.js

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

+ 37 - 32
examples/jsm/utils/SceneUtils.js

@@ -3,64 +3,69 @@ import {
 	Mesh
 } from '../../../build/three.module.js';
 
-class SceneUtils {
 
-	static createMeshesFromInstancedMesh( instancedMesh ) {
 
-		const group = new Group();
+function createMeshesFromInstancedMesh( instancedMesh ) {
 
-		const count = instancedMesh.count;
-		const geometry = instancedMesh.geometry;
-		const material = instancedMesh.material;
+	const group = new Group();
 
-		for ( let i = 0; i < count; i ++ ) {
+	const count = instancedMesh.count;
+	const geometry = instancedMesh.geometry;
+	const material = instancedMesh.material;
 
-			const mesh = new Mesh( geometry, material );
+	for ( let i = 0; i < count; i ++ ) {
 
-			instancedMesh.getMatrixAt( i, mesh.matrix );
-			mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
+		const mesh = new Mesh( geometry, material );
 
-			group.add( mesh );
+		instancedMesh.getMatrixAt( i, mesh.matrix );
+		mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
 
-		}
-
-		group.copy( instancedMesh );
-		group.updateMatrixWorld(); // ensure correct world matrices of meshes
-
-		return group;
+		group.add( mesh );
 
 	}
 
-	static createMultiMaterialObject( geometry, materials ) {
+	group.copy( instancedMesh );
+	group.updateMatrixWorld(); // ensure correct world matrices of meshes
+
+	return group;
 
-		const group = new Group();
+}
 
-		for ( let i = 0, l = materials.length; i < l; i ++ ) {
+function createMultiMaterialObject( geometry, materials ) {
 
-			group.add( new Mesh( geometry, materials[ i ] ) );
+	const group = new Group();
 
-		}
+	for ( let i = 0, l = materials.length; i < l; i ++ ) {
 
-		return group;
+		group.add( new 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 ) {
 
-	}
+	console.warn( 'THREE.SceneUtils: detach() has been deprecated. Use scene.attach( child ) instead.' );
 
-	static attach( child, scene, parent ) {
+	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 );
 
 }
 
-export { SceneUtils };
+
+
+export {
+	createMeshesFromInstancedMesh,
+	createMultiMaterialObject,
+	detach,
+	attach,
+};

+ 315 - 304
examples/jsm/utils/SkeletonUtils.js

@@ -11,565 +11,564 @@ import {
 	VectorKeyframeTrack
 } from '../../../build/three.module.js';
 
-class SkeletonUtils {
 
-	static retarget( target, source, options = {} ) {
+function retarget( target, source, options = {} ) {
 
-		const pos = new Vector3(),
-			quat = new Quaternion(),
-			scale = new Vector3(),
-			bindBoneMatrix = new Matrix4(),
-			relativeMatrix = new Matrix4(),
-			globalMatrix = new Matrix4();
+	const pos = new Vector3(),
+		quat = new Quaternion(),
+		scale = new Vector3(),
+		bindBoneMatrix = new Matrix4(),
+		relativeMatrix = new Matrix4(),
+		globalMatrix = new 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 || {};
+	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 );
+	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;
+	let bindBones,
+		bone, name, boneTo,
+		bonesPosition;
 
-		// reset bones
+	// reset bones
 
-		if ( target.isObject3D ) {
+	if ( target.isObject3D ) {
 
-			target.skeleton.pose();
-
-		} 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
+	if ( options.preserveMatrix ) {
 
-			target.updateMatrixWorld();
+		// reset matrix
 
-			target.matrixWorld.identity();
+		target.updateMatrixWorld();
 
-			// reset children matrix
+		target.matrixWorld.identity();
 
-			for ( let i = 0; i < target.children.length; ++ i ) {
+		// 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 = bones[ i ];
+			name = options.names[ bone.name ] || bone.name;
 
-					bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+			if ( options.offsets && options.offsets[ name ] ) {
 
-					bone.updateMatrixWorld();
+				bone.matrix.multiply( options.offsets[ name ] );
 
-				}
+				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
 
-				bindBones.push( bone.matrixWorld.clone() );
+				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 );
+	for ( let i = 0; i < bones.length; ++ i ) {
 
-			globalMatrix.copy( bone.matrixWorld );
+		bone = bones[ i ];
+		name = options.names[ bone.name ] || bone.name;
 
-			if ( boneTo ) {
+		boneTo = this.getBoneByName( name, sourceBones );
 
-				boneTo.updateMatrixWorld();
+		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 );
 
-				}
+			} else {
 
-				// ignore scale to extract rotation
+				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 ) );
+			scale.setFromMatrixScale( relativeMatrix );
+			relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) );
 
-				if ( target.isObject3D ) {
+			// apply to global matrix
 
-					const boneIndex = bones.indexOf( bone ),
-						wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert();
+			globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
 
-					globalMatrix.multiply( wBindMatrix );
+			if ( target.isObject3D ) {
 
-				}
+				const boneIndex = bones.indexOf( bone ),
+					wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert();
 
-				globalMatrix.copyPosition( relativeMatrix );
+				globalMatrix.multiply( wBindMatrix );
 
 			}
 
-			if ( bone.parent && bone.parent.isBone ) {
-
-				bone.matrix.copy( bone.parent.matrixWorld ).invert();
-				bone.matrix.multiply( globalMatrix );
+			globalMatrix.copyPosition( relativeMatrix );
 
-			} else {
+		}
 
-				bone.matrix.copy( globalMatrix );
+		if ( bone.parent && bone.parent.isBone ) {
 
-			}
+			bone.matrix.copy( bone.parent.matrixWorld ).invert();
+			bone.matrix.multiply( globalMatrix );
 
-			if ( options.preserveHipPosition && name === options.hip ) {
+		} else {
 
-				bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
+			bone.matrix.copy( globalMatrix );
 
-			}
+		}
 
-			bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+		if ( options.preserveHipPosition && name === options.hip ) {
 
-			bone.updateMatrixWorld();
+			bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
 
 		}
 
-		if ( options.preservePosition ) {
+		bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
 
-			for ( let i = 0; i < bones.length; ++ i ) {
+		bone.updateMatrixWorld();
 
-				bone = bones[ i ];
-				name = options.names[ bone.name ] || bone.name;
+	}
 
-				if ( name !== options.hip ) {
+	if ( options.preservePosition ) {
 
-					bone.position.copy( bonesPosition[ i ] );
+		for ( let i = 0; i < bones.length; ++ i ) {
 
-				}
+			bone = bones[ i ];
+			name = options.names[ bone.name ] || bone.name;
+
+			if ( name !== options.hip ) {
+
+				bone.position.copy( bonesPosition[ i ] );
 
 			}
 
 		}
 
-		if ( options.preserveMatrix ) {
+	}
 
-			// restore matrix
+	if ( options.preserveMatrix ) {
 
-			target.updateMatrixWorld( true );
+		// 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 AnimationMixer( source ),
-			bones = this.getBones( target.skeleton ),
-			boneDatas = [];
-		let positionOffset,
-			bone, boneTo, boneData,
-			name;
+	}
 
-		mixer.clipAction( clip ).play();
-		mixer.update( 0 );
+	const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
+		delta = 1 / options.fps,
+		convertedTracks = [],
+		mixer = new AnimationMixer( source ),
+		bones = this.getBones( target.skeleton ),
+		boneDatas = [];
+	let positionOffset,
+		bone, boneTo, boneData,
+		name;
 
-		source.updateMatrixWorld();
+	mixer.clipAction( clip ).play();
+	mixer.update( 0 );
 
-		for ( let i = 0; i < numFrames; ++ i ) {
+	source.updateMatrixWorld();
 
-			const time = i * delta;
+	for ( let i = 0; i < numFrames; ++ i ) {
 
-			this.retarget( target, source, options );
+		const time = i * delta;
 
-			for ( let j = 0; j < bones.length; ++ j ) {
+		this.retarget( target, source, options );
 
-				name = options.names[ bones[ j ].name ] || bones[ j ].name;
+		for ( let j = 0; j < bones.length; ++ j ) {
 
-				boneTo = this.getBoneByName( name, source.skeleton );
+			name = options.names[ bones[ j ].name ] || bones[ j ].name;
 
-				if ( boneTo ) {
+			boneTo = this.getBoneByName( name, source.skeleton );
 
-					bone = bones[ j ];
-					boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
-
-					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;
 
-						boneData.quat = {
-							times: new Float32Array( numFrames ),
-							values: new Float32Array( numFrames * 4 )
-						};
+					bone.position.toArray( boneData.pos.values, i * 3 );
 
-					}
+				}
 
-					boneData.quat.times[ i ] = time;
+				if ( ! boneData.quat ) {
 
-					bone.quaternion.toArray( boneData.quat.values, i * 4 );
+					boneData.quat = {
+						times: new Float32Array( numFrames ),
+						values: new Float32Array( numFrames * 4 )
+					};
 
 				}
 
-			}
+				boneData.quat.times[ i ] = time;
 
-			mixer.update( delta );
+				bone.quaternion.toArray( boneData.quat.values, i * 4 );
 
-			source.updateMatrixWorld();
+			}
 
 		}
 
-		for ( let i = 0; i < boneDatas.length; ++ i ) {
+		mixer.update( delta );
 
-			boneData = boneDatas[ i ];
+		source.updateMatrixWorld();
+
+	}
 
-			if ( boneData ) {
+	for ( let i = 0; i < boneDatas.length; ++ i ) {
 
-				if ( boneData.pos ) {
+		boneData = boneDatas[ i ];
 
-					convertedTracks.push( new VectorKeyframeTrack(
-						'.bones[' + boneData.bone.name + '].position',
-						boneData.pos.times,
-						boneData.pos.values
-					) );
+		if ( boneData ) {
 
-				}
+			if ( boneData.pos ) {
 
-				convertedTracks.push( new QuaternionKeyframeTrack(
-					'.bones[' + boneData.bone.name + '].quaternion',
-					boneData.quat.times,
-					boneData.quat.values
+				convertedTracks.push( new VectorKeyframeTrack(
+					'.bones[' + boneData.bone.name + '].position',
+					boneData.pos.times,
+					boneData.pos.values
 				) );
 
 			}
 
+			convertedTracks.push( new QuaternionKeyframeTrack(
+				'.bones[' + boneData.bone.name + '].quaternion',
+				boneData.quat.times,
+				boneData.quat.values
+			) );
+
 		}
 
-		mixer.uncacheAction( clip );
+	}
 
-		return new AnimationClip( clip.name, - 1, convertedTracks );
+	mixer.uncacheAction( clip );
 
-	}
+	return new AnimationClip( clip.name, - 1, convertedTracks );
 
-	static getHelperFromSkeleton( skeleton ) {
+}
 
-		const source = new SkeletonHelper( skeleton.bones[ 0 ] );
-		source.skeleton = skeleton;
+function getHelperFromSkeleton( skeleton ) {
 
-		return source;
+	const source = new SkeletonHelper( skeleton.bones[ 0 ] );
+	source.skeleton = skeleton;
 
-	}
+	return source;
 
-	static getSkeletonOffsets( target, source, options = {} ) {
+}
 
-		const targetParentPos = new Vector3(),
-			targetPos = new Vector3(),
-			sourceParentPos = new Vector3(),
-			sourcePos = new Vector3(),
-			targetDir = new Vector2(),
-			sourceDir = new Vector2();
+function getSkeletonOffsets( target, source, options = {} ) {
 
-		options.hip = options.hip !== undefined ? options.hip : 'hip';
-		options.names = options.names || {};
+	const targetParentPos = new Vector3(),
+		targetPos = new Vector3(),
+		sourceParentPos = new Vector3(),
+		sourcePos = new Vector3(),
+		targetDir = new Vector2(),
+		sourceDir = new Vector2();
 
-		if ( ! source.isObject3D ) {
+	options.hip = options.hip !== undefined ? options.hip : 'hip';
+	options.names = options.names || {};
 
-			source = this.getHelperFromSkeleton( source );
+	if ( ! source.isObject3D ) {
 
-		}
+		source = this.getHelperFromSkeleton( source );
 
-		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;
+	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 = [];
 
-		target.skeleton.pose();
+	let bone, boneTo,
+		name, i;
 
-		for ( i = 0; i < bones.length; ++ i ) {
+	target.skeleton.pose();
 
-			bone = bones[ i ];
-			name = options.names[ bone.name ] || bone.name;
+	for ( i = 0; i < bones.length; ++ i ) {
 
-			boneTo = this.getBoneByName( name, sourceBones );
+		bone = bones[ i ];
+		name = options.names[ bone.name ] || bone.name;
 
-			if ( boneTo && name !== options.hip ) {
+		boneTo = this.getBoneByName( name, sourceBones );
 
-				const boneParent = this.getNearestBone( bone.parent, nameKeys ),
-					boneToParent = this.getNearestBone( boneTo.parent, nameValues );
+		if ( boneTo && name !== options.hip ) {
 
-				boneParent.updateMatrixWorld();
-				boneToParent.updateMatrixWorld();
+			const boneParent = this.getNearestBone( bone.parent, nameKeys ),
+				boneToParent = this.getNearestBone( boneTo.parent, nameValues );
 
-				targetParentPos.setFromMatrixPosition( boneParent.matrixWorld );
-				targetPos.setFromMatrixPosition( bone.matrixWorld );
+			boneParent.updateMatrixWorld();
+			boneToParent.updateMatrixWorld();
 
-				sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
-				sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
+			targetParentPos.setFromMatrixPosition( boneParent.matrixWorld );
+			targetPos.setFromMatrixPosition( bone.matrixWorld );
 
-				targetDir.subVectors(
-					new Vector2( targetPos.x, targetPos.y ),
-					new Vector2( targetParentPos.x, targetParentPos.y )
-				).normalize();
+			sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
+			sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
 
-				sourceDir.subVectors(
-					new Vector2( sourcePos.x, sourcePos.y ),
-					new Vector2( sourceParentPos.x, sourceParentPos.y )
-				).normalize();
+			targetDir.subVectors(
+				new Vector2( targetPos.x, targetPos.y ),
+				new Vector2( targetParentPos.x, targetParentPos.y )
+			).normalize();
 
-				const laterialAngle = targetDir.angle() - sourceDir.angle();
+			sourceDir.subVectors(
+				new Vector2( sourcePos.x, sourcePos.y ),
+				new Vector2( sourceParentPos.x, sourceParentPos.y )
+			).normalize();
 
-				const offset = new Matrix4().makeRotationFromEuler(
-					new Euler(
-						0,
-						0,
-						laterialAngle
-					)
-				);
+			const laterialAngle = targetDir.angle() - sourceDir.angle();
 
-				bone.matrix.multiply( offset );
+			const offset = new Matrix4().makeRotationFromEuler(
+				new Euler(
+					0,
+					0,
+					laterialAngle
+				)
+			);
 
-				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+			bone.matrix.multiply( offset );
 
-				bone.updateMatrixWorld();
+			bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
 
-				offsets[ name ] = offset;
+			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 )
+function getBoneByName( name, skeleton ) {
 
-				return bones[ i ];
+	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 );
+		for ( let j = 0; j < targetBones.length; j ++ ) {
 
-					continue search;
+			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();
+function clone( source ) {
 
-		parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
+	const sourceLookup = new Map();
+	const cloneLookup = new Map();
 
-			sourceLookup.set( clonedNode, sourceNode );
-			cloneLookup.set( sourceNode, clonedNode );
+	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;
+	clone.traverse( function ( node ) {
 
-			clonedMesh.skeleton = sourceMesh.skeleton.clone();
-			clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
+		if ( ! node.isSkinnedMesh ) return;
 
-			clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
+		const clonedMesh = node;
+		const sourceMesh = sourceLookup.get( node );
+		const sourceBones = sourceMesh.skeleton.bones;
 
-				return cloneLookup.get( bone );
+		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;
 
 }
 
 
+
+
 function parallelTraverse( a, b, callback ) {
 
 	callback( a, b );
@@ -582,4 +581,16 @@ function parallelTraverse( a, b, callback ) {
 
 }
 
-export { SkeletonUtils };
+export {
+	retarget,
+	retargetClip,
+	getHelperFromSkeleton,
+	getSkeletonOffsets,
+	renameBones,
+	getBones,
+	getBoneByName,
+	getNearestBone,
+	findBoneTrackData,
+	getEqualsBonesNames,
+	clone,
+};

+ 1 - 1
examples/webgl_animation_multiple.html

@@ -19,7 +19,7 @@
 			import * as THREE from '../build/three.module.js';
 
 			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
-			import { SkeletonUtils } from './jsm/utils/SkeletonUtils.js';
+			import * as SkeletonUtils from './jsm/utils/SkeletonUtils.js';
 
 			//////////////////////////////
 			// Global objects

+ 1 - 1
examples/webgl_buffergeometry_compression.html

@@ -18,7 +18,7 @@
 
 			import Stats from './jsm/libs/stats.module.js';
 			import { OrbitControls } from './jsm/controls/OrbitControls.js';
-			import { GeometryCompressionUtils } from './jsm/utils/GeometryCompressionUtils.js';
+			import * as GeometryCompressionUtils from './jsm/utils/GeometryCompressionUtils.js';
 			import * as BufferGeometryUtils from './jsm/utils/BufferGeometryUtils.js';
 			import { TeapotGeometry } from './jsm/geometries/TeapotGeometry.js';
 			import { GUI } from './jsm/libs/dat.gui.module.js';

+ 1 - 1
examples/webgl_framebuffer_texture.html

@@ -40,7 +40,7 @@
 			import * as THREE from '../build/three.module.js';
 
 			import { OrbitControls } from './jsm/controls/OrbitControls.js';
-			import { GeometryUtils } from './jsm/utils/GeometryUtils.js';
+			import * as GeometryUtils from './jsm/utils/GeometryUtils.js';
 
 			let camera, scene, renderer;
 			let line, sprite, texture;

+ 1 - 1
examples/webgl_lines_colors.html

@@ -17,7 +17,7 @@
 
 			import * as THREE from '../build/three.module.js';
 
-			import { GeometryUtils } from './jsm/utils/GeometryUtils.js';
+			import * as GeometryUtils from './jsm/utils/GeometryUtils.js';
 
 			let mouseX = 0, mouseY = 0;
 

+ 1 - 1
examples/webgl_lines_dashed.html

@@ -17,7 +17,7 @@
 
 			import Stats from './jsm/libs/stats.module.js';
 
-			import { GeometryUtils } from './jsm/utils/GeometryUtils.js';
+			import * as GeometryUtils from './jsm/utils/GeometryUtils.js';
 
 			let renderer, scene, camera, stats;
 			const objects = [];

+ 1 - 1
examples/webgl_lines_fat.html

@@ -25,7 +25,7 @@
 			import { Line2 } from './jsm/lines/Line2.js';
 			import { LineMaterial } from './jsm/lines/LineMaterial.js';
 			import { LineGeometry } from './jsm/lines/LineGeometry.js';
-			import { GeometryUtils } from './jsm/utils/GeometryUtils.js';
+			import * as GeometryUtils from './jsm/utils/GeometryUtils.js';
 
 			let line, renderer, scene, camera, camera2, controls;
 			let line1;

+ 1 - 1
examples/webgl_portal.html

@@ -24,7 +24,7 @@
 
 			import * as THREE from '../build/three.module.js';
 
-			import { CameraUtils } from './jsm/utils/CameraUtils.js';
+			import * as CameraUtils from './jsm/utils/CameraUtils.js';
 			import { OrbitControls } from './jsm/controls/OrbitControls.js';
 
 			let camera, scene, renderer;