Reorganize JS to separate threejs scene setup from Basis texture loading.
diff --git a/webgl/gltf-demo/BasisTextureLoader.js b/webgl/gltf-demo/BasisTextureLoader.js
new file mode 100644
index 0000000..2c8cfb9
--- /dev/null
+++ b/webgl/gltf-demo/BasisTextureLoader.js
@@ -0,0 +1,169 @@
+/**
+ * @author Don McCurdy / https://www.donmccurdy.com
+ * @author Austin Eng / ?
+ * @author Shrek Shao / https://github.com/shrekshao
+ */
+
+/**
+ * An example three.js loader for Basis compressed textures.
+ */
+THREE.BasisTextureLoader = class BasisTextureLoader {
+
+  constructor () {
+
+    this.etcSupported = false;
+    this.dxtSupported = false;
+    this.pvrtcSupported = false;
+
+    this.format = null;
+
+  }
+
+  detectSupport ( renderer ) {
+
+    const context = renderer.context;
+
+    this.etcSupported = context.getExtension('WEBGL_compressed_texture_etc1');
+    this.dxtSupported = context.getExtension('WEBGL_compressed_texture_s3tc');
+    this.pvrtcSupported = context.getExtension('WEBGL_compressed_texture_pvrtc')
+      || context.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
+
+    if ( ! this.etcSupported && ! this.dxtSupported && ! this.pvrtcSupported ) {
+
+      throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
+
+    }
+
+    this.format = this.etcSupported
+      ? BASIS_FORMAT.cTFETC1
+      : ( this.dxtSupported
+        ? BASIS_FORMAT.cTFBC1
+        : BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY );
+
+     return this;
+
+  }
+
+  load ( url, onLoad, onProgress, onError ) {
+
+    fetch( url )
+      .then( ( res ) => res.arrayBuffer() )
+      .then( (arrayBuffer) => this._createTexture( arrayBuffer ) )
+      .then( onLoad )
+      .catch( onError );
+
+  }
+
+  _createTexture ( arrayBuffer ) {
+
+    const BASIS_FORMAT = THREE.BasisTextureLoader.BASIS_FORMAT;
+    const DXT_FORMAT_MAP = THREE.BasisTextureLoader.DXT_FORMAT_MAP;
+
+    const basisFile = new BasisFile( new Uint8Array( arrayBuffer ) );
+
+    const width = basisFile.getImageWidth( 0, 0 );
+    const height = basisFile.getImageHeight( 0, 0 );
+    const images = basisFile.getNumImages();
+    const levels = basisFile.getNumLevels( 0 );
+
+    function cleanup () {
+
+      basisFile.close();
+      basisFile.delete();
+
+    }
+
+    if ( ! width || ! height || ! images || ! levels ) {
+
+      cleanup();
+      throw new Error( 'THREE.BasisTextureLoader:  Invalid .basis file' );
+
+    }
+
+    if ( ! basisFile.startTranscoding() ) {
+
+      cleanup();
+      throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
+
+    }
+
+    const dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, 0, this.format ) );
+
+    const startTime = performance.now();
+
+    const status = basisFile.transcodeImage(
+      dst,
+      0,
+      0,
+      this.format,
+      this.etcSupported ? 0 : ( this.dxtSupported ? 1 : 0 ),
+      0
+    );
+
+    console.log( `THREE.BasisTextureLoader: Transcode time: ${performance.now() - startTime}ms`, dst );
+
+    cleanup();
+
+    if ( ! status ) {
+
+      throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
+
+    }
+
+    const mipmaps = [ { data: dst, width, height } ];
+
+    let texture;
+
+    if ( this.etcSupported ) {
+
+      texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
+
+    } else if ( this.dxtSupported ) {
+
+      texture = new THREE.CompressedTexture( mipmaps, width, height, DXT_FORMAT_MAP[ this.format ], THREE.UnsignedByteType );
+
+    } else if ( this.pvrtcSupported ) {
+
+      texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
+
+    } else {
+
+      throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
+
+    }
+
+    texture.minFilter = THREE.LinearMipMapLinearFilter;
+    texture.magFilter = THREE.LinearFilter;
+    texture.encoding = THREE.sRGBEncoding;
+    texture.generateMipmaps = false;
+    texture.flipY = false;
+    texture.needsUpdate = true;
+
+    return texture;
+
+  }
+}
+
+const BASIS_FORMAT = {
+  cTFETC1: 0,
+  cTFBC1: 1,
+  cTFBC4: 2,
+  cTFPVRTC1_4_OPAQUE_ONLY: 3,
+  cTFBC7_M6_OPAQUE_ONLY: 4,
+  cTFETC2: 5,
+  cTFBC3: 6,
+  cTFBC5: 7,
+};
+
+// DXT formats, from:
+// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+const DXT_FORMAT_MAP = {};
+const COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
+const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
+const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
+const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
+DXT_FORMAT_MAP[ BASIS_FORMAT.cTFBC1 ] = COMPRESSED_RGB_S3TC_DXT1_EXT;
+DXT_FORMAT_MAP[ BASIS_FORMAT.cTFBC3 ] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+THREE.BasisTextureLoader.BASIS_FORMAT = BASIS_FORMAT;
+THREE.BasisTextureLoader.DXT_FORMAT_MAP = DXT_FORMAT_MAP;
diff --git a/webgl/gltf-demo/GLTFLoader.js b/webgl/gltf-demo/GLTFLoader.js
index bb67be4..31bc8af 100644
--- a/webgl/gltf-demo/GLTFLoader.js
+++ b/webgl/gltf-demo/GLTFLoader.js
@@ -1,4 +1,9 @@
 /**
+ * Copyright 2019 the three.js authors
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
  * @author Rich Tibbett / https://github.com/richtr
  * @author mrdoob / http://mrdoob.com/
  * @author Tony Parisi / http://www.tonyparisi.com/
@@ -6,6 +11,12 @@
  * @author Don McCurdy / https://www.donmccurdy.com
  */
 
+/**
+ * A modified version of THREE.GLTFLoader, with support for the (hypothetical)
+ * GOOGLE_texture_basis extension. This extension is defined for the sake of
+ * example only – the glTF format will officially reference Basis files within
+ * a KTX2 wrapper.
+ */
 THREE.GLTFLoader = ( function () {
 
   function GLTFLoader( manager ) {
diff --git a/webgl/gltf-demo/index.html b/webgl/gltf-demo/index.html
index 8cf04b2..c37a66c 100644
--- a/webgl/gltf-demo/index.html
+++ b/webgl/gltf-demo/index.html
@@ -3,220 +3,118 @@
 <head>
   <script src="https://cdn.jsdelivr.net/npm/three@v0.104.0"></script>
   <script src="GLTFLoader.js"></script>
+  <script src="BasisTextureLoader.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/three@v0.104.0/examples/js/controls/OrbitControls.js"></script>
   <style>
     html, body { width:100%; height:100%; margin:0; padding:0; }
     canvas { display:block; }
-    #pannel { position: absolute; top: 10px; left: 10px; color: white; background-color:rgba(0.3, 0.3, 0.3, 0.3); padding: 0.5em; max-width: 25%;}
+    #panel { position: absolute; top: 10px; left: 10px; color: white; background-color:rgba(0.3, 0.3, 0.3, 0.3); padding: 0.5em; max-width: 400px;}
   </style>
 </head>
 
 <body>
   <script src="./wasm/build/basis_transcoder.js"></script>
   <script>
-    function log(s) {
-      var div = document.createElement('div');
-      div.innerHTML = s;
-      document.getElementById('log').appendChild(div);
-    }
-
-    function logTime(desc, t) {
-      log(desc + t.toFixed(1) + ' ms');
-    }
-
+    /**
+     * Demo created by donmccurdy@, austin@, shrekshao@, with thanks to the Cesium team for
+     * providing the sample model.
+     */
     Module.onRuntimeInitialized = () => {
+
       const { BasisFile, initializeBasis } = Module;
 
-      // DXT formats, from:
-      // http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
-      COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
-      COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
-      COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
-      COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
-
-      const BASIS_FORMAT = {
-        cTFETC1: 0,
-        cTFBC1: 1,
-        cTFBC4: 2,
-        cTFPVRTC1_4_OPAQUE_ONLY: 3,
-        cTFBC7_M6_OPAQUE_ONLY: 4,
-        cTFETC2: 5,
-        cTFBC3: 6,
-        cTFBC5: 7,
-      };
-
-      BASIS_FORMAT_NAMES = {};
-      for (var name in BASIS_FORMAT) {
-        BASIS_FORMAT_NAMES[BASIS_FORMAT[name]] = name;
-      }
-
-      DXT_FORMAT_MAP = {};
-      DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC1] = COMPRESSED_RGB_S3TC_DXT1_EXT;
-      DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC3] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
+      window.BasisFile = BasisFile;
 
       initializeBasis();
 
+      // Initialize three.js scene and renderer.
 
-      /**
-      * ThreeJS glTF demo created by donmccurdy@, austin@, shrekshao@ below.
-      */
+      const renderer = new THREE.WebGLRenderer( { antialias: true } );
+      renderer.setSize( window.innerWidth, window.innerHeight );
+      renderer.gammaOutput = true;
+      renderer.gammaFactor = 2.2;
 
-      (function () {
+      const scene = new THREE.Scene();
+      scene.background = new THREE.Color( 0xf0f0f0 );
 
-        const renderer = new THREE.WebGLRenderer({ antialias: true });
-        renderer.setSize(window.innerWidth, window.innerHeight);
-        renderer.gammaOutput = true;
-        renderer.gammaFactor = 2.2;
+      const light = new THREE.AmbientLight();
+      scene.add( light );
 
-        const scene = new THREE.Scene();
-        scene.background = new THREE.Color(0xf0f0f0);
+      const light2 = new THREE.PointLight();
+      scene.add( light2 );
 
-        const light = new THREE.AmbientLight();
-        scene.add(light);
+      const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
+      camera.position.set( 8, 6, 5 );
+      camera.lookAt( new THREE.Vector3( 0, -2, 0 ) );
 
-        const light2 = new THREE.PointLight();
-        scene.add(light2);
+      const controls = new THREE.OrbitControls( camera, renderer.domElement );
+      controls.autoRotate = true;
 
-        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-        camera.position.set(12, 8, 10);
-        camera.lookAt(new THREE.Vector3(0, -2, 0));
+      // Create BasisTextureLoader and detect supported target formats.
 
-        const controls = new THREE.OrbitControls( camera, renderer.domElement );
-        controls.autoRotate = true;
+      const basisLoader = new THREE.BasisTextureLoader();
+      basisLoader.detectSupport( renderer );
 
-        window.addEventListener('resize', () => {
-          camera.aspect = window.innerWidth / window.innerHeight;
-          camera.updateProjectionMatrix();
-          renderer.setSize(window.innerWidth, window.innerHeight);
-        }, false);
+      const formatName = basisLoader.etcSupported
+        ? 'ETC1'
+        : ( basisLoader.dxtSupported ? 'BC1' : 'PVRTC' );
+      log(`Transcode to: <strong>${formatName}</strong>`);
 
-        const etcSupported = renderer.context.getExtension('WEBGL_compressed_texture_etc1');
-        const dxtSupported = renderer.context.getExtension('WEBGL_compressed_texture_s3tc');
-        const pvrtcSupported = renderer.context.getExtension('WEBGL_compressed_texture_pvrtc') ||
-                            renderer.context.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
+      // Register BasisTextureLoader for .basis extension.
 
-        if (!etcSupported && !dxtSupported && !pvrtcSupported) {
-          log('No suitable compressed texture format supported!!');
-          return;
-        }
+      THREE.Loader.Handlers.add( /\.basis$/, basisLoader );
 
-        let format = etcSupported ? BASIS_FORMAT.cTFETC1 : (dxtSupported ? BASIS_FORMAT.cTFBC1 : BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY);
+      // Create GLTFLoader, load model, and render.
 
-        log('Transcode to Compressed Texture Format: <strong>' + BASIS_FORMAT_NAMES[format] + '</strong>');
+      const loader = new THREE.GLTFLoader();
 
-        class BasisTextureLoader {
-          constructor() {
-            this.count = 0;
-          }
+      loader.load( 'AGI_HQ/AgiHQ.gltf', ( gltf ) => {
 
-          load(url, onLoad, onProgress, onError) {
+        const model = gltf.scene;
+        model.scale.set( 0.01, 0.01, 0.01 );
 
-            fetch(url).then(res => res.arrayBuffer()).then(buf => {
+        scene.add( model );
 
-              log('<strong> Texture ' + (this.count++) + '</strong>');
+        document.body.appendChild( renderer.domElement );
 
-              const startTime = performance.now();
+        animate();
 
-              const basisFile = new BasisFile(new Uint8Array(buf));
+      }, undefined, ( e ) => console.error( e ) );
 
-              const width = basisFile.getImageWidth(0, 0);
-              const height = basisFile.getImageHeight(0, 0);
-              const images = basisFile.getNumImages();
-              const levels = basisFile.getNumLevels(0);
+      // Main render loop.
 
-              if (!width || !height || !images || !levels) {
-                console.warn('Invalid .basis file');
-                basisFile.close();
-                basisFile.delete();
-                return;
-              }
+      function animate() {
 
-              if (!basisFile.startTranscoding()) {
-                console.warn('startTranscoding failed');
-                basisFile.close();
-                basisFile.delete();
-                return;
-              }
+        requestAnimationFrame( animate );
+        controls.update();
+        renderer.render( scene, camera );
 
-              const dst = new Uint8Array(basisFile.getImageTranscodedSizeInBytes(0, 0, format));
+      }
 
-              if (!basisFile.transcodeImage(dst, 0, 0, format, etcSupported ? 0 : (dxtSupported ? 1 : 0), 0)) {
-                console.warn('transcodeImage failed');
-                basisFile.close();
-                basisFile.delete();
-                return;
-              }
+      // Support viewport resizing.
 
-              const elapsed = performance.now() - startTime;
+      window.addEventListener( 'resize', () => {
 
-              basisFile.close();
-              basisFile.delete();
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+        renderer.setSize( window.innerWidth, window.innerHeight );
 
-              console.log('Transcode success!', dst);
-              console.log('Time:', elapsed, 'ms');
+      }, false );
 
-              logTime('Transcode Time: ', elapsed);
+    }
 
-              const mipmaps = [ { data: dst, width, height } ];
-              let texture;
-              if (etcSupported) {
-                texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
-              } else if (dxtSupported) {
-                const type = dxtSupported ? THREE.UnsignedByteType : THREE.UnsignedShort565Type;
-                const f = dxtSupported ? DXT_FORMAT_MAP[ format ] : THREE.RGBFormat;
-                texture = new THREE.CompressedTexture( mipmaps, width, height, f, type );
-              } else if (pvrtcSupported) {
-                texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
-              }
+    function log(s) {
 
-              texture.minFilter = THREE.LinearFilter;
-              texture.magFilter = THREE.LinearFilter;
-              texture.encoding = THREE.sRGBEncoding;
-              texture.generateMipmaps = false;
-              texture.flipY = false;
-              texture.needsUpdate = true;
+      const div = document.createElement('div');
+      div.innerHTML = s;
+      document.getElementById('log').appendChild(div);
 
-              onLoad( texture );
-              
-            });
-
-          }
-        }
-
-        THREE.Loader.Handlers.add(/\.basis$/, new BasisTextureLoader());
-
-        const loader = new THREE.GLTFLoader();
-
-        function animate() {
-          requestAnimationFrame(animate);
-          controls.update();
-          renderer.render(scene, camera);
-        }
-
-        loader.load('AGI_HQ/AgiHQ.gltf', (gltf) => {
-
-          const model = gltf.scene;
-          model.scale.set(0.01, 0.01, 0.01);
-
-          const bbox = new THREE.Box3();
-          bbox.setFromObject(model);
-          console.log(bbox);
-
-          scene.add(model);
-
-          document.body.appendChild(renderer.domElement);
-
-          animate();
-
-        }, undefined, (e) => console.error(e));
-
-      }());
     }
   </script>
-  <div id="pannel">
+  <div id="panel">
     <strong>Basis Texture Transcoder glTF Demo</strong>
     <div id="log"></div>
-    
+
   </div>
 </body>
 
diff --git a/webgl/gltf-demo/third-party-licenses.txt b/webgl/gltf-demo/third-party-licenses.txt
deleted file mode 100644
index 7467971..0000000
--- a/webgl/gltf-demo/third-party-licenses.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-Three.js
-
-> The MIT License
-
-Copyright © 2010-2019 three.js authors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file