ソースを参照

Updated examples builds.

Mr.doob 3 年 前
コミット
84d012d6a7

+ 0 - 1
examples/js/controls/PointerLockControls.js

@@ -38,7 +38,6 @@
 			this.maxPolarAngle = Math.PI; // radians
 
 			this.pointerSpeed = 1.0;
-
 			const scope = this;
 
 			function onMouseMove( event ) {

+ 0 - 133
examples/js/csm/Frustum.js

@@ -1,133 +0,0 @@
-( function () {
-
-	const inverseProjectionMatrix = new THREE.Matrix4();
-
-	class Frustum {
-
-		constructor( data ) {
-
-			data = data || {};
-			this.vertices = {
-				near: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ],
-				far: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]
-			};
-
-			if ( data.projectionMatrix !== undefined ) {
-
-				this.setFromProjectionMatrix( data.projectionMatrix, data.maxFar || 10000 );
-
-			}
-
-		}
-
-		setFromProjectionMatrix( projectionMatrix, maxFar ) {
-
-			const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0;
-			inverseProjectionMatrix.copy( projectionMatrix ).invert(); // 3 --- 0  vertices.near/far order
-			// |     |
-			// 2 --- 1
-			// clip space spans from [-1, 1]
-
-			this.vertices.near[ 0 ].set( 1, 1, - 1 );
-			this.vertices.near[ 1 ].set( 1, - 1, - 1 );
-			this.vertices.near[ 2 ].set( - 1, - 1, - 1 );
-			this.vertices.near[ 3 ].set( - 1, 1, - 1 );
-			this.vertices.near.forEach( function ( v ) {
-
-				v.applyMatrix4( inverseProjectionMatrix );
-
-			} );
-			this.vertices.far[ 0 ].set( 1, 1, 1 );
-			this.vertices.far[ 1 ].set( 1, - 1, 1 );
-			this.vertices.far[ 2 ].set( - 1, - 1, 1 );
-			this.vertices.far[ 3 ].set( - 1, 1, 1 );
-			this.vertices.far.forEach( function ( v ) {
-
-				v.applyMatrix4( inverseProjectionMatrix );
-				const absZ = Math.abs( v.z );
-
-				if ( isOrthographic ) {
-
-					v.z *= Math.min( maxFar / absZ, 1.0 );
-
-				} else {
-
-					v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) );
-
-				}
-
-			} );
-			return this.vertices;
-
-		}
-
-		split( breaks, target ) {
-
-			while ( breaks.length > target.length ) {
-
-				target.push( new Frustum() );
-
-			}
-
-			target.length = breaks.length;
-
-			for ( let i = 0; i < breaks.length; i ++ ) {
-
-				const cascade = target[ i ];
-
-				if ( i === 0 ) {
-
-					for ( let j = 0; j < 4; j ++ ) {
-
-						cascade.vertices.near[ j ].copy( this.vertices.near[ j ] );
-
-					}
-
-				} else {
-
-					for ( let j = 0; j < 4; j ++ ) {
-
-						cascade.vertices.near[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] );
-
-					}
-
-				}
-
-				if ( i === breaks.length - 1 ) {
-
-					for ( let j = 0; j < 4; j ++ ) {
-
-						cascade.vertices.far[ j ].copy( this.vertices.far[ j ] );
-
-					}
-
-				} else {
-
-					for ( let j = 0; j < 4; j ++ ) {
-
-						cascade.vertices.far[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] );
-
-					}
-
-				}
-
-			}
-
-		}
-
-		toSpace( cameraMatrix, target ) {
-
-			for ( var i = 0; i < 4; i ++ ) {
-
-				target.vertices.near[ i ].copy( this.vertices.near[ i ] ).applyMatrix4( cameraMatrix );
-				target.vertices.far[ i ].copy( this.vertices.far[ i ] ).applyMatrix4( cameraMatrix );
-
-			}
-
-		}
-
-	}
-
-	THREE.Frustum = Frustum;
-
-} )();

+ 458 - 0
examples/js/exporters/EXRExporter.js

@@ -0,0 +1,458 @@
+( function () {
+
+	/**
+ * @author sciecode / https://github.com/sciecode
+ *
+ * EXR format references:
+ * 	https://www.openexr.com/documentation/openexrfilelayout.pdf
+ */
+	const textEncoder = new TextEncoder();
+	const NO_COMPRESSION = 0;
+	const ZIPS_COMPRESSION = 2;
+	const ZIP_COMPRESSION = 3;
+
+	class EXRExporter {
+
+		parse( renderer, renderTarget, options ) {
+
+			if ( ! supported( renderer, renderTarget ) ) return undefined;
+			const info = buildInfo( renderTarget, options ),
+				dataBuffer = getPixelData( renderer, renderTarget, info ),
+				rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
+				chunks = compressData( rawContentBuffer, info );
+			return fillData( chunks, info );
+
+		}
+
+	}
+
+	function supported( renderer, renderTarget ) {
+
+		if ( ! renderer || ! renderer.isWebGLRenderer ) {
+
+			console.error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer.' );
+			return false;
+
+		}
+
+		if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) {
+
+			console.error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
+			return false;
+
+		}
+
+		if ( renderTarget.texture.type !== THREE.FloatType && renderTarget.texture.type !== THREE.HalfFloatType ) {
+
+			console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
+			return false;
+
+		}
+
+		if ( renderTarget.texture.format !== THREE.RGBAFormat ) {
+
+			console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected THREE.RGBAFormat.' );
+			return false;
+
+		}
+
+		return true;
+
+	}
+
+	function buildInfo( renderTarget, options = {} ) {
+
+		const compressionSizes = {
+			0: 1,
+			2: 1,
+			3: 16
+		};
+		const WIDTH = renderTarget.width,
+			HEIGHT = renderTarget.height,
+			TYPE = renderTarget.texture.type,
+			FORMAT = renderTarget.texture.format,
+			ENCODING = renderTarget.texture.encoding,
+			COMPRESSION = options.compression !== undefined ? options.compression : ZIP_COMPRESSION,
+			EXPORTER_TYPE = options.type !== undefined ? options.type : THREE.HalfFloatType,
+			OUT_TYPE = EXPORTER_TYPE === THREE.FloatType ? 2 : 1,
+			COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
+			NUM_CHANNELS = 4;
+		return {
+			width: WIDTH,
+			height: HEIGHT,
+			type: TYPE,
+			format: FORMAT,
+			encoding: ENCODING,
+			compression: COMPRESSION,
+			blockLines: COMPRESSION_SIZE,
+			dataType: OUT_TYPE,
+			dataSize: 2 * OUT_TYPE,
+			numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
+			numInputChannels: 4,
+			numOutputChannels: NUM_CHANNELS
+		};
+
+	}
+
+	function getPixelData( renderer, rtt, info ) {
+
+		let dataBuffer;
+
+		if ( info.type === THREE.FloatType ) {
+
+			dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels );
+
+		} else {
+
+			dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels );
+
+		}
+
+		renderer.readRenderTargetPixels( rtt, 0, 0, info.width, info.height, dataBuffer );
+		return dataBuffer;
+
+	}
+
+	function reorganizeDataBuffer( inBuffer, info ) {
+
+		const w = info.width,
+			h = info.height,
+			dec = {
+				r: 0,
+				g: 0,
+				b: 0,
+				a: 0
+			},
+			offset = {
+				value: 0
+			},
+			cOffset = info.numOutputChannels == 4 ? 1 : 0,
+			getValue = info.type == THREE.FloatType ? getFloat32 : getFloat16,
+			setValue = info.dataType == 1 ? setFloat16 : setFloat32,
+			outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ),
+			dv = new DataView( outBuffer.buffer );
+
+		for ( let y = 0; y < h; ++ y ) {
+
+			for ( let x = 0; x < w; ++ x ) {
+
+				const i = y * w * 4 + x * 4;
+				const r = getValue( inBuffer, i );
+				const g = getValue( inBuffer, i + 1 );
+				const b = getValue( inBuffer, i + 2 );
+				const a = getValue( inBuffer, i + 3 );
+				const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize;
+				decodeLinear( dec, r, g, b, a );
+				offset.value = line + x * info.dataSize;
+				setValue( dv, dec.a, offset );
+				offset.value = line + cOffset * w * info.dataSize + x * info.dataSize;
+				setValue( dv, dec.b, offset );
+				offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize;
+				setValue( dv, dec.g, offset );
+				offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize;
+				setValue( dv, dec.r, offset );
+
+			}
+
+		}
+
+		return outBuffer;
+
+	}
+
+	function compressData( inBuffer, info ) {
+
+		let compress,
+			tmpBuffer,
+			sum = 0;
+		const chunks = {
+				data: new Array(),
+				totalSize: 0
+			},
+			size = info.width * info.numOutputChannels * info.blockLines * info.dataSize;
+
+		switch ( info.compression ) {
+
+			case 0:
+				compress = compressNONE;
+				break;
+
+			case 2:
+			case 3:
+				compress = compressZIP;
+				break;
+
+		}
+
+		if ( info.compression !== 0 ) {
+
+			tmpBuffer = new Uint8Array( size );
+
+		}
+
+		for ( let i = 0; i < info.numBlocks; ++ i ) {
+
+			const arr = inBuffer.subarray( size * i, size * ( i + 1 ) );
+			const block = compress( arr, tmpBuffer );
+			sum += block.length;
+			chunks.data.push( {
+				dataChunk: block,
+				size: block.length
+			} );
+
+		}
+
+		chunks.totalSize = sum;
+		return chunks;
+
+	}
+
+	function compressNONE( data ) {
+
+		return data;
+
+	}
+
+	function compressZIP( data, tmpBuffer ) {
+
+		//
+		// Reorder the pixel data.
+		//
+		let t1 = 0,
+			t2 = Math.floor( ( data.length + 1 ) / 2 ),
+			s = 0;
+		const stop = data.length - 1;
+
+		while ( true ) {
+
+			if ( s > stop ) break;
+			tmpBuffer[ t1 ++ ] = data[ s ++ ];
+			if ( s > stop ) break;
+			tmpBuffer[ t2 ++ ] = data[ s ++ ];
+
+		} //
+		// Predictor.
+		//
+
+
+		let p = tmpBuffer[ 0 ];
+
+		for ( let t = 1; t < tmpBuffer.length; t ++ ) {
+
+			const d = tmpBuffer[ t ] - p + ( 128 + 256 );
+			p = tmpBuffer[ t ];
+			tmpBuffer[ t ] = d;
+
+		}
+
+		if ( typeof fflate === 'undefined' ) {
+
+			console.error( 'THREE.EXRLoader: External \`fflate.module.js\` required' );
+
+		}
+
+		const deflate = fflate.zlibSync( tmpBuffer ); // eslint-disable-line no-undef
+
+		return deflate;
+
+	}
+
+	function fillHeader( outBuffer, chunks, info ) {
+
+		const offset = {
+			value: 0
+		};
+		const dv = new DataView( outBuffer.buffer );
+		setUint32( dv, 20000630, offset ); // magic
+
+		setUint32( dv, 2, offset ); // mask
+		// = HEADER =
+
+		setString( dv, 'compression', offset );
+		setString( dv, 'compression', offset );
+		setUint32( dv, 1, offset );
+		setUint8( dv, info.compression, offset );
+		setString( dv, 'screenWindowCenter', offset );
+		setString( dv, 'v2f', offset );
+		setUint32( dv, 8, offset );
+		setUint32( dv, 0, offset );
+		setUint32( dv, 0, offset );
+		setString( dv, 'screenWindowWidth', offset );
+		setString( dv, 'float', offset );
+		setUint32( dv, 4, offset );
+		setFloat32( dv, 1.0, offset );
+		setString( dv, 'pixelAspectRatio', offset );
+		setString( dv, 'float', offset );
+		setUint32( dv, 4, offset );
+		setFloat32( dv, 1.0, offset );
+		setString( dv, 'lineOrder', offset );
+		setString( dv, 'lineOrder', offset );
+		setUint32( dv, 1, offset );
+		setUint8( dv, 0, offset );
+		setString( dv, 'dataWindow', offset );
+		setString( dv, 'box2i', offset );
+		setUint32( dv, 16, offset );
+		setUint32( dv, 0, offset );
+		setUint32( dv, 0, offset );
+		setUint32( dv, info.width - 1, offset );
+		setUint32( dv, info.height - 1, offset );
+		setString( dv, 'displayWindow', offset );
+		setString( dv, 'box2i', offset );
+		setUint32( dv, 16, offset );
+		setUint32( dv, 0, offset );
+		setUint32( dv, 0, offset );
+		setUint32( dv, info.width - 1, offset );
+		setUint32( dv, info.height - 1, offset );
+		setString( dv, 'channels', offset );
+		setString( dv, 'chlist', offset );
+		setUint32( dv, info.numOutputChannels * 18 + 1, offset );
+		setString( dv, 'A', offset );
+		setUint32( dv, info.dataType, offset );
+		offset.value += 4;
+		setUint32( dv, 1, offset );
+		setUint32( dv, 1, offset );
+		setString( dv, 'B', offset );
+		setUint32( dv, info.dataType, offset );
+		offset.value += 4;
+		setUint32( dv, 1, offset );
+		setUint32( dv, 1, offset );
+		setString( dv, 'G', offset );
+		setUint32( dv, info.dataType, offset );
+		offset.value += 4;
+		setUint32( dv, 1, offset );
+		setUint32( dv, 1, offset );
+		setString( dv, 'R', offset );
+		setUint32( dv, info.dataType, offset );
+		offset.value += 4;
+		setUint32( dv, 1, offset );
+		setUint32( dv, 1, offset );
+		setUint8( dv, 0, offset ); // null-byte
+
+		setUint8( dv, 0, offset ); // = OFFSET TABLE =
+
+		let sum = offset.value + info.numBlocks * 8;
+
+		for ( let i = 0; i < chunks.data.length; ++ i ) {
+
+			setUint64( dv, sum, offset );
+			sum += chunks.data[ i ].size + 8;
+
+		}
+
+	}
+
+	function fillData( chunks, info ) {
+
+		const TableSize = info.numBlocks * 8,
+			HeaderSize = 259 + 18 * info.numOutputChannels,
+			// 259 + 18 * chlist
+			offset = {
+				value: HeaderSize + TableSize
+			},
+			outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ),
+			dv = new DataView( outBuffer.buffer );
+		fillHeader( outBuffer, chunks, info );
+
+		for ( let i = 0; i < chunks.data.length; ++ i ) {
+
+			const data = chunks.data[ i ].dataChunk;
+			const size = chunks.data[ i ].size;
+			setUint32( dv, i * info.blockLines, offset );
+			setUint32( dv, size, offset );
+			outBuffer.set( data, offset.value );
+			offset.value += size;
+
+		}
+
+		return outBuffer;
+
+	}
+
+	function decodeLinear( dec, r, g, b, a ) {
+
+		dec.r = r;
+		dec.g = g;
+		dec.b = b;
+		dec.a = a;
+
+	} // function decodeSRGB( dec, r, g, b, a ) {
+	// 	dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808;
+	// 	dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808;
+	// 	dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808;
+	// 	dec.a = a;
+	// }
+
+
+	function setUint8( dv, value, offset ) {
+
+		dv.setUint8( offset.value, value );
+		offset.value += 1;
+
+	}
+
+	function setUint32( dv, value, offset ) {
+
+		dv.setUint32( offset.value, value, true );
+		offset.value += 4;
+
+	}
+
+	function setFloat16( dv, value, offset ) {
+
+		dv.setUint16( offset.value, THREE.DataUtils.toHalfFloat( value ), true );
+		offset.value += 2;
+
+	}
+
+	function setFloat32( dv, value, offset ) {
+
+		dv.setFloat32( offset.value, value, true );
+		offset.value += 4;
+
+	}
+
+	function setUint64( dv, value, offset ) {
+
+		dv.setBigUint64( offset.value, BigInt( value ), true );
+		offset.value += 8;
+
+	}
+
+	function setString( dv, string, offset ) {
+
+		const tmp = textEncoder.encode( string + '\0' );
+
+		for ( let i = 0; i < tmp.length; ++ i ) {
+
+			setUint8( dv, tmp[ i ], offset );
+
+		}
+
+	}
+
+	function decodeFloat16( binary ) {
+
+		const exponent = ( binary & 0x7C00 ) >> 10,
+			fraction = binary & 0x03FF;
+		return ( binary >> 15 ? - 1 : 1 ) * ( exponent ? exponent === 0x1F ? fraction ? NaN : Infinity : Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) : 6.103515625e-5 * ( fraction / 0x400 ) );
+
+	}
+
+	function getFloat16( arr, i ) {
+
+		return decodeFloat16( arr[ i ] );
+
+	}
+
+	function getFloat32( arr, i ) {
+
+		return arr[ i ];
+
+	}
+
+	THREE.EXRExporter = EXRExporter;
+	THREE.NO_COMPRESSION = NO_COMPRESSION;
+	THREE.ZIPS_COMPRESSION = ZIPS_COMPRESSION;
+	THREE.ZIP_COMPRESSION = ZIP_COMPRESSION;
+
+} )();

+ 74 - 0
examples/js/helpers/OctreeHelper.js

@@ -0,0 +1,74 @@
+( function () {
+
+	class OctreeHelper extends THREE.LineSegments {
+
+		constructor( octree, color = 0xffff00 ) {
+
+			const vertices = [];
+
+			function traverse( tree ) {
+
+				for ( let i = 0; i < tree.length; i ++ ) {
+
+					const min = tree[ i ].box.min;
+					const max = tree[ i ].box.max;
+					vertices.push( max.x, max.y, max.z );
+					vertices.push( min.x, max.y, max.z ); // 0, 1
+
+					vertices.push( min.x, max.y, max.z );
+					vertices.push( min.x, min.y, max.z ); // 1, 2
+
+					vertices.push( min.x, min.y, max.z );
+					vertices.push( max.x, min.y, max.z ); // 2, 3
+
+					vertices.push( max.x, min.y, max.z );
+					vertices.push( max.x, max.y, max.z ); // 3, 0
+
+					vertices.push( max.x, max.y, min.z );
+					vertices.push( min.x, max.y, min.z ); // 4, 5
+
+					vertices.push( min.x, max.y, min.z );
+					vertices.push( min.x, min.y, min.z ); // 5, 6
+
+					vertices.push( min.x, min.y, min.z );
+					vertices.push( max.x, min.y, min.z ); // 6, 7
+
+					vertices.push( max.x, min.y, min.z );
+					vertices.push( max.x, max.y, min.z ); // 7, 4
+
+					vertices.push( max.x, max.y, max.z );
+					vertices.push( max.x, max.y, min.z ); // 0, 4
+
+					vertices.push( min.x, max.y, max.z );
+					vertices.push( min.x, max.y, min.z ); // 1, 5
+
+					vertices.push( min.x, min.y, max.z );
+					vertices.push( min.x, min.y, min.z ); // 2, 6
+
+					vertices.push( max.x, min.y, max.z );
+					vertices.push( max.x, min.y, min.z ); // 3, 7
+
+					traverse( tree[ i ].subTrees );
+
+				}
+
+			}
+
+			traverse( octree.subTrees );
+			const geometry = new THREE.BufferGeometry();
+			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+			super( geometry, new THREE.LineBasicMaterial( {
+				color: color,
+				toneMapped: false
+			} ) );
+			this.octree = octree;
+			this.color = color;
+			this.type = 'OctreeHelper';
+
+		}
+
+	}
+
+	THREE.OctreeHelper = OctreeHelper;
+
+} )();

+ 267 - 0
examples/js/helpers/ViewHelper.js

@@ -0,0 +1,267 @@
+( function () {
+
+	const vpTemp = new THREE.Vector4();
+
+	class ViewHelper extends THREE.Object3D {
+
+		constructor( editorCamera, dom ) {
+
+			super();
+			this.animating = false;
+			this.controls = null;
+			const color1 = new THREE.Color( '#ff3653' );
+			const color2 = new THREE.Color( '#8adb00' );
+			const color3 = new THREE.Color( '#2c8fff' );
+			const interactiveObjects = [];
+			const raycaster = new THREE.Raycaster();
+			const mouse = new THREE.Vector2();
+			const dummy = new THREE.Object3D();
+			const camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 );
+			camera.position.set( 0, 0, 2 );
+			const geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 );
+			const xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) );
+			const yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) );
+			const zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) );
+			yAxis.rotation.z = Math.PI / 2;
+			zAxis.rotation.y = - Math.PI / 2;
+			this.add( xAxis );
+			this.add( zAxis );
+			this.add( yAxis );
+			const posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) );
+			posXAxisHelper.userData.type = 'posX';
+			const posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) );
+			posYAxisHelper.userData.type = 'posY';
+			const posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) );
+			posZAxisHelper.userData.type = 'posZ';
+			const negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) );
+			negXAxisHelper.userData.type = 'negX';
+			const negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) );
+			negYAxisHelper.userData.type = 'negY';
+			const negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) );
+			negZAxisHelper.userData.type = 'negZ';
+			posXAxisHelper.position.x = 1;
+			posYAxisHelper.position.y = 1;
+			posZAxisHelper.position.z = 1;
+			negXAxisHelper.position.x = - 1;
+			negXAxisHelper.scale.setScalar( 0.8 );
+			negYAxisHelper.position.y = - 1;
+			negYAxisHelper.scale.setScalar( 0.8 );
+			negZAxisHelper.position.z = - 1;
+			negZAxisHelper.scale.setScalar( 0.8 );
+			this.add( posXAxisHelper );
+			this.add( posYAxisHelper );
+			this.add( posZAxisHelper );
+			this.add( negXAxisHelper );
+			this.add( negYAxisHelper );
+			this.add( negZAxisHelper );
+			interactiveObjects.push( posXAxisHelper );
+			interactiveObjects.push( posYAxisHelper );
+			interactiveObjects.push( posZAxisHelper );
+			interactiveObjects.push( negXAxisHelper );
+			interactiveObjects.push( negYAxisHelper );
+			interactiveObjects.push( negZAxisHelper );
+			const point = new THREE.Vector3();
+			const dim = 128;
+			const turnRate = 2 * Math.PI; // turn rate in angles per second
+
+			this.render = function ( renderer ) {
+
+				this.quaternion.copy( editorCamera.quaternion ).invert();
+				this.updateMatrixWorld();
+				point.set( 0, 0, 1 );
+				point.applyQuaternion( editorCamera.quaternion );
+
+				if ( point.x >= 0 ) {
+
+					posXAxisHelper.material.opacity = 1;
+					negXAxisHelper.material.opacity = 0.5;
+
+				} else {
+
+					posXAxisHelper.material.opacity = 0.5;
+					negXAxisHelper.material.opacity = 1;
+
+				}
+
+				if ( point.y >= 0 ) {
+
+					posYAxisHelper.material.opacity = 1;
+					negYAxisHelper.material.opacity = 0.5;
+
+				} else {
+
+					posYAxisHelper.material.opacity = 0.5;
+					negYAxisHelper.material.opacity = 1;
+
+				}
+
+				if ( point.z >= 0 ) {
+
+					posZAxisHelper.material.opacity = 1;
+					negZAxisHelper.material.opacity = 0.5;
+
+				} else {
+
+					posZAxisHelper.material.opacity = 0.5;
+					negZAxisHelper.material.opacity = 1;
+
+				} //
+
+
+				const x = dom.offsetWidth - dim;
+				renderer.clearDepth();
+				renderer.getViewport( vpTemp );
+				renderer.setViewport( x, 0, dim, dim );
+				renderer.render( this, camera );
+				renderer.setViewport( vpTemp.x, vpTemp.y, vpTemp.z, vpTemp.w );
+
+			};
+
+			const targetPosition = new THREE.Vector3();
+			const targetQuaternion = new THREE.Quaternion();
+			const q1 = new THREE.Quaternion();
+			const q2 = new THREE.Quaternion();
+			let radius = 0;
+
+			this.handleClick = function ( event ) {
+
+				if ( this.animating === true ) return false;
+				const rect = dom.getBoundingClientRect();
+				const offsetX = rect.left + ( container.dom.offsetWidth - dim );
+				const offsetY = rect.top + ( container.dom.offsetHeight - dim );
+				mouse.x = ( event.clientX - offsetX ) / ( rect.width - offsetX ) * 2 - 1;
+				mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1;
+				raycaster.setFromCamera( mouse, camera );
+				const intersects = raycaster.intersectObjects( interactiveObjects );
+
+				if ( intersects.length > 0 ) {
+
+					const intersection = intersects[ 0 ];
+					const object = intersection.object;
+					prepareAnimationData( object, this.controls.center );
+					this.animating = true;
+					return true;
+
+				} else {
+
+					return false;
+
+				}
+
+			};
+
+			this.update = function ( delta ) {
+
+				const step = delta * turnRate;
+				const focusPoint = this.controls.center; // animate position by doing a slerp and then scaling the position on the unit sphere
+
+				q1.rotateTowards( q2, step );
+				editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint ); // animate orientation
+
+				editorCamera.quaternion.rotateTowards( targetQuaternion, step );
+
+				if ( q1.angleTo( q2 ) === 0 ) {
+
+					this.animating = false;
+
+				}
+
+			};
+
+			function prepareAnimationData( object, focusPoint ) {
+
+				switch ( object.userData.type ) {
+
+					case 'posX':
+						targetPosition.set( 1, 0, 0 );
+						targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) );
+						break;
+
+					case 'posY':
+						targetPosition.set( 0, 1, 0 );
+						targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) );
+						break;
+
+					case 'posZ':
+						targetPosition.set( 0, 0, 1 );
+						targetQuaternion.setFromEuler( new THREE.Euler() );
+						break;
+
+					case 'negX':
+						targetPosition.set( - 1, 0, 0 );
+						targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) );
+						break;
+
+					case 'negY':
+						targetPosition.set( 0, - 1, 0 );
+						targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) );
+						break;
+
+					case 'negZ':
+						targetPosition.set( 0, 0, - 1 );
+						targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
+						break;
+
+					default:
+						console.error( 'ViewHelper: Invalid axis.' );
+
+				} //
+
+
+				radius = editorCamera.position.distanceTo( focusPoint );
+				targetPosition.multiplyScalar( radius ).add( focusPoint );
+				dummy.position.copy( focusPoint );
+				dummy.lookAt( editorCamera.position );
+				q1.copy( dummy.quaternion );
+				dummy.lookAt( targetPosition );
+				q2.copy( dummy.quaternion );
+
+			}
+
+			function getAxisMaterial( color ) {
+
+				return new THREE.MeshBasicMaterial( {
+					color: color,
+					toneMapped: false
+				} );
+
+			}
+
+			function getSpriteMaterial( color, text = null ) {
+
+				const canvas = document.createElement( 'canvas' );
+				canvas.width = 64;
+				canvas.height = 64;
+				const context = canvas.getContext( '2d' );
+				context.beginPath();
+				context.arc( 32, 32, 16, 0, 2 * Math.PI );
+				context.closePath();
+				context.fillStyle = color.getStyle();
+				context.fill();
+
+				if ( text !== null ) {
+
+					context.font = '24px Arial';
+					context.textAlign = 'center';
+					context.fillStyle = '#000000';
+					context.fillText( text, 32, 41 );
+
+				}
+
+				const texture = new THREE.CanvasTexture( canvas );
+				return new THREE.SpriteMaterial( {
+					map: texture,
+					toneMapped: false
+				} );
+
+			}
+
+		}
+
+	}
+
+	ViewHelper.prototype.isViewHelper = true;
+
+	THREE.ViewHelper = ViewHelper;
+
+} )();

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

@@ -88,7 +88,7 @@
 
 					for ( file in zip ) {
 
-						if ( file.toLowerCase().substr( - 4 ) === '.amf' ) {
+						if ( file.toLowerCase().slice( - 4 ) === '.amf' ) {
 
 							break;
 

+ 15 - 3
examples/js/loaders/GLTFLoader.js

@@ -1821,19 +1821,22 @@
 
 		let hasMorphPosition = false;
 		let hasMorphNormal = false;
+		let hasMorphColor = false;
 
 		for ( let i = 0, il = targets.length; i < il; i ++ ) {
 
 			const target = targets[ i ];
 			if ( target.POSITION !== undefined ) hasMorphPosition = true;
 			if ( target.NORMAL !== undefined ) hasMorphNormal = true;
-			if ( hasMorphPosition && hasMorphNormal ) break;
+			if ( target.COLOR_0 !== undefined ) hasMorphColor = true;
+			if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break;
 
 		}
 
-		if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
+		if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry );
 		const pendingPositionAccessors = [];
 		const pendingNormalAccessors = [];
+		const pendingColorAccessors = [];
 
 		for ( let i = 0, il = targets.length; i < il; i ++ ) {
 
@@ -1853,14 +1856,23 @@
 
 			}
 
+			if ( hasMorphColor ) {
+
+				const pendingAccessor = target.COLOR_0 !== undefined ? parser.getDependency( 'accessor', target.COLOR_0 ) : geometry.attributes.color;
+				pendingColorAccessors.push( pendingAccessor );
+
+			}
+
 		}
 
-		return Promise.all( [ Promise.all( pendingPositionAccessors ), Promise.all( pendingNormalAccessors ) ] ).then( function ( accessors ) {
+		return Promise.all( [ Promise.all( pendingPositionAccessors ), Promise.all( pendingNormalAccessors ), Promise.all( pendingColorAccessors ) ] ).then( function ( accessors ) {
 
 			const morphPositions = accessors[ 0 ];
 			const morphNormals = accessors[ 1 ];
+			const morphColors = accessors[ 2 ];
 			if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
 			if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
+			if ( hasMorphColor ) geometry.morphAttributes.color = morphColors;
 			geometry.morphTargetsRelative = true;
 			return geometry;
 

+ 13 - 6
examples/js/loaders/SVGLoader.js

@@ -53,7 +53,7 @@
 
 				if ( node.nodeType !== 1 ) return;
 				const transform = getNodeTransform( node );
-				let traverseChildNodes = true;
+				let isDefsNode = false;
 				let path = null;
 
 				switch ( node.nodeName ) {
@@ -105,7 +105,7 @@
 						break;
 
 					case 'defs':
-						traverseChildNodes = false;
+						isDefsNode = true;
 						break;
 
 					case 'use':
@@ -147,16 +147,23 @@
 
 				}
 
-				if ( traverseChildNodes ) {
+				const childNodes = node.childNodes;
 
-					const nodes = node.childNodes;
+				for ( let i = 0; i < childNodes.length; i ++ ) {
 
-					for ( let i = 0; i < nodes.length; i ++ ) {
+					const node = childNodes[ i ];
 
-						parseNode( nodes[ i ], style );
+					if ( isDefsNode && node.nodeName !== 'style' && node.nodeName !== 'defs' ) {
+
+						// Ignore everything in defs except CSS style definitions
+						// and nested defs, because it is OK by the standard to have
+						// <style/> there.
+						continue;
 
 					}
 
+					parseNode( node, style );
+
 				}
 
 				if ( transform ) {