# 架构解析
## 数据层

### StyleLayer (abstract class)

**说明**：根据不同形状（如：`circle`、`line`、`fill-extrusion`），被各种子类继承用的抽象类

**初始化于**：`Style#_load`

**存储位置**：`style._layers`

### Source (interface)

**说明**：对各种地图类型的抽象，用于处理源数据。

**初始化于**：`Style#addSource`

**存储位置**：`sourceCache._source`

大部分mapbox使用中，默认为的`source`为矢量瓦片`VectorTileSource`，该类实现了`Source` ，下文的`Source`均以`VectorTileSource`为例。

`VectorTileSource`是根据 `styleJSON`中的`sources`字段创建的，key为`sourceId`，value为`SourceSpecification`。

### SourceCache (class)

**说明**：数据存储类，用于存储`Source`的数据

**初始化于**：`Style#addSource`

**存储位置**：`style._sourceCaches`的

`SourceCache`和`Source`是一一对应的关系。


# 流程解析
## pattern图片渲染流程
首先使用`map.addImage(id, image)`添加到`ImageManager`。

当瓦片进行加载时(`worker_tile.js`)，会从`ImageManager`读取当前所需图片，实例化一个`ImageAtlas`对象存放图片信息，并放入`Tile`实例。

以`fill-extrusion`为例在渲染时，会执行以下流程：

`painter.render` => `sourceCache.prepare` => `tile.upload` => `tile.imageAtlasTexture = new Texture()` => `gl.texImage2D`

在绘制`fill-extrusion`时，mapbox会先激活WebGL的0号纹理单元，并调用`tile`中的`imageAtlasTexture`实例上的`bind`方法配置纹理参数，然后调用`fillExtrusionPatternUniformValues`向0号纹理单元传递纹理，最后进行绘制。

## fill-extrusion pattern贴图位置解析
以单张贴图为例，即不存在贴图转换。

### 数据流转流程
`new FillExtrusionBucket()` => `new ProgramConfiguration()` => `new CrossFadedConstantBinder()` => `drawFillExtrusion()` => `program.draw()` => `programConfiguration.setUniforms()` => `crossFadedConstantBinder.setUniform()` => `shader: pattern_from`

### pattern_from, pattern_to 赋值流程
```ts
// ImageAtlas.constructor
this.patternPositions[id] = new ImagePosition(bin, src);

// draw_fill_extrusion  draw
const atlas = tile.imageAtlas;
const posTo = atlas.patternPositions[constantPattern.to.toString() /** cat */];
const posFrom = atlas.patternPositions[constantPattern.from.toString()];
if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom);

// programConfiguration.setConstantPatternPositions
binder.setConstantPatternPositions(posTo, posFrom);

// crossFadedConstantBinder.setConstantPatternPositions
this.patternFrom = posFrom.tl.concat(posFrom.br);
this.patternTo = posTo.tl.concat(posTo.br);
```

## 顶点数据传递到着色器的流程

```ts

/**
 * 请求瓦片数据并添加顶点数据到顶点缓冲区
 */

// worker_tile parse
bucket.addFeatures(...);

// fill_extrusion_bucket addFeatures
for (const feature of this.features) {
  const { geometry } = feature;
  this.addFeature(feature, geometry, ...)
}
// fill_extrusion_bucket addFeature
addVertex(this.layoutVertexArray, ...);

/**
 * 开始渲染并将缓冲区分配给 attribute a_pos_normal_ed 变量
 */

// painter render
// sourceCache prepare
// tile upload
bucket.upload()

// fill_extrusion_bucket upload
this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, layoutAttributes);

// context createVertexBuffer
new VertexBuffer(this, array, attributes, dynamicDraw);

// 省略一些步骤...

// draw_fill_extrusion drawExtrusionTiles
program.draw(..., bucket.layoutVertexBuffer, ...)

// program draw
const vao = vaos[layerID] || (vaos[layerID] = new VertexArrayObject());
vao.bind(..., layoutVertexBuffer , ...);

// vertex_array_object bind
this.freshBind(program, layoutVertexBuffer, ...);
// vertex_array_object freshBind
layoutVertexBuffer.setVertexAttribPointers(gl, program, vertexOffset);

// vertex_buffer setVertexAttribPointers
for (let j = 0; j < this.attributes.length; j++) {
    const member = this.attributes[j];
    const attribIndex = program.attributes[member.name];  // a_pos_normal_ed

    if (attribIndex !== undefined) {
        gl.vertexAttribPointer(
            attribIndex,
            member.components,
            gl[AttributeType[member.type]],
            false,
            this.itemSize,
            member.offset + (this.itemSize * (vertexOffset || 0))
        );
    }
}
```
## 挤压几何体高度的生成逻辑

在着色器中，会判断是否有定义`uniform`变量`u_height`，有的话，就直接使用`u_height`作为`height`使用。如果没有，就会先声明三个变量：

```glsl
uniform lowp float u_height_t;
attribute lowp vec2 a_height;
varying lowp float height;
```

之后进行插值计算，变成`height`：

```glsl
height = unpack_mix_vec2(a_height, u_height_t);
```
由于`u_height_t`为`0.0`，以及`a_height`的第二个分量为`0.0`，所以`height`其实就等于`a_height[0]`。

### `a_height`来源

```ts
// html
map.addLayer({
  id: "3d-buildings",
  source: "composite",
  "source-layer": "building",
  type: "fill-extrusion",
  minzoom: 1,
  paint: {
    "fill-extrusion-height": ["get", "height"],
  },
});
// 初始化
// program_configuration ProgramConfiguration constructor
for (const property in layer.paint._values) {
  const value = layer.paint.get(property);
  const names = paintAttributeNames(property, layer.type);
  const expression = value.value;
  const type = value.property.specification.type;
  ...
  const StructArrayLayout = layoutType(property, type, 'source');
  new SourceExpressionBinder(expression, names, type, StructArrayLayout);
}

// program_configuration SourceExpressionBinder constructor
this.paintVertexAttributes = names.map((name) => ({
  name: `a_${name}`,
  type: 'Float32',
  components: type === 'color' ? 2 : 1,
  offset: 0
}));

// 传递数据
// fill_extrusion_bucket addFeature
this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, availableImages, canonical);

// program_configuration ProgramConfigurationSet populatePaintArrays
for (const key in this.programConfigurations) {
  this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, availableImages, canonical, formattedSection);
}

// program_configuration ProgramConfiguration populatePaintArrays
binder.populatePaintArray(newLength, feature, imagePositions, availableImages, canonical, formattedSection);

// program_configuration SourceExpressionBinder populatePaintArray
this._setPaintValue(start, newLength, value);
```



#### `a_height`的数组明明是`StructArrayLayout1f4`，但着色器里面的类型为什么是`vec2`？

向缓冲区分配数据时，如果`size`为1，则第二分量自动设为0。

#### 为什么我把瓦片数据中的横纵坐标和高度变成了顶点数组，在three中使用这个顶点数组，高度比mapbox的低很多？

因为mapbox在计算`gl_Position`时，会乘上一个`u_matrix`，这个矩阵可能会放大z轴。产生该矩阵的代码在`src/geo/transform.js`的`calculateProjMatrix`方法中。

# 需要完成的功能                                                                                                                      
- 暂时关闭`access_token`逻辑  完成状态：✅
- 3D建筑物贴图  完成状态：❌
- 2d飞线封装  完成状态：❌
- 3d飞线封装，参照cutmap样式  完成状态：❌
- 地标型建筑物的模型替换  完成状态：❌
- 漂浮物特效（下雪等）  完成状态：❌