<NME> GLTF.js
<BEF> /**
 * glTF Loader
 * Specification https://github.com/KhronosGroup/glTF/blob/master/specification/README.md
 *
 * TODO Morph targets
 */
import Base from '../core/Base';
import util from '../core/util';
import vendor from '../core/vendor';

    var Base = require('../core/Base');
    var request = require('../core/request');
    var util = require('../core/util');

    var Scene = require('../Scene');
    var Shader = require('../Shader');
import shaderLibrary from '../shader/library';
import Skeleton from '../Skeleton';
import Joint from '../Joint';
import PerspectiveCamera from '../camera/Perspective';
import OrthographicCamera from '../camera/Orthographic';
import glenum from '../core/glenum';

import BoundingBox from '../math/BoundingBox';

import TrackClip from '../animation/TrackClip';
import SamplerTrack from '../animation/SamplerTrack';

import Geometry from '../Geometry';

// Import builtin shader
import '../shader/builtin';
import Shader from '../Shader';

var semanticAttributeMap = {
    'NORMAL': 'normal',
    'POSITION': 'position',
    'TEXCOORD_0': 'texcoord0',
    'TEXCOORD_1': 'texcoord1',
    'WEIGHTS_0': 'weight',
    'JOINTS_0': 'joint',
    'COLOR_0': 'color'
};

var ARRAY_CTOR_MAP = {
    5120: vendor.Int8Array,
    5121: vendor.Uint8Array,
    5122: vendor.Int16Array,
    5123: vendor.Uint16Array,
    5125: vendor.Uint32Array,
    5126: vendor.Float32Array
};
var SIZE_MAP = {
    SCALAR: 1,
    VEC2: 2,
    VEC3: 3,
    VEC4: 4,
    MAT2: 4,
    MAT3: 9,
    MAT4: 16
};

function getAccessorData(json, lib, accessorIdx, isIndices) {
    var accessorInfo = json.accessors[accessorIdx];

    var buffer = lib.bufferViews[accessorInfo.bufferView];
    var byteOffset = accessorInfo.byteOffset || 0;
    var ArrayCtor = ARRAY_CTOR_MAP[accessorInfo.componentType] || vendor.Float32Array;

    var size = SIZE_MAP[accessorInfo.type];
    if (size == null && isIndices) {
        size = 1;
    }
    var arr = new ArrayCtor(buffer, byteOffset, size * accessorInfo.count);

    var quantizeExtension = accessorInfo.extensions && accessorInfo.extensions['WEB3D_quantized_attributes'];
    if (quantizeExtension) {
        var decodedArr = new vendor.Float32Array(size * accessorInfo.count);
        var decodeMatrix = quantizeExtension.decodeMatrix;
        var decodeOffset;
        var decodeScale;
        var decodeOffset = new Array(size);
        var decodeScale = new Array(size);
        for (var k = 0; k < size; k++) {
            decodeOffset[k] = decodeMatrix[size * (size + 1) + k];
            decodeScale[k] = decodeMatrix[k * (size + 1) + k];
        }
        for (var i = 0; i < accessorInfo.count; i++) {
            for (var k = 0; k < size; k++) {
                decodedArr[i * size + k] = arr[i * size + k] * decodeScale[k] + decodeOffset[k];
            }
        }

        arr = decodedArr;
    }
    return arr;
}

function base64ToBinary(input, charStart) {
    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    var lookup = new Uint8Array(130);
    for (var i = 0; i < chars.length; i++) {
        lookup[chars.charCodeAt(i)] = i;
    }
    // Ignore
    var len = input.length - charStart;
    if (input.charAt(len - 1) === '=') { len--; }
    if (input.charAt(len - 1) === '=') { len--; }

    var uarray = new Uint8Array((len / 4) * 3);

    for (var i = 0, j = charStart; i < uarray.length;) {
        var c1 = lookup[input.charCodeAt(j++)];
        var c2 = lookup[input.charCodeAt(j++)];
        var c3 = lookup[input.charCodeAt(j++)];
        var c4 = lookup[input.charCodeAt(j++)];

        uarray[i++] = (c1 << 2) | (c2 >> 4);
        uarray[i++] = ((c2 & 15) << 4) | (c3 >> 2);
        uarray[i++] = ((c3 & 3) << 6) | c4;
    }

    return uarray.buffer;
}


/**
 * @typedef {Object} clay.loader.GLTF.Result
 * @property {Object} json
 * @property {clay.Scene} scene
 * @property {clay.Node} rootNode
 * @property {clay.Camera[]} cameras
 * @property {clay.Texture[]} textures
 * @property {clay.Material[]} materials
 * @property {clay.Skeleton[]} skeletons
 * @property {clay.Mesh[]} meshes
 * @property {clay.animation.TrackClip[]} clips
 * @property {clay.Node[]} nodes
 */

/**
 * @constructor clay.loader.GLTF
 * @extends clay.core.Base
 */
var GLTFLoader = Base.extend(/** @lends clay.loader.GLTF# */ {
    /**
     *
     * @type {clay.Node}
     */
    rootNode: null,
    /**
     * Root path for uri parsing.
     * @type {string}
     */
    rootPath: null,

    /**
     * Root path for texture uri parsing. Defaultly use the rootPath
     * @type {string}
     */
    textureRootPath: null,

    /**
     * Root path for buffer uri parsing. Defaultly use the rootPath
     * @type {string}
     */
    bufferRootPath: null,

    /**
     * Shader used when creating the materials.
     * @type {string|clay.Shader}
     * @default 'clay.standard'
     */
    shader: 'clay.standard',

    /**
     * If use {@link clay.StandardMaterial}
     * @type {string}
     */
    useStandardMaterial: false,

    /**
     * If loading the cameras.
     * @type {boolean}
     */
    includeCamera: true,

    /**
     * If loading the animations.
     * @type {boolean}
     */
    includeAnimation: true,
    /**
     * If loading the meshes
     * @type {boolean}
     */
    includeMesh: true,
    /**
     * If loading the textures.
     * @type {boolean}
     */
    includeTexture: true,

    /**
     * @type {string}
     */
    crossOrigin: '',
    /**
     * @type {boolean}
     * @see https://github.com/KhronosGroup/glTF/issues/674
     */
    textureFlipY: false,

    /**
     * If convert texture to power-of-two
     * @type {boolean}
     */
    textureConvertToPOT: false,

    shaderLibrary: null
},
function () {
    if (!this.shaderLibrary) {
        this.shaderLibrary = shaderLibrary.createLibrary();
    }
},
/** @lends clay.loader.GLTF.prototype */
{
    /**
     * @param {string} url
     */
    load: function (url) {
        var self = this;
        var isBinary = url.endsWith('.glb');

        if (this.rootPath == null) {
            this.rootPath = url.slice(0, url.lastIndexOf('/'));
        }

        vendor.request.get({
            url: url,
            onprogress: function (percent, loaded, total) {
                self.trigger('progress', percent, loaded, total);
            },
            onerror: function (e) {
                self.trigger('error', e);
            },
            responseType: isBinary ? 'arraybuffer' : 'text',
            onload: function (data) {
                if (isBinary) {
                    self.parseBinary(data);
                }
                else {
                    if (typeof data === 'string') {
                        data = JSON.parse(data);
                    }
                    self.parse(data);
                }
            }
        });
    },

    /**
     * Parse glTF binary
     * @param {ArrayBuffer} buffer
     * @return {clay.loader.GLTF.Result}
     */
    parseBinary: function (buffer) {
        var header = new Uint32Array(buffer, 0, 4);
        if (header[0] !== 0x46546C67) {
            this.trigger('error', 'Invalid glTF binary format: Invalid header');
            return;
        }
        if (header[0] < 2) {
            this.trigger('error', 'Only glTF2.0 is supported.');
            return;
        }

        var dataView = new DataView(buffer, 12);
                    var offset = IBMInfo.byteOffset + bufferViewInfo.byteOffset;
                    var size = IBMInfo.count * 16;

                    var array = new Float32Array(buffer, offset, size);

                    skeleton._invBindPoseMatricesArray = array;
                    skeleton._skinMatricesArray = new Float32Array(array.length);
                }
                lib.skeletons[name] = skeleton;
            }
            // json
            if (chunkType === 0x4E4F534A) {
                var arr = new Uint8Array(buffer, i + 12, chunkLength);
                // TODO, for the browser not support TextDecoder.
                var decoder = new TextDecoder();
                var str = decoder.decode(arr);
                try {
                    json = JSON.parse(str);
                }
                catch (e) {
                    this.trigger('error', 'JSON Parse error:' + e.toString());
                    return;
                }
            }
            else if (chunkType === 0x004E4942) {
                buffers.push(buffer.slice(i + 12, i + 12 + chunkLength));
            }

            i += chunkLength;
        }
        if (!json) {
            this.trigger('error', 'Invalid glTF binary format: Can\'t find JSON.');
            return;
        }

        return this.parse(json, buffers);
    },

    /**
     * @param {Object} json
     * @param {ArrayBuffer[]} [buffer]
     * @return {clay.loader.GLTF.Result}
     */
    parse: function (json, buffers) {
        var self = this;

        var lib = {
            json: json,
            buffers: [],
            bufferViews: [],
            materials: [],
            textures: [],
            meshes: [],
            joints: [],
            skeletons: [],
            cameras: [],
            nodes: [],
            clips: []
        };
        // Mount on the root node if given
        var rootNode = this.rootNode || new Scene();

        var loading = 0;
        function checkLoad() {
            loading--;
            if (loading === 0) {
                afterLoadBuffer();
            }
        }
        // If already load buffers
        if (buffers) {
            lib.buffers = buffers.slice();
            afterLoadBuffer(true);
        }
        else {
            // Load buffers
            util.each(json.buffers, function (bufferInfo, idx) {
                loading++;
                var path = bufferInfo.uri;

                self._loadBuffers(path, function (buffer) {
                    lib.buffers[idx] = buffer;
                    checkLoad();
                }, checkLoad);
            });
        }

        function getResult() {
            return {
                json: json,
                scene: self.rootNode ? null : rootNode,
                rootNode: self.rootNode ? rootNode : null,
                cameras: lib.cameras,
                textures: lib.textures,
                materials: lib.materials,
                skeletons: lib.skeletons,
                meshes: lib.instancedMeshes,
                clips: lib.clips,
                nodes: lib.nodes
            };
        }

        function afterLoadBuffer(immediately) {
            // Buffer not load complete.
            if (lib.buffers.length !== json.buffers.length) {
                setTimeout(function () {
                    self.trigger('error', 'Buffer not load complete.');
                });
                return;
            }

            json.bufferViews.forEach(function (bufferViewInfo, idx) {
                // PENDING Performance
                lib.bufferViews[idx] = lib.buffers[bufferViewInfo.buffer]
                    .slice(bufferViewInfo.byteOffset || 0, (bufferViewInfo.byteOffset || 0) + (bufferViewInfo.byteLength || 0));
            });
            lib.buffers = null;
            if (self.includeMesh) {
                if (self.includeTexture) {
                    self._parseTextures(json, lib);
                }
                self._parseMaterials(json, lib);
                self._parseMeshes(json, lib);
            }
            self._parseNodes(json, lib);

            // Only support one scene.
            if (json.scenes) {
                var sceneInfo = json.scenes[json.scene || 0]; // Default use the first scene.
                if (sceneInfo) {
                    for (var i = 0; i < sceneInfo.nodes.length; i++) {
                        var node = lib.nodes[sceneInfo.nodes[i]];
                        node.update();
                        rootNode.add(node);
                    }
                }
            }

            if (self.includeMesh) {
                self._parseSkins(json, lib);
            }

            if (self.includeAnimation) {
                self._parseAnimations(json, lib);
            }
            if (immediately) {
                setTimeout(function () {
                    self.trigger('success', getResult());
                });
            }
            else {
                self.trigger('success', getResult());
            }
        }

        return getResult();
    },

    /**
     * Binary file path resolver. User can override it
     * @param {string} path
     */
    resolveBufferPath: function (path) {
        if (path && path.match(/^data:(.*?)base64,/)) {
            return path;
        }

        var rootPath = this.bufferRootPath;
        if (rootPath == null) {
            rootPath = this.rootPath;
        }
        return util.relative2absolute(path, rootPath);
    },

    /**
     * Texture file path resolver. User can override it
     * @param {string} path
     */
    resolveTexturePath: function (path) {
        if (path && path.match(/^data:(.*?)base64,/)) {
            return path;
        }

        var rootPath = this.textureRootPath;
        if (rootPath == null) {
            rootPath = this.rootPath;
        }
        return util.relative2absolute(path, rootPath);
    },

    /**
     * Buffer loader
     * @param {string}
     * @param {Function} onsuccess
     * @param {Function} onerror
     */
    loadBuffer: function (path, onsuccess, onerror) {
        vendor.request.get({
            url: path,
            responseType: 'arraybuffer',
            onload: function (buffer) {
                onsuccess && onsuccess(buffer);
            },
            onerror: function (buffer) {
                onerror && onerror(buffer);
            }
        });
    },

    _getShader: function () {
        if (typeof this.shader === 'string') {
            return this.shaderLibrary.get(this.shader);
        }
        else if (this.shader instanceof Shader) {
            return this.shader;
        }
    },

    _loadBuffers: function (path, onsuccess, onerror) {
        var base64Prefix = 'data:application/octet-stream;base64,';
        var strStart = path.substr(0, base64Prefix.length);
        if (strStart === base64Prefix) {
            onsuccess(
                base64ToBinary(path, base64Prefix.length)
            );
        }
        else {
            this.loadBuffer(
                this.resolveBufferPath(path),
                onsuccess,
                onerror
            );
        }
    },

    // https://github.com/KhronosGroup/glTF/issues/100
    // https://github.com/KhronosGroup/glTF/issues/193
    _parseSkins: function (json, lib) {

        // Create skeletons and joints
        var haveInvBindMatrices = false;
        util.each(json.skins, function (skinInfo, idx) {
            var skeleton = new Skeleton({
                name: skinInfo.name
            });
            for (var i = 0; i < skinInfo.joints.length; i++) {
                var nodeIdx = skinInfo.joints[i];
                var node = lib.nodes[nodeIdx];
                var joint = new Joint({
                    name: node.name,
                    node: node,
                    index: skeleton.joints.length
                });
                skeleton.joints.push(joint);
            }
            skeleton.relativeRootNode = lib.nodes[skinInfo.skeleton] || this.rootNode;
            if (skinInfo.inverseBindMatrices) {
                haveInvBindMatrices = true;
                var IBMInfo = json.accessors[skinInfo.inverseBindMatrices];
                var buffer = lib.bufferViews[IBMInfo.bufferView];

                var offset = IBMInfo.byteOffset || 0;
                var size = IBMInfo.count * 16;

                var array = new vendor.Float32Array(buffer, offset, size);

                skeleton.setJointMatricesArray(array);
                            case 0x8B50:     // FLOAT_VEC2
                                size = 2;
                                type = 'float';
                                ArrayCtor = Float32Array;
                                break;
                            case 0x8B51:     // FLOAT_VEC3
                                size = 3;
                                type = 'float';
                                ArrayCtor = Float32Array;
                                break;
                            case 0x8B52:     // FLOAT_VEC4
                                size = 4;
                                type = 'float';
                                ArrayCtor = Float32Array;
                                break;
                            case 0x1406:     // FLOAT
                                size = 1;
                                type = 'float';
                                ArrayCtor = Float32Array;
                                break;
                            default:
                                console.warn('Attribute type '+attributeInfo.type+' not support yet');
                var skinIdx = nodeInfo.skin;
                var skeleton = lib.skeletons[skinIdx];

                var node = lib.nodes[nodeIdx];
                var jointIndices = skeleton.joints.map(getJointIndex);
                if (node instanceof Mesh) {
                    enableSkinningForMesh(node, skeleton, jointIndices);
                }
                else {
                    // Mesh have multiple primitives
                    var children = node.children();
                    for (var i = 0; i < children.length; i++) {
                        enableSkinningForMesh(children[i], skeleton, jointIndices);
                    }
                }
            }
        }, this);
    },

    _parseTextures: function (json, lib) {
        util.each(json.textures, function (textureInfo, idx){
            // samplers is optional
            var samplerInfo = (json.samplers && json.samplers[textureInfo.sampler]) || {};
            var parameters = {};
            ['wrapS', 'wrapT', 'magFilter', 'minFilter'].forEach(function (name) {
                var value = samplerInfo[name];
                if (value != null) {
                    parameters[name] = value;
                }
            });
            util.defaults(parameters, {
                wrapS: Texture.REPEAT,
                wrapT: Texture.REPEAT,
                flipY: this.textureFlipY,

                    // index uint
                    if (indicesInfo.type === 0x1405) { // UNSIGNED_INT
                        geometry.faces = new Uint32Array(buffer, byteOffset, indicesInfo.count);
                    }
                    else { // UNSIGNED_SHORT, 0x1403
                        geometry.faces = new Uint16Array(buffer, byteOffset, indicesInfo.count);
                    }

                    var material = lib.materials[primitiveInfo.material];
                var texture = new Texture2D(parameters);
                var imageInfo = json.images[textureInfo.source];
                var uri;
                if (imageInfo.uri) {
                    uri = this.resolveTexturePath(imageInfo.uri);
                }
                else if (imageInfo.bufferView != null) {
                    uri = URL.createObjectURL(new Blob([lib.bufferViews[imageInfo.bufferView]], {
                        type: imageInfo.mimeType
                    }));
                }
                if (uri) {
                    texture.load(uri, this.crossOrigin);
                    lib.textures[idx] = texture;
                }
            }
        }, this);
    },

    _KHRCommonMaterialToStandard: function (materialInfo, lib) {
        var uniforms = {};
        var commonMaterialInfo = materialInfo.extensions['KHR_materials_common'];
        uniforms = commonMaterialInfo.values || {};

        if (typeof uniforms.diffuse === 'number') {
            uniforms.diffuse = lib.textures[uniforms.diffuse] || null;
        }
        if (typeof uniforms.emission === 'number') {
            uniforms.emission = lib.textures[uniforms.emission] || null;
        }

        var enabledTextures = [];
        if (uniforms['diffuse'] instanceof Texture2D) {
            enabledTextures.push('diffuseMap');
        }
        if (materialInfo.normalTexture) {
            enabledTextures.push('normalMap');
        }
        if (uniforms['emission'] instanceof Texture2D) {
            enabledTextures.push('emissiveMap');
        }
        var material;
        var isStandardMaterial = this.useStandardMaterial;
        if (isStandardMaterial) {
            material = new StandardMaterial({
                name: materialInfo.name,
                doubleSided: materialInfo.doubleSided
            });
        }
        else {
            material = new Material({
                name: materialInfo.name,
                shader: this._getShader()
            });

            material.define('fragment', 'USE_ROUGHNESS');
            material.define('fragment', 'USE_METALNESS');

            if (materialInfo.doubleSided) {
                material.define('fragment', 'DOUBLE_SIDED');
            }
        }

        if (uniforms.transparent) {
            material.depthMask = false;
            material.depthTest = true;
            material.transparent = true;
        }

        var diffuseProp = uniforms['diffuse'];
        if (diffuseProp) {
            // Color
            if (Array.isArray(diffuseProp)) {
                diffuseProp = diffuseProp.slice(0, 3);
                isStandardMaterial ? (material.color = diffuseProp)
                    : material.set('color', diffuseProp);
            }
            else { // Texture
                isStandardMaterial ? (material.diffuseMap = diffuseProp)
                    : material.set('diffuseMap', diffuseProp);
            }
        }
        var emissionProp = uniforms['emission'];
        if (emissionProp != null) {
            // Color
            if (Array.isArray(emissionProp)) {
                emissionProp = emissionProp.slice(0, 3);
                isStandardMaterial ? (material.emission = emissionProp)
                    : material.set('emission', emissionProp);
            }
            else { // Texture
                isStandardMaterial ? (material.emissiveMap = emissionProp)
                    : material.set('emissiveMap', emissionProp);
            }
        }
        if (materialInfo.normalTexture != null) {
            // TODO texCoord
            var normalTextureIndex = materialInfo.normalTexture.index;
            if (isStandardMaterial) {
                material.normalMap = lib.textures[normalTextureIndex] || null;
            }
            else {
                material.set('normalMap', lib.textures[normalTextureIndex] || null);
            }
        }
        if (uniforms['shininess'] != null) {
            var glossiness = Math.log(uniforms['shininess']) / Math.log(8192);
            // Uniform glossiness
            material.set('glossiness', glossiness);
            material.set('roughness', 1 - glossiness);
        }
        else {
            material.set('glossiness', 0.3);
            material.set('roughness', 0.3);
        }
        if (uniforms['specular'] != null) {
            material.set('specularColor', uniforms['specular'].slice(0, 3));
        }
        if (uniforms['transparency'] != null) {
            material.set('alpha', uniforms['transparency']);
        }

        return material;
    },

    _pbrMetallicRoughnessToStandard: function (materialInfo, metallicRoughnessMatInfo, lib) {
        var alphaTest = materialInfo.alphaMode === 'MASK';

        var isStandardMaterial = this.useStandardMaterial;
        var material;
        var diffuseMap, roughnessMap, metalnessMap, normalMap, emissiveMap, occlusionMap;
        var enabledTextures = [];

        /**
         * The scalar multiplier applied to each normal vector of the normal texture.
         *
         * @type {number}
         *
         * XXX This value is ignored if `materialInfo.normalTexture` is not specified.
         */
        var normalScale = 1.0;

        // TODO texCoord
        if (metallicRoughnessMatInfo.baseColorTexture) {
            diffuseMap = lib.textures[metallicRoughnessMatInfo.baseColorTexture.index] || null;
            diffuseMap && enabledTextures.push('diffuseMap');
        }
        if (metallicRoughnessMatInfo.metallicRoughnessTexture) {
            roughnessMap = metalnessMap = lib.textures[metallicRoughnessMatInfo.metallicRoughnessTexture.index] || null;
            roughnessMap && enabledTextures.push('metalnessMap', 'roughnessMap');
        }
        if (materialInfo.normalTexture) {

            normalMap = lib.textures[materialInfo.normalTexture.index] || null;
            normalMap && enabledTextures.push('normalMap');

            if (typeof materialInfo.normalTexture.scale === 'number') {
                normalScale = materialInfo.normalTexture.scale;
            }

        }
        if (materialInfo.emissiveTexture) {
            emissiveMap = lib.textures[materialInfo.emissiveTexture.index] || null;
            emissiveMap && enabledTextures.push('emissiveMap');
        }
        if (materialInfo.occlusionTexture) {
            occlusionMap = lib.textures[materialInfo.occlusionTexture.index] || null;
            occlusionMap && enabledTextures.push('occlusionMap');
        }
        var baseColor = metallicRoughnessMatInfo.baseColorFactor || [1, 1, 1, 1];

        var commonProperties = {
            diffuseMap: diffuseMap || null,
            roughnessMap: roughnessMap || null,
            metalnessMap: metalnessMap || null,
            normalMap: normalMap || null,
            occlusionMap: occlusionMap || null,
            emissiveMap: emissiveMap || null,
            color: baseColor.slice(0, 3),
            alpha: baseColor[3],
            metalness: metallicRoughnessMatInfo.metallicFactor || 0,
            roughness: metallicRoughnessMatInfo.roughnessFactor || 0,
            emission: materialInfo.emissiveFactor || [0, 0, 0],
            emissionIntensity: 1,
            alphaCutoff: materialInfo.alphaCutoff || 0.5,
            normalScale: normalScale
        };
        if (commonProperties.roughnessMap) {
            // In glTF metallicFactor will do multiply, which is different from StandardMaterial.
            // So simply ignore it
            commonProperties.metalness = 0.5;
            commonProperties.roughness = 0.5;
        }
        if (isStandardMaterial) {
            material = new StandardMaterial(util.extend({
                name: materialInfo.name,
                alphaTest: alphaTest,
                doubleSided: materialInfo.doubleSided,
                // G channel
                roughnessChannel: 1,
                // B Channel
                metalnessChannel: 2
            }, commonProperties));
        }
        else {

                            var size = 1;
                            break;
                    }
                    parameters[paramName] = new Float32Array(buffer, byteOffset, size * accessorInfo.count);
                }

                if (!parameters.TIME || !animationInfo.channels.length) {
            material.define('fragment', 'ROUGHNESS_CHANNEL', 1);
            material.define('fragment', 'METALNESS_CHANNEL', 2);

            material.define('fragment', 'DIFFUSEMAP_ALPHA_ALPHA');

            if (alphaTest) {
                material.define('fragment', 'ALPHA_TEST');
            }
            if (materialInfo.doubleSided) {
                material.define('fragment', 'DOUBLE_SIDED');
            }

            material.set(commonProperties);
        }

        if (materialInfo.alphaMode === 'BLEND') {
            material.depthMask = false;
            material.depthTest = true;
            material.transparent = true;
        }

        return material;
    },

    _pbrSpecularGlossinessToStandard: function (materialInfo, specularGlossinessMatInfo, lib) {
        var alphaTest = materialInfo.alphaMode === 'MASK';

        if (this.useStandardMaterial) {
            console.error('StandardMaterial doesn\'t support specular glossiness workflow yet');
        }

        var material;
        var diffuseMap, glossinessMap, specularMap, normalMap, emissiveMap, occlusionMap;
        var enabledTextures = [];
        // TODO texCoord
        if (specularGlossinessMatInfo.diffuseTexture) {
            diffuseMap = lib.textures[specularGlossinessMatInfo.diffuseTexture.index] || null;
            diffuseMap && enabledTextures.push('diffuseMap');
        }
        if (specularGlossinessMatInfo.specularGlossinessTexture) {
            glossinessMap = specularMap = lib.textures[specularGlossinessMatInfo.specularGlossinessTexture.index] || null;
            glossinessMap && enabledTextures.push('specularMap', 'glossinessMap');
        }
        if (materialInfo.normalTexture) {
            normalMap = lib.textures[materialInfo.normalTexture.index] || null;
            normalMap && enabledTextures.push('normalMap');
        }
        if (materialInfo.emissiveTexture) {
            emissiveMap = lib.textures[materialInfo.emissiveTexture.index] || null;
            emissiveMap && enabledTextures.push('emissiveMap');
        }
        if (materialInfo.occlusionTexture) {
            occlusionMap = lib.textures[materialInfo.occlusionTexture.index] || null;
            occlusionMap && enabledTextures.push('occlusionMap');
        }
        var diffuseColor = specularGlossinessMatInfo.diffuseFactor || [1, 1, 1, 1];

        var commonProperties = {
            diffuseMap: diffuseMap || null,
            glossinessMap: glossinessMap || null,
            specularMap: specularMap || null,
            normalMap: normalMap || null,
            emissiveMap: emissiveMap || null,
            occlusionMap: occlusionMap || null,
            color: diffuseColor.slice(0, 3),
            alpha: diffuseColor[3],
            specularColor: specularGlossinessMatInfo.specularFactor || [1, 1, 1],
            glossiness: specularGlossinessMatInfo.glossinessFactor || 0,
            emission: materialInfo.emissiveFactor || [0, 0, 0],
            emissionIntensity: 1,
            alphaCutoff: materialInfo.alphaCutoff == null ? 0.5 : materialInfo.alphaCutoff
        };
        if (commonProperties.glossinessMap) {
            // Ignore specularFactor
            commonProperties.glossiness = 0.5;
        }
        if (commonProperties.specularMap) {
            // Ignore specularFactor
            commonProperties.specularColor = [1, 1, 1];
        }

        material = new Material({
            name: materialInfo.name,
            shader: this._getShader()
        });

        material.define('fragment', 'GLOSSINESS_CHANNEL', 3);
        material.define('fragment', 'DIFFUSEMAP_ALPHA_ALPHA');

        if (alphaTest) {
            material.define('fragment', 'ALPHA_TEST');
        }
        if (materialInfo.doubleSided) {
            material.define('fragment', 'DOUBLE_SIDED');
        }

        material.set(commonProperties);

        if (materialInfo.alphaMode === 'BLEND') {
            material.depthMask = false;
            material.depthTest = true;
            material.transparent = true;
        }

        return material;
    },

    _parseMaterials: function (json, lib) {
        util.each(json.materials, function (materialInfo, idx) {
            if (materialInfo.extensions && materialInfo.extensions['KHR_materials_common']) {
                lib.materials[idx] = this._KHRCommonMaterialToStandard(materialInfo, lib);
            }
            else if (materialInfo.extensions && materialInfo.extensions['KHR_materials_pbrSpecularGlossiness']) {
                lib.materials[idx] = this._pbrSpecularGlossinessToStandard(materialInfo, materialInfo.extensions['KHR_materials_pbrSpecularGlossiness'], lib);
            }
            else {
                lib.materials[idx] = this._pbrMetallicRoughnessToStandard(materialInfo, materialInfo.pbrMetallicRoughness || {}, lib);
            }
        }, this);
    },

    _parseMeshes: function (json, lib) {
        var self = this;

        util.each(json.meshes, function (meshInfo, idx) {
            lib.meshes[idx] = [];
            // Geometry
            for (var pp = 0; pp < meshInfo.primitives.length; pp++) {
                var primitiveInfo = meshInfo.primitives[pp];
                var geometry = new Geometry({
                    dynamic: false,
                    // PENDIGN
                    name: meshInfo.name,
                    boundingBox: new BoundingBox()
                });
                // Parse attributes
                var semantics = Object.keys(primitiveInfo.attributes);
                for (var ss = 0; ss < semantics.length; ss++) {
                    var semantic = semantics[ss];
                    var accessorIdx = primitiveInfo.attributes[semantic];
                    var attributeInfo = json.accessors[accessorIdx];
                    var attributeName = semanticAttributeMap[semantic];
                    if (!attributeName) {
                        continue;
                    }
                    var size = SIZE_MAP[attributeInfo.type];
                    var attributeArray = getAccessorData(json, lib, accessorIdx);
                    // WebGL attribute buffer not support uint32.
                    // Direct use Float32Array may also have issue.
                    if (attributeArray instanceof vendor.Uint32Array) {
                        attributeArray = new Float32Array(attributeArray);
                    }
                    if (semantic === 'WEIGHTS_0' && size === 4) {
                        // Weight data in QTEK has only 3 component, the last component can be evaluated since it is normalized
                        var weightArray = new attributeArray.constructor(attributeInfo.count * 3);
                        for (var i = 0; i < attributeInfo.count; i++) {
                            var i4 = i * 4, i3 = i * 3;
                            var w1 = attributeArray[i4], w2 = attributeArray[i4 + 1], w3 = attributeArray[i4 + 2], w4 = attributeArray[i4 + 3];
                            var wSum = w1 + w2 + w3 + w4;
                            weightArray[i3] = w1 / wSum;
                            weightArray[i3 + 1] = w2 / wSum;
                            weightArray[i3 + 2] = w3 / wSum;
                        }
                        geometry.attributes[attributeName].value = weightArray;
                    }
                    else if (semantic === 'COLOR_0' && size === 3) {
                        var colorArray = new attributeArray.constructor(attributeInfo.count * 4);
                        for (var i = 0; i < attributeInfo.count; i++) {
                            var i4 = i * 4, i3 = i * 3;
                            colorArray[i4] = attributeArray[i3];
                            colorArray[i4 + 1] = attributeArray[i3 + 1];
                            colorArray[i4 + 2] = attributeArray[i3 + 2];
                            colorArray[i4 + 3] = 1;
                        }
                        geometry.attributes[attributeName].value = colorArray;
                    }
                    else {
                        geometry.attributes[attributeName].value = attributeArray;
                    }

                    var attributeType = 'float';
                    if (attributeArray instanceof vendor.Uint16Array) {
                        attributeType = 'ushort';
                    }
                    else if (attributeArray instanceof vendor.Int16Array) {
                        attributeType = 'short';
                    }
                    else if (attributeArray instanceof vendor.Uint8Array) {
                        attributeType = 'ubyte';
                    }
                    else if (attributeArray instanceof vendor.Int8Array) {
                        attributeType = 'byte';
                    }
                    geometry.attributes[attributeName].type = attributeType;

                    if (semantic === 'POSITION') {
                        // Bounding Box
                        var min = attributeInfo.min;
                        var max = attributeInfo.max;
                        if (min) {
                            geometry.boundingBox.min.set(min[0], min[1], min[2]);
                        }
                        if (max) {
                            geometry.boundingBox.max.set(max[0], max[1], max[2]);
                        }
                    }
                }

                // Parse indices
                if (primitiveInfo.indices != null) {
                    geometry.indices = getAccessorData(json, lib, primitiveInfo.indices, true);
                    if (geometry.vertexCount <= 0xffff && geometry.indices instanceof vendor.Uint32Array) {
                        geometry.indices = new vendor.Uint16Array(geometry.indices);
                    }
                    if(geometry.indices instanceof vendor.Uint8Array) {
                        geometry.indices = new vendor.Uint16Array(geometry.indices);
                    }
                }

                var material = lib.materials[primitiveInfo.material];
                var materialInfo = (json.materials || [])[primitiveInfo.material];
                // Use default material
                if (!material) {
                    material = new Material({
                        shader: self._getShader()
                    });
                }
                var mesh = new Mesh({
                    geometry: geometry,
                    material: material,
                    mode: [Mesh.POINTS, Mesh.LINES, Mesh.LINE_LOOP, Mesh.LINE_STRIP, Mesh.TRIANGLES, Mesh.TRIANGLE_STRIP, Mesh.TRIANGLE_FAN][primitiveInfo.mode] || Mesh.TRIANGLES,
                    ignoreGBuffer: material.transparent
                });
                if (materialInfo != null) {
                    mesh.culling = !materialInfo.doubleSided;
                }
                if (!mesh.geometry.attributes.normal.value) {
                    mesh.geometry.generateVertexNormals();
                }
                if (((material instanceof StandardMaterial) && material.normalMap)
                    || (material.isTextureEnabled('normalMap'))
                ) {
                    if (!mesh.geometry.attributes.tangent.value) {
                        mesh.geometry.generateTangents();
                    }
                }
                if (mesh.geometry.attributes.color.value) {
                    mesh.material.define('VERTEX_COLOR');
                }

                mesh.name = GLTFLoader.generateMeshName(json.meshes, idx, pp);

                lib.meshes[idx].push(mesh);
            }
        }, this);
    },

    _instanceCamera: function (json, nodeInfo) {
        var cameraInfo = json.cameras[nodeInfo.camera];

        if (cameraInfo.type === 'perspective') {
            var perspectiveInfo = cameraInfo.perspective || {};
            return new PerspectiveCamera({
                name: nodeInfo.name,
                aspect: perspectiveInfo.aspectRatio,
                fov: perspectiveInfo.yfov / Math.PI * 180,
                far: perspectiveInfo.zfar,
                near: perspectiveInfo.znear
            });
        }
        else {
            var orthographicInfo = cameraInfo.orthographic || {};
            return new OrthographicCamera({
                name: nodeInfo.name,
                top: orthographicInfo.ymag,
                right: orthographicInfo.xmag,
                left: -orthographicInfo.xmag,
                bottom: -orthographicInfo.ymag,
                near: orthographicInfo.znear,
                far: orthographicInfo.zfar
            });
        }
    },

    _parseNodes: function (json, lib) {

        function instanceMesh(mesh) {
            return new Mesh({
                name: mesh.name,
                geometry: mesh.geometry,
                material: mesh.material,
                culling: mesh.culling,
                mode: mesh.mode
            });
        }

        lib.instancedMeshes = [];

        util.each(json.nodes, function (nodeInfo, idx) {
            var node;
            if (nodeInfo.camera != null && this.includeCamera) {
                node = this._instanceCamera(json, nodeInfo);
                lib.cameras.push(node);
            }
            else if (nodeInfo.mesh != null && this.includeMesh) {
                var primitives = lib.meshes[nodeInfo.mesh];
                if (primitives) {
                    if (primitives.length === 1) {
                        // Replace the node with mesh directly
                        node = instanceMesh(primitives[0]);
                        node.setName(nodeInfo.name);
                        lib.instancedMeshes.push(node);
                    }
                    else {
                        node = new Node();
                        node.setName(nodeInfo.name);
                        for (var j = 0; j < primitives.length; j++) {
                            var newMesh = instanceMesh(primitives[j]);
                            node.add(newMesh);
                            lib.instancedMeshes.push(newMesh);
                        }
                    }
                }
            }
            else {
                node = new Node();
                // PENDING Dulplicate name.
                node.setName(nodeInfo.name);
            }
            if (nodeInfo.matrix) {
                node.localTransform.setArray(nodeInfo.matrix);
                node.decomposeLocalTransform();
            }
            else {
                if (nodeInfo.translation) {
                    node.position.setArray(nodeInfo.translation);
                }
                if (nodeInfo.rotation) {
                    node.rotation.setArray(nodeInfo.rotation);
                }
                if (nodeInfo.scale) {
                    node.scale.setArray(nodeInfo.scale);
                }
            }

            lib.nodes[idx] = node;
        }, this);

        // Build hierarchy
        util.each(json.nodes, function (nodeInfo, idx) {
            var node = lib.nodes[idx];
            if (nodeInfo.children) {
                for (var i = 0; i < nodeInfo.children.length; i++) {
                    var childIdx = nodeInfo.children[i];
                    var child = lib.nodes[childIdx];
                    node.add(child);
                }
            }
        });
        },

    _parseAnimations: function (json, lib) {
        function checkChannelPath(channelInfo) {
            if (channelInfo.path === 'weights') {
                console.warn('GLTFLoader not support morph targets yet.');
                return false;
            }
            return true;
        }

        function getChannelHash(channelInfo, animationInfo) {
            return channelInfo.target.node + '_' + animationInfo.samplers[channelInfo.sampler].input;
        }

        var timeAccessorMultiplied = {};
        util.each(json.animations, function (animationInfo, idx) {
            var channels = animationInfo.channels.filter(checkChannelPath);

            if (!channels.length) {
                return;
            }
            var tracks = {};
            for (var i = 0; i < channels.length; i++) {
                var channelInfo = channels[i];
                var channelHash = getChannelHash(channelInfo, animationInfo);

                var targetNode = lib.nodes[channelInfo.target.node];
                var track = tracks[channelHash];
                var samplerInfo = animationInfo.samplers[channelInfo.sampler];

                if (!track) {
                    track = tracks[channelHash] = new SamplerTrack({
                        name: targetNode ? targetNode.name : '',
                        target: targetNode
                    });
                    track.targetNodeIndex = channelInfo.target.node;
                    track.channels.time = getAccessorData(json, lib, samplerInfo.input);
                    var frameLen = track.channels.time.length;
                    if (!timeAccessorMultiplied[samplerInfo.input]) {
                        for (var k = 0; k < frameLen; k++) {
                            track.channels.time[k] *= 1000;
                        }
                        timeAccessorMultiplied[samplerInfo.input] = true;
                    }
                }

                var interpolation = samplerInfo.interpolation || 'LINEAR';
                if (interpolation !== 'LINEAR') {
                    console.warn('GLTFLoader only support LINEAR interpolation.');
                }

                var path = channelInfo.target.path;
                if (path === 'translation') {
                    path = 'position';
                }

                track.channels[path] = getAccessorData(json, lib, samplerInfo.output);
            }
            var tracksList = [];
            for (var hash in tracks) {
                tracksList.push(tracks[hash]);
            }
            var clip = new TrackClip({
                name: animationInfo.name,
                loop: true,
                tracks: tracksList
            });
            lib.clips.push(clip);
        }, this);


        // PENDING
        var maxLife = lib.clips.reduce(function (maxTime, clip) {
            return Math.max(maxTime, clip.life);
        }, 0);
        lib.clips.forEach(function (clip) {
            clip.life = maxLife;
        });

        return lib.clips;
    }
});

GLTFLoader.generateMeshName = function (meshes, idx, primitiveIdx) {
    var meshInfo = meshes[idx];
    var meshName = meshInfo.name || ('mesh_' + idx);
    return primitiveIdx === 0 ? meshName : (meshName + '$' + primitiveIdx);
};

export default GLTFLoader;

<MSG> Add vendor, avoid TypedArray error in IE9

<DFF> @@ -11,6 +11,7 @@ define(function(require) {
     var Base = require('../core/Base');
     var request = require('../core/request');
     var util = require('../core/util');
+    var vendor = require('../core/vendor');
 
     var Scene = require('../Scene');
     var Shader = require('../Shader');
@@ -280,10 +281,10 @@ define(function(require) {
                     var offset = IBMInfo.byteOffset + bufferViewInfo.byteOffset;
                     var size = IBMInfo.count * 16;
 
-                    var array = new Float32Array(buffer, offset, size);
+                    var array = new vendor.Float32Array(buffer, offset, size);
 
                     skeleton._invBindPoseMatricesArray = array;
-                    skeleton._skinMatricesArray = new Float32Array(array.length);
+                    skeleton._skinMatricesArray = new vendor.Float32Array(array.length);
                 }
                 lib.skeletons[name] = skeleton;
             }
@@ -547,22 +548,22 @@ define(function(require) {
                             case 0x8B50:     // FLOAT_VEC2
                                 size = 2;
                                 type = 'float';
-                                ArrayCtor = Float32Array;
+                                ArrayCtor = vendor.Float32Array;
                                 break;
                             case 0x8B51:     // FLOAT_VEC3
                                 size = 3;
                                 type = 'float';
-                                ArrayCtor = Float32Array;
+                                ArrayCtor = vendor.Float32Array;
                                 break;
                             case 0x8B52:     // FLOAT_VEC4
                                 size = 4;
                                 type = 'float';
-                                ArrayCtor = Float32Array;
+                                ArrayCtor = vendor.Float32Array;
                                 break;
                             case 0x1406:     // FLOAT
                                 size = 1;
                                 type = 'float';
-                                ArrayCtor = Float32Array;
+                                ArrayCtor = vendor.Float32Array;
                                 break;
                             default:
                                 console.warn('Attribute type '+attributeInfo.type+' not support yet');
@@ -603,10 +604,10 @@ define(function(require) {
 
                     // index uint
                     if (indicesInfo.type === 0x1405) { // UNSIGNED_INT
-                        geometry.faces = new Uint32Array(buffer, byteOffset, indicesInfo.count);
+                        geometry.faces = new vendor.Uint32Array(buffer, byteOffset, indicesInfo.count);
                     }
                     else { // UNSIGNED_SHORT, 0x1403
-                        geometry.faces = new Uint16Array(buffer, byteOffset, indicesInfo.count);
+                        geometry.faces = new vendor.Uint16Array(buffer, byteOffset, indicesInfo.count);
                     }
 
                     var material = lib.materials[primitiveInfo.material];
@@ -819,7 +820,7 @@ define(function(require) {
                             var size = 1;
                             break;
                     }
-                    parameters[paramName] = new Float32Array(buffer, byteOffset, size * accessorInfo.count);
+                    parameters[paramName] = new vendor.Float32Array(buffer, byteOffset, size * accessorInfo.count);
                 }
 
                 if (!parameters.TIME || !animationInfo.channels.length) {

{'commit': "diff --git a/src/DynamicGeometry.js b/src/DynamicGeometry.js\nindex 745fb0cc..7c6bde45 100644\n--- a/src/DynamicGeometry.js\n+++ b/src/DynamicGeometry.js\n@@ -11,6 +11,7 @@ define(function(require) {\n     var BoundingBox = require('./math/BoundingBox');\n     var glenum = require('./core/glenum');\n     var glinfo = require('./core/glinfo');\n+    var vendor = require('./core/vendor');\n \n     var glMatrix = require('./dep/glmatrix');\n     var vec3 = glMatrix.vec3;\n@@ -21,6 +22,7 @@ define(function(require) {\n \n     var arrSlice = Array.prototype.slice;\n     var Attribute = Geometry.Attribute;\n+\n     /**\n      * @constructor qtek.DynamicGeometry\n      * @extends qtek.Geometry\n@@ -240,19 +242,19 @@ define(function(require) {\n                 // Type can be byte, ubyte, short, ushort, float\n                 switch(type) {\n                     case 'byte':\n-                        ArrayConstructors[name] = Int8Array;\n+                        ArrayConstructors[name] = vendor.Int8Array;\n                         break;\n                     case 'ubyte':\n-                        ArrayConstructors[name] = Uint8Array;\n+                        ArrayConstructors[name] = vendor.Uint8Array;\n                         break;\n                     case 'short':\n-                        ArrayConstructors[name] = Int16Array;\n+                        ArrayConstructors[name] = vendor.Int16Array;\n                         break;\n                     case 'ushort':\n-                        ArrayConstructors[name] = Uint16Array;\n+                        ArrayConstructors[name] = vendor.Uint16Array;\n                         break;\n                     default:\n-                        ArrayConstructors[name] = Float32Array;\n+                        ArrayConstructors[name] = vendor.Float32Array;\n                         break;\n                 }\n             }\ndiff --git a/src/Geometry.js b/src/Geometry.js\nindex 983422d8..b50e3dd1 100644\n--- a/src/Geometry.js\n+++ b/src/Geometry.js\n@@ -5,6 +5,7 @@ define(function(require) {\n     var Base = require('./core/Base');\n     var glenum = require('./core/glenum');\n     var Cache = require('./core/Cache');\n+    var vendor = require('./core/vendor');\n     var glmatrix = require('./dep/glmatrix');\n     var vec2 = glmatrix.vec2;\n     var vec3 = glmatrix.vec3;\n@@ -149,19 +150,19 @@ define(function(require) {\n                 var ArrayConstructor;\n                 switch(this.type) {\n                     case 'byte':\n-                        ArrayConstructor = Int8Array;\n+                        ArrayConstructor = vendor.Int8Array;\n                         break;\n                     case 'ubyte':\n-                        ArrayConstructor = Uint8Array;\n+                        ArrayConstructor = vendor.Uint8Array;\n                         break;\n                     case 'short':\n-                        ArrayConstructor = Int16Array;\n+                        ArrayConstructor = vendor.Int16Array;\n                         break;\n                     case 'ushort':\n-                        ArrayConstructor = Uint16Array;\n+                        ArrayConstructor = vendor.Uint16Array;\n                         break;\n                     default:\n-                        ArrayConstructor = Float32Array;\n+                        ArrayConstructor = vendor.Float32Array;\n                         break;\n                 }\n                 this.value = new ArrayConstructor(nVertex * this.size);\ndiff --git a/src/Shader.js b/src/Shader.js\nindex 29b3800f..12f4273d 100644\n--- a/src/Shader.js\n+++ b/src/Shader.js\n@@ -13,6 +13,7 @@ define(function(require) {\n     var Base = require('./core/Base');\n     var util = require('./core/util');\n     var Cache = require('./core/Cache');\n+    var vendor = require('./core/vendor');\n     var glMatrix = require('./dep/glmatrix');\n     var mat2 = glMatrix.mat2;\n     var mat3 = glMatrix.mat3;\n@@ -490,7 +491,7 @@ define(function(require) {\n                     break;\n                 case 'm4v':\n                     if (value instanceof Array) {\n-                        var array = new Float32Array(value.length * 16);\n+                        var array = new vendor.Float32Array(value.length * 16);\n                         var cursor = 0;\n                         for (var i = 0; i < value.length; i++) {\n                             var item = value[i];\n@@ -500,7 +501,7 @@ define(function(require) {\n                         }\n                         _gl.uniformMatrix4fv(location, false, array);\n                     // Raw value\n-                    } else if (value instanceof Float32Array) {   // ArrayBufferView\n+                    } else if (value instanceof vendor.Float32Array) {   // ArrayBufferView\n                         _gl.uniformMatrix4fv(location, false, value);\n                     }\n                     break;\n@@ -748,7 +749,7 @@ define(function(require) {\n                 if (arrayStr) {\n                     var arr = arrayStr.split(/\\s*,\\s*/);\n                     return function() {\n-                        return new Float32Array(arr);\n+                        return new vendor.Float32Array(arr);\n                     };\n                 } else {\n                     // Invalid value\ndiff --git a/src/StaticGeometry.js b/src/StaticGeometry.js\nindex e6a66a25..037bb93b 100644\n--- a/src/StaticGeometry.js\n+++ b/src/StaticGeometry.js\n@@ -8,6 +8,7 @@ define(function(require) {\n     var Geometry = require('./Geometry');\n     var BoundingBox = require('./math/BoundingBox');\n     var glMatrix = require('./dep/glmatrix');\n+    var vendor = require('./core/vendor');\n     var glenum = require('./core/glenum');\n     var mat4 = glMatrix.mat4;\n     var vec3 = glMatrix.vec3;\n@@ -17,7 +18,6 @@ define(function(require) {\n     var vec3Add = vec3.add;\n     var vec3Set = vec3.set;\n \n-    var Float32Array = typeof window.Float32Array === 'undefined' ? Array : window.Float32Array;\n     /**\n      * @constructor qtek.StaticGeometry\n      * @extends qtek.Geometry\n@@ -230,7 +230,7 @@ define(function(require) {\n             var normals = attributes.normal.value;\n \n             if (!normals || normals.length !== positions.length) {\n-                normals = attributes.normal.value = new Float32Array(positions.length);\n+                normals = attributes.normal.value = new vendor.Float32Array(positions.length);\n             } else {\n                 // Reset\n                 for (var i = 0; i < normals.length; i++) {\ndiff --git a/src/core/vendor.js b/src/core/vendor.js\nnew file mode 100644\nindex 00000000..b82f7e68\n--- /dev/null\n+++ b/src/core/vendor.js\n@@ -0,0 +1,39 @@\n+define(function (require) {\n+\n+    'use strict';\n+\n+    var supportWebGL = true;\n+    try {\n+        var canvas = document.createElement('canvas');\n+        var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');\n+        if (!gl) {\n+            throw new Error();\n+        }\n+    } catch (e) {\n+        supportWebGL = false;\n+    }\n+\n+    var vendor = {};\n+\n+    /**\n+     * If support WebGL\n+     * @return {boolean}\n+     */\n+    vendor.supportWebGL = function () {\n+        return supportWebGL;\n+    };\n+\n+    var strUndefined = 'undefined';\n+\n+    vendor.Int8Array = typeof Int8Array == strUndefined ? Array : Int8Array;\n+\n+    vendor.Uint8Array = typeof Uint8Array == strUndefined ? Array : Uint8Array;\n+\n+    vendor.Uint16Array = typeof Uint16Array == strUndefined ? Array : Uint16Array;\n+\n+    vendor.Int16Array = typeof Int16Array == strUndefined ? Array : Int16Array;\n+\n+    vendor.Float32Array = typeof Float32Array == strUndefined ? Array : Float32Array;\n+\n+    return vendor;\n+});\n\\ No newline at end of file\ndiff --git a/src/loader/GLTF.js b/src/loader/GLTF.js\nindex 3cb17c72..49d96770 100644\n--- a/src/loader/GLTF.js\n+++ b/src/loader/GLTF.js\n@@ -11,6 +11,7 @@ define(function(require) {\n     var Base = require('../core/Base');\n     var request = require('../core/request');\n     var util = require('../core/util');\n+    var vendor = require('../core/vendor');\n \n     var Scene = require('../Scene');\n     var Shader = require('../Shader');\n@@ -280,10 +281,10 @@ define(function(require) {\n                     var offset = IBMInfo.byteOffset + bufferViewInfo.byteOffset;\n                     var size = IBMInfo.count * 16;\n \n-                    var array = new Float32Array(buffer, offset, size);\n+                    var array = new vendor.Float32Array(buffer, offset, size);\n \n                     skeleton._invBindPoseMatricesArray = array;\n-                    skeleton._skinMatricesArray = new Float32Array(array.length);\n+                    skeleton._skinMatricesArray = new vendor.Float32Array(array.length);\n                 }\n                 lib.skeletons[name] = skeleton;\n             }\n@@ -547,22 +548,22 @@ define(function(require) {\n                             case 0x8B50:     // FLOAT_VEC2\n                                 size = 2;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             case 0x8B51:     // FLOAT_VEC3\n                                 size = 3;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             case 0x8B52:     // FLOAT_VEC4\n                                 size = 4;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             case 0x1406:     // FLOAT\n                                 size = 1;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             default:\n                                 console.warn('Attribute type '+attributeInfo.type+' not support yet');\n@@ -603,10 +604,10 @@ define(function(require) {\n \n                     // index uint\n                     if (indicesInfo.type === 0x1405) { // UNSIGNED_INT\n-                        geometry.faces = new Uint32Array(buffer, byteOffset, indicesInfo.count);\n+                        geometry.faces = new vendor.Uint32Array(buffer, byteOffset, indicesInfo.count);\n                     }\n                     else { // UNSIGNED_SHORT, 0x1403\n-                        geometry.faces = new Uint16Array(buffer, byteOffset, indicesInfo.count);\n+                        geometry.faces = new vendor.Uint16Array(buffer, byteOffset, indicesInfo.count);\n                     }\n \n                     var material = lib.materials[primitiveInfo.material];\n@@ -819,7 +820,7 @@ define(function(require) {\n                             var size = 1;\n                             break;\n                     }\n-                    parameters[paramName] = new Float32Array(buffer, byteOffset, size * accessorInfo.count);\n+                    parameters[paramName] = new vendor.Float32Array(buffer, byteOffset, size * accessorInfo.count);\n                 }\n \n                 if (!parameters.TIME || !animationInfo.channels.length) {\n", 'commit_subject': 'Add vendor, avoid TypedArray error in IE9', 'repo_name': 'pissang/claygl', 'committer': {'name': 'lang', 'email': '480ad739152c3cd786975b3f11c1065b9cf8ba60@gmail.com', 'time_sec': '1429592773', 'tz_offset': '480', 'date': {'seconds': '1429592773'}}, 'commit_tree': '19171b3da73c98e2c65fff0b40bf8b9313f3c3e3', 'commit_message': 'Add vendor, avoid TypedArray error in IE9\n', 'language_name': 'JavaScript', 'license': 'bsd-2-clause', 'hunks': "@@ -11,6 +11,7 @@ define(function(require) {\n     var Base = require('../core/Base');\n     var request = require('../core/request');\n     var util = require('../core/util');\n+    var vendor = require('../core/vendor');\n \n     var Scene = require('../Scene');\n     var Shader = require('../Shader');\n@@ -280,10 +281,10 @@ define(function(require) {\n                     var offset = IBMInfo.byteOffset + bufferViewInfo.byteOffset;\n                     var size = IBMInfo.count * 16;\n \n-                    var array = new Float32Array(buffer, offset, size);\n+                    var array = new vendor.Float32Array(buffer, offset, size);\n \n                     skeleton._invBindPoseMatricesArray = array;\n-                    skeleton._skinMatricesArray = new Float32Array(array.length);\n+                    skeleton._skinMatricesArray = new vendor.Float32Array(array.length);\n                 }\n                 lib.skeletons[name] = skeleton;\n             }\n@@ -547,22 +548,22 @@ define(function(require) {\n                             case 0x8B50:     // FLOAT_VEC2\n                                 size = 2;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             case 0x8B51:     // FLOAT_VEC3\n                                 size = 3;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             case 0x8B52:     // FLOAT_VEC4\n                                 size = 4;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             case 0x1406:     // FLOAT\n                                 size = 1;\n                                 type = 'float';\n-                                ArrayCtor = Float32Array;\n+                                ArrayCtor = vendor.Float32Array;\n                                 break;\n                             default:\n                                 console.warn('Attribute type '+attributeInfo.type+' not support yet');\n@@ -603,10 +604,10 @@ define(function(require) {\n \n                     // index uint\n                     if (indicesInfo.type === 0x1405) { // UNSIGNED_INT\n-                        geometry.faces = new Uint32Array(buffer, byteOffset, indicesInfo.count);\n+                        geometry.faces = new vendor.Uint32Array(buffer, byteOffset, indicesInfo.count);\n                     }\n                     else { // UNSIGNED_SHORT, 0x1403\n-                        geometry.faces = new Uint16Array(buffer, byteOffset, indicesInfo.count);\n+                        geometry.faces = new vendor.Uint16Array(buffer, byteOffset, indicesInfo.count);\n                     }\n \n                     var material = lib.materials[primitiveInfo.material];\n@@ -819,7 +820,7 @@ define(function(require) {\n                             var size = 1;\n                             break;\n                     }\n-                    parameters[paramName] = new Float32Array(buffer, byteOffset, size * accessorInfo.count);\n+                    parameters[paramName] = new vendor.Float32Array(buffer, byteOffset, size * accessorInfo.count);\n                 }\n \n                 if (!parameters.TIME || !animationInfo.channels.length) {\n", 'addition_count': 10, 'deletion_count': 9, 'src_file': 'a/src/loader/GLTF.js', 'tgt_file': 'b/src/loader/GLTF.js', 'file_extension': '.js', 'before_file': "/**\n * glTF Loader\n * Specification https://github.com/KhronosGroup/glTF/blob/master/specification/README.md\n *\n * TODO Morph targets\n */\nimport Base from '../core/Base';\nimport util from '../core/util';\nimport vendor from '../core/vendor';\n\n    var Base = require('../core/Base');\n    var request = require('../core/request');\n    var util = require('../core/util');\n\n    var Scene = require('../Scene');\n    var Shader = require('../Shader');\nimport shaderLibrary from '../shader/library';\nimport Skeleton from '../Skeleton';\nimport Joint from '../Joint';\nimport PerspectiveCamera from '../camera/Perspective';\nimport OrthographicCamera from '../camera/Orthographic';\nimport glenum from '../core/glenum';\n\nimport BoundingBox from '../math/BoundingBox';\n\nimport TrackClip from '../animation/TrackClip';\nimport SamplerTrack from '../animation/SamplerTrack';\n\nimport Geometry from '../Geometry';\n\n// Import builtin shader\nimport '../shader/builtin';\nimport Shader from '../Shader';\n\nvar semanticAttributeMap = {\n    'NORMAL': 'normal',\n    'POSITION': 'position',\n    'TEXCOORD_0': 'texcoord0',\n    'TEXCOORD_1': 'texcoord1',\n    'WEIGHTS_0': 'weight',\n    'JOINTS_0': 'joint',\n    'COLOR_0': 'color'\n};\n\nvar ARRAY_CTOR_MAP = {\n    5120: vendor.Int8Array,\n    5121: vendor.Uint8Array,\n    5122: vendor.Int16Array,\n    5123: vendor.Uint16Array,\n    5125: vendor.Uint32Array,\n    5126: vendor.Float32Array\n};\nvar SIZE_MAP = {\n    SCALAR: 1,\n    VEC2: 2,\n    VEC3: 3,\n    VEC4: 4,\n    MAT2: 4,\n    MAT3: 9,\n    MAT4: 16\n};\n\nfunction getAccessorData(json, lib, accessorIdx, isIndices) {\n    var accessorInfo = json.accessors[accessorIdx];\n\n    var buffer = lib.bufferViews[accessorInfo.bufferView];\n    var byteOffset = accessorInfo.byteOffset || 0;\n    var ArrayCtor = ARRAY_CTOR_MAP[accessorInfo.componentType] || vendor.Float32Array;\n\n    var size = SIZE_MAP[accessorInfo.type];\n    if (size == null && isIndices) {\n        size = 1;\n    }\n    var arr = new ArrayCtor(buffer, byteOffset, size * accessorInfo.count);\n\n    var quantizeExtension = accessorInfo.extensions && accessorInfo.extensions['WEB3D_quantized_attributes'];\n    if (quantizeExtension) {\n        var decodedArr = new vendor.Float32Array(size * accessorInfo.count);\n        var decodeMatrix = quantizeExtension.decodeMatrix;\n        var decodeOffset;\n        var decodeScale;\n        var decodeOffset = new Array(size);\n        var decodeScale = new Array(size);\n        for (var k = 0; k < size; k++) {\n            decodeOffset[k] = decodeMatrix[size * (size + 1) + k];\n            decodeScale[k] = decodeMatrix[k * (size + 1) + k];\n        }\n        for (var i = 0; i < accessorInfo.count; i++) {\n            for (var k = 0; k < size; k++) {\n                decodedArr[i * size + k] = arr[i * size + k] * decodeScale[k] + decodeOffset[k];\n            }\n        }\n\n        arr = decodedArr;\n    }\n    return arr;\n}\n\nfunction base64ToBinary(input, charStart) {\n    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n    var lookup = new Uint8Array(130);\n    for (var i = 0; i < chars.length; i++) {\n        lookup[chars.charCodeAt(i)] = i;\n    }\n    // Ignore\n    var len = input.length - charStart;\n    if (input.charAt(len - 1) === '=') { len--; }\n    if (input.charAt(len - 1) === '=') { len--; }\n\n    var uarray = new Uint8Array((len / 4) * 3);\n\n    for (var i = 0, j = charStart; i < uarray.length;) {\n        var c1 = lookup[input.charCodeAt(j++)];\n        var c2 = lookup[input.charCodeAt(j++)];\n        var c3 = lookup[input.charCodeAt(j++)];\n        var c4 = lookup[input.charCodeAt(j++)];\n\n        uarray[i++] = (c1 << 2) | (c2 >> 4);\n        uarray[i++] = ((c2 & 15) << 4) | (c3 >> 2);\n        uarray[i++] = ((c3 & 3) << 6) | c4;\n    }\n\n    return uarray.buffer;\n}\n\n\n/**\n * @typedef {Object} clay.loader.GLTF.Result\n * @property {Object} json\n * @property {clay.Scene} scene\n * @property {clay.Node} rootNode\n * @property {clay.Camera[]} cameras\n * @property {clay.Texture[]} textures\n * @property {clay.Material[]} materials\n * @property {clay.Skeleton[]} skeletons\n * @property {clay.Mesh[]} meshes\n * @property {clay.animation.TrackClip[]} clips\n * @property {clay.Node[]} nodes\n */\n\n/**\n * @constructor clay.loader.GLTF\n * @extends clay.core.Base\n */\nvar GLTFLoader = Base.extend(/** @lends clay.loader.GLTF# */ {\n    /**\n     *\n     * @type {clay.Node}\n     */\n    rootNode: null,\n    /**\n     * Root path for uri parsing.\n     * @type {string}\n     */\n    rootPath: null,\n\n    /**\n     * Root path for texture uri parsing. Defaultly use the rootPath\n     * @type {string}\n     */\n    textureRootPath: null,\n\n    /**\n     * Root path for buffer uri parsing. Defaultly use the rootPath\n     * @type {string}\n     */\n    bufferRootPath: null,\n\n    /**\n     * Shader used when creating the materials.\n     * @type {string|clay.Shader}\n     * @default 'clay.standard'\n     */\n    shader: 'clay.standard',\n\n    /**\n     * If use {@link clay.StandardMaterial}\n     * @type {string}\n     */\n    useStandardMaterial: false,\n\n    /**\n     * If loading the cameras.\n     * @type {boolean}\n     */\n    includeCamera: true,\n\n    /**\n     * If loading the animations.\n     * @type {boolean}\n     */\n    includeAnimation: true,\n    /**\n     * If loading the meshes\n     * @type {boolean}\n     */\n    includeMesh: true,\n    /**\n     * If loading the textures.\n     * @type {boolean}\n     */\n    includeTexture: true,\n\n    /**\n     * @type {string}\n     */\n    crossOrigin: '',\n    /**\n     * @type {boolean}\n     * @see https://github.com/KhronosGroup/glTF/issues/674\n     */\n    textureFlipY: false,\n\n    /**\n     * If convert texture to power-of-two\n     * @type {boolean}\n     */\n    textureConvertToPOT: false,\n\n    shaderLibrary: null\n},\nfunction () {\n    if (!this.shaderLibrary) {\n        this.shaderLibrary = shaderLibrary.createLibrary();\n    }\n},\n/** @lends clay.loader.GLTF.prototype */\n{\n    /**\n     * @param {string} url\n     */\n    load: function (url) {\n        var self = this;\n        var isBinary = url.endsWith('.glb');\n\n        if (this.rootPath == null) {\n            this.rootPath = url.slice(0, url.lastIndexOf('/'));\n        }\n\n        vendor.request.get({\n            url: url,\n            onprogress: function (percent, loaded, total) {\n                self.trigger('progress', percent, loaded, total);\n            },\n            onerror: function (e) {\n                self.trigger('error', e);\n            },\n            responseType: isBinary ? 'arraybuffer' : 'text',\n            onload: function (data) {\n                if (isBinary) {\n                    self.parseBinary(data);\n                }\n                else {\n                    if (typeof data === 'string') {\n                        data = JSON.parse(data);\n                    }\n                    self.parse(data);\n                }\n            }\n        });\n    },\n\n    /**\n     * Parse glTF binary\n     * @param {ArrayBuffer} buffer\n     * @return {clay.loader.GLTF.Result}\n     */\n    parseBinary: function (buffer) {\n        var header = new Uint32Array(buffer, 0, 4);\n        if (header[0] !== 0x46546C67) {\n            this.trigger('error', 'Invalid glTF binary format: Invalid header');\n            return;\n        }\n        if (header[0] < 2) {\n            this.trigger('error', 'Only glTF2.0 is supported.');\n            return;\n        }\n\n        var dataView = new DataView(buffer, 12);\n                    var offset = IBMInfo.byteOffset + bufferViewInfo.byteOffset;\n                    var size = IBMInfo.count * 16;\n\n                    var array = new Float32Array(buffer, offset, size);\n\n                    skeleton._invBindPoseMatricesArray = array;\n                    skeleton._skinMatricesArray = new Float32Array(array.length);\n                }\n                lib.skeletons[name] = skeleton;\n            }\n            // json\n            if (chunkType === 0x4E4F534A) {\n                var arr = new Uint8Array(buffer, i + 12, chunkLength);\n                // TODO, for the browser not support TextDecoder.\n                var decoder = new TextDecoder();\n                var str = decoder.decode(arr);\n                try {\n                    json = JSON.parse(str);\n                }\n                catch (e) {\n                    this.trigger('error', 'JSON Parse error:' + e.toString());\n                    return;\n                }\n            }\n            else if (chunkType === 0x004E4942) {\n                buffers.push(buffer.slice(i + 12, i + 12 + chunkLength));\n            }\n\n            i += chunkLength;\n        }\n        if (!json) {\n            this.trigger('error', 'Invalid glTF binary format: Can\\'t find JSON.');\n            return;\n        }\n\n        return this.parse(json, buffers);\n    },\n\n    /**\n     * @param {Object} json\n     * @param {ArrayBuffer[]} [buffer]\n     * @return {clay.loader.GLTF.Result}\n     */\n    parse: function (json, buffers) {\n        var self = this;\n\n        var lib = {\n            json: json,\n            buffers: [],\n            bufferViews: [],\n            materials: [],\n            textures: [],\n            meshes: [],\n            joints: [],\n            skeletons: [],\n            cameras: [],\n            nodes: [],\n            clips: []\n        };\n        // Mount on the root node if given\n        var rootNode = this.rootNode || new Scene();\n\n        var loading = 0;\n        function checkLoad() {\n            loading--;\n            if (loading === 0) {\n                afterLoadBuffer();\n            }\n        }\n        // If already load buffers\n        if (buffers) {\n            lib.buffers = buffers.slice();\n            afterLoadBuffer(true);\n        }\n        else {\n            // Load buffers\n            util.each(json.buffers, function (bufferInfo, idx) {\n                loading++;\n                var path = bufferInfo.uri;\n\n                self._loadBuffers(path, function (buffer) {\n                    lib.buffers[idx] = buffer;\n                    checkLoad();\n                }, checkLoad);\n            });\n        }\n\n        function getResult() {\n            return {\n                json: json,\n                scene: self.rootNode ? null : rootNode,\n                rootNode: self.rootNode ? rootNode : null,\n                cameras: lib.cameras,\n                textures: lib.textures,\n                materials: lib.materials,\n                skeletons: lib.skeletons,\n                meshes: lib.instancedMeshes,\n                clips: lib.clips,\n                nodes: lib.nodes\n            };\n        }\n\n        function afterLoadBuffer(immediately) {\n            // Buffer not load complete.\n            if (lib.buffers.length !== json.buffers.length) {\n                setTimeout(function () {\n                    self.trigger('error', 'Buffer not load complete.');\n                });\n                return;\n            }\n\n            json.bufferViews.forEach(function (bufferViewInfo, idx) {\n                // PENDING Performance\n                lib.bufferViews[idx] = lib.buffers[bufferViewInfo.buffer]\n                    .slice(bufferViewInfo.byteOffset || 0, (bufferViewInfo.byteOffset || 0) + (bufferViewInfo.byteLength || 0));\n            });\n            lib.buffers = null;\n            if (self.includeMesh) {\n                if (self.includeTexture) {\n                    self._parseTextures(json, lib);\n                }\n                self._parseMaterials(json, lib);\n                self._parseMeshes(json, lib);\n            }\n            self._parseNodes(json, lib);\n\n            // Only support one scene.\n            if (json.scenes) {\n                var sceneInfo = json.scenes[json.scene || 0]; // Default use the first scene.\n                if (sceneInfo) {\n                    for (var i = 0; i < sceneInfo.nodes.length; i++) {\n                        var node = lib.nodes[sceneInfo.nodes[i]];\n                        node.update();\n                        rootNode.add(node);\n                    }\n                }\n            }\n\n            if (self.includeMesh) {\n                self._parseSkins(json, lib);\n            }\n\n            if (self.includeAnimation) {\n                self._parseAnimations(json, lib);\n            }\n            if (immediately) {\n                setTimeout(function () {\n                    self.trigger('success', getResult());\n                });\n            }\n            else {\n                self.trigger('success', getResult());\n            }\n        }\n\n        return getResult();\n    },\n\n    /**\n     * Binary file path resolver. User can override it\n     * @param {string} path\n     */\n    resolveBufferPath: function (path) {\n        if (path && path.match(/^data:(.*?)base64,/)) {\n            return path;\n        }\n\n        var rootPath = this.bufferRootPath;\n        if (rootPath == null) {\n            rootPath = this.rootPath;\n        }\n        return util.relative2absolute(path, rootPath);\n    },\n\n    /**\n     * Texture file path resolver. User can override it\n     * @param {string} path\n     */\n    resolveTexturePath: function (path) {\n        if (path && path.match(/^data:(.*?)base64,/)) {\n            return path;\n        }\n\n        var rootPath = this.textureRootPath;\n        if (rootPath == null) {\n            rootPath = this.rootPath;\n        }\n        return util.relative2absolute(path, rootPath);\n    },\n\n    /**\n     * Buffer loader\n     * @param {string}\n     * @param {Function} onsuccess\n     * @param {Function} onerror\n     */\n    loadBuffer: function (path, onsuccess, onerror) {\n        vendor.request.get({\n            url: path,\n            responseType: 'arraybuffer',\n            onload: function (buffer) {\n                onsuccess && onsuccess(buffer);\n            },\n            onerror: function (buffer) {\n                onerror && onerror(buffer);\n            }\n        });\n    },\n\n    _getShader: function () {\n        if (typeof this.shader === 'string') {\n            return this.shaderLibrary.get(this.shader);\n        }\n        else if (this.shader instanceof Shader) {\n            return this.shader;\n        }\n    },\n\n    _loadBuffers: function (path, onsuccess, onerror) {\n        var base64Prefix = 'data:application/octet-stream;base64,';\n        var strStart = path.substr(0, base64Prefix.length);\n        if (strStart === base64Prefix) {\n            onsuccess(\n                base64ToBinary(path, base64Prefix.length)\n            );\n        }\n        else {\n            this.loadBuffer(\n                this.resolveBufferPath(path),\n                onsuccess,\n                onerror\n            );\n        }\n    },\n\n    // https://github.com/KhronosGroup/glTF/issues/100\n    // https://github.com/KhronosGroup/glTF/issues/193\n    _parseSkins: function (json, lib) {\n\n        // Create skeletons and joints\n        var haveInvBindMatrices = false;\n        util.each(json.skins, function (skinInfo, idx) {\n            var skeleton = new Skeleton({\n                name: skinInfo.name\n            });\n            for (var i = 0; i < skinInfo.joints.length; i++) {\n                var nodeIdx = skinInfo.joints[i];\n                var node = lib.nodes[nodeIdx];\n                var joint = new Joint({\n                    name: node.name,\n                    node: node,\n                    index: skeleton.joints.length\n                });\n                skeleton.joints.push(joint);\n            }\n            skeleton.relativeRootNode = lib.nodes[skinInfo.skeleton] || this.rootNode;\n            if (skinInfo.inverseBindMatrices) {\n                haveInvBindMatrices = true;\n                var IBMInfo = json.accessors[skinInfo.inverseBindMatrices];\n                var buffer = lib.bufferViews[IBMInfo.bufferView];\n\n                var offset = IBMInfo.byteOffset || 0;\n                var size = IBMInfo.count * 16;\n\n                var array = new vendor.Float32Array(buffer, offset, size);\n\n                skeleton.setJointMatricesArray(array);\n                            case 0x8B50:     // FLOAT_VEC2\n                                size = 2;\n                                type = 'float';\n                                ArrayCtor = Float32Array;\n                                break;\n                            case 0x8B51:     // FLOAT_VEC3\n                                size = 3;\n                                type = 'float';\n                                ArrayCtor = Float32Array;\n                                break;\n                            case 0x8B52:     // FLOAT_VEC4\n                                size = 4;\n                                type = 'float';\n                                ArrayCtor = Float32Array;\n                                break;\n                            case 0x1406:     // FLOAT\n                                size = 1;\n                                type = 'float';\n                                ArrayCtor = Float32Array;\n                                break;\n                            default:\n                                console.warn('Attribute type '+attributeInfo.type+' not support yet');\n                var skinIdx = nodeInfo.skin;\n                var skeleton = lib.skeletons[skinIdx];\n\n                var node = lib.nodes[nodeIdx];\n                var jointIndices = skeleton.joints.map(getJointIndex);\n                if (node instanceof Mesh) {\n                    enableSkinningForMesh(node, skeleton, jointIndices);\n                }\n                else {\n                    // Mesh have multiple primitives\n                    var children = node.children();\n                    for (var i = 0; i < children.length; i++) {\n                        enableSkinningForMesh(children[i], skeleton, jointIndices);\n                    }\n                }\n            }\n        }, this);\n    },\n\n    _parseTextures: function (json, lib) {\n        util.each(json.textures, function (textureInfo, idx){\n            // samplers is optional\n            var samplerInfo = (json.samplers && json.samplers[textureInfo.sampler]) || {};\n            var parameters = {};\n            ['wrapS', 'wrapT', 'magFilter', 'minFilter'].forEach(function (name) {\n                var value = samplerInfo[name];\n                if (value != null) {\n                    parameters[name] = value;\n                }\n            });\n            util.defaults(parameters, {\n                wrapS: Texture.REPEAT,\n                wrapT: Texture.REPEAT,\n                flipY: this.textureFlipY,\n\n                    // index uint\n                    if (indicesInfo.type === 0x1405) { // UNSIGNED_INT\n                        geometry.faces = new Uint32Array(buffer, byteOffset, indicesInfo.count);\n                    }\n                    else { // UNSIGNED_SHORT, 0x1403\n                        geometry.faces = new Uint16Array(buffer, byteOffset, indicesInfo.count);\n                    }\n\n                    var material = lib.materials[primitiveInfo.material];\n                var texture = new Texture2D(parameters);\n                var imageInfo = json.images[textureInfo.source];\n                var uri;\n                if (imageInfo.uri) {\n                    uri = this.resolveTexturePath(imageInfo.uri);\n                }\n                else if (imageInfo.bufferView != null) {\n                    uri = URL.createObjectURL(new Blob([lib.bufferViews[imageInfo.bufferView]], {\n                        type: imageInfo.mimeType\n                    }));\n                }\n                if (uri) {\n                    texture.load(uri, this.crossOrigin);\n                    lib.textures[idx] = texture;\n                }\n            }\n        }, this);\n    },\n\n    _KHRCommonMaterialToStandard: function (materialInfo, lib) {\n        var uniforms = {};\n        var commonMaterialInfo = materialInfo.extensions['KHR_materials_common'];\n        uniforms = commonMaterialInfo.values || {};\n\n        if (typeof uniforms.diffuse === 'number') {\n            uniforms.diffuse = lib.textures[uniforms.diffuse] || null;\n        }\n        if (typeof uniforms.emission === 'number') {\n            uniforms.emission = lib.textures[uniforms.emission] || null;\n        }\n\n        var enabledTextures = [];\n        if (uniforms['diffuse'] instanceof Texture2D) {\n            enabledTextures.push('diffuseMap');\n        }\n        if (materialInfo.normalTexture) {\n            enabledTextures.push('normalMap');\n        }\n        if (uniforms['emission'] instanceof Texture2D) {\n            enabledTextures.push('emissiveMap');\n        }\n        var material;\n        var isStandardMaterial = this.useStandardMaterial;\n        if (isStandardMaterial) {\n            material = new StandardMaterial({\n                name: materialInfo.name,\n                doubleSided: materialInfo.doubleSided\n            });\n        }\n        else {\n            material = new Material({\n                name: materialInfo.name,\n                shader: this._getShader()\n            });\n\n            material.define('fragment', 'USE_ROUGHNESS');\n            material.define('fragment', 'USE_METALNESS');\n\n            if (materialInfo.doubleSided) {\n                material.define('fragment', 'DOUBLE_SIDED');\n            }\n        }\n\n        if (uniforms.transparent) {\n            material.depthMask = false;\n            material.depthTest = true;\n            material.transparent = true;\n        }\n\n        var diffuseProp = uniforms['diffuse'];\n        if (diffuseProp) {\n            // Color\n            if (Array.isArray(diffuseProp)) {\n                diffuseProp = diffuseProp.slice(0, 3);\n                isStandardMaterial ? (material.color = diffuseProp)\n                    : material.set('color', diffuseProp);\n            }\n            else { // Texture\n                isStandardMaterial ? (material.diffuseMap = diffuseProp)\n                    : material.set('diffuseMap', diffuseProp);\n            }\n        }\n        var emissionProp = uniforms['emission'];\n        if (emissionProp != null) {\n            // Color\n            if (Array.isArray(emissionProp)) {\n                emissionProp = emissionProp.slice(0, 3);\n                isStandardMaterial ? (material.emission = emissionProp)\n                    : material.set('emission', emissionProp);\n            }\n            else { // Texture\n                isStandardMaterial ? (material.emissiveMap = emissionProp)\n                    : material.set('emissiveMap', emissionProp);\n            }\n        }\n        if (materialInfo.normalTexture != null) {\n            // TODO texCoord\n            var normalTextureIndex = materialInfo.normalTexture.index;\n            if (isStandardMaterial) {\n                material.normalMap = lib.textures[normalTextureIndex] || null;\n            }\n            else {\n                material.set('normalMap', lib.textures[normalTextureIndex] || null);\n            }\n        }\n        if (uniforms['shininess'] != null) {\n            var glossiness = Math.log(uniforms['shininess']) / Math.log(8192);\n            // Uniform glossiness\n            material.set('glossiness', glossiness);\n            material.set('roughness', 1 - glossiness);\n        }\n        else {\n            material.set('glossiness', 0.3);\n            material.set('roughness', 0.3);\n        }\n        if (uniforms['specular'] != null) {\n            material.set('specularColor', uniforms['specular'].slice(0, 3));\n        }\n        if (uniforms['transparency'] != null) {\n            material.set('alpha', uniforms['transparency']);\n        }\n\n        return material;\n    },\n\n    _pbrMetallicRoughnessToStandard: function (materialInfo, metallicRoughnessMatInfo, lib) {\n        var alphaTest = materialInfo.alphaMode === 'MASK';\n\n        var isStandardMaterial = this.useStandardMaterial;\n        var material;\n        var diffuseMap, roughnessMap, metalnessMap, normalMap, emissiveMap, occlusionMap;\n        var enabledTextures = [];\n\n        /**\n         * The scalar multiplier applied to each normal vector of the normal texture.\n         *\n         * @type {number}\n         *\n         * XXX This value is ignored if `materialInfo.normalTexture` is not specified.\n         */\n        var normalScale = 1.0;\n\n        // TODO texCoord\n        if (metallicRoughnessMatInfo.baseColorTexture) {\n            diffuseMap = lib.textures[metallicRoughnessMatInfo.baseColorTexture.index] || null;\n            diffuseMap && enabledTextures.push('diffuseMap');\n        }\n        if (metallicRoughnessMatInfo.metallicRoughnessTexture) {\n            roughnessMap = metalnessMap = lib.textures[metallicRoughnessMatInfo.metallicRoughnessTexture.index] || null;\n            roughnessMap && enabledTextures.push('metalnessMap', 'roughnessMap');\n        }\n        if (materialInfo.normalTexture) {\n\n            normalMap = lib.textures[materialInfo.normalTexture.index] || null;\n            normalMap && enabledTextures.push('normalMap');\n\n            if (typeof materialInfo.normalTexture.scale === 'number') {\n                normalScale = materialInfo.normalTexture.scale;\n            }\n\n        }\n        if (materialInfo.emissiveTexture) {\n            emissiveMap = lib.textures[materialInfo.emissiveTexture.index] || null;\n            emissiveMap && enabledTextures.push('emissiveMap');\n        }\n        if (materialInfo.occlusionTexture) {\n            occlusionMap = lib.textures[materialInfo.occlusionTexture.index] || null;\n            occlusionMap && enabledTextures.push('occlusionMap');\n        }\n        var baseColor = metallicRoughnessMatInfo.baseColorFactor || [1, 1, 1, 1];\n\n        var commonProperties = {\n            diffuseMap: diffuseMap || null,\n            roughnessMap: roughnessMap || null,\n            metalnessMap: metalnessMap || null,\n            normalMap: normalMap || null,\n            occlusionMap: occlusionMap || null,\n            emissiveMap: emissiveMap || null,\n            color: baseColor.slice(0, 3),\n            alpha: baseColor[3],\n            metalness: metallicRoughnessMatInfo.metallicFactor || 0,\n            roughness: metallicRoughnessMatInfo.roughnessFactor || 0,\n            emission: materialInfo.emissiveFactor || [0, 0, 0],\n            emissionIntensity: 1,\n            alphaCutoff: materialInfo.alphaCutoff || 0.5,\n            normalScale: normalScale\n        };\n        if (commonProperties.roughnessMap) {\n            // In glTF metallicFactor will do multiply, which is different from StandardMaterial.\n            // So simply ignore it\n            commonProperties.metalness = 0.5;\n            commonProperties.roughness = 0.5;\n        }\n        if (isStandardMaterial) {\n            material = new StandardMaterial(util.extend({\n                name: materialInfo.name,\n                alphaTest: alphaTest,\n                doubleSided: materialInfo.doubleSided,\n                // G channel\n                roughnessChannel: 1,\n                // B Channel\n                metalnessChannel: 2\n            }, commonProperties));\n        }\n        else {\n\n                            var size = 1;\n                            break;\n                    }\n                    parameters[paramName] = new Float32Array(buffer, byteOffset, size * accessorInfo.count);\n                }\n\n                if (!parameters.TIME || !animationInfo.channels.length) {\n            material.define('fragment', 'ROUGHNESS_CHANNEL', 1);\n            material.define('fragment', 'METALNESS_CHANNEL', 2);\n\n            material.define('fragment', 'DIFFUSEMAP_ALPHA_ALPHA');\n\n            if (alphaTest) {\n                material.define('fragment', 'ALPHA_TEST');\n            }\n            if (materialInfo.doubleSided) {\n                material.define('fragment', 'DOUBLE_SIDED');\n            }\n\n            material.set(commonProperties);\n        }\n\n        if (materialInfo.alphaMode === 'BLEND') {\n            material.depthMask = false;\n            material.depthTest = true;\n            material.transparent = true;\n        }\n\n        return material;\n    },\n\n    _pbrSpecularGlossinessToStandard: function (materialInfo, specularGlossinessMatInfo, lib) {\n        var alphaTest = materialInfo.alphaMode === 'MASK';\n\n        if (this.useStandardMaterial) {\n            console.error('StandardMaterial doesn\\'t support specular glossiness workflow yet');\n        }\n\n        var material;\n        var diffuseMap, glossinessMap, specularMap, normalMap, emissiveMap, occlusionMap;\n        var enabledTextures = [];\n        // TODO texCoord\n        if (specularGlossinessMatInfo.diffuseTexture) {\n            diffuseMap = lib.textures[specularGlossinessMatInfo.diffuseTexture.index] || null;\n            diffuseMap && enabledTextures.push('diffuseMap');\n        }\n        if (specularGlossinessMatInfo.specularGlossinessTexture) {\n            glossinessMap = specularMap = lib.textures[specularGlossinessMatInfo.specularGlossinessTexture.index] || null;\n            glossinessMap && enabledTextures.push('specularMap', 'glossinessMap');\n        }\n        if (materialInfo.normalTexture) {\n            normalMap = lib.textures[materialInfo.normalTexture.index] || null;\n            normalMap && enabledTextures.push('normalMap');\n        }\n        if (materialInfo.emissiveTexture) {\n            emissiveMap = lib.textures[materialInfo.emissiveTexture.index] || null;\n            emissiveMap && enabledTextures.push('emissiveMap');\n        }\n        if (materialInfo.occlusionTexture) {\n            occlusionMap = lib.textures[materialInfo.occlusionTexture.index] || null;\n            occlusionMap && enabledTextures.push('occlusionMap');\n        }\n        var diffuseColor = specularGlossinessMatInfo.diffuseFactor || [1, 1, 1, 1];\n\n        var commonProperties = {\n            diffuseMap: diffuseMap || null,\n            glossinessMap: glossinessMap || null,\n            specularMap: specularMap || null,\n            normalMap: normalMap || null,\n            emissiveMap: emissiveMap || null,\n            occlusionMap: occlusionMap || null,\n            color: diffuseColor.slice(0, 3),\n            alpha: diffuseColor[3],\n            specularColor: specularGlossinessMatInfo.specularFactor || [1, 1, 1],\n            glossiness: specularGlossinessMatInfo.glossinessFactor || 0,\n            emission: materialInfo.emissiveFactor || [0, 0, 0],\n            emissionIntensity: 1,\n            alphaCutoff: materialInfo.alphaCutoff == null ? 0.5 : materialInfo.alphaCutoff\n        };\n        if (commonProperties.glossinessMap) {\n            // Ignore specularFactor\n            commonProperties.glossiness = 0.5;\n        }\n        if (commonProperties.specularMap) {\n            // Ignore specularFactor\n            commonProperties.specularColor = [1, 1, 1];\n        }\n\n        material = new Material({\n            name: materialInfo.name,\n            shader: this._getShader()\n        });\n\n        material.define('fragment', 'GLOSSINESS_CHANNEL', 3);\n        material.define('fragment', 'DIFFUSEMAP_ALPHA_ALPHA');\n\n        if (alphaTest) {\n            material.define('fragment', 'ALPHA_TEST');\n        }\n        if (materialInfo.doubleSided) {\n            material.define('fragment', 'DOUBLE_SIDED');\n        }\n\n        material.set(commonProperties);\n\n        if (materialInfo.alphaMode === 'BLEND') {\n            material.depthMask = false;\n            material.depthTest = true;\n            material.transparent = true;\n        }\n\n        return material;\n    },\n\n    _parseMaterials: function (json, lib) {\n        util.each(json.materials, function (materialInfo, idx) {\n            if (materialInfo.extensions && materialInfo.extensions['KHR_materials_common']) {\n                lib.materials[idx] = this._KHRCommonMaterialToStandard(materialInfo, lib);\n            }\n            else if (materialInfo.extensions && materialInfo.extensions['KHR_materials_pbrSpecularGlossiness']) {\n                lib.materials[idx] = this._pbrSpecularGlossinessToStandard(materialInfo, materialInfo.extensions['KHR_materials_pbrSpecularGlossiness'], lib);\n            }\n            else {\n                lib.materials[idx] = this._pbrMetallicRoughnessToStandard(materialInfo, materialInfo.pbrMetallicRoughness || {}, lib);\n            }\n        }, this);\n    },\n\n    _parseMeshes: function (json, lib) {\n        var self = this;\n\n        util.each(json.meshes, function (meshInfo, idx) {\n            lib.meshes[idx] = [];\n            // Geometry\n            for (var pp = 0; pp < meshInfo.primitives.length; pp++) {\n                var primitiveInfo = meshInfo.primitives[pp];\n                var geometry = new Geometry({\n                    dynamic: false,\n                    // PENDIGN\n                    name: meshInfo.name,\n                    boundingBox: new BoundingBox()\n                });\n                // Parse attributes\n                var semantics = Object.keys(primitiveInfo.attributes);\n                for (var ss = 0; ss < semantics.length; ss++) {\n                    var semantic = semantics[ss];\n                    var accessorIdx = primitiveInfo.attributes[semantic];\n                    var attributeInfo = json.accessors[accessorIdx];\n                    var attributeName = semanticAttributeMap[semantic];\n                    if (!attributeName) {\n                        continue;\n                    }\n                    var size = SIZE_MAP[attributeInfo.type];\n                    var attributeArray = getAccessorData(json, lib, accessorIdx);\n                    // WebGL attribute buffer not support uint32.\n                    // Direct use Float32Array may also have issue.\n                    if (attributeArray instanceof vendor.Uint32Array) {\n                        attributeArray = new Float32Array(attributeArray);\n                    }\n                    if (semantic === 'WEIGHTS_0' && size === 4) {\n                        // Weight data in QTEK has only 3 component, the last component can be evaluated since it is normalized\n                        var weightArray = new attributeArray.constructor(attributeInfo.count * 3);\n                        for (var i = 0; i < attributeInfo.count; i++) {\n                            var i4 = i * 4, i3 = i * 3;\n                            var w1 = attributeArray[i4], w2 = attributeArray[i4 + 1], w3 = attributeArray[i4 + 2], w4 = attributeArray[i4 + 3];\n                            var wSum = w1 + w2 + w3 + w4;\n                            weightArray[i3] = w1 / wSum;\n                            weightArray[i3 + 1] = w2 / wSum;\n                            weightArray[i3 + 2] = w3 / wSum;\n                        }\n                        geometry.attributes[attributeName].value = weightArray;\n                    }\n                    else if (semantic === 'COLOR_0' && size === 3) {\n                        var colorArray = new attributeArray.constructor(attributeInfo.count * 4);\n                        for (var i = 0; i < attributeInfo.count; i++) {\n                            var i4 = i * 4, i3 = i * 3;\n                            colorArray[i4] = attributeArray[i3];\n                            colorArray[i4 + 1] = attributeArray[i3 + 1];\n                            colorArray[i4 + 2] = attributeArray[i3 + 2];\n                            colorArray[i4 + 3] = 1;\n                        }\n                        geometry.attributes[attributeName].value = colorArray;\n                    }\n                    else {\n                        geometry.attributes[attributeName].value = attributeArray;\n                    }\n\n                    var attributeType = 'float';\n                    if (attributeArray instanceof vendor.Uint16Array) {\n                        attributeType = 'ushort';\n                    }\n                    else if (attributeArray instanceof vendor.Int16Array) {\n                        attributeType = 'short';\n                    }\n                    else if (attributeArray instanceof vendor.Uint8Array) {\n                        attributeType = 'ubyte';\n                    }\n                    else if (attributeArray instanceof vendor.Int8Array) {\n                        attributeType = 'byte';\n                    }\n                    geometry.attributes[attributeName].type = attributeType;\n\n                    if (semantic === 'POSITION') {\n                        // Bounding Box\n                        var min = attributeInfo.min;\n                        var max = attributeInfo.max;\n                        if (min) {\n                            geometry.boundingBox.min.set(min[0], min[1], min[2]);\n                        }\n                        if (max) {\n                            geometry.boundingBox.max.set(max[0], max[1], max[2]);\n                        }\n                    }\n                }\n\n                // Parse indices\n                if (primitiveInfo.indices != null) {\n                    geometry.indices = getAccessorData(json, lib, primitiveInfo.indices, true);\n                    if (geometry.vertexCount <= 0xffff && geometry.indices instanceof vendor.Uint32Array) {\n                        geometry.indices = new vendor.Uint16Array(geometry.indices);\n                    }\n                    if(geometry.indices instanceof vendor.Uint8Array) {\n                        geometry.indices = new vendor.Uint16Array(geometry.indices);\n                    }\n                }\n\n                var material = lib.materials[primitiveInfo.material];\n                var materialInfo = (json.materials || [])[primitiveInfo.material];\n                // Use default material\n                if (!material) {\n                    material = new Material({\n                        shader: self._getShader()\n                    });\n                }\n                var mesh = new Mesh({\n                    geometry: geometry,\n                    material: material,\n                    mode: [Mesh.POINTS, Mesh.LINES, Mesh.LINE_LOOP, Mesh.LINE_STRIP, Mesh.TRIANGLES, Mesh.TRIANGLE_STRIP, Mesh.TRIANGLE_FAN][primitiveInfo.mode] || Mesh.TRIANGLES,\n                    ignoreGBuffer: material.transparent\n                });\n                if (materialInfo != null) {\n                    mesh.culling = !materialInfo.doubleSided;\n                }\n                if (!mesh.geometry.attributes.normal.value) {\n                    mesh.geometry.generateVertexNormals();\n                }\n                if (((material instanceof StandardMaterial) && material.normalMap)\n                    || (material.isTextureEnabled('normalMap'))\n                ) {\n                    if (!mesh.geometry.attributes.tangent.value) {\n                        mesh.geometry.generateTangents();\n                    }\n                }\n                if (mesh.geometry.attributes.color.value) {\n                    mesh.material.define('VERTEX_COLOR');\n                }\n\n                mesh.name = GLTFLoader.generateMeshName(json.meshes, idx, pp);\n\n                lib.meshes[idx].push(mesh);\n            }\n        }, this);\n    },\n\n    _instanceCamera: function (json, nodeInfo) {\n        var cameraInfo = json.cameras[nodeInfo.camera];\n\n        if (cameraInfo.type === 'perspective') {\n            var perspectiveInfo = cameraInfo.perspective || {};\n            return new PerspectiveCamera({\n                name: nodeInfo.name,\n                aspect: perspectiveInfo.aspectRatio,\n                fov: perspectiveInfo.yfov / Math.PI * 180,\n                far: perspectiveInfo.zfar,\n                near: perspectiveInfo.znear\n            });\n        }\n        else {\n            var orthographicInfo = cameraInfo.orthographic || {};\n            return new OrthographicCamera({\n                name: nodeInfo.name,\n                top: orthographicInfo.ymag,\n                right: orthographicInfo.xmag,\n                left: -orthographicInfo.xmag,\n                bottom: -orthographicInfo.ymag,\n                near: orthographicInfo.znear,\n                far: orthographicInfo.zfar\n            });\n        }\n    },\n\n    _parseNodes: function (json, lib) {\n\n        function instanceMesh(mesh) {\n            return new Mesh({\n                name: mesh.name,\n                geometry: mesh.geometry,\n                material: mesh.material,\n                culling: mesh.culling,\n                mode: mesh.mode\n            });\n        }\n\n        lib.instancedMeshes = [];\n\n        util.each(json.nodes, function (nodeInfo, idx) {\n            var node;\n            if (nodeInfo.camera != null && this.includeCamera) {\n                node = this._instanceCamera(json, nodeInfo);\n                lib.cameras.push(node);\n            }\n            else if (nodeInfo.mesh != null && this.includeMesh) {\n                var primitives = lib.meshes[nodeInfo.mesh];\n                if (primitives) {\n                    if (primitives.length === 1) {\n                        // Replace the node with mesh directly\n                        node = instanceMesh(primitives[0]);\n                        node.setName(nodeInfo.name);\n                        lib.instancedMeshes.push(node);\n                    }\n                    else {\n                        node = new Node();\n                        node.setName(nodeInfo.name);\n                        for (var j = 0; j < primitives.length; j++) {\n                            var newMesh = instanceMesh(primitives[j]);\n                            node.add(newMesh);\n                            lib.instancedMeshes.push(newMesh);\n                        }\n                    }\n                }\n            }\n            else {\n                node = new Node();\n                // PENDING Dulplicate name.\n                node.setName(nodeInfo.name);\n            }\n            if (nodeInfo.matrix) {\n                node.localTransform.setArray(nodeInfo.matrix);\n                node.decomposeLocalTransform();\n            }\n            else {\n                if (nodeInfo.translation) {\n                    node.position.setArray(nodeInfo.translation);\n                }\n                if (nodeInfo.rotation) {\n                    node.rotation.setArray(nodeInfo.rotation);\n                }\n                if (nodeInfo.scale) {\n                    node.scale.setArray(nodeInfo.scale);\n                }\n            }\n\n            lib.nodes[idx] = node;\n        }, this);\n\n        // Build hierarchy\n        util.each(json.nodes, function (nodeInfo, idx) {\n            var node = lib.nodes[idx];\n            if (nodeInfo.children) {\n                for (var i = 0; i < nodeInfo.children.length; i++) {\n                    var childIdx = nodeInfo.children[i];\n                    var child = lib.nodes[childIdx];\n                    node.add(child);\n                }\n            }\n        });\n        },\n\n    _parseAnimations: function (json, lib) {\n        function checkChannelPath(channelInfo) {\n            if (channelInfo.path === 'weights') {\n                console.warn('GLTFLoader not support morph targets yet.');\n                return false;\n            }\n            return true;\n        }\n\n        function getChannelHash(channelInfo, animationInfo) {\n            return channelInfo.target.node + '_' + animationInfo.samplers[channelInfo.sampler].input;\n        }\n\n        var timeAccessorMultiplied = {};\n        util.each(json.animations, function (animationInfo, idx) {\n            var channels = animationInfo.channels.filter(checkChannelPath);\n\n            if (!channels.length) {\n                return;\n            }\n            var tracks = {};\n            for (var i = 0; i < channels.length; i++) {\n                var channelInfo = channels[i];\n                var channelHash = getChannelHash(channelInfo, animationInfo);\n\n                var targetNode = lib.nodes[channelInfo.target.node];\n                var track = tracks[channelHash];\n                var samplerInfo = animationInfo.samplers[channelInfo.sampler];\n\n                if (!track) {\n                    track = tracks[channelHash] = new SamplerTrack({\n                        name: targetNode ? targetNode.name : '',\n                        target: targetNode\n                    });\n                    track.targetNodeIndex = channelInfo.target.node;\n                    track.channels.time = getAccessorData(json, lib, samplerInfo.input);\n                    var frameLen = track.channels.time.length;\n                    if (!timeAccessorMultiplied[samplerInfo.input]) {\n                        for (var k = 0; k < frameLen; k++) {\n                            track.channels.time[k] *= 1000;\n                        }\n                        timeAccessorMultiplied[samplerInfo.input] = true;\n                    }\n                }\n\n                var interpolation = samplerInfo.interpolation || 'LINEAR';\n                if (interpolation !== 'LINEAR') {\n                    console.warn('GLTFLoader only support LINEAR interpolation.');\n                }\n\n                var path = channelInfo.target.path;\n                if (path === 'translation') {\n                    path = 'position';\n                }\n\n                track.channels[path] = getAccessorData(json, lib, samplerInfo.output);\n            }\n            var tracksList = [];\n            for (var hash in tracks) {\n                tracksList.push(tracks[hash]);\n            }\n            var clip = new TrackClip({\n                name: animationInfo.name,\n                loop: true,\n                tracks: tracksList\n            });\n            lib.clips.push(clip);\n        }, this);\n\n\n        // PENDING\n        var maxLife = lib.clips.reduce(function (maxTime, clip) {\n            return Math.max(maxTime, clip.life);\n        }, 0);\n        lib.clips.forEach(function (clip) {\n            clip.life = maxLife;\n        });\n\n        return lib.clips;\n    }\n});\n\nGLTFLoader.generateMeshName = function (meshes, idx, primitiveIdx) {\n    var meshInfo = meshes[idx];\n    var meshName = meshInfo.name || ('mesh_' + idx);\n    return primitiveIdx === 0 ? meshName : (meshName + '$' + primitiveIdx);\n};\n\nexport default GLTFLoader;\n", 'message': 'Add vendor, avoid TypedArray error in IE9\n', 'author_name': 'lang', 'lang': 'js'}