有问题?可以Cesium论坛上讨论量化quanyized-mesh规范。

quanyized-mesh-1.0格式的地形瓦块是根据瓦块地图服务(TMS布局和全大地轮廓的简单多分辨率四叉树金字塔高度图。所有瓦块均具有扩展名.terrain。因此,如果瓦块集的瓦块URL为:

http://assets.agi.com/stk-terrain/world/tiles

然后在以下URL处找到金字塔的两个根文件:

在以下URL可以找到下一级的八个磁贴:

请求瓦块时,请确保在请求中包含以下HTTP标头:

Accept: application/vnd.quantized-mesh,application/octet-stream;q=0.9

否则,某些服务器可能会返回与此处所述不同的瓦块表示。

每个瓦块都是经过特殊编码的三角形网格,其中在瓦块边缘顶点和相邻重叠。换句话说,在根部,西瓦块中的最东顶点与东瓦块中的最西顶点具有相同的经度。

地形砖用拉链拉成。提取后,瓦块就是低端字节的二进制数据。文件的第一部分是具有以下格式的标头。双精度数是IEEE 754 64位浮点数,浮点数是IEEE 754 32位浮点数。

struct QuantizedMeshHeader

{

    // The center of the tile in Earth-centered Fixed coordinates.

    double CenterX;

    double CenterY;

    double CenterZ;

 

    // The minimum and maximum heights in the area covered by this tile.

    // The minimum may be lower and the maximum may be higher than

    // the height of any vertex in this tile in the case that the min/max vertex

    // was removed during mesh simplification, but these are the appropriate

    // values to use for analysis or visualization.

    float MinimumHeight;

    float MaximumHeight;

 

    // The tile’s bounding sphere.  The X,Y,Z coordinates are again expressed

    // in Earth-centered Fixed coordinates, and the radius is in meters.

    double BoundingSphereCenterX;

    double BoundingSphereCenterY;

    double BoundingSphereCenterZ;

    double BoundingSphereRadius;

 

    // The horizon occlusion point, expressed in the ellipsoid-scaled Earth-centered Fixed frame.

    // If this point is below the horizon, the entire tile is below the horizon.

    // See http://cesiumjs.org/2013/04/25/Horizon-culling/ for more information.

    double HorizonOcclusionPointX;

    double HorizonOcclusionPointY;

    double HorizonOcclusionPointZ;

};

头之后紧跟的是顶点数据。unsigned int是32位无符号整数,而unsigned short是16位无符号整数。

struct VertexData

{

    unsigned int vertexCount;

    unsigned short u[vertexCount];

    unsigned short v[vertexCount];

    unsigned short height[vertexCount];

};

vertexCount字段指示后面三个数组的大小。这三个数组包含为了使整数变小Zig-zag编码(与前一个值的插值)后的值,以使小整数(无论其符号如何)都使用少量存储单位。解码值很简单:

var u = 0;var v = 0;var height = 0;

function zigZagDecode(value) {

    return (value >> 1) ^ (-(value & 1));

}

for (i = 0; i < vertexCount; ++i) {

    u += zigZagDecode(uBuffer[i]);

    v += zigZagDecode(vBuffer[i]);

    height += zigZagDecode(heightBuffer[i]);

 

    uBuffer[i] = u;

    vBuffer[i] = v;

    heightBuffer[i] = height;

}

这是编码伪代码:

int u = 0;

int v = 0;

int h = 0;

int prev_u = 0;

int prev_v = 0;

int prev_h = 0;

 

for (int idx = 0; idx < _vertexCount; idx++)

{

    u = quantize_coordinate(_vertex[idx].X, minLong, maxLong);

    v = quantize_coordinate(_vertex[idx].Y, minLat, maxLat);

    h = quantize_coordinate(_vertex[idx].Z, _minimumHeight, _maximumHeight);

 

    checkInvalid(u >= 0 && u <= QUANTIZED_COORDINATE_SIZE);

    checkInvalid(v >= 0 && v <= QUANTIZED_COORDINATE_SIZE);

    checkInvalid(h >= 0 && h <= QUANTIZED_COORDINATE_SIZE);

 

    checkInvalid(u - prev_u >= -32768 && u - prev_u <= 32767);

    checkInvalid(v - prev_v >= -32768 && v - prev_v <= 32767);

    checkInvalid(h - prev_h >= -32768 && h - prev_h <= 32767);

 

    us[idx] = zig_zag_encode((short)(u - prev_u));

    vs[idx] = zig_zag_encode((short)(v - prev_v));

    hs[idx] = zig_zag_encode((short)(h - prev_h));

 

    prev_u = u;

    prev_v = v;

    prev_h = h;

 

    points.Add(new Cartesian3(us[idx], vs[idx], hs[idx]));

}

 private int quantize_coordinate(double v, double min, double max)

{

    checkInvalid(v >= min && v <= max);

    double delta = max - min;

    if (delta == 0)

    {

        return 0;

    }

    checkInvalid(delta > 0);

    double offset_to_min = (v - min);

    checkInvalid(offset_to_min >= 0 && offset_to_min <= delta);

    return scale_coordinate(offset_to_min / delta);

}

 

private ushort zig_zag_encode(short i)

{

    return (ushort)((i >> 15) ^ (i << 1));

}

解码后,每个数组中值的含义如下:

方面

含义

u

瓦片中顶点的水平坐标。当u值为0时,顶点位于瓦块的西边缘。值为32767时,顶点在瓦块的东边缘。对于其他值,顶点的经度是瓦块西边和东边的经度之间的线性插值。

v

瓦片中顶点的垂直坐标。当v值为0时,顶点位于瓦块的南部边缘。值为32767时,顶点位于瓦块的北边缘。对于其他值,顶点的纬度是瓦块的南边和北边的纬度之间的线性插值。

height

瓦片中顶点的高度。当height值为0时,顶点的高度等于tile头中指定的tile内的最小高度。值为32767时,顶点的高度等于瓦块内的最大高度。对于其他值,顶点的高度是最小和最大高度之间的线性插值。

紧跟着顶点数据的是索引数据。索引指定顶点如何链接在一起成为三角形。如果瓦块具有超过65536个顶点,则该瓦块使用IndexData32结构编码索引。否则,它将使用IndexData16结构。

为了强制正确的字节对齐,在IndexData之前添加填充以确保IndexData16的2字节对齐和IndexData32的4字节对齐。

struct IndexData16

{

    unsigned int triangleCount;

    unsigned short indices[triangleCount * 3];

}

struct IndexData32

{

    unsigned int triangleCount;

    unsigned int indices[triangleCount * 3];

}

索引编码使用来自webgl-loader的高水位标记编码。索引解码如下:

var highest = 0;for (var i = 0; i < indices.length; ++i) {

    var code = indices[i];

    indices[i] = highest - code;

    if (code === 0) {

        ++highest;

    }

}

索引编码代码如下:

 private byte[] CreateIndicesBuffer()

{

    List<int> indices = new List<int>();

    int highest = 0;

 

    for (int i = 0; i < _triangleCount * 3; i++)

    {

        int delta = highest - _triangleIndices[i];

 

        indices.Add(delta);

        if (_triangleIndices[i] == highest)

        {

            highest++;

        }

    }

 

    bool isUint16 = _vertexCount <= 64 * 1024;

    int dataType = isUint16 ? 2 : 4;

 

    byte[] inidicesByteData = new byte[indices.Count * dataType];

    int offset = 0;

    for (int idx = 0; idx < _triangleIndices.Count; idx++)

    {

        if (isUint16)

        {

            BitConverter.GetBytes((UInt16)indices[idx]).CopyTo(inidicesByteData, offset);

            offset += 2;

        }

        else

        {

            BitConverter.GetBytes((UInt32)indices[idx]).CopyTo(inidicesByteData, offset);

            offset += 4;

        }

    }

 

    return inidicesByteData;

}

索引的每个三元组均按逆时针缠绕顺序指定要渲染的一个三角形。在三角形索引之后是另外四个索引列表:

struct EdgeIndices16

{

    unsigned int westVertexCount;

    unsigned short westIndices[westVertexCount];

 

    unsigned int southVertexCount;

    unsigned short southIndices[southVertexCount];

 

    unsigned int eastVertexCount;

    unsigned short eastIndices[eastVertexCount];

 

    unsigned int northVertexCount;

    unsigned short northIndices[northVertexCount];

}

struct EdgeIndices32

{

    unsigned int westVertexCount;

    unsigned int westIndices[westVertexCount];

 

    unsigned int southVertexCount;

    unsigned int southIndices[southVertexCount];

 

    unsigned int eastVertexCount;

    unsigned int eastIndices[eastVertexCount];

 

    unsigned int northVertexCount;

    unsigned int northIndices[northVertexCount];

}

这些索引列表枚举了瓦块边缘上的顶点。知道哪些顶点在边缘上以增加裙边以隐藏相邻细节层之间的裂缝是很有帮助的。

扩展名

可以跟随扩展数据用附加信息来补充quantized-mesh。每个扩展名以ExtensionHeader开头,由唯一的标识符和扩展数据的大小(以字节为单位)组成。unsigned char是一个8位无符号整数。

struct  ExtensionHeader

{

    unsigned  char extensionId;

    unsigned  int extensionLength;

}

定义新扩展名后,将为其分配一个唯一的标识符。如果未为瓦块集定义任何扩展名,则ExtensionHeader将不包含quanitzed-mesh中。多个扩展可以附加到quanitzed-mesh数据上,其中每个扩展的顺序由服务器确定。

客户端可以通过使用-分隔扩展名来请求多个扩展。例如,客户端可以使用以下Accept标头请求顶点法线和水标记:

Accept : 'application/vnd.quantized-mesh;extensions=octvertexnormals-watermask'

可以为量化网格定义以下扩展:

地形照明

名称十进制编码的顶点法线

ID: 1

说明:将每个顶点照明属性添加到quanitzed-mesh。每个顶点法线都使用八进制编码将传统的x,y,z 96位浮点单位向量压缩为x,y 16位表示形式。Coctlle等人在2014年的“独立单位向量的有效表示调查”中描述了'oct'编码:

http://jcgt.org/published/0003/02/01/

数据定义:

struct OctEncodedVertexNormals

{

    unsigned  char xy [vertexCount * 2 ];

}

 

请求:要使量化网格中包含八进制编码的每个顶点法线,客户端必须使用以下HTTP标头请求此扩展名:

Accept : 'application/vnd.quantized-mesh;extensions=octvertexnormals'

注释:此扩展的原始实现要求使用vertexnormals扩展名。该vertexnormals扩展标识符已被弃用和现在实施方式必须通过在请求头扩展参数添加octvertexnormals请求顶点法线,如上所示。

标记

名称:水标记

ID: 2

说明:添加用于渲染水效果的海岸线数据。如果瓦块全部为陆地或全部水,水掩码均1字节,如果瓦块具有陆地和水的混合物则水掩码有256 * 256 * 1 = 65536字节。每个标记值对于陆地为0,对于水为255。标记中的值是从北向南,从西向东定义的;标记中的第一个字节定义了瓦块西北角的水标记值。为了支持海岸线的抗锯齿,也允许使用0到255之间的值。

数据定义:一个字节定义了完全被陆地或水覆盖的地形瓦块。

struct WaterMask

{

    unsigned char mask;

}

包含土地和水的混合的地形瓦块定义了256 * 256的高度值网格。

struct WaterMask

{

    unsigned char mask[256 * 256];

}

请求:要使水标记包含在quanitzed-mesh中,客户端必须使用以下HTTP标头来请求此扩展:

Accept : 'application/vnd.quantized-mesh;extensions=watermask'

元数据

名称:元数据

编号: 4

说明:向每个瓦块添加一个JSON对象,该对象可以存储有关瓦块的额外信息。潜在用途包括存储瓦片中的土地类型(例如森林,沙漠等)或子瓦片的可用性。

数据定义:

struct Metadata

{

    unsigned int jsonLength;

    char json[jsonLength];

}

请求:为使元数据包含在quanitzed-mesh中,客户端必须使用以下HTTP标头来请求此扩展:

Accept : 'application/vnd.quantized-mesh;extensions=metadata'