Source: loader/GLTFParser.js

import Class from '../core/Class';
import Node from '../core/Node';
import Skeleton from '../core/Skeleton';
import BasicMaterial from '../material/BasicMaterial';
import PBRMaterial from '../material/PBRMaterial';
import Geometry from '../geometry/Geometry';
import MorphGeometry from '../geometry/MorphGeometry';
import GeometryData from '../geometry/GeometryData';
import Mesh from '../core/Mesh';
import SkinedMesh from '../core/SkinedMesh';
import LazyTexture from '../texture/LazyTexture';
import math from '../math/math';
import Matrix4 from '../math/Matrix4';
import Matrix3 from '../math/Matrix3';
import Color from '../math/Color';
import AnimationStates from '../animation/AnimationStates';
import Animation from '../animation/Animation';
import PerspectiveCamera from '../camera/PerspectiveCamera';
import OrthographicCamera from '../camera/OrthographicCamera';
import log from '../utils/log';
import BasicLoader from './BasicLoader';
import * as util from '../utils/util';
import * as extensionHandlers from './GLTFExtensions';

import constants from '../constants';

const {
    BLEND,
    DEPTH_TEST,
    CULL_FACE,
    FRONT,
    BACK,
    FRONT_AND_BACK
} = constants;

const ComponentTypeMap = {
    5120: [1, Int8Array],
    5121: [1, Uint8Array],
    5122: [2, Int16Array],
    5123: [2, Uint16Array],
    5125: [4, Uint32Array],
    5126: [4, Float32Array]
};

const ComponentNumberMap = {
    SCALAR: 1,
    VEC2: 2,
    VEC3: 3,
    VEC4: 4,
    MAT2: 4,
    MAT3: 9,
    MAT4: 16
};

const glTFAttrToGeometry = {
    POSITION: {
        name: 'vertices',
        decodeMatName: 'positionDecodeMat'
    },
    TEXCOORD_0: {
        name: 'uvs',
        decodeMatName: 'uvDecodeMat'
    },
    TEXCOORD_1: {
        name: 'uvs1',
        decodeMatName: 'uv1DecodeMat',
    },
    NORMAL: {
        name: 'normals',
        decodeMatName: 'normalDecodeMat'
    },
    JOINT: {
        name: 'skinIndices'
    },
    JOINTS_0: {
        name: 'skinIndices'
    },
    WEIGHT: {
        name: 'skinWeights'
    },
    WEIGHTS_0: {
        name: 'skinWeights'
    },
    TANGENT: {
        name: 'tangents'
    },
    COLOR_0: {
        name: 'colors'
    }
};


/**
 * @class
 */
const GLTFParser = Class.create(/** @lends GLTFParser.prototype */{
    /**
     * @default true
     * @type {boolean}
     */
    isGLTFParser: true,
    /**
     * @default GLTFParser
     * @type {string}
     */
    className: 'GLTFParser',
    Statics: /** @lends GLTFParser */ {
        MAGIC: 'glTF',
        /**
         * 扩展接口
         * @type {Object}
         */
        extensionHandlers,
        /**
         * 注册扩展接口
         * @param  {String} extensionName 接口名称
         * @param  {IGLTFExtensionHandler} handler 接口
         */
        registerExtensionHandler(extensionName, handler) {
            this.extensionHandlers[extensionName] = handler;
        },
        /**
         * 取消注册扩展接口
         * @param  {String} extensionName 接口名称
         */
        unregisterExtensionHandler(extensionName) {
            if (this.extensionHandlers[extensionName]) {
                delete this.extensionHandlers[extensionName];
            }
        }
    },
    isMultiAnim: true,
    isProgressive: false,
    isUnQuantizeInShader: true,
    isLoadAllTextures: false,
    preHandlerImageURI: null,
    preHandlerBufferURI: null,
    customMaterialCreator: null,
    ignoreTextureError: false,
    forceCreateNewBuffer: false,
    src: '',
    /**
     * @constructs
     * @param  {ArrayBuffer|String} content
     * @param  {Object} params
     */
    constructor(content, params) {
        Object.assign(this, params);
        this.content = content;
    },
    parse(loader) {
        if (this.content instanceof ArrayBuffer) {
            let buffer = this.content;
            let magic = util.convertUint8ArrayToString(new Uint8Array(buffer, 0, 4));
            if (magic === GLTFParser.MAGIC) {
                this.parseBinary(buffer);
            } else {
                let content = util.convertUint8ArrayToString(new Uint8Array(buffer), true);
                this.json = JSON.parse(content);
            }
        } else {
            this.json = JSON.parse(this.content);
        }
        this.glTFVersion = parseFloat(this.json.asset.version);
        if (this.glTFVersion >= 2) {
            this.isGLTF2 = true;
        }

        this.parseExtensionUsed();

        return this.loadResources(loader)
            .then(() => {
                this.parseExtensions(this.json.extensions, null, {
                    isGlobal: true,
                    methodName: 'parseOnLoad'
                });
                return Promise.resolve();
            })
            .then(() => this.parseGeometries())
            .then(() => this.parseScene());
    },
    parseExtensionUsed() {
        this.extensionsUsed = {};
        util.each(this.json.extensionsUsed, (name) => {
            this.extensionsUsed[name] = true;
        });

        if (!this.extensionsUsed.WEB3D_quantized_attributes) {
            // this glTF model havn't use quantize!
            this.isUnQuantizeInShader = false;
        }
    },
    getExtensionHandler(name) {
        return this.extensionHandlers && this.extensionHandlers[name] || GLTFParser.extensionHandlers[name];
    },
    parseExtension(extensions, name, result, options = {}) {
        const info = extensions[name];
        const extension = this.getExtensionHandler(name);
        if (extension && extension.parse) {
            return extension.parse(info, this, result, options);
        }

        return result;
    },
    parseExtensions(extensions, result, options = {}) {
        util.each(extensions, (info, name) => {
            if (options.ignoreExtensions && options.ignoreExtensions[name]) {
                return;
            }
            const extension = this.getExtensionHandler(name);
            const methodName = options.methodName || 'parse';
            if (extension && extension[methodName]) {
                result = extension[methodName](info, this, result, options);
            }
        });

        return result;
    },
    isUseExtension(data, extensionName) {
        return !!(data && data.extensions && data.extensions[extensionName]);
    },
    parseBinary(buffer) {
        this.isBinary = true;
        const infoDataView = new DataView(buffer);
        const version = infoDataView.getUint32(4, true);
        const totalLength = infoDataView.getUint32(8, true);
        let content;
        let start = 12;
        if (version < 2) {
            const contentLength = infoDataView.getUint32(start, true);
            content = new Uint8Array(buffer, 20, contentLength);
            content = util.convertUint8ArrayToString(content, true);
            this.json = JSON.parse(content);
            this.binaryBody = buffer.slice(20 + contentLength);
        } else if (version === 2) {
            while (start < totalLength) {
                let chunkLength = infoDataView.getUint32(start, true);
                let chunkType = infoDataView.getUint32(start + 4, true);
                if (chunkType === 0x4E4F534A) {
                    // JSON...
                    content = new Uint8Array(buffer, start + 8, chunkLength);
                    content = util.convertUint8ArrayToString(content, true);
                    this.json = JSON.parse(content);
                } else if (chunkType === 0x004E4942) {
                    // binary
                    this.binaryBody = buffer.slice(start + 8, start + 8 + chunkLength);
                }
                start += 8 + chunkLength;
            }
        } else {
            throw new Error(`Dont support glTF version ${version}`);
        }
    },
    loadResources(loader) {
        const actions = [];

        for (let extensionName in this.extensionsUsed) {
            const extension = this.getExtensionHandler(extensionName);
            if (extension && extension.init) {
                actions.push(extension.init(loader, this));
            }
        }

        if (this.isBinary) {
            actions.push(this.loadBuffers(loader).then(() => {
                return this.loadTextures(loader);
            }));
        } else {
            actions.push(this.loadBuffers(loader));
            actions.push(this.loadTextures(loader));
        }

        return Promise.all(actions);
    },
    getBufferUri(bufferName) {
        let uri = util.getRelativePath(this.src, this.json.buffers[bufferName].uri);
        if (this.preHandlerBufferURI) {
            uri = this.preHandlerBufferURI(uri, this.json.buffers[bufferName]);
        }
        return uri;
    },
    loadBuffers(loader) {
        this.buffers = {};

        if (this.isBinary) {
            if (this.isGLTF2) {
                this.buffers[0] = this.binaryBody;
            } else {
                this.buffers.binary_glTF = this.binaryBody;
            }
            this.parseBufferViews();
            return Promise.resolve();
        }

        return Promise.all(Object.keys(this.json.buffers || []).map((bufferName) => {
            const uri = this.getBufferUri(bufferName);
            return loader.loadRes(uri, BasicLoader.TYPE_BUFFER)
                .then((buffer) => {
                    this.buffers[bufferName] = buffer;
                });
        })).then(() => {
            this.parseBufferViews();
        });
    },
    getImageUri(imageName) {
        const imgData = this.json.images[imageName];
        let uri = imgData.uri;
        if (this.isUseExtension(imgData, 'KHR_binary_glTF')) {
            const binaryInfo = imgData.extensions.KHR_binary_glTF;
            const bufferView = this.bufferViews[binaryInfo.bufferView];
            const data = new Uint8Array(bufferView.buffer, bufferView.byteOffset, bufferView.byteLength);
            uri = util.getBlobUrl(binaryInfo.mimeType, data);
        } else if (uri) {
            uri = util.getRelativePath(this.src, uri);
        } else if ('bufferView' in imgData) {
            const bufferView = this.bufferViews[imgData.bufferView];
            const data = new Uint8Array(bufferView.buffer, bufferView.byteOffset, bufferView.byteLength);
            if (imgData.mimeType === 'image/ktx') {
                uri = data;
            } else {
                uri = util.getBlobUrl(imgData.mimeType, data);
            }
        }

        if (this.preHandlerImageURI) {
            uri = this.preHandlerImageURI(uri, imgData);
        }

        return uri;
    },
    getImageType(imageName) {
        const imgData = this.json.images[imageName];
        let type = '';
        if (imgData && /^image\/(.*)$/.test(imgData.mimeType)) {
            type = RegExp.$1;
        }
        if (['ktx'].indexOf(type) < 0) {
            // clear type if type is not valid
            type = '';
        }
        return type;
    },
    getUsedTextureNameMap() {
        const map = {};
        util.each(this.json.materials, (material) => {
            let values = material;
            let isKMC = false;
            if (this.isUseExtension(material, 'KHR_materials_common')) {
                isKMC = true;
                values = material.extensions.KHR_materials_common.values;
            }
            if (this.isGLTF2 && !isKMC) {
                // glTF 2.0
                if (values.normalTexture) {
                    map[values.normalTexture.index] = true;
                }
                if (values.occlusionTexture) {
                    map[values.occlusionTexture.index] = true;
                }
                if (values.emissiveTexture) {
                    map[values.emissiveTexture.index] = true;
                }
                if (values.transparencyTexture) {
                    map[values.transparencyTexture.index] = true;
                }

                if (values.extensions) {
                    util.each(values.extensions, (extensionValue, extensionName) => {
                        const extensionHandler = this.getExtensionHandler(extensionName);
                        if (extensionHandler && extensionHandler.getUsedTextureNameMap) {
                            extensionHandler.getUsedTextureNameMap(extensionValue, map, this);
                        }
                    });
                }

                if (!this.isUseExtension(values, 'KHR_materials_pbrSpecularGlossiness') && values.pbrMetallicRoughness) {
                    const subValues = values.pbrMetallicRoughness;
                    if (subValues.baseColorTexture) {
                        map[subValues.baseColorTexture.index] = true;
                    }
                    if (subValues.metallicRoughnessTexture) {
                        map[subValues.metallicRoughnessTexture.index] = true;
                    }
                }
            } else {
                // glTF 1.0
                if (!isKMC) {
                    values = material.values;
                }
                [
                    'diffuse',
                    'specular',
                    'emission',
                    'ambient',
                    'transparency',
                    'normalMap'
                ].forEach((name) => {
                    let value = values[name];
                    if (value instanceof Object && 'index' in value) {
                        value = value.index;
                    }
                    if (util.isStrOrNumber(value) && this.json.textures[value]) {
                        map[value] = true;
                    }
                });
            }
        });
        return map;
    },
    loadTextures() {
        this.textures = {};

        if (!this.json.textures) {
            return Promise.resolve();
        }

        let needLoadTextures = Object.keys(this.json.textures);
        if (!this.isLoadAllTextures) {
            const usedTextures = this.getUsedTextureNameMap();
            needLoadTextures = needLoadTextures.filter((textureName) => {
                return usedTextures[textureName];
            });
        }

        return Promise.all(needLoadTextures.map((textureName) => {
            let textureData = this.json.textures[textureName];
            let uri = this.getImageUri(textureData.source);

            let texture = new LazyTexture(textureData);
            texture.uv = undefined;
            texture.autoLoad = this.isProgressive;
            texture.crossOrigin = true;
            texture.resType = this.getImageType(textureData.source);
            texture.src = uri;
            texture.name = textureData.name || textureName;

            // In glTF spec: Any colorspace information (such as ICC profiles, intents, etc) from PNG or JPEG containers must be ignored.
            texture.colorSpaceConversion = false;

            if (util.isBlobUrl(uri)) {
                const onTextureLoad = () => {
                    util.revokeBlobUrl(uri);
                    texture.off('load', onTextureLoad);
                    texture.off('error', onTextureLoad);
                };
                texture.on('load', onTextureLoad, true);
                texture.on('error', onTextureLoad, true);
            }

            if (this.json.samplers) {
                Object.assign(texture, this.json.samplers[textureData.sampler]);
            }

            if (textureData.extensions) {
                texture = this.parseExtensions(textureData.extensions, texture);
            }

            this.textures[textureName] = texture;

            if (!this.isProgressive) {
                return texture.load(!this.ignoreTextureError);
            }
            return Promise.resolve();
        }));
    },
    parseBufferViews() {
        this.bufferViews = {};
        util.each(this.json.bufferViews, (data, name) => {
            const buffer = this.buffers[data.buffer];
            const byteOffset = data.byteOffset || 0;
            const byteLength = data.byteLength;
            this.bufferViews[name] = {
                id: math.generateUUID('bufferView'),
                byteOffset,
                byteLength,
                buffer,
                byteStride: data.byteStride
            };
        });

        if (!this.isBinary) {
            delete this.buffers;
        }
    },
    // get Texture for glTF 2.0
    getTexture(textureInfo) {
        let texture = this.textures[textureInfo.index];
        if (!texture) {
            return null;
        }
        const texCoord = textureInfo.texCoord || 0;

        const key = textureInfo.index + '_' + texCoord;
        if (this.textures[key]) {
            texture = this.textures[key];
        } else if (typeof texture.uv === 'number' && texture.uv !== texCoord) {
            texture = texture.clone();
            this.textures[key] = texture;
        }
        texture.uv = texCoord;
        texture.__gltfTextureInfo = textureInfo;

        return texture;
    },
    getColorOrTexture(value) {
        if (Array.isArray(value)) {
            return new Color(value[0], value[1], value[2]);
        }

        if (value instanceof Object && 'index' in value) {
            value = value.index;
        }
        return this.textures[value];
    },
    parseMaterialCommonProps(material, materialData) {
        switch (materialData.alphaMode) {
            case 'BLEND':
                material.transparent = true;
                break;
            case 'MASK':
                if ('alphaCutoff' in materialData) {
                    material.alphaCutoff = materialData.alphaCutoff;
                } else {
                    material.alphaCutoff = 0.5;
                }
                break;
            case 'OPAQUE':
            default:
                material.ignoreTranparent = true;
                break;
        }

        if (!materialData.doubleSided) {
            material.side = FRONT;
        } else {
            material.side = FRONT_AND_BACK;
        }

        if (materialData.transparencyTexture) {
            material.transparency = this.getTexture(materialData.transparencyTexture);
        }
    },
    createPBRMaterial(materialData) {
        const material = new PBRMaterial();
        let values = materialData;

        const needLight = !this.isUseExtension(values, 'KHR_materials_unlit');

        if (needLight) {
            const normalTexture = values.normalTexture;
            if (normalTexture) {
                material.normalMap = this.getTexture(normalTexture);
                if (normalTexture.scale !== undefined) {
                    material.normalMapScale = normalTexture.scale;
                } else {
                    material.normalMapScale = 1;
                }
            }

            const occlusionTexture = values.occlusionTexture;
            if (occlusionTexture) {
                material.occlusionMap = this.getTexture(occlusionTexture);
                if (occlusionTexture.strength !== undefined) {
                    material.occlusionStrength = occlusionTexture.strength;
                } else {
                    material.occlusionStrength = 1;
                }
            }

            const emissiveTexture = values.emissiveTexture;
            if (emissiveTexture) {
                material.emission = this.getTexture(emissiveTexture);
            }

            const emissiveFactor = values.emissiveFactor;
            if (emissiveFactor) {
                material.emissionFactor.fromArray(emissiveFactor);
                material.emissionFactor.a = 1;
            }
        } else {
            material.lightType = 'NONE';
        }

        if (this.isUseExtension(values, 'KHR_materials_pbrSpecularGlossiness')) {
            this.parseExtension(values.extensions, 'KHR_materials_pbrSpecularGlossiness', material);
        } else if (values.pbrMetallicRoughness) {
            const subValues = values.pbrMetallicRoughness;
            if (subValues.baseColorFactor) {
                material.baseColor.fromArray(subValues.baseColorFactor);
            }
            if (subValues.baseColorTexture) {
                material.baseColorMap = this.getTexture(subValues.baseColorTexture);
            }

            if (needLight) {
                if (subValues.metallicRoughnessTexture) {
                    material.metallicRoughnessMap = this.getTexture(subValues.metallicRoughnessTexture);
                    if (material.occlusionMap === material.metallicRoughnessMap) {
                        material.occlusionMap = null;
                        material.isOcclusionInMetallicRoughnessMap = true;
                    }
                }
                if ('roughnessFactor' in subValues) {
                    material.roughness = subValues.roughnessFactor;
                }
                if ('metallicFactor' in subValues) {
                    material.metallic = subValues.metallicFactor;
                }
            }
        }

        if (material.baseColorMap) {
            this._parseTextureTransform(material, material.baseColorMap);
        }
        return material;
    },
    _parseTextureTransform(material, texture) {
        const textureInfo = texture.__gltfTextureInfo;
        if (this.isUseExtension(textureInfo, 'KHR_texture_transform')) {
            const transformInfo = textureInfo.extensions.KHR_texture_transform;

            if (transformInfo.texCoord !== undefined) {
                texture.uv = transformInfo.texCoord;
            }
            if (transformInfo.offset || transformInfo.rotation || transformInfo.scale) {
                const offset = transformInfo.offset || [0, 0];
                const rotation = transformInfo.rotation || 0;
                const scale = transformInfo.scale || [1, 1];

                const uvMatrix = new Matrix3().fromRotationTranslationScale(rotation, offset[0], offset[1], scale[0], scale[1]);
                if (texture.uv === 0) {
                    material.uvMatrix = uvMatrix;
                } else if (texture.uv === 1) {
                    material.uvMatrix1 = uvMatrix;
                }
            }
        }
    },
    createKMCMaterial(materialData, kmc) {
        const material = new BasicMaterial();
        let values;
        if (kmc) {
            values = kmc.values;
            material.lightType = kmc.technique;
        } else {
            values = materialData.values;
        }
        // glTF 1.0 or KMC
        material.diffuse = this.getColorOrTexture(values.diffuse) || material.diffuse;
        material.specular = this.getColorOrTexture(values.specular) || material.specular;
        material.emission = this.getColorOrTexture(values.emission) || material.emission;
        material.ambient = this.getColorOrTexture(values.ambient) || material.ambient;

        if (values.normalMap) {
            material.normalMap = this.getColorOrTexture(values.normalMap);
        }

        if (typeof values.transparency === 'number') {
            material.transparency = values.transparency;
            if (material.transparency < 1) {
                material.transparent = true;
            }
        } else if (typeof values.transparency === 'string') {
            material.transparency = this.getColorOrTexture(values.transparency);
            material.transparent = true;
        }

        if (values.transparent === true) {
            material.transparent = true;
        }

        if ('shininess' in values) {
            material.shininess = values.shininess;
        }

        this._parseTextureTransform(material, material.diffuse);
        return material;
    },
    parseMaterials() {
        this.materials = {};
        util.each(this.json.materials, (materialData, name) => {
            if (this.customMaterialCreator) {
                const material = this.customMaterialCreator(name, materialData, this.json, this);
                if (material) {
                    this.materials[name] = material;
                    return;
                }
            }

            let kmc = null;
            if (this.isUseExtension(materialData, 'KHR_materials_common')) {
                kmc = materialData.extensions.KHR_materials_common;
            }

            let material;
            if (this.isGLTF2 && !kmc) {
                if (this.isUseExtension(materialData, 'KHR_techniques_webgl')) {
                    material = this.parseExtension(materialData.extensions, 'KHR_techniques_webgl');
                } else {
                    material = this.createPBRMaterial(materialData);
                }
                this.parseMaterialCommonProps(material, materialData);
            } else {
                material = this.createKMCMaterial(materialData, kmc);
            }

            material = this.parseExtensions(materialData.extensions, material, {
                ignoreExtensions: {
                    KHR_techniques_webgl: 1,
                    KHR_materials_common: 1,
                    KHR_materials_pbrSpecularGlossiness: 1
                },
                isMaterial: true
            });


            material.name = materialData.name || name;
            this.materials[name] = material;

            this.parseTechnique(materialData, material);
        });
    },
    sparseAccessorHandler(data, sparse) {
        if (!sparse) {
            return data;
        }
        const count = sparse.count;
        // if dont create a new TpyedArray here, it will change the origin data in buffer
        let TypedArray = data.data.constructor;
        const newArray = new TypedArray(data.realLength);
        newArray.set(data.data);
        data.data = newArray;
        // values
        let buffer = this.bufferViews[sparse.values.bufferView];
        const values = new TypedArray(buffer.buffer, buffer.byteOffset + (sparse.values.byteOffset || 0), count * data.size);
        // indices
        TypedArray = ComponentTypeMap[sparse.indices.componentType][1];
        buffer = this.bufferViews[sparse.indices.bufferView];
        const indices = new TypedArray(buffer.buffer, buffer.byteOffset + (sparse.indices.byteOffset || 0), count);
        // change it
        for (let i = 0; i < count; i++) {
            util.copyArrayData(newArray, values, indices[i] * data.size, i * data.size, data.size);
        }
        return data;
    },
    getAccessorData(name, isDecode) {
        let accessor = this.json.accessors[name];
        if (accessor.data) {
            return accessor.data;
        }
        let [, TypedArray] = ComponentTypeMap[accessor.componentType];
        let number = ComponentNumberMap[accessor.type];
        let bufferView = this.bufferViews[accessor.bufferView];
        let count = accessor.count * number;
        let result;
        if (bufferView) {
            if (bufferView.byteStride && bufferView.byteStride > number * TypedArray.BYTES_PER_ELEMENT) {
                bufferView.array = new TypedArray(bufferView.buffer, bufferView.byteOffset, bufferView.byteLength / TypedArray.BYTES_PER_ELEMENT);

                result = new GeometryData(bufferView.array, number, {
                    offset: accessor.byteOffset || 0,
                    stride: bufferView.byteStride,
                    bufferViewId: bufferView.id
                });
            } else {
                let offset = (accessor.byteOffset || 0) + bufferView.byteOffset;
                let array;
                if (offset % TypedArray.BYTES_PER_ELEMENT || this.forceCreateNewBuffer) {
                    let buffer = bufferView.buffer.slice(offset, offset + count * TypedArray.BYTES_PER_ELEMENT);
                    array = new TypedArray(buffer);
                } else {
                    array = new TypedArray(bufferView.buffer, offset, count);
                }
                result = new GeometryData(array, number);
            }
        }

        if (accessor.sparse) {
            if (!result) {
                result = new GeometryData(new TypedArray(count), number);
            }
            result = this.sparseAccessorHandler(result, accessor.sparse);
        }

        result = this.parseExtensions(accessor.extensions, result, {
            isDecode,
            isAccessor: true
        });

        accessor.data = result;
        if (accessor.normalized) {
            result.normalized = true;
        }
        return result;
    },
    getArrayByAccessor(name, isDecode) {
        let accessor = this.json.accessors[name];
        if (accessor.array) {
            return accessor.array;
        }
        let data = this.getAccessorData(name, isDecode);
        if (!data.stride && !data.offset && data.size === 1) {
            return data.data;
        }

        const result = [];
        data.traverse((d) => {
            result.push(d.toArray ? d.toArray() : d);
        });
        accessor.array = result;
        return result;
    },
    parseTechnique(materialData, material) {
        let technique = null;
        if (this.json.techniques) {
            technique = this.json.techniques[materialData.technique];
        }
        if (!technique) {
            return;
        }
        if (!technique.states) {
            return;
        }

        technique.states.enable.forEach((flag) => {
            switch (flag) {
                case BLEND:
                    material.blend = true;
                    break;
                case DEPTH_TEST:
                    material.depthTest = true;
                    break;
                case CULL_FACE:
                    material.cullFace = true;
                    break;
                default:
                    break;
            }
        });

        util.each(technique.states.functions, (value, fnName) => {
            switch (fnName) {
                case 'blendEquationSeparate':
                    material.blendEquation = value[0];
                    material.blendEquationAlpha = value[1];
                    break;
                case 'blendFuncSeparate':
                    material.blendSrc = value[0];
                    material.blendDst = value[1];
                    material.blendSrcAlpha = value[2];
                    material.blendDstAlpha = value[3];
                    break;
                case 'depthMask':
                    material.depthMask = value[0];
                    break;
                case 'cullFace':
                    material.cullFaceType = value[0];
                    break;
                default:
                    material[fnName] = value;
                    break;
            }
        });

        if (material.cullFace) {
            material.side = material.cullFaceType === FRONT ? BACK : FRONT;
        } else {
            material.side = FRONT_AND_BACK;
        }
    },
    createMorphGeometry(primitive, weights) {
        // MorphGeometry
        const geometry = new MorphGeometry();
        const targets = geometry.targets = {};
        util.each(primitive.targets, (target) => {
            util.each(target, (accessorName, name) => {
                const geometryName = glTFAttrToGeometry[name].name;
                if (!targets[geometryName]) {
                    targets[geometryName] = [];
                }
                const data = this.getAccessorData(accessorName, true);
                targets[geometryName].push(data);
            });
        });
        if (weights) {
            geometry.weights = weights;
        } else {
            geometry.weights = new Float32Array(primitive.targets.length);
        }
        return geometry;
    },
    handlerGeometry(geometry, primitive) {
        const mode = primitive.mode === undefined ? 4 : primitive.mode;
        if (primitive.extensions) {
            const extensionGeometry = this.parseExtensions(primitive.extensions, null, {
                primitive,
                isPrimitive: true
            });
            if (extensionGeometry) {
                extensionGeometry.mode = mode;
                return extensionGeometry;
            }
        }

        if (!geometry) {
            geometry = new Geometry({
                mode
            });
        }
        if ('indices' in primitive) {
            geometry.indices = this.getAccessorData(primitive.indices);
        }
        let attr = primitive.attributes;
        for (let name in attr) {
            let info = glTFAttrToGeometry[name];
            if (!info) {
                log.warn(`Unknow attribute named ${name}!`);
                continue;
            }
            let isDecode = !(this.isUnQuantizeInShader && info.decodeMatName);

            geometry[info.name] = this.getAccessorData(attr[name], isDecode);
            if (!isDecode) {
                geometry[info.decodeMatName] = geometry[info.name].decodeMat;
                delete geometry[info.name].decodeMat;
            }
        }
        return geometry;
    },
    handlerSkinedMesh(mesh, skeleton) {
        if (!skeleton) {
            return;
        }

        mesh.skeleton = skeleton;
        if (this.useInstanced) {
            mesh.useInstanced = true;
        }
    },
    fixProgressiveGeometry(primitive, geometry) {
        primitive._geometry = geometry;
        if (this.isProgressive && primitive._meshes) {
            primitive._meshes.forEach((mesh) => {
                mesh.visible = true;
                mesh.geometry = geometry;
            });
        }
    },
    parseGeometries() {
        const promise = util.serialRun(this.json.meshes, (meshData) => {
            return util.serialRun(meshData.primitives, (primitive) => {
                let geometry;
                if (primitive.targets && primitive.targets.length) {
                    geometry = this.createMorphGeometry(primitive, meshData.weights);
                }
                primitive._geometry = geometry;
                let result = this.handlerGeometry(geometry, primitive);
                if (result && result.then) {
                    return result.then((geometry) => {
                        this.fixProgressiveGeometry(primitive, geometry);
                    }, (err) => {
                        log.error('geometry parse error', err);
                    });
                }
                this.fixProgressiveGeometry(primitive, result);
                return result;
            });
        });
        return this.isProgressive ? null : promise;
    },
    parseMesh(meshName, node, nodeData) {
        let meshData = this.json.meshes[meshName];
        meshData.primitives.forEach((primitive) => {
            let mesh;
            const skin = this.skins && this.skins[nodeData.skin];
            if (primitive.meshNode) {
                mesh = primitive.meshNode.clone();
            } else {
                let material = this.materials[primitive.material] || new BasicMaterial();
                const MeshClass = skin ? SkinedMesh : Mesh;
                mesh = new MeshClass({
                    geometry: primitive._geometry,
                    material,
                    name: 'mesh-' + (meshData.name || meshName)
                });

                primitive.meshNode = mesh;
            }
            this.handlerSkinedMesh(mesh, skin);
            if (this.isProgressive && !mesh.geometry) {
                mesh.visible = false;
                primitive._meshes = primitive._meshes || [];
                primitive._meshes.push(mesh);
            }
            node.addChild(mesh);
            this.meshes.push(mesh);
        });
    },
    parseCameras() {
        this.cameras = {};
        const defaultAspect = window.innerWidth / window.innerHeight;
        util.each(this.json.cameras, (cameraData, name) => {
            let camera;
            if (cameraData.type === 'perspective' && cameraData.perspective) {
                camera = new PerspectiveCamera();
                camera.fov = math.radToDeg(cameraData.perspective.yfov);
                camera.near = cameraData.perspective.znear;
                camera.far = cameraData.perspective.zfar;
                if (cameraData.aspectRatio) {
                    camera.aspect = cameraData.aspectRatio;
                } else {
                    camera.aspect = defaultAspect;
                }
            } else if (cameraData.type === 'orthographic' && cameraData.orthographic) {
                camera = new OrthographicCamera();
                camera.near = cameraData.orthographic.znear;
                camera.far = cameraData.orthographic.zfar;
                camera.right = cameraData.orthographic.xmag;
                camera.left = camera.right * -1;
                camera.top = cameraData.orthographic.ymag;
                camera.bottom = camera.top * -1;
            }

            camera = this.parseExtensions(cameraData.extensions, camera, {
                isCamera: true
            });

            if (camera) {
                camera.name = cameraData.name || name;
                this.cameras[name] = camera;
            }
        });
    },
    handlerNodeTransform(node, data) {
        if (data.matrix) {
            node.matrix.fromArray(data.matrix);
        } else {
            if (data.rotation) {
                node.quaternion.fromArray(data.rotation);
            }
            if (data.scale) {
                node.setScale(data.scale[0], data.scale[1], data.scale[2]);
            }
            if (data.translation) {
                node.x = data.translation[0];
                node.y = data.translation[1];
                node.z = data.translation[2];
            }
        }
    },
    parseNode(nodeName, parentNode) {
        let node;
        let data = this.json.nodes[nodeName];

        if (!data) {
            log.warn(`GLTFParser.parseNode: nodes[${nodeName}] has nothing.`);
        }

        node = new Node({
            name: data.name,
            animationId: nodeName
        });

        node = this.parseExtensions(data.extensions, node, {
            isNode: true
        });

        if ('camera' in data && this.cameras[data.camera]) {
            node.addChild(this.cameras[data.camera]);
        }
        this.handlerNodeTransform(node, data);

        if (data.jointName) {
            node.jointName = data.jointName;
            this.jointMap[node.jointName] = node;
        } else if (this.isGLTF2) {
            node.jointName = nodeName;
            this.jointMap[nodeName] = node;
        }

        if (data.meshes) {
            data.meshes.forEach(meshName => this.parseMesh(meshName, node, data));
        } else if ('mesh' in data) {
            this.parseMesh(data.mesh, node, data);
        }

        if (data.children) {
            data.children.forEach(name => this.parseNode(name, node));
        }

        parentNode.addChild(node);
    },
    parseAnimations() {
        if (!this.json.animations) {
            return null;
        }
        const isMultiAnim = this.isMultiAnim;
        const clips = {};
        let animStatesList = [];
        const validAnimationIds = {};
        util.each(this.json.animations, (info) => {
            info.channels.forEach((channel) => {
                let path = channel.target.path;
                let nodeId = channel.target.id;
                if (this.isGLTF2) {
                    nodeId = channel.target.node;
                }

                const sampler = info.samplers[channel.sampler];
                const inputAccessName = this.isGLTF2 ? sampler.input : info.parameters[sampler.input];
                const outputAccessName = this.isGLTF2 ? sampler.output : info.parameters[path];
                const keyTime = this.getArrayByAccessor(inputAccessName, true);
                let states = this.getArrayByAccessor(outputAccessName, true);
                if (path === 'rotation') {
                    path = 'quaternion';
                }
                const animStates = new AnimationStates({
                    interpolationType: sampler.interpolation || 'LINEAR',
                    nodeName: nodeId,
                    keyTime,
                    states,
                    type: AnimationStates.getType(path)
                });
                animStatesList.push(animStates);
                validAnimationIds[nodeId] = true;
            });

            if (isMultiAnim && animStatesList.length) {
                clips[info.name] = {
                    animStatesList
                };
                animStatesList = [];
            }
        });
        if (isMultiAnim && Object.keys(clips).length > 0) {
            animStatesList = Object.values(clips)[0].animStatesList;
        }
        if (animStatesList.length) {
            return new Animation({
                rootNode: this.node,
                animStatesList,
                validAnimationIds,
                clips
            });
        }

        return null;
    },
    parseScene() {
        this.parseMaterials();
        this.jointMap = {};
        this.meshes = [];
        this.lights = [];

        this.node = new Node({
            needCallChildUpdate: false
        });

        this.parseCameras();

        const scene = this.json.scenes[this.getDefaultSceneName()];
        if (!scene) {
            log.warn('GLTFParser:no scene!');
            return {
                node: this.node,
                meshes: [],
                cameras: [],
                lights: [],
                textures: [],
                materials: []
            };
        }

        const nodes = scene.nodes;
        this.parseSkins();
        nodes.forEach(node => this.parseNode(node, this.node));
        this.resetSkinInfo(this.node);

        const model = {
            node: this.node,
            scene: this.node,
            meshes: this.meshes,
            json: this.json,
            cameras: Object.values(this.cameras),
            lights: this.lights,
            textures: Object.values(this.textures),
            materials: Object.values(this.materials)
        };

        const anim = this.parseAnimations();
        if (anim) {
            this.node.setAnim(anim);
            anim.play();
            model.anim = anim;
        }

        this.parseExtensions(scene.extensions, null, {
            isScene: true
        });

        this.parseExtensions(this.json.extensions, model, {
            isGlobal: true,
            methodName: 'parseOnEnd'
        });

        return model;
    },
    getDefaultSceneName() {
        if (this.defaultScene !== undefined) {
            return this.defaultScene;
        }

        if (this.json.scenes) {
            return Object.keys(this.json.scenes)[0];
        }

        return null;
    },
    parseSkins() {
        this.skins = [];
        const skins = this.json.skins;
        if (skins && skins.length) {
            this.skins = skins.map((skin) => {
                const skeleton = new Skeleton();
                const jointCount = skin.joints.length;

                const inverseBindMatrices = this.getArrayByAccessor(skin.inverseBindMatrices, true);
                for (let i = 0; i < jointCount; i++) {
                    const inverseBindMatrice = new Matrix4().fromArray(inverseBindMatrices[i]);
                    skeleton.inverseBindMatrices.push(inverseBindMatrice);
                }
                skeleton.jointNames = skin.joints;
                return skeleton;
            });
        }
    },
    /**
     * 重设 jointName,使其保持唯一性。
     * @private
     * @param  {Node} rootNode
     */
    resetSkinInfo(rootNode) {
        const jointNameMap = {};
        rootNode.traverse((node) => {
            const newJointName = `${node.id}_${node.name}`;
            jointNameMap[node.jointName] = newJointName;
            node.jointName = newJointName;
        });

        this.skins.forEach((skin) => {
            skin.jointNames = skin.jointNames.map(jointName => jointNameMap[jointName]);
        });

        rootNode.resetSkinedMeshRootNode();
    }
});

export default GLTFParser;