Ver Fonte

r138 (bis) (bis)

Mr.doob há 3 anos atrás
pai
commit
c12c9a166a

+ 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 ) {

+ 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;
+
+} )();

+ 41 - 52
examples/js/exporters/GLTFExporter.js

@@ -626,68 +626,55 @@
 
 		}
 
-		buildORMTexture( material ) {
+		buildMetalRoughTexture( metalnessMap, roughnessMap ) {
 
-			const occlusion = material.aoMap?.image;
-			const roughness = material.roughnessMap?.image;
-			const metalness = material.metalnessMap?.image;
-			if ( occlusion === roughness && roughness === metalness ) return material.aoMap;
+			if ( metalnessMap === roughnessMap ) return metalnessMap;
+			console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
+			const metalness = metalnessMap?.image;
+			const roughness = roughnessMap?.image;
+			const width = Math.max( metalness?.width || 0, roughness?.width || 0 );
+			const height = Math.max( metalness?.height || 0, roughness?.height || 0 );
+			const canvas = document.createElement( 'canvas' );
+			canvas.width = width;
+			canvas.height = height;
+			const context = canvas.getContext( '2d' );
+			context.fillStyle = '#00ffff';
+			context.fillRect( 0, 0, width, height );
+			const composite = context.getImageData( 0, 0, width, height );
 
-			if ( occlusion || roughness || metalness ) {
+			if ( metalness ) {
 
-				const width = Math.max( occlusion?.width || 0, roughness?.width || 0, metalness?.width || 0 );
-				const height = Math.max( occlusion?.height || 0, roughness?.height || 0, metalness?.height || 0 );
-				const canvas = document.createElement( 'canvas' );
-				canvas.width = width;
-				canvas.height = height;
-				const context = canvas.getContext( '2d' );
-				context.fillStyle = '#ffffff';
-				context.fillRect( 0, 0, width, height );
-				const composite = context.getImageData( 0, 0, width, height );
+				context.drawImage( metalness, 0, 0, width, height );
+				const data = context.getImageData( 0, 0, width, height ).data;
 
-				if ( occlusion ) {
+				for ( let i = 2; i < data.length; i += 4 ) {
 
-					context.drawImage( occlusion, 0, 0, width, height );
-					const data = context.getImageData( 0, 0, width, height ).data;
-
-					for ( let i = 0; i < data.length; i += 4 ) {
-
-						composite.data[ i ] = data[ i ];
-
-					}
+					composite.data[ i ] = data[ i ];
 
 				}
 
-				if ( roughness ) {
+			}
 
-					context.drawImage( roughness, 0, 0, width, height );
-					const data = context.getImageData( 0, 0, width, height ).data;
+			if ( roughness ) {
 
-					for ( let i = 1; i < data.length; i += 4 ) {
+				context.drawImage( roughness, 0, 0, width, height );
+				const data = context.getImageData( 0, 0, width, height ).data;
 
-						composite.data[ i ] = data[ i ];
+				for ( let i = 1; i < data.length; i += 4 ) {
 
-					}
+					composite.data[ i ] = data[ i ];
 
 				}
 
-				if ( metalness ) {
-
-					context.drawImage( metalness, 0, 0, width, height );
-					const data = context.getImageData( 0, 0, width, height ).data;
-
-					for ( let i = 2; i < data.length; i += 4 ) {
-
-						composite.data[ i ] = data[ i ];
-
-					}
+			}
 
-				}
+			context.putImageData( composite, 0, 0 ); //
 
-				context.putImageData( composite, 0, 0 );
-				return new THREE.Texture( canvas );
+			const reference = metalnessMap || roughnessMap;
+			const texture = reference.clone(); // TODO Use new Source() instead?
 
-			}
+			texture.source = new THREE.Texture( canvas ).source;
+			return texture;
 
 		}
 		/**
@@ -933,11 +920,12 @@
    * @param  {Image} image to process
    * @param  {Integer} format of the image (THREE.RGBAFormat)
    * @param  {Boolean} flipY before writing out the image
+   * @param  {String} mimeType export format
    * @return {Integer}     Index of the processed texture in the "images" array
    */
 
 
-		processImage( image, format, flipY ) {
+		processImage( image, format, flipY, mimeType = 'image/png' ) {
 
 			const writer = this;
 			const cache = writer.cache;
@@ -946,7 +934,6 @@
 			const pending = writer.pending;
 			if ( ! cache.images.has( image ) ) cache.images.set( image, {} );
 			const cachedImages = cache.images.get( image );
-			const mimeType = 'image/png';
 			const key = mimeType + ':flipY/' + flipY.toString();
 			if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ];
 			if ( ! json.images ) json.images = [];
@@ -1068,9 +1055,11 @@
 			const json = this.json;
 			if ( cache.textures.has( map ) ) return cache.textures.get( map );
 			if ( ! json.textures ) json.textures = [];
+			let mimeType = map.userData.mimeType;
+			if ( mimeType === 'image/webp' ) mimeType = 'image/png';
 			const textureDef = {
 				sampler: this.processSampler( map ),
-				source: this.processImage( map.image, map.format, map.flipY )
+				source: this.processImage( map.image, map.format, map.flipY, mimeType )
 			};
 			if ( map.name ) textureDef.name = map.name;
 
@@ -1136,16 +1125,16 @@
 				materialDef.pbrMetallicRoughness.metallicFactor = 0.5;
 				materialDef.pbrMetallicRoughness.roughnessFactor = 0.5;
 
-			}
+			} // pbrMetallicRoughness.metallicRoughnessTexture
 
-			const ormTexture = this.buildORMTexture( material ); // pbrMetallicRoughness.metallicRoughnessTexture
 
 			if ( material.metalnessMap || material.roughnessMap ) {
 
+				const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap );
 				const metalRoughMapDef = {
-					index: this.processTexture( ormTexture )
+					index: this.processTexture( metalRoughTexture )
 				};
-				this.applyTextureTransform( metalRoughMapDef, material.metalnessMap || material.roughnessMap );
+				this.applyTextureTransform( metalRoughMapDef, metalRoughTexture );
 				materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
 
 			} // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture
@@ -1217,7 +1206,7 @@
 			if ( material.aoMap ) {
 
 				const occlusionMapDef = {
-					index: this.processTexture( ormTexture ),
+					index: this.processTexture( material.aoMap ),
 					texCoord: 1
 				};
 

+ 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;
 

+ 24 - 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;
 
@@ -1969,6 +1981,14 @@
 
 		}
 
+	}
+
+	function getImageURIMimeType( uri ) {
+
+		if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg';
+		if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp';
+		return 'image/png';
+
 	}
 	/* GLTF PARSER */
 
@@ -2671,6 +2691,7 @@
 
 				}
 
+				texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri );
 				return texture;
 
 			} ).catch( function ( error ) {

+ 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 ) {

+ 6 - 7
examples/js/renderers/CSS2DRenderer.js

@@ -102,17 +102,16 @@
 
 				if ( object.isCSS2DObject ) {
 
-					const visible = object.visible && _vector.z >= - 1 && _vector.z <= 1 && object.layers.test( camera.layers );
-					object.element.style.display = visible ? '' : 'none';
+					_vector.setFromMatrixPosition( object.matrixWorld );
 
-					if ( visible ) {
+					_vector.applyMatrix4( _viewProjectionMatrix );
 
-						object.onBeforeRender( _this, scene, camera );
-
-						_vector.setFromMatrixPosition( object.matrixWorld );
+					const visible = object.visible === true && _vector.z >= - 1 && _vector.z <= 1 && object.layers.test( camera.layers ) === true;
+					object.element.style.display = visible === true ? '' : 'none';
 
-						_vector.applyMatrix4( _viewProjectionMatrix );
+					if ( visible === true ) {
 
+						object.onBeforeRender( _this, scene, camera );
 						const element = object.element;
 
 						if ( /apple/i.test( navigator.vendor ) ) {

+ 3 - 3
examples/js/renderers/CSS3DRenderer.js

@@ -181,10 +181,10 @@
 
 				if ( object.isCSS3DObject ) {
 
-					const visible = object.visible && object.layers.test( camera.layers );
-					object.element.style.display = visible ? '' : 'none'; // only getObjectCSSMatrix when object.visible
+					const visible = object.visible === true && object.layers.test( camera.layers ) === true;
+					object.element.style.display = visible === true ? '' : 'none';
 
-					if ( visible ) {
+					if ( visible === true ) {
 
 						object.onBeforeRender( _this, scene, camera );
 						let style;

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "three",
-  "version": "0.138.1",
+  "version": "0.138.2",
   "description": "JavaScript 3D library",
   "type": "module",
   "main": "./build/three.js",