/**
 * Cesium - https://github.com/CesiumGS/cesium
 *
 * Copyright 2011-2020 Cesium Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Columbus View (Pat. Pend.)
 *
 * Portions licensed separately.
 * See https://github.com/CesiumGS/cesium/blob/master/LICENSE.md for full licensing details.
 */
define(['./when-8d13db60', './Check-70bec281', './Math-61ede240', './Cartographic-f2a06374', './Cartesian2-16a61632', './BoundingSphere-d018a565', './Cartesian4-5af5bb24', './RuntimeError-ba10bc3e', './WebGLConstants-4c11ee5f', './ComponentDatatype-5862616f', './PrimitiveType-97893bc7', './FeatureDetection-7bd32c34', './buildModuleUrl-392763e2', './IndexDatatype-9435b55f', './createTaskProcessorWorker', './arrayFill-9766fb2e', './BoundingRectangle-5c75c80b', './Color-69f1845f', './pako_inflate-8ea163f9', './S3MCompressType-8157e6e2', './unzip-9ad5f9b4', './PixelFormat-e6d821ed'], function (when, Check, _Math, Cartographic, Cartesian2, BoundingSphere, Cartesian4, RuntimeError, WebGLConstants, ComponentDatatype, PrimitiveType, FeatureDetection, buildModuleUrl, IndexDatatype, createTaskProcessorWorker, arrayFill, BoundingRectangle, Color, pako_inflate, S3MCompressType, unzip, PixelFormat) { 'use strict';

    /**
         * Create a shallow copy of an array from begin to end.
         *
         * @param {Array} array The array to fill.
         * @param {Number} [begin=0] The index to start at.
         * @param {Number} [end=array.length] The index to end at which is not included.
         *
         * @returns {Array} The resulting array.
         * @private
         */
        function arraySlice(array, begin, end) {
            //>>includeStart('debug', pragmas.debug);
            Check.Check.defined('array', array);
            if (when.defined(begin)) {
                Check.Check.typeOf.number('begin', begin);
            }
            if (when.defined(end)) {
                Check.Check.typeOf.number('end', end);
            }
            //>>includeEnd('debug');

            if (typeof array.slice === 'function') {
                return array.slice(begin, end);
            }

            var copy = Array.prototype.slice.call(array, begin, end);
            var typedArrayTypes = FeatureDetection.FeatureDetection.typedArrayTypes;
            var length = typedArrayTypes.length;
            for (var i = 0; i < length; ++i) {
                if (array instanceof typedArrayTypes[i]) {
                    copy = new typedArrayTypes[i](copy);
                    break;
                }
            }

            return copy;
        }

    function S3MDracoDecode() {
    }

    var draco;

    function decodeIndexArray(dracoGeometry, dracoDecoder) {
        var numPoints = dracoGeometry.num_points();
        var numFaces = dracoGeometry.num_faces();
        var faceIndices = new draco.DracoInt32Array();
        var numIndices = numFaces * 3;
        var indexArray = IndexDatatype.IndexDatatype.createTypedArray(numPoints, numIndices);

        var offset = 0;
        for (var i = 0; i < numFaces; ++i) {
            dracoDecoder.GetFaceFromMesh(dracoGeometry, i, faceIndices);
            indexArray[offset + 0] = faceIndices.GetValue(0);
            indexArray[offset + 1] = faceIndices.GetValue(1);
            indexArray[offset + 2] = faceIndices.GetValue(2);
            offset += 3;
        }

        var indexDataType = IndexDatatype.IndexDatatype.UNSIGNED_SHORT;
        if (indexArray instanceof Uint32Array) {
            indexDataType = IndexDatatype.IndexDatatype.UNSIGNED_INT;
        }

        draco.destroy(faceIndices);
        return {
            typedArray : indexArray,
            numberOfIndices : numIndices,
            indexDataType : indexDataType
        };
    }


    function decodeQuantizedDracoTypedArray(dracoGeometry, dracoDecoder, dracoAttribute, quantization, vertexArrayLength) {
        var vertexArray;
        var attributeData;
        if (quantization.quantizationBits <= 8) {
            attributeData = new draco.DracoUInt8Array();
            vertexArray = new Uint8Array(vertexArrayLength);
            dracoDecoder.GetAttributeUInt8ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
        } else {
            attributeData = new draco.DracoUInt16Array();
            vertexArray = new Uint16Array(vertexArrayLength);
            dracoDecoder.GetAttributeUInt16ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
        }

        for (var i = 0; i < vertexArrayLength; ++i) {
            vertexArray[i] = attributeData.GetValue(i);
        }

        draco.destroy(attributeData);
        return vertexArray;
    }

    function decodeDracoTypedArray(dracoGeometry, dracoDecoder, dracoAttribute, vertexArrayLength) {
        var vertexArray;
        var attributeData;

        // Some attribute types are casted down to 32 bit since Draco only returns 32 bit values
        switch (dracoAttribute.data_type()) {
            case 1: case 11: // DT_INT8 or DT_BOOL
            attributeData = new draco.DracoInt8Array();
            vertexArray = new Int8Array(vertexArrayLength);
            dracoDecoder.GetAttributeInt8ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
            break;
            case 2: // DT_UINT8
                attributeData = new draco.DracoUInt8Array();
                vertexArray = new Uint8Array(vertexArrayLength);
                dracoDecoder.GetAttributeUInt8ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
                break;
            case 3: // DT_INT16
                attributeData = new draco.DracoInt16Array();
                vertexArray = new Int16Array(vertexArrayLength);
                dracoDecoder.GetAttributeInt16ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
                break;
            case 4: // DT_UINT16
                attributeData = new draco.DracoUInt16Array();
                vertexArray = new Uint16Array(vertexArrayLength);
                dracoDecoder.GetAttributeUInt16ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
                break;
            case 5: case 7: // DT_INT32 or DT_INT64
            attributeData = new draco.DracoInt32Array();
            vertexArray = new Int32Array(vertexArrayLength);
            dracoDecoder.GetAttributeInt32ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
            break;
            case 6: case 8: // DT_UINT32 or DT_UINT64
            attributeData = new draco.DracoUInt32Array();
            vertexArray = new Uint32Array(vertexArrayLength);
            dracoDecoder.GetAttributeUInt32ForAllPoints(dracoGeometry, dracoAttribute, attributeData);
            break;
            case 9: case 10: // DT_FLOAT32 or DT_FLOAT64
            attributeData = new draco.DracoFloat32Array();
            vertexArray = new Float32Array(vertexArrayLength);
            dracoDecoder.GetAttributeFloatForAllPoints(dracoGeometry, dracoAttribute, attributeData);
            break;
        }

        for (var i = 0; i < vertexArrayLength; ++i) {
            vertexArray[i] = attributeData.GetValue(i);
        }

        draco.destroy(attributeData);
        return vertexArray;
    }

    function decodeAttribute(dracoGeometry, dracoDecoder, dracoAttribute) {
        var numPoints = dracoGeometry.num_points();
        var numComponents = dracoAttribute.num_components();

        var quantization;
        var transform = new draco.AttributeQuantizationTransform();
        if (transform.InitFromAttribute(dracoAttribute)) {
            var minValues = new Array(numComponents);
            for (var i = 0; i < numComponents; ++i) {
                minValues[i] = transform.min_value(i);
            }
            quantization = {
                quantizationBits : transform.quantization_bits(),
                minValues : minValues,
                range : transform.range(),
                octEncoded : false
            };
        }
        draco.destroy(transform);

        transform = new draco.AttributeOctahedronTransform();
        if (transform.InitFromAttribute(dracoAttribute)) {
            quantization = {
                quantizationBits : transform.quantization_bits(),
                octEncoded : true
            };
        }
        draco.destroy(transform);

        var vertexArrayLength = numPoints * numComponents;
        var vertexArray;
        if (when.defined(quantization)) {
            vertexArray = decodeQuantizedDracoTypedArray(dracoGeometry, dracoDecoder, dracoAttribute, quantization, vertexArrayLength);
        } else {
            vertexArray = decodeDracoTypedArray(dracoGeometry, dracoDecoder, dracoAttribute, vertexArrayLength);
        }

        var componentDatatype = ComponentDatatype.ComponentDatatype.fromTypedArray(vertexArray);

        return {
            array : vertexArray,
            data : {
                componentsPerAttribute : numComponents,
                componentDatatype : componentDatatype,
                byteOffset : dracoAttribute.byte_offset(),
                byteStride : ComponentDatatype.ComponentDatatype.getSizeInBytes(componentDatatype) * numComponents,
                normalized : dracoAttribute.normalized(),
                quantization : quantization
            }
        };
    }

    function decodeAllAttributes(dracoGeometry, dracoDecoder, vertexPackage, vertexUniqueIDs){
        var attributes = vertexPackage.vertexAttributes;
        var attrLocation = vertexPackage.attrLocation;
        vertexPackage.nCompressOptions = 0;
        if(when.defined(vertexUniqueIDs.posUniqueID) && vertexUniqueIDs.posUniqueID >= 0){
            vertexPackage.nCompressOptions |= S3MCompressType.VertexCompressOption.SVC_Vertex;
            var posAttribute = dracoDecoder.GetAttribute(dracoGeometry, vertexUniqueIDs.posUniqueID);
            var posAttributeData = decodeAttribute(dracoGeometry, dracoDecoder, posAttribute);
            var componentsPerAttribute = posAttributeData.data.componentsPerAttribute;
            vertexPackage.verticesCount = posAttributeData.array.length / componentsPerAttribute;
            vertexPackage.vertCompressConstant = posAttributeData.data.quantization.range / (1 << posAttributeData.data.quantization.quantizationBits);
            var minValuesArray = posAttributeData.data.quantization.minValues;
            vertexPackage.minVerticesValue = new Cartesian4.Cartesian4(minValuesArray[0], minValuesArray[1], minValuesArray[2], 1.0);
            if(componentsPerAttribute > 3){
                vertexPackage.minVerticesValue.w = minValuesArray[3];
            }
            attrLocation['aPosition'] = attributes.length;
            attributes.push({
                index: attrLocation['aPosition'],
                typedArray: posAttributeData.array,
                componentsPerAttribute: componentsPerAttribute,
                componentDatatype: posAttributeData.data.componentDatatype,
                offsetInBytes: posAttributeData.data.byteOffset,
                strideInBytes: posAttributeData.data.byteStride,
                normalize: posAttributeData.data.normalized
            });
        }
        if(when.defined(vertexUniqueIDs.normalUniqueID) && vertexUniqueIDs.normalUniqueID >= 0){
            vertexPackage.nCompressOptions |= S3MCompressType.VertexCompressOption.SVC_Normal;
            var normalAttribute = dracoDecoder.GetAttribute(dracoGeometry, vertexUniqueIDs.normalUniqueID);
            var normalAttributeData = decodeAttribute(dracoGeometry, dracoDecoder, normalAttribute);
            var normalQuantization = normalAttributeData.data.quantization;
            vertexPackage.normalRangeConstant = (1 << normalQuantization.quantizationBits) - 1.0;
            attrLocation['aNormal'] = attributes.length;
            attributes.push({
                index: attrLocation['aNormal'],
                typedArray: normalAttributeData.array,
                componentsPerAttribute: normalAttributeData.data.componentsPerAttribute,
                componentDatatype: normalAttributeData.data.componentDatatype,
                offsetInBytes: normalAttributeData.data.byteOffset,
                strideInBytes: normalAttributeData.data.byteStride,
                normalize: normalAttributeData.data.normalized
            });
        }
        if(when.defined(vertexUniqueIDs.colorUniqueID) && vertexUniqueIDs.colorUniqueID >= 0){
            vertexPackage.nCompressOptions |= S3MCompressType.VertexCompressOption.SVC_VertexColor;
            var colorAttribute = dracoDecoder.GetAttribute(dracoGeometry, vertexUniqueIDs.colorUniqueID);
            var colorAttributeData = decodeAttribute(dracoGeometry, dracoDecoder, colorAttribute);
            attrLocation['aColor'] = attributes.length;
            attributes.push({
                index: attrLocation['aColor'],
                typedArray: colorAttributeData.array,
                componentsPerAttribute: colorAttributeData.data.componentsPerAttribute,
                componentDatatype: colorAttributeData.data.componentDatatype,
                offsetInBytes: colorAttributeData.data.byteOffset,
                strideInBytes: colorAttributeData.data.byteStride,
                normalize: colorAttributeData.data.normalized
            });
        }

        for(var i = 0 ; i < vertexUniqueIDs.texCoordUniqueIDs.length; i++){
            vertexPackage.texCoordCompressConstant = [];
            vertexPackage.minTexCoordValue = [];
            var texCoordUniqueID = vertexUniqueIDs.texCoordUniqueIDs[i];
            if(texCoordUniqueID < 0){
                continue;
            }
            var texCoordAttribute = dracoDecoder.GetAttribute(dracoGeometry, texCoordUniqueID);
            var texAttributeData = decodeAttribute(dracoGeometry, dracoDecoder, texCoordAttribute);
            if(when.defined(texAttributeData.data.quantization)){
                vertexPackage.nCompressOptions |= S3MCompressType.VertexCompressOption.SVC_TexutreCoord;
                vertexPackage.texCoordCompressConstant.push(texAttributeData.data.quantization.range / (1 << texAttributeData.data.quantization.quantizationBits));
                var minValuesArray = texAttributeData.data.quantization.minValues;
                vertexPackage.minTexCoordValue.push(new Cartesian2.Cartesian2(minValuesArray[0], minValuesArray[1]));
            }
            var attName = 'aTexCoord' + i;
            attrLocation[attName] = attributes.length;
            attributes.push({
                index: attrLocation[attName],
                typedArray: texAttributeData.array,
                componentsPerAttribute: texAttributeData.data.componentsPerAttribute,
                componentDatatype: texAttributeData.data.componentDatatype,
                offsetInBytes: texAttributeData.data.byteOffset,
                strideInBytes: texAttributeData.data.byteStride,
                normalize: texAttributeData.data.normalized
            });
        }
    }

    S3MDracoDecode.dracoDecodePointCloud = function(dracoLib, dataBuffer, byteLength, vertexPackage, vertexUniqueIDs){
        draco = dracoLib;
        var dracoDecoder = new draco.Decoder();

        // Skip all parameter types except generic
        var attributesToSkip = ['POSITION', 'NORMAL', 'COLOR'];
        for (var i = 0; i < attributesToSkip.length; ++i) {
            dracoDecoder.SkipAttributeTransform(draco[attributesToSkip[i]]);
        }

        var buffer = new draco.DecoderBuffer();
        buffer.Init(dataBuffer, byteLength);

        var geometryType = dracoDecoder.GetEncodedGeometryType(buffer);
        if (geometryType !== draco.POINT_CLOUD) {
            throw new RuntimeError.RuntimeError('Draco geometry type must be POINT_CLOUD.');
        }

        var dracoPointCloud = new draco.PointCloud();
        var decodingStatus = dracoDecoder.DecodeBufferToPointCloud(buffer, dracoPointCloud);
        if (!decodingStatus.ok() || dracoPointCloud.ptr === 0) {
            throw new RuntimeError.RuntimeError('Error decoding draco point cloud: ' + decodingStatus.error_msg());
        }

        draco.destroy(buffer);

        decodeAllAttributes(dracoPointCloud, dracoDecoder, vertexPackage, vertexUniqueIDs);

        draco.destroy(dracoPointCloud);
        draco.destroy(dracoDecoder);
    };

    S3MDracoDecode.dracoDecodeMesh = function(dracoLib, dataBuffer, byteLength, vertexPackage, indexPackage, vertexUniqueIDs){
        draco = dracoLib;
        var dracoDecoder = new draco.Decoder();

        // Skip all parameter types except generic
        var attributesToSkip = ['POSITION', 'NORMAL', 'COLOR', 'TEX_COORD'];
        for (var i = 0; i < attributesToSkip.length; ++i) {
            dracoDecoder.SkipAttributeTransform(draco[attributesToSkip[i]]);
        }

        var buffer = new draco.DecoderBuffer();
        buffer.Init(dataBuffer, byteLength);

        var geometryType = dracoDecoder.GetEncodedGeometryType(buffer);
        if (geometryType !== draco.TRIANGULAR_MESH) {
            throw new RuntimeError.RuntimeError('Unsupported draco mesh geometry type.');
        }

        var dracoGeometry = new draco.Mesh();
        var decodingStatus = dracoDecoder.DecodeBufferToMesh(buffer, dracoGeometry);
        if (!decodingStatus.ok() || dracoGeometry.ptr === 0) {
            throw new RuntimeError.RuntimeError('Error decoding draco mesh geometry: ' + decodingStatus.error_msg());
        }
        draco.destroy(buffer);

        decodeAllAttributes(dracoGeometry, dracoDecoder, vertexPackage, vertexUniqueIDs);

        var indexArray = decodeIndexArray(dracoGeometry, dracoDecoder);
        indexPackage.indicesTypedArray = indexArray.typedArray;
        indexPackage.indicesCount = indexArray.numberOfIndices;
        indexPackage.indexType = indexArray.indexDataType;
        indexPackage.primitiveType = PrimitiveType.PrimitiveType.TRIANGLES;

        draco.destroy(dracoGeometry);
        draco.destroy(dracoDecoder);
    };

    var VERSION = {
        S3M : 49,
        S3M4 : 1
    };

    var S3MVersion = Object.freeze(VERSION);

    var CRN_FORMAT = {
        cCRNFmtInvalid: -1,

        cCRNFmtDXT1: 0,
        // cCRNFmtDXT3 is not currently supported when writing to CRN - only DDS.
        cCRNFmtDXT3: 1,
        cCRNFmtDXT5: 2

        // Crunch supports more formats than this, but we can't use them here.
    };

    // Mapping of Crunch formats to DXT formats.
    var DXT_FORMAT_MAP = {};
    DXT_FORMAT_MAP[CRN_FORMAT.cCRNFmtDXT1] = PixelFormat.PixelFormat.RGB_DXT1;
    DXT_FORMAT_MAP[CRN_FORMAT.cCRNFmtDXT3] = PixelFormat.PixelFormat.RGBA_DXT3;
    DXT_FORMAT_MAP[CRN_FORMAT.cCRNFmtDXT5] = PixelFormat.PixelFormat.RGBA_DXT5;

    var dst;
    var dxtData;
    var cachedDstSize = 0;

    var crunch;
    var crunchInitialized = false;

    // Copy an array of bytes into or out of the emscripten heap.
    function arrayBufferCopy(src, dst, dstByteOffset, numBytes) {
        var i;
        var dst32Offset = dstByteOffset / 4;
        var tail = (numBytes % 4);
        var src32 = new Uint32Array(src.buffer, 0, (numBytes - tail) / 4);
        var dst32 = new Uint32Array(dst.buffer);
        for (i = 0; i < src32.length; i++) {
            dst32[dst32Offset + i] = src32[i];
        }
        for (i = numBytes - tail; i < numBytes; i++) {
            dst[dstByteOffset + i] = src[i];
        }
    }

    /**
     * @private
     */
    function convertCRNToDXT(parameters, transferableObjects) {
        // Copy the contents of the arrayBuffer into emscriptens heap.
        var arrayBuffer = parameters.data;
        var srcSize = arrayBuffer.byteLength;
        var bytes = new Uint8Array(arrayBuffer);
        var src = crunch._malloc(srcSize);
        arrayBufferCopy(bytes, crunch.HEAPU8, src, srcSize);

        // Determine what type of compressed data the file contains.
        var crnFormat = crunch._crn_get_dxt_format(src, srcSize);
        var format = DXT_FORMAT_MAP[crnFormat];
        if (!when.defined(format)) {
            throw new RuntimeError.RuntimeError('Unsupported compressed format.');
        }

        // Gather basic metrics about the DXT data.
        var levels = crunch._crn_get_levels(src, srcSize);
        var width = crunch._crn_get_width(src, srcSize);
        var height = crunch._crn_get_height(src, srcSize);

        // Determine the size of the decoded DXT data.
        var dstSize = 0;
        var i;
        for (i = 0; i < levels; ++i) {
            dstSize += PixelFormat.PixelFormat.compressedTextureSizeInBytes(format, width >> i, height >> i);
        }

        // Allocate enough space on the emscripten heap to hold the decoded DXT data
        // or reuse the existing allocation if a previous call to this function has
        // already acquired a large enough buffer.
        if(cachedDstSize < dstSize) {
            if(when.defined(dst)) {
                crunch._free(dst);
            }
            dst = crunch._malloc(dstSize);
            dxtData = new Uint8Array(crunch.HEAPU8.buffer, dst, dstSize);
            cachedDstSize = dstSize;
        }

        // Decompress the DXT data from the Crunch file into the allocated space.
        crunch._crn_decompress(src, srcSize, dst, dstSize, 0, levels);

        // Release the crunch file data from the emscripten heap.
        crunch._free(src);

        var bOutMipMapData = when.defaultValue(parameters.bMipMap, false);
        if(bOutMipMapData){
            var dXTDataMipMap = dxtData.slice(0, dstSize);
            transferableObjects.push(dXTDataMipMap.buffer);
            return new PixelFormat.CompressedTextureBuffer(format, width, height, dXTDataMipMap);
        }
        else {
            // Mipmaps are unsupported, so copy the level 0 texture
            // When mipmaps are supported, a copy will still be necessary as dxtData is a view on the heap.
            var length = PixelFormat.PixelFormat.compressedTextureSizeInBytes(format, width, height);

            // Get a copy of the 0th mip level. dxtData will exceed length when there are more mip levels.
            // Equivalent to dxtData.slice(0, length), which is not supported in IE11
            var level0DXTDataView = dxtData.subarray(0, length);
            var level0DXTData = new Uint8Array(length);
            level0DXTData.set(level0DXTDataView, 0);

            transferableObjects.push(level0DXTData.buffer);
            return new PixelFormat.CompressedTextureBuffer(format, width, height, level0DXTData);
        }
    }

    var S3MBVertexOptions = {
        SVO_HasInstSelInfo: 1
    };

    var S3MBVertexTag = {
        SV_Unkown: 0,
        SV_Standard: 1,
        SV_Compressed: 2,
        SV_DracoCompressed: 3
    };

    var dracoLib;
    var colorScratch = new Color.Color();
    var CLAMP_GROUND_LINE_PASS_NAME = "ClampGroundAndObjectLinePass";
    var unzipwasmReady = false;
    if (when.defined(unzip.unzip)) {
        unzip.unzip.onRuntimeInitialized = function () {
            unzipwasmReady = true;
        };
        var unzipwasm = unzip.unzip.cwrap('unzip', 'number', ['number', 'number', 'number', 'number']);
        var freec = unzip.unzip.cwrap('freePointer', null, ['number']);
    }
    function Bound3D(left,bottom,right,top,minHeight,maxHeight){
        this.left = left;
        this.bottom = bottom;
        this.right = right;
        this.top = top;
        this.minHeight = minHeight;
        this.maxHeight = maxHeight;
        this.width = right-left;
        this.length = top - bottom;
        this.height = maxHeight - minHeight;
    }
    function loadStream(dataView, dataBuffer, byteOffset) {
        var newByteOffset = byteOffset;
        var streamSize = dataView.getUint32(newByteOffset, true);
        newByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        var bufferByteOffset = newByteOffset;
            var buffer = new Uint8Array(dataBuffer, newByteOffset, streamSize);
        newByteOffset += streamSize * Uint8Array.BYTES_PER_ELEMENT;
        return {
            dataViewByteOffset: bufferByteOffset,
            byteOffset: newByteOffset,
            buffer: buffer
        };
    }

    function loadString(dataView, viewByteOffset, typedArray, bufferByteOffset) {
        var stringLength = dataView.getUint32(bufferByteOffset + viewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        var stringBuffer = typedArray.subarray(bufferByteOffset, bufferByteOffset + stringLength);
        var strResult = S3MCompressType.getStringFromTypedArray(stringBuffer);
        bufferByteOffset += stringLength;
        return {
            string: strResult,
            bytesOffset: bufferByteOffset
        }
    }

    function loadTexCoord(view, typedArray, bufferByteOffset, viewByteOffset, vertexPackage, isOldVersion) {
        var newBytesOffset = bufferByteOffset;
        var nTexCount = view.getUint16(bufferByteOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;

        if (!isOldVersion) {
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        }

        for (var i = 0; i < nTexCount; i++) {
            var nTexCoordCount = view.getUint32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            var nDimension = view.getUint16(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
            var nTexCoordStride = view.getUint16(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
            if (nDimension == 20 || nDimension == 35) ;
            else {
                var byteLength = nTexCoordCount * nDimension * Float32Array.BYTES_PER_ELEMENT;
                var texCoordBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
                newBytesOffset += byteLength;
                var str = 'aTexCoord' + i;
                var attributes = vertexPackage.vertexAttributes;
                var attrLocation = vertexPackage.attrLocation;
                attrLocation[str] = attributes.length;
                attributes.push({
                    index: attrLocation[str],
                    typedArray: texCoordBuffer,
                    componentsPerAttribute: nDimension,
                    componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                    offsetInBytes: 0,
                    strideInBytes: nDimension * Float32Array.BYTES_PER_ELEMENT,
                    normalize: false
                });
            }
        }
        return {
            bytesOffset: newBytesOffset
        };
    }

    function loadCompressTexCoord(view, typedArray, bufferByteOffset, viewByteOffset, vertexPackage) {
        vertexPackage.texCoordCompressConstant = [];
        vertexPackage.minTexCoordValue = [];
        var newBytesOffset = bufferByteOffset;
        var nTexCount = view.getUint16(bufferByteOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var texIndex = 0;
        for (var i = 0; i < nTexCount; i++) {
            var bNeedTexCoordZ = view.getUint8(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT;
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT * 3;
            var nTexCoordCount = view.getUint32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            var nDimension = view.getUint16(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
            var nTexCoordStride = view.getUint16(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;

            var texCoordCompressConstant = view.getFloat32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
            vertexPackage.texCoordCompressConstant.push(texCoordCompressConstant);

            var minTexCoordValue = new Cartesian4.Cartesian4();
            minTexCoordValue.x = view.getFloat32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
            minTexCoordValue.y = view.getFloat32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
            minTexCoordValue.z = view.getFloat32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
            minTexCoordValue.w = view.getFloat32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
            vertexPackage.minTexCoordValue.push(minTexCoordValue);

            var byteLength = nTexCoordCount * nDimension * Int16Array.BYTES_PER_ELEMENT;
            var texCoordBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
            newBytesOffset += byteLength;
            var align = newBytesOffset % 4;
            if (align !== 0) {
                newBytesOffset += (4 - align);
            }


            var str = 'aTexCoord' + texIndex;
            var attributes = vertexPackage.vertexAttributes;
            var attrLocation = vertexPackage.attrLocation;
            attrLocation[str] = attributes.length;
            attributes.push({
                index: attrLocation[str],
                typedArray: texCoordBuffer,
                componentsPerAttribute: nDimension,
                componentDatatype: ComponentDatatype.ComponentDatatype.SHORT,
                offsetInBytes: 0,
                strideInBytes: nDimension * Int16Array.BYTES_PER_ELEMENT,
                normalize: false
            });

            if (bNeedTexCoordZ) {
                byteLength = nTexCoordCount * Float32Array.BYTES_PER_ELEMENT;
                var texCoordZBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
                newBytesOffset += byteLength;
                vertexPackage.texCoordZMatrix = true;
                str = 'aTexCoordZ' + texIndex;
                attrLocation[str] = attributes.length;
                attributes.push({
                    index: attrLocation[str],
                    typedArray: texCoordZBuffer,
                    componentsPerAttribute: 1,
                    componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                    offsetInBytes: 0,
                    strideInBytes: Float32Array.BYTES_PER_ELEMENT,
                    normalize: false
                });
            }
            texIndex++;
        }
        return {
            bytesOffset: newBytesOffset
        };
    }

    function loadInstanceInfo(view, typedArray, bufferByteOffset, viewByteOffset, vertexPackage) {
        var newBytesOffset = bufferByteOffset;
        var nInstanceInfo = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var attributes = vertexPackage.vertexAttributes;
        var attrLocation = vertexPackage.attrLocation;

        for (var iIndex = 0; iIndex < nInstanceInfo; iIndex++) {
            var nTexCoordCount = view.getUint32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            var nTexDimensions = view.getUint16(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;

            if (nTexDimensions === 16) {
                newBytesOffset -= Uint16Array.BYTES_PER_ELEMENT;
                var byteLength = nTexCoordCount * (nTexDimensions * Float32Array.BYTES_PER_ELEMENT + Uint16Array.BYTES_PER_ELEMENT);
                var rowArray = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
                newBytesOffset += byteLength; // 每个矩阵前有一个dim为16

                var instanceBuffer = new Uint8Array(Float32Array.BYTES_PER_ELEMENT * nTexDimensions * nTexCoordCount);
                vertexPackage.instanceCount = nTexCoordCount;
                vertexPackage.instanceMode = nTexDimensions;
                vertexPackage.instanceBuffer = instanceBuffer;
                vertexPackage.instanceIndex = 1;

                var perLength = Float32Array.BYTES_PER_ELEMENT * nTexDimensions + Uint16Array.BYTES_PER_ELEMENT;
                for (var i = 0; i < nTexCoordCount; i++) {
                    var start = i * perLength + Uint16Array.BYTES_PER_ELEMENT;
                    var t = rowArray.subarray(start, start + perLength);
                    instanceBuffer.set(t, i * (perLength - Uint16Array.BYTES_PER_ELEMENT));
                }

                byteStride = Float32Array.BYTES_PER_ELEMENT * 16;
                attrLocation['uv2'] = attributes.length;
                attributes.push({
                    index: attrLocation['uv2'],
                    componentsPerAttribute: 4,
                    componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                    normalize: false,
                    offsetInBytes: 0,
                    strideInBytes: byteStride,
                    instanceDivisor: 1
                });

                attrLocation['uv3'] = attributes.length;
                attributes.push({
                    index: attrLocation['uv3'],
                    componentsPerAttribute: 4,
                    componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                    normalize: false,
                    offsetInBytes: 4 * Float32Array.BYTES_PER_ELEMENT,
                    strideInBytes: byteStride,
                    instanceDivisor: 1
                });

                attrLocation['uv4'] = attributes.length;
                attributes.push({
                    index: attrLocation['uv4'],
                    componentsPerAttribute: 4,
                    componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                    normalize: false,
                    offsetInBytes: 8 * Float32Array.BYTES_PER_ELEMENT,
                    strideInBytes: byteStride,
                    instanceDivisor: 1
                });

                attrLocation['secondary_colour'] = attributes.length;
                attributes.push({
                    index: attrLocation['secondary_colour'],
                    componentsPerAttribute: 4,
                    componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                    normalize: false,
                    offsetInBytes: 12 * Float32Array.BYTES_PER_ELEMENT,
                    strideInBytes: byteStride,
                    instanceDivisor: 1
                });
            } else {
                var nTexCoordStride = view.getUint16(newBytesOffset + viewByteOffset, true);
                newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
                var byteLength = nTexCoordCount * nTexDimensions * Float32Array.BYTES_PER_ELEMENT;
                if (nTexDimensions === 17 || nTexDimensions === 29) {
                    var instanceBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
                    vertexPackage.instanceCount = nTexCoordCount;
                    vertexPackage.instanceMode = nTexDimensions;
                    vertexPackage.instanceBuffer = instanceBuffer;
                    vertexPackage.instanceIndex = 1;
                    var byteStride;
                    if (nTexDimensions === 17) {
                        byteStride = Float32Array.BYTES_PER_ELEMENT * 17;
                        attrLocation['uv2'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv2'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 0,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });

                        attrLocation['uv3'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv3'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 4 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });

                        attrLocation['uv4'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv4'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 8 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });

                        attrLocation['secondary_colour'] = attributes.length;
                        attributes.push({
                            index: attrLocation['secondary_colour'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 12 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });

                        attrLocation['uv6'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv6'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.UNSIGNED_BYTE,
                            normalize: true,
                            offsetInBytes: 16 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                    } else if (nTexDimensions === 29) {
                        byteStride = Float32Array.BYTES_PER_ELEMENT * 29;
                        attrLocation['uv1'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv1'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 0,
                            strideInBytes: byteStride,
                            instanceDivisor: 1,
                            byteLength: byteLength
                        });
                        attrLocation['uv2'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv2'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 4 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['uv3'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv3'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 8 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['uv4'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv4'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 12 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['uv5'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv5'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 16 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['uv6'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv6'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 20 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['uv7'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv7'],
                            componentsPerAttribute: 3,
                            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                            normalize: false,
                            offsetInBytes: 24 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['secondary_colour'] = attributes.length;
                        attributes.push({
                            index: attrLocation['secondary_colour'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.UNSIGNED_BYTE,
                            normalize: true,
                            offsetInBytes: 27 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                        attrLocation['uv9'] = attributes.length;
                        attributes.push({
                            index: attrLocation['uv9'],
                            componentsPerAttribute: 4,
                            componentDatatype: ComponentDatatype.ComponentDatatype.UNSIGNED_BYTE,
                            normalize: true,
                            offsetInBytes: 28 * Float32Array.BYTES_PER_ELEMENT,
                            strideInBytes: byteStride,
                            instanceDivisor: 1
                        });
                    }
                } else {
                    var valueCount = nTexCoordCount * nTexDimensions;
                    vertexPackage.instanceBounds = new Float32Array(valueCount);
                    for (var k = 0; k < valueCount; k++) {
                        vertexPackage.instanceBounds[k] = view.getFloat32(newBytesOffset + viewByteOffset + k * Float32Array.BYTES_PER_ELEMENT, true);
                    }
                }
                newBytesOffset += byteLength;
            }
        }
        return {
            bytesOffset: newBytesOffset
        };
    }

    function loadVertex(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage, modelMatrix) {
        var newBytesOffset = bufferByteOffset;
        var nVerticesCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        vertexPackage.verticesCount = nVerticesCount;
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        if (nVerticesCount <= 0) {
            return {
                bytesOffset: newBytesOffset
            };
        }
        var nVertexDimension = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var nVertexStride = view.getUint16(newBytesOffset + viewByteOffset, true);
        nVertexStride = nVertexDimension * Float32Array.BYTES_PER_ELEMENT;
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;

        var byteLength = nVerticesCount * nVertexDimension * Float32Array.BYTES_PER_ELEMENT;
        var vertexBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
        newBytesOffset += byteLength;

        if(nVertexDimension === 3 && when.defined(modelMatrix)) { // 计算W位片元高度
            var scratchPositionMC = new Cartographic.Cartesian3();
            var scratchPositionWC = new Cartographic.Cartesian3();
            var positionsFloat32 = new Float32Array(vertexBuffer.buffer, vertexBuffer.byteOffset, vertexBuffer.byteLength / 4);
            var positionsHasW = new Float32Array(vertexBuffer.byteLength / 4 + nVerticesCount); // 多申请一个W位
            var len = positionsFloat32.length;
            for(var i = 0, j = 0; i < len; i+=3,j+=4) {
                positionsHasW[j] = positionsFloat32[i];
                positionsHasW[j + 1] = positionsFloat32[i + 1];
                positionsHasW[j + 2] = positionsFloat32[i + 2];
                BoundingSphere.Matrix4.multiplyByPoint(modelMatrix, Cartographic.Cartesian3.fromElements(positionsHasW[j], positionsHasW[j + 1], positionsHasW[j + 2], scratchPositionMC), scratchPositionWC);
                positionsHasW[j + 3] = Cartographic.Cartographic.fromCartesian(scratchPositionWC).height;
            }
            vertexBuffer = positionsHasW;
            nVertexDimension = 4;
            nVertexStride = nVertexDimension * Float32Array.BYTES_PER_ELEMENT;
        }

        var attributes = vertexPackage.vertexAttributes;
        var attrLocation = vertexPackage.attrLocation;
        attrLocation['aPosition'] = attributes.length;
        attributes.push({
            index: attrLocation['aPosition'],
            typedArray: vertexBuffer,
            componentsPerAttribute: nVertexDimension,
            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
            offsetInBytes: 0,
            strideInBytes: nVertexStride,
            normalize: false
        });
        return {
            bytesOffset: newBytesOffset
        }
    }

    function loadCompressVertex(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage) {
        var newBytesOffset = bufferByteOffset;
        var nVerticesCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        vertexPackage.verticesCount = nVerticesCount;
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        if (nVerticesCount <= 0) {
            return {
                bytesOffset: newBytesOffset
            };
        }
        var nVertexDimension = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var nVertexStride = view.getUint16(newBytesOffset + viewByteOffset, true);
        nVertexStride = nVertexDimension * Int16Array.BYTES_PER_ELEMENT;
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;

        var fVertCompressConstant = view.getFloat32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
        var minVerticesValue = new Cartesian4.Cartesian4();
        minVerticesValue.x = view.getFloat32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
        minVerticesValue.y = view.getFloat32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
        minVerticesValue.z = view.getFloat32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Float32Array.BYTES_PER_ELEMENT;
        minVerticesValue.w = view.getFloat32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Float32Array.BYTES_PER_ELEMENT;

        vertexPackage.vertCompressConstant = fVertCompressConstant;
        vertexPackage.minVerticesValue = minVerticesValue;

        var byteLength = nVerticesCount * nVertexDimension * Int16Array.BYTES_PER_ELEMENT;
        var vertexBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
        newBytesOffset += byteLength;

        var attributes = vertexPackage.vertexAttributes;
        var attrLocation = vertexPackage.attrLocation;
        attrLocation['aPosition'] = attributes.length;
        attributes.push({
            index: attrLocation['aPosition'],
            typedArray: vertexBuffer,
            componentsPerAttribute: nVertexDimension,
            componentDatatype: ComponentDatatype.ComponentDatatype.SHORT,
            offsetInBytes: 0,
            strideInBytes: nVertexStride,
            normalize: false
        });
        return {
            bytesOffset: newBytesOffset
        }
    }

    function loadNormal(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage) {
        var newBytesOffset = bufferByteOffset;
        var nNormalCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        if (nNormalCount <= 0) {
            return {
                bytesOffset: newBytesOffset
            };
        }
        var nNormalDimension = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var nNormalStride = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var byteLength = nNormalCount * nNormalDimension * Float32Array.BYTES_PER_ELEMENT;
        var normalBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
        newBytesOffset += byteLength;
        if (!vertexPackage.ignoreNormal) {
            var attributes = vertexPackage.vertexAttributes;
            var attrLocation = vertexPackage.attrLocation;
            attrLocation['aNormal'] = attributes.length;
            attributes.push({
                index: attrLocation['aNormal'],
                typedArray: normalBuffer,
                componentsPerAttribute: nNormalDimension,
                componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
                offsetInBytes: 0,
                strideInBytes: nNormalStride,
                normalize: false
            });
        }
        return {
            bytesOffset: newBytesOffset
        }
    }

    function loadCompressNormal(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage) {
        var newBytesOffset = bufferByteOffset;
        var nNormalCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        if (nNormalCount <= 0) {
            return {
                bytesOffset: newBytesOffset
            };
        }
        var nNormalDimension = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var nNormalStride = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        var byteLength = nNormalCount * 2 * Int16Array.BYTES_PER_ELEMENT;
        var normalBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
        newBytesOffset += byteLength;
        if (!vertexPackage.ignoreNormal) {
            var attributes = vertexPackage.vertexAttributes;
            var attrLocation = vertexPackage.attrLocation;
            attrLocation['aNormal'] = attributes.length;
            attributes.push({
                index: attrLocation['aNormal'],
                typedArray: normalBuffer,
                componentsPerAttribute: 2,
                componentDatatype: ComponentDatatype.ComponentDatatype.SHORT,
                offsetInBytes: 0,
                strideInBytes: nNormalStride,
                normalize: false
            });
        }
        return {
            bytesOffset: newBytesOffset
        }
    }

    function loadVertexColor(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage) {
        var newBytesOffset = bufferByteOffset;
        var nColorCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        var verticesCount = vertexPackage.verticesCount;
        var vertexColor;
        if (nColorCount > 0) {
            var colorStride = view.getUint16(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT * 2;
            var byteLength = nColorCount * Uint8Array.BYTES_PER_ELEMENT * 4;
            vertexColor = arraySlice(typedArray, newBytesOffset, newBytesOffset + byteLength);
            newBytesOffset += byteLength;
            var attributes = vertexPackage.vertexAttributes;
            var attrLocation = vertexPackage.attrLocation;
            attrLocation['aColor'] = attributes.length;
            attributes.push({
                index: attrLocation['aColor'],
                typedArray: vertexColor,
                componentsPerAttribute: 4,
                componentDatatype: ComponentDatatype.ComponentDatatype.UNSIGNED_BYTE,
                offsetInBytes: 0,
                strideInBytes: 4,
                normalize: true
            });
        }

        return {
            bytesOffset: newBytesOffset
        };
    }

    function loadSecondColor(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage) {
        var newBytesOffset = bufferByteOffset;
        var nSecondColorCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        if (nSecondColorCount <= 0) {
            return {
                bytesOffset: newBytesOffset
            };
        }
        var secondColorStride = view.getUint16(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint16Array.BYTES_PER_ELEMENT;
        newBytesOffset += Uint8Array.BYTES_PER_ELEMENT * 2;
        var byteLength = nSecondColorCount * Uint8Array.BYTES_PER_ELEMENT * 4;
        newBytesOffset += byteLength;
        return {
            bytesOffset: newBytesOffset
        };
    }

    function loadIndexPackage(typedArray, view, viewByteOffset, bufferByteOffset) {
        var newBytesOffset = bufferByteOffset;
        var arrIndexPackage = [];
        var nIndexPackageCount = view.getUint32(newBytesOffset + viewByteOffset, true);
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        for (var k = 0; k < nIndexPackageCount; k++) {
            var indexPackage = {};
            var nIndexCount = view.getUint32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            var enIndexType = view.getUint8(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT;
            var bUseIndex = view.getUint8(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT;
            var operationType = view.getUint8(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT;
            newBytesOffset += Uint8Array.BYTES_PER_ELEMENT;
            if (nIndexCount > 0) {
                var byteLength = 0;
                var indexBuffer = null;
                if (enIndexType === 1 || enIndexType === 3) {
                    byteLength = nIndexCount * Uint32Array.BYTES_PER_ELEMENT;
                    indexBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
                }
                else {
                    byteLength = nIndexCount * Uint16Array.BYTES_PER_ELEMENT;
                    indexBuffer = typedArray.subarray(newBytesOffset, newBytesOffset + byteLength);
                    if (nIndexCount % 2 != 0) {
                        byteLength += 2;
                    }
                }
                indexPackage.indicesTypedArray = indexBuffer;
                newBytesOffset += byteLength;
            }
            indexPackage.indicesCount = nIndexCount;
            indexPackage.indexType = enIndexType;
            indexPackage.primitiveType = operationType;

            var arrPassName = [];
            var nPassNameCount = view.getUint32(newBytesOffset + viewByteOffset, true);
            newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            for (var passIndex = 0; passIndex < nPassNameCount; passIndex++) {
                var res = loadString(view, viewByteOffset, typedArray, newBytesOffset);
                var strPassName = res.string;
                newBytesOffset = res.bytesOffset;
                arrPassName.push(strPassName);
                indexPackage.materialCode = strPassName;
            }
            arrIndexPackage.push(indexPackage);

            var align = newBytesOffset % 4;
            if (align !== 0) {
                var nReserved = 4 - newBytesOffset % 4;
                newBytesOffset += nReserved;
            }
        }
        return {
            bytesOffset: newBytesOffset,
            arrIndexPackage: arrIndexPackage
        };
    }

    function loadCompressSkeleton(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage, isOldVersion, modelMatrix) {
        var newBytesOffset = bufferByteOffset;
        var nCompressOptions = view.getUint32(newBytesOffset + viewByteOffset, true);
        vertexPackage.nCompressOptions = nCompressOptions;
        var result;
        newBytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        if ((nCompressOptions & S3MCompressType.VertexCompressOption.SVC_Vertex) == S3MCompressType.VertexCompressOption.SVC_Vertex) {
            result = loadCompressVertex(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage);
            newBytesOffset = result.bytesOffset;
        }
        else {
            result = loadVertex(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage, modelMatrix);
            newBytesOffset = result.bytesOffset;
        }

        if ((nCompressOptions & S3MCompressType.VertexCompressOption.SVC_Normal) == S3MCompressType.VertexCompressOption.SVC_Normal) {
            result = loadCompressNormal(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage);
            newBytesOffset = result.bytesOffset;
        }
        else {
            result = loadNormal(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage);
            newBytesOffset = result.bytesOffset;
        }

        result = loadVertexColor(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage);
        newBytesOffset = result.bytesOffset;

        result = loadSecondColor(typedArray, view, viewByteOffset, newBytesOffset);
        newBytesOffset = result.bytesOffset;

        if ((nCompressOptions & S3MCompressType.VertexCompressOption.SVC_TexutreCoord) == S3MCompressType.VertexCompressOption.SVC_TexutreCoord) {
            result = loadCompressTexCoord(view, typedArray, newBytesOffset, viewByteOffset, vertexPackage);
            newBytesOffset = result.bytesOffset;
        }
        else {
            result = loadTexCoord(view, typedArray, newBytesOffset, viewByteOffset, vertexPackage, isOldVersion);
            newBytesOffset = result.bytesOffset;
        }

        if ((nCompressOptions & S3MCompressType.VertexCompressOption.SVC_TexutreCoordIsW) == S3MCompressType.VertexCompressOption.SVC_TexutreCoordIsW) {
            vertexPackage.textureCoordIsW = true;
        }

        result = loadInstanceInfo(view, typedArray, newBytesOffset, viewByteOffset, vertexPackage);
        newBytesOffset = result.bytesOffset;

        return {
            bytesOffset: newBytesOffset
        }
    }

    function loadStandardSkeleton(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage, isOldVersion, modelMatrix) {
        var newBytesOffset = bufferByteOffset;
        var result;
        result = loadVertex(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage, modelMatrix);
        newBytesOffset = result.bytesOffset;

        result = loadNormal(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage);
        newBytesOffset = result.bytesOffset;

        result = loadVertexColor(typedArray, view, viewByteOffset, newBytesOffset, vertexPackage);
        newBytesOffset = result.bytesOffset;

        result = loadSecondColor(typedArray, view, viewByteOffset, newBytesOffset);
        newBytesOffset = result.bytesOffset;

        result = loadTexCoord(view, typedArray, newBytesOffset, viewByteOffset, vertexPackage, isOldVersion);
        newBytesOffset = result.bytesOffset;

        result = loadInstanceInfo(view, typedArray, newBytesOffset, viewByteOffset, vertexPackage);
        newBytesOffset = result.bytesOffset;

        return {
            bytesOffset: newBytesOffset
        }
    }

    function isClampGroundLinePass(arrIndexPackage) {
        if (arrIndexPackage.length === 0) {
            return false;
        }
        return arrIndexPackage[0].materialCode === CLAMP_GROUND_LINE_PASS_NAME;
    }

    function loadSkeletonEntities(skeletonBuffer, view, viewByteOffset, geoPackage, isOldVersion, transferableObjects, isHasOBB, version, modelMatrix, geoMatMap) {
        var typedArray = skeletonBuffer;
        var bufferByteOffset = 0;
        var nCount = view.getUint32(bufferByteOffset + viewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        geoMatMap = when.defaultValue(geoMatMap, when.defaultValue.EMPTY_OBJECT);
        var scratchMat = undefined;
        for (var i = 0; i < nCount; i++) {
            // S3MB头名字长度
            var result = loadString(view, viewByteOffset, typedArray, bufferByteOffset);
            var strGeometryName = result.string;
            if(when.defined(modelMatrix)) {
                var geoMat = when.defaultValue(geoMatMap[strGeometryName], BoundingSphere.Matrix4.IDENTITY);
                scratchMat = new BoundingSphere.Matrix4();
                BoundingSphere.Matrix4.multiply(modelMatrix, geoMat, scratchMat);
            }

            bufferByteOffset = result.bytesOffset;
            var align = bufferByteOffset % 4;
            if (align !== 0) {
                bufferByteOffset += (4 - align);
            }

            var nTagValue = S3MBVertexTag.SV_Unkown;
            nTagValue = view.getUint32(bufferByteOffset + viewByteOffset, true);
            bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;

            var vertexPackage = {};
            vertexPackage.vertexAttributes = [];
            vertexPackage.attrLocation = {};
            vertexPackage.instanceCount = 0;
            vertexPackage.instanceMode = 0;
            vertexPackage.instanceIndex = -1;
            vertexPackage.ignoreNormal = geoPackage.ignoreNormal;

            if (nTagValue == S3MBVertexTag.SV_DracoCompressed) {
                var vertexUniqueIDs = {};
                vertexUniqueIDs.posUniqueID = view.getInt32(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;
                vertexUniqueIDs.normalUniqueID = view.getInt32(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;
                vertexUniqueIDs.colorUniqueID = view.getInt32(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;
                vertexUniqueIDs.secondColorUniqueID = view.getInt32(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;

                var nTextureCoord = view.getUint16(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int16Array.BYTES_PER_ELEMENT;

                var texCoordUniqueIDs = [];
                for (var nTexCoordIdx = 0; nTexCoordIdx < nTextureCoord; nTexCoordIdx++) {
                    var nTexCoordUniqueID = view.getInt32(bufferByteOffset + viewByteOffset, true);
                    texCoordUniqueIDs.push(nTexCoordUniqueID);
                    bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;
                }
                vertexUniqueIDs.texCoordUniqueIDs = texCoordUniqueIDs;

                var nIndexPackageCount = view.getInt32(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;
                var arrIndexPackage = [];
                // 目前只支持单索引
                var indexPackage = {};
                if (nIndexPackageCount > 0) {
                    var res = loadString(view, viewByteOffset, typedArray, bufferByteOffset);
                    var strPassName = res.string;
                    bufferByteOffset = res.bytesOffset;
                    indexPackage.materialCode = strPassName;
                    arrIndexPackage.push(indexPackage);
                }

                var nDracoBufferSize = view.getUint32(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Int32Array.BYTES_PER_ELEMENT;
                var dataBuffer = arraySlice(typedArray, bufferByteOffset, bufferByteOffset + nDracoBufferSize);
                if (nIndexPackageCount > 0) {
                    S3MDracoDecode.dracoDecodeMesh(dracoLib, dataBuffer, nDracoBufferSize, vertexPackage, indexPackage, vertexUniqueIDs);
                }
                else {
                    S3MDracoDecode.dracoDecodePointCloud(dracoLib, dataBuffer, nDracoBufferSize, vertexPackage, vertexUniqueIDs);
                }

                bufferByteOffset += nDracoBufferSize;
                geoPackage[strGeometryName] = {
                    vertexPackage: vertexPackage,
                    arrIndexPackage: arrIndexPackage
                };
            }
            else {
                if (nTagValue == S3MBVertexTag.SV_Standard) {
                    result = loadStandardSkeleton(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage, isOldVersion, scratchMat);
                    bufferByteOffset = result.bytesOffset;
                }
                else if (nTagValue == S3MBVertexTag.SV_Compressed) {
                    result = loadCompressSkeleton(typedArray, view, viewByteOffset, bufferByteOffset, vertexPackage, isOldVersion, scratchMat);
                    bufferByteOffset = result.bytesOffset;
                }

                result = loadIndexPackage(typedArray, view, viewByteOffset, bufferByteOffset);
                var arrIndexPackage = result.arrIndexPackage;
                if (isClampGroundLinePass(arrIndexPackage)) {
                    vertexPackage.clampRegionEdge = true;
                }

                var edgeGeometry;

                if(arrIndexPackage.length === 2 && arrIndexPackage[1].primitiveType === 13 && arrIndexPackage[1].indicesCount >= 3){ // 13表示EffectOutline线框
                    edgeGeometry = S3MCompressType.S3MEdgeProcessor.createEdgeDataByIndices(vertexPackage, arrIndexPackage[1], transferableObjects);
                }

                bufferByteOffset = result.bytesOffset;
                geoPackage[strGeometryName] = {
                    vertexPackage: vertexPackage,
                    arrIndexPackage: arrIndexPackage,
                    edgeGeometry: edgeGeometry
                };
            }
            if(when.defined(isHasOBB) && isHasOBB){
                var bufferState = view.getUint16(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Uint16Array.BYTES_PER_ELEMENT;
                //关键帧属性
                if(bufferState === 1){
                    var keyFrameCount = view.getUint32(bufferByteOffset + viewByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var attributeCount = view.getUint32(bufferByteOffset + viewByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var sumTime = view.getFloat32(bufferByteOffset + viewByteOffset, true);
                    bufferByteOffset += Float32Array.BYTES_PER_ELEMENT;
                    var v;
                    var keyFrameTimes = new Array(keyFrameCount);
                    var attrModeArr = new Array(keyFrameCount);
                    var attrDimArr = new Array(keyFrameCount);
                    var attrValueArr = new Array(keyFrameCount);
                    for(v = 0;v < keyFrameCount;v++){
                        var time = view.getFloat32(bufferByteOffset + viewByteOffset, true);
                        bufferByteOffset += Float32Array.BYTES_PER_ELEMENT;
                        keyFrameTimes[v] = time;

                        var attrMode = view.getUint16(bufferByteOffset + viewByteOffset, true);
                        bufferByteOffset += Uint16Array.BYTES_PER_ELEMENT;
                        attrModeArr[v] = attrMode;

                        var attrDim = view.getUint16(bufferByteOffset + viewByteOffset, true);
                        bufferByteOffset += Uint16Array.BYTES_PER_ELEMENT;
                        attrDimArr[v] = attrDim;

                        var len = attrDim * attributeCount;
                        var attrArr = new Array(len);
                        for(var t = 0;t < len;t++){
                            var attr = view.getFloat32(bufferByteOffset + viewByteOffset, true);
                            bufferByteOffset += Float32Array.BYTES_PER_ELEMENT;
                            attrArr[t] = attr;
                        }
                        attrValueArr[v] = attrArr;
                    }
                }
                var min = new Cartographic.Cartesian3();
                var max = new Cartographic.Cartesian3();
                min.x = view.getFloat64(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
                min.y = view.getFloat64(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
                min.z = view.getFloat64(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
                max.x = view.getFloat64(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
                max.y = view.getFloat64(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
                max.z = view.getFloat64(bufferByteOffset + viewByteOffset, true);
                bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;

                geoPackage[strGeometryName].min = min;
                geoPackage[strGeometryName].max = max;

                var vertexPackage = geoPackage[strGeometryName].vertexPackage;

                if (when.defined(vertexPackage.instanceBuffer) && version === 2) {
                    vertexPackage.instanceBounds = new Float32Array(6);
                    Cartographic.Cartesian3.pack(min, vertexPackage.instanceBounds, 0);
                    Cartographic.Cartesian3.pack(max, vertexPackage.instanceBounds, 3);
                }
            }
        }
    }

    function loadGeodeEntities(shellBuffer, view, bufferByteOffset, dataViewByteOffset, geoMatMap) {
        var geode = {};
        var skeletonNames = [];
        var geoMatrix = new BoundingSphere.Matrix4();
        var typedArray = shellBuffer;
        geoMatMap = when.defaultValue(geoMatMap, {});
        for (var matIndex = 0; matIndex < 16; matIndex++) {
            geoMatrix[matIndex] = view.getFloat64(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
        }
        geode.matrix = geoMatrix;
        geode.skeletonNames = skeletonNames;
        var nSkeletonCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        for (var i = 0; i < nSkeletonCount; i++) {
            var res = loadString(view, dataViewByteOffset, typedArray, bufferByteOffset);
            var strSkeletonName = res.string;
            bufferByteOffset = res.bytesOffset;
            skeletonNames.push(strSkeletonName);
            geoMatMap[strSkeletonName] = geoMatrix;
        }
        return {
            byteOffset: bufferByteOffset,
            geode: geode
        }
    }

    function removeUnusedStringTileName(oldTileName) {
        var index = oldTileName.indexOf('Geometry');
        if (index === -1) {
            return oldTileName;
        }
        var ignoreString = oldTileName.substring(index, oldTileName.length);
        return oldTileName.replace(ignoreString, '');
    }

    function loadPageLODEntities(shellBuffer, view, bufferByteOffset, dataViewByteOffset, geoMatMap) {
        var pageLOD = {};
        var dbDis = view.getFloat32(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Float32Array.BYTES_PER_ELEMENT;
        var uRangeMode = view.getUint16(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Uint16Array.BYTES_PER_ELEMENT;
        pageLOD.rangeMode = uRangeMode;
        pageLOD.rangeList = dbDis;

        var boundingSphereCenter = new Cartographic.Cartesian3();
        boundingSphereCenter.x = view.getFloat64(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
        boundingSphereCenter.y = view.getFloat64(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
        boundingSphereCenter.z = view.getFloat64(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
        var radius = view.getFloat64(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Float64Array.BYTES_PER_ELEMENT;
        pageLOD.boundingSphere = new BoundingSphere.BoundingSphere(boundingSphereCenter, radius);

        var typedArray = shellBuffer;
        var res = loadString(view, dataViewByteOffset, typedArray, bufferByteOffset);
        var strChildTile = res.string;
        bufferByteOffset = res.bytesOffset;

        strChildTile = strChildTile.replace(/(\.s3mblock)|(\.s3mbz)|(\.s3mb)/gi, '');
        strChildTile = removeUnusedStringTileName(strChildTile);

        pageLOD.childTile = strChildTile;
        pageLOD.geodes = [];
        var nGeodeCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        for (var i = 0; i < nGeodeCount; i++) {
            var res = loadGeodeEntities(shellBuffer, view, bufferByteOffset, dataViewByteOffset, geoMatMap);
            bufferByteOffset = res.byteOffset;
            pageLOD.geodes.push(res.geode);
        }

        return {
            pageLOD: pageLOD,
            bytesOffset: bufferByteOffset
        }
    }

    function loadShellEntites(shellBuffer, view, dataViewByteOffset, geoMatMap) {
        var bufferByteOffset = 0;
        var groupNode = {};
        var pageLods = [];
        var nCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        for (var i = 0; i < nCount; i++) {
            var res = loadPageLODEntities(shellBuffer, view, bufferByteOffset, dataViewByteOffset, geoMatMap);
            bufferByteOffset = res.bytesOffset;
            pageLods.push(res.pageLOD);
        }
        groupNode.pageLods = pageLods;
        return groupNode;
    }

    function loadTextureEntities(supportCompressType, textureDataBuffer, dataView, dataViewByteOffset, texturePackage, transferableObjects) {
        var bufferByteOffset = 0;
        var nTextureCount = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        for (var i = 0; i < nTextureCount; i++) {
            var res = loadString(dataView, dataViewByteOffset, textureDataBuffer, bufferByteOffset);
            var strTextureName = res.string;
            bufferByteOffset = res.bytesOffset;
            var align = bufferByteOffset % 4;
            if (align !== 0) {
                bufferByteOffset += (4 - align);
            }

            var nLevel = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var width = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var height = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var compressType = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var nSize = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var pixelFormat = dataView.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;

            var textureData = textureDataBuffer.subarray(bufferByteOffset, bufferByteOffset + nSize);
            bufferByteOffset += nSize;

            var imageTypedArray = null;
            if (compressType === S3MCompressType.S3MCompressType.enrS3TCDXTN && supportCompressType != 1) {
                S3MCompressType.DXTTextureDecode.decode(imageTypedArray, width, height, textureData, pixelFormat);
                if (pixelFormat > S3MCompressType.S3MPixelFormat.BGR || pixelFormat === S3MCompressType.S3MPixelFormat.LUMINANCE_ALPHA) {
                    imageTypedArray = new Uint8Array(width * height * 4);
                }
                else {
                    imageTypedArray = new Uint16Array(width * height);
                }
                S3MCompressType.DXTTextureDecode.decode(imageTypedArray, width, height, textureData, pixelFormat);
                transferableObjects.push(imageTypedArray.buffer);
                compressType = 0;
            }
            else {
                imageTypedArray = textureData;
            }

            texturePackage[strTextureName] = {
                id: strTextureName,
                width: width,
                height: height,
                compressType: compressType,
                nFormat: pixelFormat,
                imageBuffer: imageTypedArray,
                mipmapLevel: nLevel
            };
        }
    }

    function createTexBatchIdAttribute(vertexPackage, typedArray, texUnitIndex) {
        var vertexAttributes = vertexPackage.vertexAttributes;
        var attrLocation = vertexPackage.attrLocation;
        var len = vertexAttributes.length;
        attrLocation['aTextureBatchId' + texUnitIndex] = len;
        vertexAttributes.push({
            index: len,
            typedArray: typedArray,
            componentsPerAttribute: 1,
            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
            offsetInBytes: 0,
            strideInBytes: 0
        });
    }

    function createTextureBatch(rootInfo, geoPackages, subTexInfos, batchIdsMap) {
        var len = subTexInfos.length;
        for(var i = 0;i < len;i++){
            var subInfo = subTexInfos[i];
            var subName = subInfo.subName.split('_')[0];
            var subVertexOffsetArr = subInfo.subVertexOffsetArr;
            for(var j = 0;j < subVertexOffsetArr.length;j++){
                var subVertexOffsetInfo = subVertexOffsetArr[j];
                var geoName = subVertexOffsetInfo.geoName;
                var offset = subVertexOffsetInfo.offset;
                var count = subVertexOffsetInfo.count;
                var texUnitIndex = subVertexOffsetInfo.texUnitIndex;
                var vertexPackage = geoPackages[geoName].vertexPackage;
                var verticesCount = vertexPackage.verticesCount;
                var batchIdsObj = batchIdsMap[geoName];
                if(!when.defined(batchIdsObj)){
                    batchIdsObj = batchIdsMap[geoName] = {};
                }

                var batchIds = batchIdsObj[texUnitIndex];
                if(!when.defined(batchIds)){
                    batchIds = batchIdsObj[texUnitIndex] = new Float32Array(verticesCount);
                    arrayFill.arrayFill(batchIds, -1);
                }

                var batchId = when.defined(rootInfo) ? rootInfo[subName] : i;
                arrayFill.arrayFill(batchIds, batchId, offset, offset + count);
            }
        }
    }

    function loadTextureEntitiesForBlock(geoPackages, rootMap, ancestorMap, isRoot, supportCompressType, textureDataBuffer, dataView, dataViewByteOffset, texturePackage, transferableObjects) {
        var bufferByteOffset = dataViewByteOffset;
        var nTextureCount = dataView.getUint32(bufferByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        var batchIdsMap = {};
        for (var i = 0; i < nTextureCount; i++) {
            var len = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var curTextureName = S3MCompressType.getStringFromTypedArray(textureDataBuffer, bufferByteOffset - dataViewByteOffset, len);
            bufferByteOffset += len;

            var align = bufferByteOffset % 4;
            if (align !== 0) {
                bufferByteOffset += (4 - align);
            }

            var nLevel = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;

            var isSaveData = dataView.getUint8(bufferByteOffset, true);
            bufferByteOffset += Uint8Array.BYTES_PER_ELEMENT;

            var width = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;

            var height = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;

            var compressType = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;

            var size = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;

            var format = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var textureData;
            if(isRoot){
                var offset = bufferByteOffset - dataViewByteOffset;
                textureData = textureDataBuffer.subarray(offset, offset + size);
                bufferByteOffset += size;
            }

            var childTexCount = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var childrenTexNames = [];
            for(var j = 0;j < childTexCount;j++){
                len = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var childTexName = S3MCompressType.getStringFromTypedArray(textureDataBuffer, bufferByteOffset - dataViewByteOffset, len);
                bufferByteOffset += len;
                childrenTexNames.push(childTexName);
                ancestorMap[childTexName] = curTextureName;
            }

            var requestNameCount = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var requestNames = [];
            for(j = 0;j < requestNameCount;j++){
                len = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var reqName = S3MCompressType.getStringFromTypedArray(textureDataBuffer, bufferByteOffset - dataViewByteOffset, len);
                bufferByteOffset += len;
                requestNames.push(reqName);
            }

            var subTexCount = dataView.getUint32(bufferByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var subTexInfos = [];
            var rootInfo = undefined;
            var rootName = curTextureName;
            if(isRoot){
                rootInfo = rootMap[curTextureName] = {};
            }
            else{
                var parent = ancestorMap[curTextureName];
                rootName = parent;
                while(when.defined(parent)){
                    rootName = parent;
                    parent = ancestorMap[parent];
                }

                if(when.defined(rootName)){
                    rootInfo = rootMap[rootName];
                }

            }
            var decream = 0;
            for(j = 0;j < subTexCount;j++){
                len = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var subName = S3MCompressType.getStringFromTypedArray(textureDataBuffer, bufferByteOffset - dataViewByteOffset, len);
                bufferByteOffset += len;
                if(isRoot){
                    var firstName = subName.split('_')[0];
                    if(!when.defined(rootInfo[firstName])){
                        rootInfo[firstName] = j - decream;
                    }
                    else{
                        decream++;
                    }
                }

                var offsetX = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var offsetY = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var subWidth = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var subHeight = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var geoCount = dataView.getUint32(bufferByteOffset, true);
                bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                var subVertexOffsetArr = [];
                for(var k = 0;k < geoCount;k++){
                    len = dataView.getUint32(bufferByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var geoName = S3MCompressType.getStringFromTypedArray(textureDataBuffer, bufferByteOffset - dataViewByteOffset, len);
                    bufferByteOffset += len;
                    var vertexOffset = dataView.getUint32(bufferByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var vertexCount = dataView.getUint32(bufferByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var texUnitIndex = dataView.getUint32(bufferByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    subVertexOffsetArr.push({
                        geoName : geoName,
                        offset : vertexOffset,
                        count : vertexCount,
                        texUnitIndex : texUnitIndex
                    });
                }

                subTexInfos.push({
                    subName : subName,
                    offsetX : offsetX,
                    offsetY : offsetY,
                    width : subWidth,
                    height : subHeight,
                    subVertexOffsetArr : subVertexOffsetArr
                });
            }

            createTextureBatch(rootInfo, geoPackages, subTexInfos, batchIdsMap);

            if(when.defined(textureData) && compressType === S3MCompressType.S3MPixelFormat.CRN_DXT5 && crunchInitialized){
                textureData = convertCRNToDXT({data : textureData}, transferableObjects).bufferView;
            }

            texturePackage[curTextureName] = {
                id: curTextureName,
                rootTextureName : rootName,
                width : width,
                height : height,
                compressType : compressType,
                size : size,
                format : format,
                textureData : textureData,
                subTexInfos : subTexInfos,
                requestNames : requestNames
            };
        }

        for(var geoName in batchIdsMap){
            if(batchIdsMap.hasOwnProperty(geoName)){
                var vertexPackage = geoPackages[geoName].vertexPackage;
                var obj = batchIdsMap[geoName];
                for(var texUnitIndex in obj){
                    if(obj.hasOwnProperty(texUnitIndex)){
                        var batchIds = obj[texUnitIndex];
                        createTexBatchIdAttribute(vertexPackage, batchIds, texUnitIndex);
                    }
                }
            }
        }
    }

    function createBatchIdAttribute(vertexPackage, typedArray, instanceDivisor) {
        var vertexAttributes = vertexPackage.vertexAttributes;
        var attrLocation = vertexPackage.attrLocation;
        var len = vertexAttributes.length;
        var attrName = instanceDivisor === 1 ? 'instanceId' : 'batchId';
        attrLocation[attrName] = len;
        vertexAttributes.push({
            index: len,
            typedArray: typedArray,
            componentsPerAttribute: 1,
            componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
            offsetInBytes: 0,
            strideInBytes: 0,
            instanceDivisor: instanceDivisor
        });
    }

    var LEFT_16 = 65536;
    function loadSelectionInfo(selectionInfoBuffer, view, dataViewByteOffset, geoPackage, version) {
        var bufferByteOffset = 0;
        var typedArray = selectionInfoBuffer;
        var nGeometryCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
        for (var i = 0; i < nGeometryCount; i++) {
            // S3MB头名字长度
            var result = loadString(view, dataViewByteOffset, typedArray, bufferByteOffset);
            var strGeometryName = result.string;
            bufferByteOffset = result.bytesOffset;
            var nSelectInfoCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
            var pickInfo = {};
            geoPackage[strGeometryName].pickInfo = pickInfo;
            // 非实例化的选择信息
            var bInstanced = geoPackage[strGeometryName].vertexPackage.instanceIndex;
            if (bInstanced == -1) {
                var batchIds = new Float32Array(geoPackage[strGeometryName].vertexPackage.verticesCount);
                for (var j = 0; j < nSelectInfoCount; j++) {
                    var nDictID = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var nSize = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var vertexCount = 0, vertexColorOffset = 0;
                    pickInfo[nDictID] = {
                        batchId: j
                    };
                    for (var k = 0; k < nSize; k++) {
                        vertexColorOffset = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                        vertexCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                        arrayFill.arrayFill(batchIds, j, vertexColorOffset, vertexColorOffset + vertexCount);
                    }

                    pickInfo[nDictID].vertexColorOffset = vertexColorOffset;
                    pickInfo[nDictID].vertexCount = vertexCount;
                }

                createBatchIdAttribute(geoPackage[strGeometryName].vertexPackage, batchIds, undefined);
            }
            else {
                var instanceCount = geoPackage[strGeometryName].vertexPackage.instanceCount;
                var instanceArray = geoPackage[strGeometryName].vertexPackage.instanceBuffer;
                var instanceMode = geoPackage[strGeometryName].vertexPackage.instanceMode;
                var instanceIds = new Float32Array(instanceCount);
                var selectionId = [];
                for (var j = 0; j < nSelectInfoCount; j++) {
                    var nDictID = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                    selectionId.push(nDictID);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    var nSize = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                    bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                    for (var k = 0; k < nSize; k++) {
                        var instanceId = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                        bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                        if (version === 3) {
                            var vertexCount = view.getUint32(bufferByteOffset + dataViewByteOffset, true);
                            bufferByteOffset += Uint32Array.BYTES_PER_ELEMENT;
                        }
                    }
                }
                var beginOffset = instanceMode === 17 ? 16 : 28;
                beginOffset *= Float32Array.BYTES_PER_ELEMENT;
                for (j = 0; j < instanceCount; j++) {
                    instanceIds[j] = j;
                    var offset = j * instanceMode * Float32Array.BYTES_PER_ELEMENT + beginOffset;
                    Color.Color.unpack(instanceArray, offset, colorScratch);
                    var pickId = version === 2 ? selectionId[j] : colorScratch.red + colorScratch.green * 256 + colorScratch.blue * LEFT_16;
                    if (pickInfo[pickId] === undefined) {
                        pickInfo[pickId] = {
                            vertexColorCount: 1,
                            instanceIds: [],
                            vertexColorOffset: j
                        };
                    }

                    pickInfo[pickId].instanceIds.push(j);
                }

                createBatchIdAttribute(geoPackage[strGeometryName].vertexPackage, instanceIds, 1);
            }
        }
    }

    function OGDCIS0(x) {
        return (((x) < 1e-10) && ((x) > -1e-10));
    }

    function unzipWithwasm(datazip, unzipSize) {
        var unzipsize = unzipSize || datazip.length * 4;//unzipSize;//
        var offset = unzip.unzip._malloc(Uint8Array.BYTES_PER_ELEMENT * unzipsize); //开辟内存
        var tar = new Uint8Array(unzipsize);
        unzip.unzip.HEAPU8.set(tar, offset / Uint8Array.BYTES_PER_ELEMENT);
        var offset1 = unzip.unzip._malloc(Uint8Array.BYTES_PER_ELEMENT * datazip.length);
        unzip.unzip.HEAPU8.set(datazip, offset1 / Uint8Array.BYTES_PER_ELEMENT);
        
        var resultLen;
        while ((resultLen = unzipwasm(offset, unzipsize, offset1, datazip.length)) == 0) {
            freec(offset); //释放内存
            unzipsize *= 4;
            offset = unzip.unzip._malloc(Uint8Array.BYTES_PER_ELEMENT * unzipsize);
            tar = new Uint8Array(unzipsize);
            unzip.unzip.HEAPU8.set(tar, offset / Uint8Array.BYTES_PER_ELEMENT);
        }
        var res = new Uint8Array(unzip.unzip.HEAPU8.buffer, offset, resultLen);
        datazip = null;
        tar = null;
        var buffer = new Uint8Array(res).buffer;
        freec(offset);
        freec(offset1);
        return buffer;
    }

    function parseBuffer(oriBuffer, totalByteLength, bytesOffset, rootMap, ancestorMap, isRoot, childGroup, transferableObjects) {
        var supportCompressType = 1;
        var fileType = 1;
        var view = new DataView(oriBuffer);
        var typedArray = new Uint8Array(oriBuffer);
        var len = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        var name = S3MCompressType.getStringFromTypedArray(typedArray, bytesOffset, len);
        name = name.replace(/(\.s3mblock)|(\.s3mbz)|(\.s3mb)/gi, '');
        bytesOffset += len;

        var pagelodCount = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        for(var i = 0;i < pagelodCount;i++){
            var pageLOD = {};
            var dbDis = view.getFloat32(bytesOffset, true);
            bytesOffset += Float32Array.BYTES_PER_ELEMENT;
            var uRangeMode = view.getUint16(bytesOffset, true);
            bytesOffset += Uint16Array.BYTES_PER_ELEMENT;
            pageLOD.rangeMode = uRangeMode;
            pageLOD.rangeList = dbDis;

            var boundingSphereCenter = {};
            boundingSphereCenter.x = view.getFloat64(bytesOffset, true);
            bytesOffset += Float64Array.BYTES_PER_ELEMENT;
            boundingSphereCenter.y = view.getFloat64(bytesOffset, true);
            bytesOffset += Float64Array.BYTES_PER_ELEMENT;
            boundingSphereCenter.z = view.getFloat64(bytesOffset, true);
            bytesOffset += Float64Array.BYTES_PER_ELEMENT;
            var radius = view.getFloat64(bytesOffset, true);
            bytesOffset += Float64Array.BYTES_PER_ELEMENT;
            pageLOD.boundingSphere = {
                center : boundingSphereCenter,
                radius : radius
            };

            len = view.getUint32(bytesOffset, true);
            bytesOffset += Uint32Array.BYTES_PER_ELEMENT;

            var childTileName = S3MCompressType.getStringFromTypedArray(typedArray, bytesOffset, len);
            bytesOffset += len;

            childTileName = childTileName.replace(/(\.s3mblock)|(\.s3mbz)|(\.s3mb)/gi, '');
            childTileName = removeUnusedStringTileName(childTileName);
            pageLOD.childTile = childTileName;
        }

        var geoPackage = {};

        var version = view.getFloat32(bytesOffset, true);
        bytesOffset += Float32Array.BYTES_PER_ELEMENT;
        var isOldVersion = false;

        var unzipByteSize = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        var byteSize = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        var datazip = new Uint8Array(oriBuffer, bytesOffset, byteSize);

        var oriOffset = bytesOffset + byteSize;
        var buffer = pako_inflate.pako.inflate(datazip).buffer;

        transferableObjects.push(buffer);
        view = new DataView(buffer);
        var typedArray = new Uint8Array(buffer);
        bytesOffset = 0;

        var nOptions = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;

        // load Shell
        var loadStreamResult = loadStream(view, buffer, bytesOffset);
        var shellBuffer = loadStreamResult.buffer;
        bytesOffset = loadStreamResult.byteOffset;
        var groupNode = loadShellEntites(shellBuffer, view, loadStreamResult.dataViewByteOffset);
        var align = bytesOffset % 4;
        if (align !== 0) {
            bytesOffset += (4 - align);
        }

        // load skeleton
        loadStreamResult = loadStream(view, buffer, bytesOffset);
        var skeletonBuffer = loadStreamResult.buffer;
        loadSkeletonEntities(skeletonBuffer, view, loadStreamResult.dataViewByteOffset, geoPackage, isOldVersion);
        bytesOffset = loadStreamResult.byteOffset;

        // load secondColor
        loadStreamResult = loadStream(view, buffer, bytesOffset);
        var secondColorBuffer = loadStreamResult.buffer;
        bytesOffset = loadStreamResult.byteOffset;

        // load textureData
        loadStreamResult = loadStream(view, buffer, bytesOffset);
        var textureDataBuffer = loadStreamResult.buffer;
        var texturePackage = {};
        loadTextureEntitiesForBlock(geoPackage, rootMap, ancestorMap, isRoot, supportCompressType, textureDataBuffer, view, loadStreamResult.dataViewByteOffset, texturePackage, transferableObjects);
        bytesOffset = loadStreamResult.byteOffset;

        var strJsonMaterialsLength = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        var materialBuffer = typedArray.subarray(bytesOffset, bytesOffset + strJsonMaterialsLength);
        var strJsonMaterials = S3MCompressType.getStringFromTypedArray(materialBuffer);
        bytesOffset += strJsonMaterialsLength;
        var matrialObj = JSON.parse(strJsonMaterials);

        var bHasSelectionInfo = (nOptions & S3MBVertexOptions.SVO_HasInstSelInfo) == S3MBVertexOptions.SVO_HasInstSelInfo;
        if (bHasSelectionInfo) {
            loadStreamResult = loadStream(view, buffer, bytesOffset);
            var selectionInfoBuffer = loadStreamResult.buffer;
            loadSelectionInfo(selectionInfoBuffer, view, loadStreamResult.dataViewByteOffset, geoPackage, version);
            bytesOffset = loadStreamResult.byteOffset;
        }


        var pagelodList = groupNode.pageLods;
        var isLeafNode = true;
        for (var i = 0; i < pagelodList.length; i++) {
            var pagelodNode = pagelodList[i];
            isLeafNode = pagelodNode.childTile === '';

            var geodeList = pagelodNode.geodes;
            for (var m = 0; m < geodeList.length; m++) {
                var geodeNode = geodeList[m];
                var skeletonNames = geodeNode.skeletonNames;
                for (var n = 0; n < skeletonNames.length; n++) {
                    var geoName = skeletonNames[n];
                    if (isLeafNode) {
                        var geo = geoPackage[geoName];
                        var vertexPackage = geo.vertexPackage;
                        vertexPackage.boundingSphere = S3MCompressType.S3MVertexPackage.calcBoundingSphereInWorker(fileType, vertexPackage);
                    }
                }
            }
        }

        childGroup[name] = {
            result: true,
            groupNode: groupNode,
            geoPackage: geoPackage,
            matrials: matrialObj,
            texturePackage: texturePackage,
            version: S3MVersion.S3M4,
            rootBatchIdMap : rootMap,
            ancestorMap : ancestorMap
        };

        if(oriOffset < totalByteLength){
            parseBuffer(oriBuffer, totalByteLength, oriOffset, rootMap, ancestorMap, false, childGroup, transferableObjects);
        }
    }

    function combineIndexPackage(geoPackage, texturePackage, materialObj){
        delete geoPackage['ignoreNormal'];
        for(var key in geoPackage){
            if(geoPackage.hasOwnProperty(key)){
                var geo = geoPackage[key];
                var arrIndexPackage = geo.arrIndexPackage;
                if(arrIndexPackage.length < 1){
                    continue ;
                }

                if(arrIndexPackage.length === 1){
                    var uvLocation = geo.vertexPackage.attrLocation['aTexCoord0'];
                    if(uvLocation !== undefined){
                        var attr = geo.vertexPackage.vertexAttributes[uvLocation];
                        var uvValues = new Float32Array(attr.typedArray.buffer, attr.typedArray.byteOffset, attr.typedArray.byteLength / 4);
                        if(attr.componentsPerAttribute === 3 && uvValues[2] < 0){
                            continue ;
                        }
                    }
                }

                var totalLen = 0;
                var result = {};
                var i, j;
                for(i = 0,j = arrIndexPackage.length;i < j;i++){
                    totalLen += arrIndexPackage[i].indicesTypedArray.byteLength;
                    if(i === 0){
                        result.indicesCount = 0;
                        result.indexType = arrIndexPackage[i].indexType;
                        result.primitiveType = arrIndexPackage[i].primitiveType;
                        result.materialCode = arrIndexPackage[i].materialCode;
                    }
                }

                result.indicesCount = totalLen / 2;

                var indices = new Uint8Array(totalLen);
                var offset = 0;
                for(i = 0,j = arrIndexPackage.length;i < j;i++){
                    var indexPackage = arrIndexPackage[i];
                    indices.set(indexPackage.indicesTypedArray, offset, offset + indexPackage.indicesTypedArray.byteLength);
                    offset += indexPackage.indicesTypedArray.byteLength;
                }


                result.indicesTypedArray = indices;
                geo.arrIndexPackage = [result];

                var len = arrIndexPackage.length * 2 * 4;
                var pbrBatchTextureValues = new Float32Array(len);
                var mapPass = {};
                for(i = 0,j = materialObj.material.length;i < j;i++){
                    var material = materialObj.material[i].material;
                    mapPass[material.id] = material;
                }

                for(i = 0,j = arrIndexPackage.length;i < j;i++){
                    var indexPackage = arrIndexPackage[i];
                    var materialCode = indexPackage.materialCode;
                    var material = mapPass[materialCode];
                    if(!material){
                        continue;
                    }
                    var pbrParameter = material.pbrMetallicRoughness;
                    if(!pbrParameter){
                        continue ;
                    }
                    pbrBatchTextureValues[8 * i] = pbrParameter.metallicFactor;
                    pbrBatchTextureValues[8 * i + 1] = pbrParameter.roughnessFactor;
                    pbrBatchTextureValues[8 * i + 2] = material.alphaCutoff;
                    var alphaMode = material.alphaMode === '' ? 0 : 1;
                    var doubleSide = material.cullMode === 'none' ? 0 : 1;
                    pbrBatchTextureValues[8 * i + 3] = doubleSide | (alphaMode << 16);
                    pbrBatchTextureValues[8 * i + 4] = pbrParameter.emissiveFactor.x;
                    pbrBatchTextureValues[8 * i + 5] = pbrParameter.emissiveFactor.y;
                    pbrBatchTextureValues[8 * i + 6] = pbrParameter.emissiveFactor.z;
                    pbrBatchTextureValues[8 * i + 7] = 0;

                    material.pbrIndex = i;
                }

                var pbrName = 'PBRMaterialParam_' + key;
                for(i = 0,j = materialObj.material.length;i < j;i++){
                    var material = materialObj.material[i].material;
                    if(material.id === result.materialCode) {
                        material.textureunitstates.push({
                            textureunitstate : {
                                addressmode : {u: 0, v: 0, w: 0},
                                filteringoption : 0,
                                filtermax : 2,
                                filtermin : 2,
                                id : pbrName,
                                texmodmatrix : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
                                url : ""
                            }
                        });
                        break;
                    }
                }

                var vertexPackage = geo.vertexPackage;
                var attrLocation = vertexPackage.attrLocation['aTexCoord1'];
                var uvs;
                if(attrLocation !== undefined){
                    var attr = vertexPackage.vertexAttributes[attrLocation];
                    uvs = new Float32Array(vertexPackage.verticesCount * 2);
                    attr.typedArray = uvs;
                }
                else {
                    uvs = new Float32Array(vertexPackage.verticesCount * 2);
                    attrLocation = vertexPackage.vertexAttributes.length;
                    vertexPackage.attrLocation['aTexCoord1'] = attrLocation;
                    vertexPackage.vertexAttributes.push({
                        index : attrLocation,
                        typedArray : uvs,
                        componentsPerAttribute : 2,
                        componentDatatype : 5126,
                        offsetInBytes : 0,
                        strideInBytes : 8,
                        normalize: false
                    });
                }

                var aColors;
                attrLocation = vertexPackage.attrLocation['aColor'];
                if(attrLocation !== undefined) {
                    var attr = vertexPackage.vertexAttributes[attrLocation];
                    aColors = attr.typedArray;
                }

                for(i = 0,j = arrIndexPackage.length;i < j;i++){
                    var indexPackage = arrIndexPackage[i];
                    var materialCode = indexPackage.materialCode;
                    var material = mapPass[materialCode];
                    if(!material || !material.pbrMetallicRoughness){
                        continue ;
                    }

                    var baseColor = material.pbrMetallicRoughness.baseColor;
                    var isResetColor = aColors !== undefined;
                    var u = material.pbrIndex;

                    var indices = indexPackage.indicesTypedArray;
                    indices = new Uint16Array(indices.buffer, indices.byteOffset, indices.byteLength / 2);
                    for(var m = 0,n = indices.length;m < n;m++){
                        var index = indices[m];
                        uvs[index * 2] = u;

                        if(isResetColor){
                            aColors[index * 4] = baseColor.x * 255;
                            aColors[index * 4 + 1] = baseColor.y * 255;
                            aColors[index * 4 + 2] = baseColor.z * 255;
                            aColors[index * 4 + 3] = baseColor.w * 255;
                        }

                    }
                }

                texturePackage[pbrName] = {
                    id : pbrName,
                    width : arrIndexPackage.length * 2,
                    height : 1,
                    compressType: 0,
                    nFormat: 25,
                    imageBuffer : pbrBatchTextureValues,
                    mipmapLevel : 0
                };

            }
        }
    }

    function parseS3MB(parameters, transferableObjects) {
        var buffer = parameters.buffer;
        var bZip = parameters.isS3MZ;
        var fileType = parameters.fileType;
        var supportCompressType = parameters.supportCompressType;
        var bVolume = parameters.bVolume;//是否是体渲染数据;
        var isS3MBlock = parameters.isS3MBlock;
        var modelMatrix = parameters.modelMatrix;
        var materialType = parameters.materialType;
        var bound3D = null;
        var volBounds = null;
        var volImageBuffer = null;
        if(bVolume){
            if(parameters.volbuffer.byteLength < 8){
                bVolume = false;
            }
        }
        if(bVolume){
            var volData = parameters.volbuffer;
            var dataZip = new Uint8Array(volData,8);
            var volumeBuffer = pako_inflate.pako.inflate(dataZip).buffer;
            var volVersion = new Float64Array(volumeBuffer,0,1);
            var volFormat = new Uint32Array(volumeBuffer,48,1);

            if(volVersion[0] === 0.0 || volFormat[0] === 3200 || volFormat[0] === 3201){
                var nHeaderOffset = 0;
                if(volVersion[0] == 0.0){
                    nHeaderOffset = 8;
                }

                transferableObjects.push(volumeBuffer);
                var boundsArray = new Float64Array(volumeBuffer, nHeaderOffset, 6);
                var left = boundsArray[0];
                var top = boundsArray[1];
                var right = boundsArray[2];
                var bottom = boundsArray[3];
                var minHeight = boundsArray[4] < boundsArray[5] ? boundsArray[4] : boundsArray[5];
                var maxHeight = boundsArray[4] > boundsArray[5] ? boundsArray[4] : boundsArray[5];
                bound3D = new Bound3D(left, bottom, right, top, minHeight, maxHeight);

                volBounds = {
                    left: left,
                    top: top,
                    right: right,
                    bottom: bottom,
                    minHeight: minHeight,
                    maxHeight: maxHeight,
                    width: bound3D.width,
                    length: bound3D.length,
                    height: bound3D.height
                };

                // 中间空出来
                var infoVolume = new Uint32Array(volumeBuffer, 48 + nHeaderOffset, 7);
                var nFormat = infoVolume[0];
                var nSideBlockCount = infoVolume[1];
                var nBlockLength = infoVolume[2];
                var nLength = infoVolume[3];
                var nWidth = infoVolume[4];
                var nHeight = infoVolume[5];
                var nDepth = infoVolume[6];
                var nCount = nLength * nLength * 4;
                var image = new Uint8Array(volumeBuffer, 76 + nHeaderOffset, nCount);
                volImageBuffer = {
                    nFormat: nFormat,
                    nSideBlockCount: nSideBlockCount,
                    nBlockLength: nBlockLength,
                    nLength: nLength,
                    nWidth: nWidth,
                    nHeight: nHeight,
                    nDepth: nDepth,
                    imageArray: image
                };
            }
        }
        var bytesOffset = 0;
        var geoPackage = {};
        geoPackage.ignoreNormal = parameters.ignoreNormal;
        var rootMap = parameters.rootBatchIdMap || {};
        var ancesotrMap = parameters.ancestorMap || {};
        var childGroup = {};

        var view = new DataView(buffer);
        var version = view.getFloat32(bytesOffset, true);
        bytesOffset += Float32Array.BYTES_PER_ELEMENT;
        if(version > 2.2){
            return {
                result : false
            };
        }
        if(isS3MBlock){
            var count = view.getUint32(bytesOffset, true);
            bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            var totalByteLength = buffer.byteLength;
            parseBuffer(buffer, totalByteLength, bytesOffset, rootMap, ancesotrMap, parameters.isRoot, childGroup, transferableObjects);
            return childGroup;
        }
        var isOldVersion = false;
        var unzipSize;
        if (version >= 2) {
            unzipSize = view.getUint32(bytesOffset, true);
            bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        }
        if (OGDCIS0(version - 1) || OGDCIS0(version - 2) || (version > 2.09  && version < 2.11)) {
            //总字节大小
            var byteSize = view.getUint32(bytesOffset, true);
            bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            var datazip = new Uint8Array(buffer, bytesOffset, byteSize);

            if (unzipwasmReady === true) {
                buffer = unzipWithwasm(datazip, unzipSize);
            } else {
                buffer = pako_inflate.pako.inflate(datazip).buffer;
            }

            transferableObjects.push(buffer);
            view = new DataView(buffer);
            bytesOffset = 0;
        }
        // 不zip压缩的解析性能，测试用
        else if (version > 1.199 && version < 1.201) {
            var byteSize = view.getUint32(bytesOffset, true);
            bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
            transferableObjects.push(buffer);
        }
        else {
            //老版本的s3mb缓存,解析方式跟UGC保持一致
            isOldVersion = true;
            bytesOffset = 0;
            var byteSize = view.getInt32(bytesOffset, true);
            bytesOffset += Int32Array.BYTES_PER_ELEMENT;
            bytesOffset += Uint8Array.BYTES_PER_ELEMENT * byteSize;

            if (bZip) {
                var zipSize = view.getUint32(bytesOffset, true);
                bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
                var dataZip = new Uint8Array(buffer, bytesOffset);
                buffer = pako_inflate.pako.inflate(dataZip).buffer;
                transferableObjects.push(buffer);
                view = new DataView(buffer);
                bytesOffset = 0;
            }
        }

        var nOptions = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;

        // load Shell
        var loadStreamResult = loadStream(view, buffer, bytesOffset);
        var shellBuffer = loadStreamResult.buffer;
        bytesOffset = loadStreamResult.byteOffset;
        var geoMatMap = {};
        var groupNode = loadShellEntites(shellBuffer, view, loadStreamResult.dataViewByteOffset, geoMatMap);
        var align = bytesOffset % 4;
        if (align !== 0) {
            bytesOffset += (4 - align);
        }

        // load skeleton
        loadStreamResult = loadStream(view, buffer, bytesOffset);
        var skeletonBuffer = loadStreamResult.buffer;
        var hasOBB = version > 2.09 ? true:false;
        loadSkeletonEntities(skeletonBuffer, view, loadStreamResult.dataViewByteOffset, geoPackage, isOldVersion, transferableObjects, hasOBB, version, modelMatrix, geoMatMap);
        bytesOffset = loadStreamResult.byteOffset;

        if(hasOBB){
            //计算OBB
            for(var i=0; i<groupNode.pageLods.length; i++){
                var pageLod = groupNode.pageLods[i];
                var geodes = pageLod.geodes;

                for(var j=0; j<geodes.length; j++){
                    var skeletonNames = geodes[j].skeletonNames;

                    for(var k = 0; k<skeletonNames.length; k++){
                        var skeletonName = skeletonNames[k];

                        if(when.defined(geoPackage[skeletonName].max)){

                            if(!when.defined(pageLod.max)){
                                pageLod.max = geoPackage[skeletonName].max;
                                pageLod.min = geoPackage[skeletonName].min;
                            }
                            else {
                                pageLod.max.x = Math.max(geoPackage[skeletonName].max.x, pageLod.max.x);
                                pageLod.max.y = Math.max(geoPackage[skeletonName].max.y, pageLod.max.y);
                                pageLod.max.z = Math.max(geoPackage[skeletonName].max.z, pageLod.max.z);

                                pageLod.min.x = Math.min(geoPackage[skeletonName].min.x, pageLod.min.x);
                                pageLod.min.y = Math.min(geoPackage[skeletonName].min.y, pageLod.min.y);
                                pageLod.min.z = Math.min(geoPackage[skeletonName].min.z, pageLod.min.z);
                            }

                        }

                    }
                }
            }
        }

        // load secondColor
        loadStreamResult = loadStream(view, buffer, bytesOffset);
        var secondColorBuffer = loadStreamResult.buffer;
        bytesOffset = loadStreamResult.byteOffset;

        // load textureData
        loadStreamResult = loadStream(view, buffer, bytesOffset);
        var textureDataBuffer = loadStreamResult.buffer;
        var texturePackage = {};
        loadTextureEntities(supportCompressType, textureDataBuffer, view, loadStreamResult.dataViewByteOffset, texturePackage, transferableObjects);
        bytesOffset = loadStreamResult.byteOffset;

        var strJsonMaterialsLength = view.getUint32(bytesOffset, true);
        bytesOffset += Uint32Array.BYTES_PER_ELEMENT;
        var typedArray = new Uint8Array(buffer);
        var materialBuffer = typedArray.subarray(bytesOffset, bytesOffset + strJsonMaterialsLength);
        var strJsonMaterials = S3MCompressType.getStringFromTypedArray(materialBuffer);
        bytesOffset += strJsonMaterialsLength;
        strJsonMaterials = strJsonMaterials.replace(/\n\0/, "");// 兼容大疆倾斜数据
        var matrialObj = JSON.parse(strJsonMaterials);

        var bHasSelectionInfo = (nOptions & S3MBVertexOptions.SVO_HasInstSelInfo) == S3MBVertexOptions.SVO_HasInstSelInfo;
        if (bHasSelectionInfo) {
            loadStreamResult = loadStream(view, buffer, bytesOffset);
            var selectionInfoBuffer = loadStreamResult.buffer;
            loadSelectionInfo(selectionInfoBuffer, view, loadStreamResult.dataViewByteOffset, geoPackage, version);
        }


        var pagelodList = groupNode.pageLods;
        var isLeafNode = true;
        for (var i = 0; i < pagelodList.length; i++) {
            var pagelodNode = pagelodList[i];
            isLeafNode = pagelodNode.childTile === '';

            var geodeList = pagelodNode.geodes;
            for (var m = 0; m < geodeList.length; m++) {
                var geodeNode = geodeList[m];
                var skeletonNames = geodeNode.skeletonNames;
                for (var n = 0; n < skeletonNames.length; n++) {
                    var geoName = skeletonNames[n];
                    if (isLeafNode) {
                        var geo = geoPackage[geoName];
                        var vertexPackage = geo.vertexPackage;
                        vertexPackage.boundingSphere = S3MCompressType.S3MVertexPackage.calcBoundingSphereInWorker(fileType, vertexPackage);
                    }
                }
            }
        }

        if(materialType === 'BatchPBR'){
            combineIndexPackage(geoPackage, texturePackage, matrialObj);
        }

        return {
            result: true,
            groupNode: groupNode,
            geoPackage: geoPackage,
            matrials: matrialObj,
            texturePackage: texturePackage,
            version: S3MVersion.S3M4,
            volImageBuffer:volImageBuffer,
            volBounds:volBounds
        };
    }

    function initWorker() {
        if(when.defined(crunch) && when.defined(dracoLib)){
            crunch.onRuntimeInitialized = function () {
                crunchInitialized = true;
            };

            self.onmessage = createTaskProcessorWorker(parseS3MB);
            self.postMessage(true);
        }
    }

    function S3MBTilesParser(event) {
        if( typeof WebAssembly === 'undefined')
        {
            self.onmessage = createTaskProcessorWorker(parseS3MB);
            self.postMessage(true);
            
            return ;
        }
        var data = event.data;

        // Expect the first message to be to load a web assembly module
        var wasmConfig = data.webAssemblyConfig;
        if (when.defined(wasmConfig)) {
            if (FeatureDetection.FeatureDetection.isInternetExplorer()) {
                return require([buildModuleUrl.buildModuleUrl('ThirdParty/Workers/ie-webworker-promise-polyfill.js')], function (e) {
                    self.Promise = e;
                    if(wasmConfig.modulePath.indexOf('crunch') !== -1){
                        return require([wasmConfig.modulePath], function (crnModule) {
                            if (when.defined(wasmConfig.wasmBinaryFile)) {
                                if (!when.defined(crnModule)) {
                                    crnModule = self.Module;
                                }
                                crunch = crnModule;
                                initWorker();
                            } else {
                                crunch = crnModule;
                                initWorker();
                            }
                        });
                    }

                    return require([wasmConfig.modulePath], function (dracoModule) {
                        if (when.defined(wasmConfig.wasmBinaryFile)) {
                            if (!when.defined(dracoModule)) {
                                dracoModule = self.DracoDecoderModule;
                            }

                            dracoModule(wasmConfig).then(function (compiledModule) {
                                dracoLib = compiledModule;
                                initWorker();
                            });
                        } else {
                            dracoLib = dracoModule();
                            initWorker();
                        }
                    });
                });
            }
            // Require and compile WebAssembly module, or use fallback if not supported
            if(wasmConfig.modulePath.indexOf('crunch') !== -1){
                return require([wasmConfig.modulePath], function (crnModule) {
                    if (when.defined(wasmConfig.wasmBinaryFile)) {
                        if (!when.defined(crnModule)) {
                            crnModule = self.Module;
                        }
                        crunch = crnModule;
                        initWorker();
                    } else {
                        crunch = crnModule;
                        initWorker();
                    }
                });
            }

            return require([wasmConfig.modulePath], function (dracoModule) {
                if (when.defined(wasmConfig.wasmBinaryFile)) {
                    if (!when.defined(dracoModule)) {
                        dracoModule = self.DracoDecoderModule;
                    }

                    dracoModule(wasmConfig).then(function (compiledModule) {
                        dracoLib = compiledModule;
                        initWorker();
                    });
                } else {
                    dracoLib = dracoModule();
                    initWorker();
                }
            });
        }
    }
    //export default createTaskProcessorWorker(S3MBTilesParser);

    return S3MBTilesParser;

});
