> **本文回答**：`fill-extrusion` 建筑物 3D 拉伸与动画。

## 一、基础

```js
map.addSource("bldg", {
  type: "geojson",
  data: featureCollectionWithHeight
});

map.addLayer({
  id: "bldg-3d",
  type: "fill-extrusion",
  source: "bldg",
  paint: {
    "fill-extrusion-color":  ["get", "color"],
    "fill-extrusion-height": ["get", "height"],
    "fill-extrusion-base":   ["get", "base"],     // 起始高度
    "fill-extrusion-opacity": 0.9
  }
});
```

需要 `map.getPitch() > 0`（或构造时 `pitch: 60`）才能看到 3D 效果。

## 二、vjmap 封装

```js
new vjmap.FillExtrusion({
  data: { type: "FeatureCollection", features: [...] },
  fillExtrusionColor: ["get", "color"],
  fillExtrusionHeight: ["get", "height"],
  fillExtrusionBase: 0,
  fillExtrusionOpacity: 0.85
}).addTo(map);
```

## 三、Draw 内的 `extrusionHeight`

对 Draw 里的多边形实体，设置 `extrusionHeight` 自动变 3D：

```js
draw.setFeatureProperty(featureId, "extrusionHeight", 100);  // 引擎墨卡托"米"；按 CAD 长度给高度用 map.geoLengthToHeight(cadLen)
// 取消
draw.setFeatureProperty(featureId, "extrusionHeight", undefined);
```

## 四、zoom / pitch 联动

当 zoom 小的时候隐藏 3D（性能）：

```js
map.setLayerZoomRange("bldg-3d", 14, 22);
```

或用表达式：

```js
"fill-extrusion-opacity": [
  "interpolate", ["linear"], ["zoom"],
  13, 0,
  14, 1
]
```

## 五、动画 —— 高度 grow

```js
let t = 0;
function animate() {
  t += 0.02;
  map.setPaintProperty("bldg-3d", "fill-extrusion-height", [
    "*", ["get", "height"], Math.min(t, 1)
  ]);
  if (t < 1) requestAnimationFrame(animate);
}
animate();
```

## 六、光照 light

```js
map.setLight({
  anchor: "viewport",
  color: "#fff8e0",
  intensity: 0.6,
  position: [1.15, 210, 30]       // radial, azimuth, polar
});
```

- `anchor: "map"`：光源随地图旋转。
- `anchor: "viewport"`：光源固定到屏幕。

## 七、阴影效果（高度差分层）

vjmap 的 `vjmapext.fx` 有 shadow / glow 等 GPU 效果（属于 mapext，不在 vjmap core）。详见 mapext 文档。

## 八、点击拾取

```js
map.on("click", "bldg-3d", (e) => {
  const h = e.features[0].properties.height;
  alert(`高度：${h} 米`);
});

map.on("mouseenter", "bldg-3d", () => map.getCanvas().style.cursor = "pointer");
map.on("mouseleave", "bldg-3d", () => map.getCanvas().style.cursor = "");
```

## 九、与 CAD 图结合

CAD 面状图层（如 POLYGON 实体）可以通过 `svrStyleVar` 或 `svc.conditionQueryFeature`（矢量瓦片模式，见 [backend/query-sql](../backend/query-sql.md)）取得高度属性，再做 fill-extrusion。

## 十、常见坑

1. **pitch=0 看不到 3D**：必须设 `pitch: 60` 之类。
2. **顶层 fill 遮住底层**：多个 fill-extrusion 叠加会有深度冲突，统一放同一图层。
3. **height 单位是引擎的墨卡托"米"，不是 CAD 单位**：GeoProjection 把整张 CAD 图映射到整个墨卡托世界，高度值与 CAD 长度的比例取决于图的范围（`高度 = CAD 长度 × 赤道周长 / 图宽`），简单 `/1000` 换算不对。要"高度 = CAD 里的 L"用 `map.geoLengthToHeight(L)`；要"高度 = 当前 zoom 下 N 像素"用 `map.pixelToHeight(N, map.getZoom())`。
4. **移动时卡**：`fill-extrusion-pattern` 用贴图很慢，尽量用纯色。
5. **透明度叠加怪异**：`fill-extrusion-opacity` < 1 时 depth sorting 不完美。
6. **按 `type` 找层会撞上 Draw 内置层**：Draw 控件自带 `gl-draw-fill-extrusion-inactive/-static` 层（`fill-extrusion-opacity` 恒 0.9）。用 `getStyle().layers.find(l => l.type === "fill-extrusion")` 找到的多半是它而不是你的层——`vjmap.FillExtrusion` 生成的层名是 `custom_fillExtrusion_layer_<rand>`、源名 `custom_fillExtrusion_<rand>`，按 `layerId`/`sourceId` 匹配。
7. **`FillExtrusion` 的 options 键是 camelCase**（`fillExtrusionOpacity`），内部 kebab 化进 `paint`；getter/setter 委托 `map.getPaintProperty` / `map.setPaintProperty`（回读值建议 `Number()` 归一后再严格比较）。
