{
  "lead": "游戏里无限延伸的地图（如《我的世界》）通常不是手工绘制的，而是由「噪声函数」实时计算出来的：给定任意坐标，都能立即算出那里的海拔，而且相邻位置的数值平滑过渡。这个示例用 Rust 手写 Perlin 噪声，把多层不同尺度的噪声叠加（分形布朗运动），再按海拔上色、加上山体阴影，得到一张看起来很自然的地图。",
  "formulas": [
    [
      "height = Σ<sub>i</sub> p<sup>i</sup> · noise(2<sup>i</sup> · x)",
      "fBm：频率每层翻倍，振幅乘以粗糙度 p"
    ]
  ],
  "terms": [
    [
      "Perlin 梯度噪声",
      "把平面划分成单位网格，每个格点随机分配一个梯度方向（由按种子打乱的排列表决定）。对任意一点，计算它到四个角格点的偏移与各自梯度的点积，再平滑插值。因此噪声在整数格点上恰好为 0，在格点之间连续起伏，而且同一个坐标永远得到同一个值。"
    ],
    [
      "平滑曲线 fade",
      "插值权重不是直接用线性的 t，而是 <code>6t⁵ − 15t⁴ + 10t³</code>。它在 0 和 1 处的一阶、二阶导数都为 0，相邻网格拼接处不会出现折痕，这是 Ken Perlin 在 2002 年改进版中的关键修改。"
    ],
    [
      "分形布朗运动（fBm）",
      "单层噪声只有一种尺度的起伏，看起来像光滑的丘陵。把频率翻倍、振幅减半（粗糙度 0.5）的噪声一层层叠加：第 1 层决定大陆轮廓，后面各层依次加上山脉、丘陵、岩石等越来越细的细节。每一层叫一个「八度」。"
    ],
    [
      "着色与山体阴影",
      "海拔低于海平面的画成水，越深颜色越暗；陆地按相对高度依次过渡为沙滩、草地、森林、岩石和雪顶。山体阴影用相邻像素的高度差估算坡度：朝向左上方光源的坡面变亮，背光的坡面变暗，平面地图因此有了立体感。"
    ]
  ],
  "flow": [
    [
      "js",
      "滑块改变或拖拽平移时，JS 用 <code>requestAnimationFrame</code> 合并请求，每帧最多重新生成一次。"
    ],
    [
      "rust",
      "调用 <code>terrain_render(宽, 高, 种子, 缩放, 八度, 粗糙度, 偏移x, 偏移y, 海平面, 阴影)</code>。Rust 为 256,000 个像素逐一计算 fBm 高度，再上色和计算阴影，返回 RGBA 数组。"
    ],
    [
      "js",
      "JS 用 <code>putImageData()</code> 把图像画到画布上。"
    ]
  ],
  "flow_notes": [
    "地图没有边界：拖拽只是改变采样坐标的偏移量，任何位置的地形都是当场算出来的，不需要预先生成或存储。换一个种子，就是一个全新的世界。"
  ],
  "perf": [
    "计算量 = 像素数 × 八度数。参考数据（一台 Linux 测试机，Chromium 152，640×400）：1 个八度约 12ms，6 个八度约 35~47ms，8 个八度约 69ms。",
    "6 个八度时每帧要算约 150 万次噪声，拖拽时大约 20~30 fps。游戏引擎通常把地形切成区块，只计算新露出来的部分，或者交给 GPU 的着色器并行计算。",
    "每个八度的采样坐标都加了一个不同的偏移，避免所有层的「格点为零」恰好对齐在原点附近，形成可见的规则图案。"
  ],
  "src": [
    [
      "crates/graphics/terrain/src/lib.rs",
      ""
    ],
    [
      "www/terrain/index.js",
      ""
    ]
  ]
}
