Bladeren bron

feat: 更换本地资源

jinx 2 jaren geleden
bovenliggende
commit
a0ced14d90

File diff suppressed because it is too large
+ 1535 - 1521
dist/three-loader-3dtiles.esm.js


+ 14 - 12
examples/demos/realitycapture/index.html

@@ -8,6 +8,7 @@
       margin: 0;
       padding: 0;
     }
+  
     body {
       width: 100vw;
       height: 100vh;
@@ -78,26 +79,27 @@
       </ul>
     </p>
   </div>
-  <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
+  <script src="../../resource/js/vconsole.min.js"></script>
   <script>
     // VConsole 默认会挂载到 `window.VConsole` 上
     var vConsole = new window.VConsole();
   </script>
-  <script async src="https://ga.jspm.io/npm:es-module-shims@1.4.4/dist/es-module-shims.js"></script>
+  <!-- <script async src="https://ga.jspm.io/npm:es-module-shims@1.4.4/dist/es-module-shims.js"></script> -->
+  <script async src="../../resource/js/es-module-shims.js"></script>
   <script type="importmap">
     {
       "imports": {
-        "three": "https://cdn.skypack.dev/three@0.137.0",
-        "three/examples/jsm/loaders/GLTFLoader.js": "https://cdn.skypack.dev/three@v0.137.0/examples/jsm/loaders/GLTFLoader",
-        "three/examples/jsm/loaders/DRACOLoader.js": "https://cdn.skypack.dev/three@v0.137.0/examples/jsm/loaders/DRACOLoader",
-        "three/examples/jsm/loaders/KTX2Loader.js": "https://cdn.skypack.dev/three@v0.137.0/examples/jsm/loaders/KTX2Loader",
-        "three/examples/jsm/libs/stats.module.js": "https://cdn.skypack.dev/three@v0.137.0/examples/jsm/libs/stats.module",
-        "three/examples/jsm/controls/OrbitControls": "https://cdn.skypack.dev/three@v0.137.0/examples/jsm/controls/OrbitControls",
-        "@probe.gl/stats" : "https://cdn.skypack.dev/@probe.gl/stats@3.3.1",
-        "@probe.gl/stats-widget" : "https://cdn.skypack.dev/@probe.gl/stats-widget@3.5.0",
+        "three": "../../resource/js/three.js",
+        "three/examples/jsm/loaders/GLTFLoader.js": "../../resource/js/GLTFLoader.js",
+        "three/examples/jsm/loaders/DRACOLoader.js": "../../resource/js/DRACOLoader.js",
+        "three/examples/jsm/loaders/KTX2Loader.js": "../../resource/js/KTX2Loader.js",
+        "three/examples/jsm/libs/stats.module.js": "../../resource/js/stats.module.js",
+        "three/examples/jsm/controls/OrbitControls": "../../resource/js/OrbitControls.js",
+        "@probe.gl/stats" : "../../resource/js/stats.js",
+        "@probe.gl/stats-widget" : "../../resource/js/stats-widget.js",
         "three-loader-3dtiles" : "../../../dist/three-loader-3dtiles.esm.js",
-        "gsap": "https://cdn.skypack.dev/gsap@3.6.1",
-        "three-story-controls" : "https://cdn.skypack.dev/three-story-controls@1.0.0"
+        "gsap": "../../resource/js/gsap.js",
+        "three-story-controls" : "../../resource/js/three-story-controls.js"
       }
     }
   </script>

+ 587 - 0
examples/resource/js/DRACOLoader.js

@@ -0,0 +1,587 @@
+import {
+	BufferAttribute,
+	BufferGeometry,
+	FileLoader,
+	Loader
+} from 'three';
+
+const _taskCache = new WeakMap();
+
+class DRACOLoader extends Loader {
+
+	constructor( manager ) {
+
+		super( manager );
+
+		this.decoderPath = '';
+		this.decoderConfig = {};
+		this.decoderBinary = null;
+		this.decoderPending = null;
+
+		this.workerLimit = 4;
+		this.workerPool = [];
+		this.workerNextTaskID = 1;
+		this.workerSourceURL = '';
+
+		this.defaultAttributeIDs = {
+			position: 'POSITION',
+			normal: 'NORMAL',
+			color: 'COLOR',
+			uv: 'TEX_COORD'
+		};
+		this.defaultAttributeTypes = {
+			position: 'Float32Array',
+			normal: 'Float32Array',
+			color: 'Float32Array',
+			uv: 'Float32Array'
+		};
+
+	}
+
+	setDecoderPath( path ) {
+
+		this.decoderPath = path;
+
+		return this;
+
+	}
+
+	setDecoderConfig( config ) {
+
+		this.decoderConfig = config;
+
+		return this;
+
+	}
+
+	setWorkerLimit( workerLimit ) {
+
+		this.workerLimit = workerLimit;
+
+		return this;
+
+	}
+
+	load( url, onLoad, onProgress, onError ) {
+
+		const loader = new FileLoader( this.manager );
+
+		loader.setPath( this.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+
+		loader.load( url, ( buffer ) => {
+
+			const taskConfig = {
+				attributeIDs: this.defaultAttributeIDs,
+				attributeTypes: this.defaultAttributeTypes,
+				useUniqueIDs: false
+			};
+
+			this.decodeGeometry( buffer, taskConfig )
+				.then( onLoad )
+				.catch( onError );
+
+		}, onProgress, onError );
+
+	}
+
+	/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
+	decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) {
+
+		const taskConfig = {
+			attributeIDs: attributeIDs || this.defaultAttributeIDs,
+			attributeTypes: attributeTypes || this.defaultAttributeTypes,
+			useUniqueIDs: !! attributeIDs
+		};
+
+		this.decodeGeometry( buffer, taskConfig ).then( callback );
+
+	}
+
+	decodeGeometry( buffer, taskConfig ) {
+
+		// TODO: For backward-compatibility, support 'attributeTypes' objects containing
+		// references (rather than names) to typed array constructors. These must be
+		// serialized before sending them to the worker.
+		for ( const attribute in taskConfig.attributeTypes ) {
+
+			const type = taskConfig.attributeTypes[ attribute ];
+
+			if ( type.BYTES_PER_ELEMENT !== undefined ) {
+
+				taskConfig.attributeTypes[ attribute ] = type.name;
+
+			}
+
+		}
+
+		//
+
+		const taskKey = JSON.stringify( taskConfig );
+
+		// Check for an existing task using this buffer. A transferred buffer cannot be transferred
+		// again from this thread.
+		if ( _taskCache.has( buffer ) ) {
+
+			const cachedTask = _taskCache.get( buffer );
+
+			if ( cachedTask.key === taskKey ) {
+
+				return cachedTask.promise;
+
+			} else if ( buffer.byteLength === 0 ) {
+
+				// Technically, it would be possible to wait for the previous task to complete,
+				// transfer the buffer back, and decode again with the second configuration. That
+				// is complex, and I don't know of any reason to decode a Draco buffer twice in
+				// different ways, so this is left unimplemented.
+				throw new Error(
+
+					'THREE.DRACOLoader: Unable to re-decode a buffer with different ' +
+					'settings. Buffer has already been transferred.'
+
+				);
+
+			}
+
+		}
+
+		//
+
+		let worker;
+		const taskID = this.workerNextTaskID ++;
+		const taskCost = buffer.byteLength;
+
+		// Obtain a worker and assign a task, and construct a geometry instance
+		// when the task completes.
+		const geometryPending = this._getWorker( taskID, taskCost )
+			.then( ( _worker ) => {
+
+				worker = _worker;
+
+				return new Promise( ( resolve, reject ) => {
+
+					worker._callbacks[ taskID ] = { resolve, reject };
+
+					worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] );
+
+					// this.debug();
+
+				} );
+
+			} )
+			.then( ( message ) => this._createGeometry( message.geometry ) );
+
+		// Remove task from the task list.
+		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
+		geometryPending
+			.catch( () => true )
+			.then( () => {
+
+				if ( worker && taskID ) {
+
+					this._releaseTask( worker, taskID );
+
+					// this.debug();
+
+				}
+
+			} );
+
+		// Cache the task result.
+		_taskCache.set( buffer, {
+
+			key: taskKey,
+			promise: geometryPending
+
+		} );
+
+		return geometryPending;
+
+	}
+
+	_createGeometry( geometryData ) {
+
+		const geometry = new BufferGeometry();
+
+		if ( geometryData.index ) {
+
+			geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) );
+
+		}
+
+		for ( let i = 0; i < geometryData.attributes.length; i ++ ) {
+
+			const attribute = geometryData.attributes[ i ];
+			const name = attribute.name;
+			const array = attribute.array;
+			const itemSize = attribute.itemSize;
+
+			geometry.setAttribute( name, new BufferAttribute( array, itemSize ) );
+
+		}
+
+		return geometry;
+
+	}
+
+	_loadLibrary( url, responseType ) {
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.decoderPath );
+		loader.setResponseType( responseType );
+		loader.setWithCredentials( this.withCredentials );
+
+		return new Promise( ( resolve, reject ) => {
+
+			loader.load( url, resolve, undefined, reject );
+
+		} );
+
+	}
+
+	preload() {
+
+		this._initDecoder();
+
+		return this;
+
+	}
+
+	_initDecoder() {
+
+		if ( this.decoderPending ) return this.decoderPending;
+
+		const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
+		const librariesPending = [];
+
+		if ( useJS ) {
+
+			librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
+
+		} else {
+
+			librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
+			librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
+
+		}
+
+		this.decoderPending = Promise.all( librariesPending )
+			.then( ( libraries ) => {
+
+				const jsContent = libraries[ 0 ];
+
+				if ( ! useJS ) {
+
+					this.decoderConfig.wasmBinary = libraries[ 1 ];
+
+				}
+
+				const fn = DRACOWorker.toString();
+
+				const body = [
+					'/* draco decoder */',
+					jsContent,
+					'',
+					'/* worker */',
+					fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
+				].join( '\n' );
+
+				this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
+
+			} );
+
+		return this.decoderPending;
+
+	}
+
+	_getWorker( taskID, taskCost ) {
+
+		return this._initDecoder().then( () => {
+
+			if ( this.workerPool.length < this.workerLimit ) {
+
+				const worker = new Worker( this.workerSourceURL );
+
+				worker._callbacks = {};
+				worker._taskCosts = {};
+				worker._taskLoad = 0;
+
+				worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
+
+				worker.onmessage = function ( e ) {
+
+					const message = e.data;
+
+					switch ( message.type ) {
+
+						case 'decode':
+							worker._callbacks[ message.id ].resolve( message );
+							break;
+
+						case 'error':
+							worker._callbacks[ message.id ].reject( message );
+							break;
+
+						default:
+							console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
+
+					}
+
+				};
+
+				this.workerPool.push( worker );
+
+			} else {
+
+				this.workerPool.sort( function ( a, b ) {
+
+					return a._taskLoad > b._taskLoad ? - 1 : 1;
+
+				} );
+
+			}
+
+			const worker = this.workerPool[ this.workerPool.length - 1 ];
+			worker._taskCosts[ taskID ] = taskCost;
+			worker._taskLoad += taskCost;
+			return worker;
+
+		} );
+
+	}
+
+	_releaseTask( worker, taskID ) {
+
+		worker._taskLoad -= worker._taskCosts[ taskID ];
+		delete worker._callbacks[ taskID ];
+		delete worker._taskCosts[ taskID ];
+
+	}
+
+	debug() {
+
+		console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
+
+	}
+
+	dispose() {
+
+		for ( let i = 0; i < this.workerPool.length; ++ i ) {
+
+			this.workerPool[ i ].terminate();
+
+		}
+
+		this.workerPool.length = 0;
+
+		return this;
+
+	}
+
+}
+
+/* WEB WORKER */
+
+function DRACOWorker() {
+
+	let decoderConfig;
+	let decoderPending;
+
+	onmessage = function ( e ) {
+
+		const message = e.data;
+
+		switch ( message.type ) {
+
+			case 'init':
+				decoderConfig = message.decoderConfig;
+				decoderPending = new Promise( function ( resolve/*, reject*/ ) {
+
+					decoderConfig.onModuleLoaded = function ( draco ) {
+
+						// Module is Promise-like. Wrap before resolving to avoid loop.
+						resolve( { draco: draco } );
+
+					};
+
+					DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef
+
+				} );
+				break;
+
+			case 'decode':
+				const buffer = message.buffer;
+				const taskConfig = message.taskConfig;
+				decoderPending.then( ( module ) => {
+
+					const draco = module.draco;
+					const decoder = new draco.Decoder();
+					const decoderBuffer = new draco.DecoderBuffer();
+					decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
+
+					try {
+
+						const geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
+
+						const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
+
+						if ( geometry.index ) buffers.push( geometry.index.array.buffer );
+
+						self.postMessage( { type: 'decode', id: message.id, geometry }, buffers );
+
+					} catch ( error ) {
+
+						console.error( error );
+
+						self.postMessage( { type: 'error', id: message.id, error: error.message } );
+
+					} finally {
+
+						draco.destroy( decoderBuffer );
+						draco.destroy( decoder );
+
+					}
+
+				} );
+				break;
+
+		}
+
+	};
+
+	function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
+
+		const attributeIDs = taskConfig.attributeIDs;
+		const attributeTypes = taskConfig.attributeTypes;
+
+		let dracoGeometry;
+		let decodingStatus;
+
+		const geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
+
+		if ( geometryType === draco.TRIANGULAR_MESH ) {
+
+			dracoGeometry = new draco.Mesh();
+			decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry );
+
+		} else if ( geometryType === draco.POINT_CLOUD ) {
+
+			dracoGeometry = new draco.PointCloud();
+			decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry );
+
+		} else {
+
+			throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
+
+		}
+
+		if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
+
+			throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
+
+		}
+
+		const geometry = { index: null, attributes: [] };
+
+		// Gather all vertex attributes.
+		for ( const attributeName in attributeIDs ) {
+
+			const attributeType = self[ attributeTypes[ attributeName ] ];
+
+			let attribute;
+			let attributeID;
+
+			// A Draco file may be created with default vertex attributes, whose attribute IDs
+			// are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
+			// a Draco file may contain a custom set of attributes, identified by known unique
+			// IDs. glTF files always do the latter, and `.drc` files typically do the former.
+			if ( taskConfig.useUniqueIDs ) {
+
+				attributeID = attributeIDs[ attributeName ];
+				attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID );
+
+			} else {
+
+				attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] );
+
+				if ( attributeID === - 1 ) continue;
+
+				attribute = decoder.GetAttribute( dracoGeometry, attributeID );
+
+			}
+
+			geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
+
+		}
+
+		// Add index.
+		if ( geometryType === draco.TRIANGULAR_MESH ) {
+
+			geometry.index = decodeIndex( draco, decoder, dracoGeometry );
+
+		}
+
+		draco.destroy( dracoGeometry );
+
+		return geometry;
+
+	}
+
+	function decodeIndex( draco, decoder, dracoGeometry ) {
+
+		const numFaces = dracoGeometry.num_faces();
+		const numIndices = numFaces * 3;
+		const byteLength = numIndices * 4;
+
+		const ptr = draco._malloc( byteLength );
+		decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
+		const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
+		draco._free( ptr );
+
+		return { array: index, itemSize: 1 };
+
+	}
+
+	function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
+
+		const numComponents = attribute.num_components();
+		const numPoints = dracoGeometry.num_points();
+		const numValues = numPoints * numComponents;
+		const byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
+		const dataType = getDracoDataType( draco, attributeType );
+
+		const ptr = draco._malloc( byteLength );
+		decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
+		const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
+		draco._free( ptr );
+
+		return {
+			name: attributeName,
+			array: array,
+			itemSize: numComponents
+		};
+
+	}
+
+	function getDracoDataType( draco, attributeType ) {
+
+		switch ( attributeType ) {
+
+			case Float32Array: return draco.DT_FLOAT32;
+			case Int8Array: return draco.DT_INT8;
+			case Int16Array: return draco.DT_INT16;
+			case Int32Array: return draco.DT_INT32;
+			case Uint8Array: return draco.DT_UINT8;
+			case Uint16Array: return draco.DT_UINT16;
+			case Uint32Array: return draco.DT_UINT32;
+
+		}
+
+	}
+
+}
+
+export { DRACOLoader };

File diff suppressed because it is too large
+ 4347 - 0
examples/resource/js/GLTFLoader.js


+ 600 - 0
examples/resource/js/KTX2Loader.js

@@ -0,0 +1,600 @@
+/**
+ * Loader for KTX 2.0 GPU Texture containers.
+ *
+ * KTX 2.0 is a container format for various GPU texture formats. The loader
+ * supports Basis Universal GPU textures, which can be quickly transcoded to
+ * a wide variety of GPU texture compression formats. While KTX 2.0 also allows
+ * other hardware-specific formats, this loader does not yet parse them.
+ *
+ * References:
+ * - KTX: http://github.khronos.org/KTX-Specification/
+ * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
+ */
+
+import {
+	CompressedTexture,
+	FileLoader,
+	LinearEncoding,
+	LinearFilter,
+	LinearMipmapLinearFilter,
+	Loader,
+	RGBAFormat,
+	RGBA_ASTC_4x4_Format,
+	RGBA_BPTC_Format,
+	RGBA_ETC2_EAC_Format,
+	RGBA_PVRTC_4BPPV1_Format,
+	RGBA_S3TC_DXT5_Format,
+	RGB_ETC1_Format,
+	RGB_ETC2_Format,
+	RGB_PVRTC_4BPPV1_Format,
+	RGB_S3TC_DXT1_Format,
+	sRGBEncoding,
+	UnsignedByteType
+} from 'three';
+import { WorkerPool } from './WorkerPool.js';
+
+const KTX2TransferSRGB = 2;
+const KTX2_ALPHA_PREMULTIPLIED = 1;
+const _taskCache = new WeakMap();
+
+let _activeLoaders = 0;
+
+class KTX2Loader extends Loader {
+
+	constructor( manager ) {
+
+		super( manager );
+
+		this.transcoderPath = '';
+		this.transcoderBinary = null;
+		this.transcoderPending = null;
+
+		this.workerPool = new WorkerPool();
+		this.workerSourceURL = '';
+		this.workerConfig = null;
+
+		if ( typeof MSC_TRANSCODER !== 'undefined' ) {
+
+			console.warn(
+
+				'THREE.KTX2Loader: Please update to latest "basis_transcoder".'
+				+ ' "msc_basis_transcoder" is no longer supported in three.js r125+.'
+
+			);
+
+		}
+
+	}
+
+	setTranscoderPath( path ) {
+
+		this.transcoderPath = path;
+
+		return this;
+
+	}
+
+	setWorkerLimit( num ) {
+
+		this.workerPool.setWorkerLimit( num );
+
+		return this;
+
+	}
+
+	detectSupport( renderer ) {
+
+		this.workerConfig = {
+			astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
+			etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
+			etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
+			dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
+			bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
+			pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
+				|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
+		};
+
+
+		if ( renderer.capabilities.isWebGL2 ) {
+
+			// https://github.com/mrdoob/three.js/pull/22928
+			this.workerConfig.etc1Supported = false;
+
+		}
+
+		return this;
+
+	}
+
+	dispose() {
+
+		this.workerPool.dispose();
+		if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL );
+
+		return this;
+
+	}
+
+	init() {
+
+		if ( ! this.transcoderPending ) {
+
+			// Load transcoder wrapper.
+			const jsLoader = new FileLoader( this.manager );
+			jsLoader.setPath( this.transcoderPath );
+			jsLoader.setWithCredentials( this.withCredentials );
+			const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' );
+
+			// Load transcoder WASM binary.
+			const binaryLoader = new FileLoader( this.manager );
+			binaryLoader.setPath( this.transcoderPath );
+			binaryLoader.setResponseType( 'arraybuffer' );
+			binaryLoader.setWithCredentials( this.withCredentials );
+			const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' );
+
+			this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
+				.then( ( [ jsContent, binaryContent ] ) => {
+
+					const fn = KTX2Loader.BasisWorker.toString();
+
+					const body = [
+						'/* constants */',
+						'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ),
+						'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ),
+						'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ),
+						'/* basis_transcoder.js */',
+						jsContent,
+						'/* worker */',
+						fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
+					].join( '\n' );
+
+					this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
+					this.transcoderBinary = binaryContent;
+
+					this.workerPool.setWorkerCreator( () => {
+
+						const worker = new Worker( this.workerSourceURL );
+						const transcoderBinary = this.transcoderBinary.slice( 0 );
+
+						worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] );
+
+						return worker;
+
+					} );
+
+				} );
+
+			if ( _activeLoaders > 0 ) {
+
+				// Each instance loads a transcoder and allocates workers, increasing network and memory cost.
+
+				console.warn(
+
+					'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.'
+					+ ' Use a single KTX2Loader instance, or call .dispose() on old instances.'
+
+				);
+
+			}
+
+			_activeLoaders ++;
+
+		}
+
+		return this.transcoderPending;
+
+	}
+
+	load( url, onLoad, onProgress, onError ) {
+
+		if ( this.workerConfig === null ) {
+
+			throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' );
+
+		}
+
+		const loader = new FileLoader( this.manager );
+
+		loader.setResponseType( 'arraybuffer' );
+		loader.setWithCredentials( this.withCredentials );
+
+		const texture = new CompressedTexture();
+
+		loader.load( url, ( buffer ) => {
+
+			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
+			// again from this thread.
+			if ( _taskCache.has( buffer ) ) {
+
+				const cachedTask = _taskCache.get( buffer );
+
+				return cachedTask.promise.then( onLoad ).catch( onError );
+
+			}
+
+			this._createTexture( [ buffer ] )
+				.then( function ( _texture ) {
+
+					texture.copy( _texture );
+					texture.needsUpdate = true;
+
+					if ( onLoad ) onLoad( texture );
+
+				} )
+				.catch( onError );
+
+		}, onProgress, onError );
+
+		return texture;
+
+	}
+
+	_createTextureFrom( transcodeResult ) {
+
+		const { mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags } = transcodeResult;
+
+		if ( type === 'error' ) return Promise.reject( error );
+
+		const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType );
+		texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
+		texture.magFilter = LinearFilter;
+		texture.generateMipmaps = false;
+		texture.needsUpdate = true;
+		texture.encoding = dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding;
+		texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED );
+
+		return texture;
+
+	}
+
+	/**
+	 * @param {ArrayBuffer[]} buffers
+	 * @param {object?} config
+	 * @return {Promise<CompressedTexture>}
+	 */
+	_createTexture( buffers, config = {} ) {
+
+		const taskConfig = config;
+		const texturePending = this.init().then( () => {
+
+			return this.workerPool.postMessage( { type: 'transcode', buffers, taskConfig: taskConfig }, buffers );
+
+		} ).then( ( e ) => this._createTextureFrom( e.data ) );
+
+		// Cache the task result.
+		_taskCache.set( buffers[ 0 ], { promise: texturePending } );
+
+		return texturePending;
+
+	}
+
+	dispose() {
+
+		URL.revokeObjectURL( this.workerSourceURL );
+		this.workerPool.dispose();
+
+		_activeLoaders --;
+
+		return this;
+
+	}
+
+}
+
+
+/* CONSTANTS */
+
+KTX2Loader.BasisFormat = {
+	ETC1S: 0,
+	UASTC_4x4: 1,
+};
+
+KTX2Loader.TranscoderFormat = {
+	ETC1: 0,
+	ETC2: 1,
+	BC1: 2,
+	BC3: 3,
+	BC4: 4,
+	BC5: 5,
+	BC7_M6_OPAQUE_ONLY: 6,
+	BC7_M5: 7,
+	PVRTC1_4_RGB: 8,
+	PVRTC1_4_RGBA: 9,
+	ASTC_4x4: 10,
+	ATC_RGB: 11,
+	ATC_RGBA_INTERPOLATED_ALPHA: 12,
+	RGBA32: 13,
+	RGB565: 14,
+	BGR565: 15,
+	RGBA4444: 16,
+};
+
+KTX2Loader.EngineFormat = {
+	RGBAFormat: RGBAFormat,
+	RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format,
+	RGBA_BPTC_Format: RGBA_BPTC_Format,
+	RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format,
+	RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format,
+	RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format,
+	RGB_ETC1_Format: RGB_ETC1_Format,
+	RGB_ETC2_Format: RGB_ETC2_Format,
+	RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format,
+	RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format,
+};
+
+
+/* WEB WORKER */
+
+KTX2Loader.BasisWorker = function () {
+
+	let config;
+	let transcoderPending;
+	let BasisModule;
+
+	const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
+	const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
+	const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
+
+	self.addEventListener( 'message', function ( e ) {
+
+		const message = e.data;
+
+		switch ( message.type ) {
+
+			case 'init':
+				config = message.config;
+				init( message.transcoderBinary );
+				break;
+
+			case 'transcode':
+				transcoderPending.then( () => {
+
+					try {
+
+						const { width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags } = transcode( message.buffers[ 0 ] );
+
+						const buffers = [];
+
+						for ( let i = 0; i < mipmaps.length; ++ i ) {
+
+							buffers.push( mipmaps[ i ].data.buffer );
+
+						}
+
+						self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags }, buffers );
+
+					} catch ( error ) {
+
+						console.error( error );
+
+						self.postMessage( { type: 'error', id: message.id, error: error.message } );
+
+					}
+
+				} );
+				break;
+
+		}
+
+	} );
+
+	function init( wasmBinary ) {
+
+		transcoderPending = new Promise( ( resolve ) => {
+
+			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
+			BASIS( BasisModule ); // eslint-disable-line no-undef
+
+		} ).then( () => {
+
+			BasisModule.initializeBasis();
+
+			if ( BasisModule.KTX2File === undefined ) {
+
+				console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' );
+
+			}
+
+		} );
+
+	}
+
+	function transcode( buffer ) {
+
+		const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) );
+
+		function cleanup() {
+
+			ktx2File.close();
+			ktx2File.delete();
+
+		}
+
+		if ( ! ktx2File.isValid() ) {
+
+			cleanup();
+			throw new Error( 'THREE.KTX2Loader:	Invalid or unsupported .ktx2 file' );
+
+		}
+
+		const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
+		const width = ktx2File.getWidth();
+		const height = ktx2File.getHeight();
+		const levels = ktx2File.getLevels();
+		const hasAlpha = ktx2File.getHasAlpha();
+		const dfdTransferFn = ktx2File.getDFDTransferFunc();
+		const dfdFlags = ktx2File.getDFDFlags();
+
+		const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+
+		if ( ! width || ! height || ! levels ) {
+
+			cleanup();
+			throw new Error( 'THREE.KTX2Loader:	Invalid texture' );
+
+		}
+
+		if ( ! ktx2File.startTranscoding() ) {
+
+			cleanup();
+			throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' );
+
+		}
+
+		const mipmaps = [];
+
+		for ( let mip = 0; mip < levels; mip ++ ) {
+
+			const levelInfo = ktx2File.getImageLevelInfo( mip, 0, 0 );
+			const mipWidth = levelInfo.origWidth;
+			const mipHeight = levelInfo.origHeight;
+			const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, 0, 0, transcoderFormat ) );
+
+			const status = ktx2File.transcodeImage(
+				dst,
+				mip,
+				0,
+				0,
+				transcoderFormat,
+				0,
+				- 1,
+				- 1,
+			);
+
+			if ( ! status ) {
+
+				cleanup();
+				throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' );
+
+			}
+
+			mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
+
+		}
+
+		cleanup();
+
+		return { width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags };
+
+	}
+
+	//
+
+	// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
+	// device capabilities, and texture dimensions. The list below ranks the formats separately
+	// for ETC1S and UASTC.
+	//
+	// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
+	// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
+	// chooses RGBA32 only as a last resort and does not expose that option to the caller.
+	const FORMAT_OPTIONS = [
+		{
+			if: 'astcSupported',
+			basisFormat: [ BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
+			engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
+			priorityETC1S: Infinity,
+			priorityUASTC: 1,
+			needsPowerOfTwo: false,
+		},
+		{
+			if: 'bptcSupported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
+			engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
+			priorityETC1S: 3,
+			priorityUASTC: 2,
+			needsPowerOfTwo: false,
+		},
+		{
+			if: 'dxtSupported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
+			engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
+			priorityETC1S: 4,
+			priorityUASTC: 5,
+			needsPowerOfTwo: false,
+		},
+		{
+			if: 'etc2Supported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
+			engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
+			priorityETC1S: 1,
+			priorityUASTC: 3,
+			needsPowerOfTwo: false,
+		},
+		{
+			if: 'etc1Supported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.ETC1 ],
+			engineFormat: [ EngineFormat.RGB_ETC1_Format ],
+			priorityETC1S: 2,
+			priorityUASTC: 4,
+			needsPowerOfTwo: false,
+		},
+		{
+			if: 'pvrtcSupported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
+			engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
+			priorityETC1S: 5,
+			priorityUASTC: 6,
+			needsPowerOfTwo: true,
+		},
+	];
+
+	const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
+
+		return a.priorityETC1S - b.priorityETC1S;
+
+	} );
+	const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
+
+		return a.priorityUASTC - b.priorityUASTC;
+
+	} );
+
+	function getTranscoderFormat( basisFormat, width, height, hasAlpha ) {
+
+		let transcoderFormat;
+		let engineFormat;
+
+		const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
+
+		for ( let i = 0; i < options.length; i ++ ) {
+
+			const opt = options[ i ];
+
+			if ( ! config[ opt.if ] ) continue;
+			if ( ! opt.basisFormat.includes( basisFormat ) ) continue;
+			if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue;
+			if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
+
+			transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
+			engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
+
+			return { transcoderFormat, engineFormat };
+
+		}
+
+		console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' );
+
+		transcoderFormat = TranscoderFormat.RGBA32;
+		engineFormat = EngineFormat.RGBAFormat;
+
+		return { transcoderFormat, engineFormat };
+
+	}
+
+	function isPowerOfTwo( value ) {
+
+		if ( value <= 2 ) return true;
+
+		return ( value & ( value - 1 ) ) === 0 && value !== 0;
+
+	}
+
+};
+
+export { KTX2Loader };

File diff suppressed because it is too large
+ 1252 - 0
examples/resource/js/OrbitControls.js


+ 102 - 0
examples/resource/js/WorkerPool.js

@@ -0,0 +1,102 @@
+/**
+ * @author Deepkolos / https://github.com/deepkolos
+ */
+
+export class WorkerPool {
+
+	constructor( pool = 4 ) {
+
+		this.pool = pool;
+		this.queue = [];
+		this.workers = [];
+		this.workersResolve = [];
+		this.workerStatus = 0;
+
+	}
+
+	_initWorker( workerId ) {
+
+		if ( ! this.workers[ workerId ] ) {
+
+			const worker = this.workerCreator();
+			worker.addEventListener( 'message', this._onMessage.bind( this, workerId ) );
+			this.workers[ workerId ] = worker;
+
+		}
+
+	}
+
+	_getIdleWorker() {
+
+		for ( let i = 0; i < this.pool; i ++ )
+			if ( ! ( this.workerStatus & ( 1 << i ) ) ) return i;
+
+		return - 1;
+
+	}
+
+	_onMessage( workerId, msg ) {
+
+		const resolve = this.workersResolve[ workerId ];
+		resolve && resolve( msg );
+
+		if ( this.queue.length ) {
+
+			const { resolve, msg, transfer } = this.queue.shift();
+			this.workersResolve[ workerId ] = resolve;
+			this.workers[ workerId ].postMessage( msg, transfer );
+
+		} else {
+
+			this.workerStatus ^= 1 << workerId;
+
+		}
+
+	}
+
+	setWorkerCreator( workerCreator ) {
+
+		this.workerCreator = workerCreator;
+
+	}
+
+	setWorkerLimit( pool ) {
+
+		this.pool = pool;
+
+	}
+
+	postMessage( msg, transfer ) {
+
+		return new Promise( ( resolve ) => {
+
+			const workerId = this._getIdleWorker();
+
+			if ( workerId !== - 1 ) {
+
+				this._initWorker( workerId );
+				this.workerStatus |= 1 << workerId;
+				this.workersResolve[ workerId ] = resolve;
+				this.workers[ workerId ].postMessage( msg, transfer );
+
+			} else {
+
+				this.queue.push( { resolve, msg, transfer } );
+
+			}
+
+		} );
+
+	}
+
+	dispose() {
+
+		this.workers.forEach( ( worker ) => worker.terminate() );
+		this.workersResolve.length = 0;
+		this.workers.length = 0;
+		this.queue.length = 0;
+		this.workerStatus = 0;
+
+	}
+
+}

File diff suppressed because it is too large
+ 3 - 0
examples/resource/js/es-module-shims.js


File diff suppressed because it is too large
+ 3541 - 0
examples/resource/js/gsap.js


+ 238 - 0
examples/resource/js/stats-widget.js

@@ -0,0 +1,238 @@
+import {Stats} from "./stats.js";
+function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value,
+      enumerable: true,
+      configurable: true,
+      writable: true
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
+const KB = 1024;
+const MB = 1024 * KB;
+const GB = 1024 * MB;
+function formatTime(t) {
+  let value;
+  let unit;
+  let precision;
+  if (t < 1) {
+    value = t * 1e3;
+    unit = "\u03BCs";
+    precision = 0;
+  } else if (t < 1e3) {
+    value = t;
+    unit = "ms";
+    precision = 2;
+  } else {
+    value = t / 1e3;
+    unit = "s";
+    precision = 2;
+  }
+  return "".concat(value.toFixed(precision)).concat(unit);
+}
+function formatMemory(b) {
+  let value;
+  let unit;
+  let precision;
+  if (b < KB) {
+    value = b;
+    unit = " bytes";
+    precision = 0;
+  } else if (b < MB) {
+    value = b / KB;
+    unit = "kB";
+    precision = 2;
+  } else if (b < GB) {
+    value = b / MB;
+    unit = "MB";
+    precision = 2;
+  } else {
+    value = b / GB;
+    unit = "GB";
+    precision = 2;
+  }
+  return "".concat(value.toFixed(precision)).concat(unit);
+}
+const RIGHT_ARROW = "\u25B6";
+const DOWN_ARROW = "\u2B07";
+const DEFAULT_CSS = {
+  css: {
+    position: "fixed",
+    zIndex: 1e4,
+    color: "#ccc",
+    background: "#000",
+    fontFamily: "Helvetica,Arial,sans-serif",
+    padding: "8px",
+    fontSize: "12px",
+    lineSpacing: 6
+  },
+  headerCSS: {
+    fontSize: "16px",
+    cursor: "pointer"
+  },
+  itemCSS: {
+    paddingLeft: "8px"
+  }
+};
+const DEFAULT_FORMATTERS = {
+  count: (stat) => "".concat(stat.name, ": ").concat(stat.count),
+  averageTime: (stat) => "".concat(stat.name, ": ").concat(formatTime(stat.getAverageTime())),
+  totalTime: (stat) => "".concat(stat.name, ": ").concat(formatTime(stat.time)),
+  fps: (stat) => "".concat(stat.name, ": ").concat(Math.round(stat.getHz()), "fps"),
+  memory: (stat) => "".concat(stat.name, ": ").concat(formatMemory(stat.count))
+};
+class StatsWidget {
+  constructor(stats2, options) {
+    _defineProperty(this, "stats", void 0);
+    _defineProperty(this, "title", void 0);
+    _defineProperty(this, "collapsed", false);
+    _defineProperty(this, "_framesPerUpdate", void 0);
+    _defineProperty(this, "_formatters", DEFAULT_FORMATTERS);
+    _defineProperty(this, "_css", void 0);
+    _defineProperty(this, "_headerCSS", void 0);
+    _defineProperty(this, "_itemCSS", void 0);
+    _defineProperty(this, "_container", null);
+    _defineProperty(this, "_innerContainer", null);
+    _defineProperty(this, "_statsContainer", null);
+    _defineProperty(this, "_header", null);
+    _defineProperty(this, "_resetOnUpdate", {});
+    _defineProperty(this, "_counter", 0);
+    _defineProperty(this, "_items", {});
+    _defineProperty(this, "_added", false);
+    this.stats = stats2;
+    this.title = options === null || options === void 0 ? void 0 : options.title;
+    this._framesPerUpdate = Math.round(Math.max((options === null || options === void 0 ? void 0 : options.framesPerUpdate) || 1, 1));
+    this._initializeFormatters(options);
+    this._initializeUpdateConfigs(options);
+    this._css = Object.assign({}, DEFAULT_CSS.css, options === null || options === void 0 ? void 0 : options.css);
+    this._headerCSS = Object.assign({}, DEFAULT_CSS.headerCSS, this._css.header);
+    this._itemCSS = Object.assign({}, DEFAULT_CSS.itemCSS, this._css.item);
+    delete this._css.header;
+    delete this._css.item;
+    this._createDOM(options === null || options === void 0 ? void 0 : options.container);
+    this._createDOMStats();
+  }
+  setStats(stats2) {
+    this.stats = stats2;
+    this._createDOMStats();
+    this._setHeaderContent();
+    this.update();
+  }
+  update() {
+    const stats2 = this.stats && this.stats.stats;
+    if (!stats2 || Object.keys(stats2).length === 0) {
+      return;
+    }
+    if (this._counter++ % this._framesPerUpdate !== 0) {
+      return;
+    }
+    this._update();
+  }
+  remove() {
+    this._container.removeChild(this._innerContainer);
+  }
+  setCollapsed(collapsed) {
+    this.collapsed = collapsed;
+    if (this._statsContainer) {
+      this._statsContainer.style.display = this.collapsed ? "none" : "block";
+    }
+    this._setHeaderContent();
+  }
+  _update() {
+    this.stats.forEach((stat) => {
+      this._createDOMItem(stat.name);
+      this._items[stat.name].innerHTML = this._getLines(stat).join("<BR>");
+      if (this._resetOnUpdate[stat.name]) {
+        stat.reset();
+      }
+    });
+  }
+  _initializeFormatters(options) {
+    if (options !== null && options !== void 0 && options.formatters) {
+      for (const name in options.formatters) {
+        let formatter = options.formatters[name];
+        if (typeof formatter === "string") {
+          formatter = DEFAULT_FORMATTERS[formatter];
+        }
+        this._formatters[name] = formatter;
+      }
+    }
+  }
+  _initializeUpdateConfigs(options) {
+    if (options !== null && options !== void 0 && options.resetOnUpdate) {
+      for (const name in options.resetOnUpdate) {
+        this._resetOnUpdate[name] = options.resetOnUpdate[name];
+      }
+    }
+  }
+  _createDOM(container) {
+    if (typeof document === "undefined" || !document) {
+      return;
+    }
+    this._container = container;
+    if (!this._container) {
+      this._container = document.createElement("div");
+      for (const name in this._css) {
+        this._container.style[name] = this._css[name];
+      }
+      document.body.appendChild(this._container);
+    }
+    this._innerContainer = document.createElement("div");
+    this._container.appendChild(this._innerContainer);
+    this._createDOMHeader();
+    this._statsContainer = document.createElement("div");
+    this._statsContainer.style.display = "block";
+    this._innerContainer.appendChild(this._statsContainer);
+  }
+  _createDOMHeader() {
+    if (!this._header) {
+      this._header = document.createElement("div");
+      for (const name in this._headerCSS) {
+        this._header.style[name] = this._headerCSS[name];
+      }
+      this._header.onclick = this._toggleCollapsed.bind(this);
+      this._innerContainer.appendChild(this._header);
+    }
+    this._setHeaderContent();
+  }
+  _setHeaderContent() {
+    if (this._header) {
+      const collapsedState = this.collapsed ? RIGHT_ARROW : DOWN_ARROW;
+      const title = this.title || this.stats && this.stats.id || "Stats";
+      this._header.innerText = "".concat(collapsedState, " ").concat(title);
+    }
+  }
+  _toggleCollapsed() {
+    this.setCollapsed(!this.collapsed);
+  }
+  _createDOMStats() {
+    if (this.stats instanceof Stats) {
+      this.stats.forEach((stat) => {
+        this._createDOMItem(stat.name);
+      });
+    }
+  }
+  _createDOMItem(statName) {
+    if (!this._statsContainer) {
+      return;
+    }
+    if (this._items[statName]) {
+      return;
+    }
+    this._items[statName] = document.createElement("div");
+    for (const name in this._itemCSS) {
+      this._items[statName].style[name] = this._itemCSS[name];
+    }
+    this._statsContainer.appendChild(this._items[statName]);
+  }
+  _getLines(stat) {
+    const formatter = this._formatters[stat.name] || this._formatters[stat.type] || DEFAULT_FORMATTERS.count;
+    return formatter(this.stats.get(stat.name)).split("\n");
+  }
+}
+export default StatsWidget;
+export {StatsWidget};

+ 394 - 0
examples/resource/js/stats.js

@@ -0,0 +1,394 @@
+function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value,
+      enumerable: true,
+      configurable: true,
+      writable: true
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
+function defaultSetTimout() {
+  throw new Error("setTimeout has not been defined");
+}
+function defaultClearTimeout() {
+  throw new Error("clearTimeout has not been defined");
+}
+var cachedSetTimeout = defaultSetTimout;
+var cachedClearTimeout = defaultClearTimeout;
+var globalContext;
+if (typeof window !== "undefined") {
+  globalContext = window;
+} else if (typeof self !== "undefined") {
+  globalContext = self;
+} else {
+  globalContext = {};
+}
+if (typeof globalContext.setTimeout === "function") {
+  cachedSetTimeout = setTimeout;
+}
+if (typeof globalContext.clearTimeout === "function") {
+  cachedClearTimeout = clearTimeout;
+}
+function runTimeout(fun) {
+  if (cachedSetTimeout === setTimeout) {
+    return setTimeout(fun, 0);
+  }
+  if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
+    cachedSetTimeout = setTimeout;
+    return setTimeout(fun, 0);
+  }
+  try {
+    return cachedSetTimeout(fun, 0);
+  } catch (e) {
+    try {
+      return cachedSetTimeout.call(null, fun, 0);
+    } catch (e2) {
+      return cachedSetTimeout.call(this, fun, 0);
+    }
+  }
+}
+function runClearTimeout(marker) {
+  if (cachedClearTimeout === clearTimeout) {
+    return clearTimeout(marker);
+  }
+  if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
+    cachedClearTimeout = clearTimeout;
+    return clearTimeout(marker);
+  }
+  try {
+    return cachedClearTimeout(marker);
+  } catch (e) {
+    try {
+      return cachedClearTimeout.call(null, marker);
+    } catch (e2) {
+      return cachedClearTimeout.call(this, marker);
+    }
+  }
+}
+var queue = [];
+var draining = false;
+var currentQueue;
+var queueIndex = -1;
+function cleanUpNextTick() {
+  if (!draining || !currentQueue) {
+    return;
+  }
+  draining = false;
+  if (currentQueue.length) {
+    queue = currentQueue.concat(queue);
+  } else {
+    queueIndex = -1;
+  }
+  if (queue.length) {
+    drainQueue();
+  }
+}
+function drainQueue() {
+  if (draining) {
+    return;
+  }
+  var timeout = runTimeout(cleanUpNextTick);
+  draining = true;
+  var len = queue.length;
+  while (len) {
+    currentQueue = queue;
+    queue = [];
+    while (++queueIndex < len) {
+      if (currentQueue) {
+        currentQueue[queueIndex].run();
+      }
+    }
+    queueIndex = -1;
+    len = queue.length;
+  }
+  currentQueue = null;
+  draining = false;
+  runClearTimeout(timeout);
+}
+function nextTick(fun) {
+  var args = new Array(arguments.length - 1);
+  if (arguments.length > 1) {
+    for (var i = 1; i < arguments.length; i++) {
+      args[i - 1] = arguments[i];
+    }
+  }
+  queue.push(new Item(fun, args));
+  if (queue.length === 1 && !draining) {
+    runTimeout(drainQueue);
+  }
+}
+function Item(fun, array) {
+  this.fun = fun;
+  this.array = array;
+}
+Item.prototype.run = function() {
+  this.fun.apply(null, this.array);
+};
+var title = "browser";
+var platform = "browser";
+var browser = true;
+var argv = [];
+var version = "";
+var versions = {};
+var release = {};
+var config = {};
+function noop() {
+}
+var on = noop;
+var addListener = noop;
+var once = noop;
+var off = noop;
+var removeListener = noop;
+var removeAllListeners = noop;
+var emit = noop;
+function binding(name) {
+  throw new Error("process.binding is not supported");
+}
+function cwd() {
+  return "/";
+}
+function chdir(dir) {
+  throw new Error("process.chdir is not supported");
+}
+function umask() {
+  return 0;
+}
+var performance = globalContext.performance || {};
+var performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || function() {
+  return new Date().getTime();
+};
+function hrtime(previousTimestamp) {
+  var clocktime = performanceNow.call(performance) * 1e-3;
+  var seconds = Math.floor(clocktime);
+  var nanoseconds = Math.floor(clocktime % 1 * 1e9);
+  if (previousTimestamp) {
+    seconds = seconds - previousTimestamp[0];
+    nanoseconds = nanoseconds - previousTimestamp[1];
+    if (nanoseconds < 0) {
+      seconds--;
+      nanoseconds += 1e9;
+    }
+  }
+  return [seconds, nanoseconds];
+}
+var startTime = new Date();
+function uptime() {
+  var currentTime = new Date();
+  var dif = currentTime - startTime;
+  return dif / 1e3;
+}
+var process = {
+  nextTick,
+  title,
+  browser,
+  env: {NODE_ENV: "production"},
+  argv,
+  version,
+  versions,
+  on,
+  addListener,
+  once,
+  off,
+  removeListener,
+  removeAllListeners,
+  emit,
+  binding,
+  cwd,
+  chdir,
+  umask,
+  hrtime,
+  platform,
+  release,
+  config,
+  uptime
+};
+function getHiResTimestamp() {
+  let timestamp;
+  if (typeof window !== "undefined" && window.performance) {
+    timestamp = window.performance.now();
+  } else if (typeof process !== "undefined" && process.hrtime) {
+    const timeParts = process.hrtime();
+    timestamp = timeParts[0] * 1e3 + timeParts[1] / 1e6;
+  } else {
+    timestamp = Date.now();
+  }
+  return timestamp;
+}
+class Stat {
+  constructor(name, type) {
+    _defineProperty(this, "name", void 0);
+    _defineProperty(this, "type", void 0);
+    _defineProperty(this, "sampleSize", 1);
+    _defineProperty(this, "time", void 0);
+    _defineProperty(this, "count", void 0);
+    _defineProperty(this, "samples", void 0);
+    _defineProperty(this, "lastTiming", void 0);
+    _defineProperty(this, "lastSampleTime", void 0);
+    _defineProperty(this, "lastSampleCount", void 0);
+    _defineProperty(this, "_count", 0);
+    _defineProperty(this, "_time", 0);
+    _defineProperty(this, "_samples", 0);
+    _defineProperty(this, "_startTime", 0);
+    _defineProperty(this, "_timerPending", false);
+    this.name = name;
+    this.type = type;
+    this.reset();
+  }
+  setSampleSize(samples) {
+    this.sampleSize = samples;
+    return this;
+  }
+  incrementCount() {
+    this.addCount(1);
+    return this;
+  }
+  decrementCount() {
+    this.subtractCount(1);
+    return this;
+  }
+  addCount(value) {
+    this._count += value;
+    this._samples++;
+    this._checkSampling();
+    return this;
+  }
+  subtractCount(value) {
+    this._count -= value;
+    this._samples++;
+    this._checkSampling();
+    return this;
+  }
+  addTime(time) {
+    this._time += time;
+    this.lastTiming = time;
+    this._samples++;
+    this._checkSampling();
+    return this;
+  }
+  timeStart() {
+    this._startTime = getHiResTimestamp();
+    this._timerPending = true;
+    return this;
+  }
+  timeEnd() {
+    if (!this._timerPending) {
+      return this;
+    }
+    this.addTime(getHiResTimestamp() - this._startTime);
+    this._timerPending = false;
+    this._checkSampling();
+    return this;
+  }
+  getSampleAverageCount() {
+    return this.sampleSize > 0 ? this.lastSampleCount / this.sampleSize : 0;
+  }
+  getSampleAverageTime() {
+    return this.sampleSize > 0 ? this.lastSampleTime / this.sampleSize : 0;
+  }
+  getSampleHz() {
+    return this.lastSampleTime > 0 ? this.sampleSize / (this.lastSampleTime / 1e3) : 0;
+  }
+  getAverageCount() {
+    return this.samples > 0 ? this.count / this.samples : 0;
+  }
+  getAverageTime() {
+    return this.samples > 0 ? this.time / this.samples : 0;
+  }
+  getHz() {
+    return this.time > 0 ? this.samples / (this.time / 1e3) : 0;
+  }
+  reset() {
+    this.time = 0;
+    this.count = 0;
+    this.samples = 0;
+    this.lastTiming = 0;
+    this.lastSampleTime = 0;
+    this.lastSampleCount = 0;
+    this._count = 0;
+    this._time = 0;
+    this._samples = 0;
+    this._startTime = 0;
+    this._timerPending = false;
+    return this;
+  }
+  _checkSampling() {
+    if (this._samples === this.sampleSize) {
+      this.lastSampleTime = this._time;
+      this.lastSampleCount = this._count;
+      this.count += this._count;
+      this.time += this._time;
+      this.samples += this._samples;
+      this._time = 0;
+      this._count = 0;
+      this._samples = 0;
+    }
+  }
+}
+class Stats {
+  constructor(options) {
+    _defineProperty(this, "id", void 0);
+    _defineProperty(this, "stats", {});
+    this.id = options.id;
+    this.stats = {};
+    this._initializeStats(options.stats);
+    Object.seal(this);
+  }
+  get(name, type = "count") {
+    return this._getOrCreate({
+      name,
+      type
+    });
+  }
+  get size() {
+    return Object.keys(this.stats).length;
+  }
+  reset() {
+    for (const key in this.stats) {
+      this.stats[key].reset();
+    }
+    return this;
+  }
+  forEach(fn) {
+    for (const key in this.stats) {
+      fn(this.stats[key]);
+    }
+  }
+  getTable() {
+    const table = {};
+    this.forEach((stat) => {
+      table[stat.name] = {
+        time: stat.time || 0,
+        count: stat.count || 0,
+        average: stat.getAverageTime() || 0,
+        hz: stat.getHz() || 0
+      };
+    });
+    return table;
+  }
+  _initializeStats(stats = []) {
+    stats.forEach((stat) => this._getOrCreate(stat));
+  }
+  _getOrCreate(stat) {
+    if (!stat || !stat.name) {
+      return null;
+    }
+    const {
+      name,
+      type
+    } = stat;
+    if (!this.stats[name]) {
+      if (stat instanceof Stat) {
+        this.stats[name] = stat;
+      } else {
+        this.stats[name] = new Stat(name, type);
+      }
+    }
+    return this.stats[name];
+  }
+}
+export {Stat, Stats, getHiResTimestamp as _getHiResTimestamp};
+export default null;

+ 167 - 0
examples/resource/js/stats.module.js

@@ -0,0 +1,167 @@
+var Stats = function () {
+
+	var mode = 0;
+
+	var container = document.createElement( 'div' );
+	container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
+	container.addEventListener( 'click', function ( event ) {
+
+		event.preventDefault();
+		showPanel( ++ mode % container.children.length );
+
+	}, false );
+
+	//
+
+	function addPanel( panel ) {
+
+		container.appendChild( panel.dom );
+		return panel;
+
+	}
+
+	function showPanel( id ) {
+
+		for ( var i = 0; i < container.children.length; i ++ ) {
+
+			container.children[ i ].style.display = i === id ? 'block' : 'none';
+
+		}
+
+		mode = id;
+
+	}
+
+	//
+
+	var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
+
+	var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
+	var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) );
+
+	if ( self.performance && self.performance.memory ) {
+
+		var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) );
+
+	}
+
+	showPanel( 0 );
+
+	return {
+
+		REVISION: 16,
+
+		dom: container,
+
+		addPanel: addPanel,
+		showPanel: showPanel,
+
+		begin: function () {
+
+			beginTime = ( performance || Date ).now();
+
+		},
+
+		end: function () {
+
+			frames ++;
+
+			var time = ( performance || Date ).now();
+
+			msPanel.update( time - beginTime, 200 );
+
+			if ( time >= prevTime + 1000 ) {
+
+				fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );
+
+				prevTime = time;
+				frames = 0;
+
+				if ( memPanel ) {
+
+					var memory = performance.memory;
+					memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 );
+
+				}
+
+			}
+
+			return time;
+
+		},
+
+		update: function () {
+
+			beginTime = this.end();
+
+		},
+
+		// Backwards Compatibility
+
+		domElement: container,
+		setMode: showPanel
+
+	};
+
+};
+
+Stats.Panel = function ( name, fg, bg ) {
+
+	var min = Infinity, max = 0, round = Math.round;
+	var PR = round( window.devicePixelRatio || 1 );
+
+	var WIDTH = 80 * PR, HEIGHT = 48 * PR,
+		TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
+		GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
+		GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
+
+	var canvas = document.createElement( 'canvas' );
+	canvas.width = WIDTH;
+	canvas.height = HEIGHT;
+	canvas.style.cssText = 'width:80px;height:48px';
+
+	var context = canvas.getContext( '2d' );
+	context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
+	context.textBaseline = 'top';
+
+	context.fillStyle = bg;
+	context.fillRect( 0, 0, WIDTH, HEIGHT );
+
+	context.fillStyle = fg;
+	context.fillText( name, TEXT_X, TEXT_Y );
+	context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
+
+	context.fillStyle = bg;
+	context.globalAlpha = 0.9;
+	context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
+
+	return {
+
+		dom: canvas,
+
+		update: function ( value, maxValue ) {
+
+			min = Math.min( min, value );
+			max = Math.max( max, value );
+
+			context.fillStyle = bg;
+			context.globalAlpha = 1;
+			context.fillRect( 0, 0, WIDTH, GRAPH_Y );
+			context.fillStyle = fg;
+			context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );
+
+			context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );
+
+			context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );
+
+			context.fillStyle = bg;
+			context.globalAlpha = 0.9;
+			context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );
+
+		}
+
+	};
+
+};
+
+export default Stats;

File diff suppressed because it is too large
+ 686 - 0
examples/resource/js/three-story-controls.js


File diff suppressed because it is too large
+ 28495 - 0
examples/resource/js/three.js


File diff suppressed because it is too large
+ 10 - 0
examples/resource/js/vconsole.min.js