{
  "lead": "游戏里飘动的旗帜、角色的披风，常用的做法是把布看成一张由质点连成的网：每个质点受重力和风力推动自由运动，相邻质点之间有一根「不能拉长」的连接，每一帧把被拉长或压短的连接往回调整几次。这种方法（Jakobsen 2001，Verlet 积分 + 约束松弛）简单又稳定，还能轻松实现剪开和撕破。",
  "formulas": [
    [
      "x<sub>新</sub> = x + (x − x<sub>上一帧</sub>) · 阻尼 + a · Δt²",
      "Verlet 积分：速度隐含在两帧位置之差里，不用单独存"
    ],
    [
      "Δ = (|p<sub>b</sub> − p<sub>a</sub>| − L) / |p<sub>b</sub> − p<sub>a</sub>|，两端各移动 Δ/2",
      "距离约束：把连接的两端沿连线方向拉回到原长 L"
    ]
  ],
  "terms": [
    [
      "Verlet 积分",
      "每个质点只记住当前位置和上一帧的位置，两者之差就是速度。好处是修改位置（比如约束把质点推回来、撞到地面被截住）会自动反映到速度上，不需要额外处理，所以数值上很稳定，不容易「爆炸」。"
    ],
    [
      "约束松弛",
      "一个质点连着四根连接，同时满足所有连接的长度是一个大方程组。这里不去精确求解，而是逐根调整：每根都把两端拉回原长（一端被固定时只移动另一端），整张网重复调整若干遍。遍数越多布越「硬」、越接近不可拉伸；遍数少时布像橡皮一样松软下垂。"
    ],
    [
      "撕裂与剪开",
      "每一步检查每根连接的长度，超过原长的 1.9 倍就删掉它。实测最大风力只能把连接拉到约 1.5 倍，而用手猛拉会超过 2 倍，所以风吹不破、猛拉会破。剪刀直接删除鼠标轨迹附近的连接。约束迭代调到 1~2 次时布太松，在强风里也可能被自己扯破。"
    ],
    [
      "绘制",
      "四条边都完好的小方格填充颜色，颜色由方格面积相对原来的比例决定：被挤压（褶皱）的地方暗，被拉伸的地方亮并偏红，于是不用光照计算也能看出布的起伏。断开后剩下的零散连接画成线，按张力着色。"
    ]
  ],
  "flow": [
    [
      "js",
      "每一帧按真实时间累积，每满 1/60 秒调用一次 <code>cloth_step(dt, 迭代次数, 重力, 风力, 撕裂阈值)</code>，所以物理速度与屏幕刷新率无关。"
    ],
    [
      "rust",
      "Rust 对 1600 个质点做 Verlet 积分，按设定遍数松弛 3118 根连接，删掉拉断的连接，并把质点限制在画布范围内（碰到地面和墙壁有摩擦）。"
    ],
    [
      "js",
      "JS 读取 <code>cloth_positions()</code>、<code>cloth_links()</code>、<code>cloth_stretch()</code> 并绘制。按下鼠标时 <code>cloth_grab</code> 找到最近的质点，拖动时用 <code>cloth_drag</code> 把它固定在指针位置；剪刀模式调用 <code>cloth_cut</code>。"
    ]
  ],
  "perf": [
    "实测（桌面 Chromium）：50×32 个质点、15 遍约束迭代，每步约 0.5ms；读回位置和连接不到 0.03ms。计算量与「连接数 × 迭代次数」成正比。",
    "绘制（约 1500 个填充四边形）比物理计算更耗时，这部分由 Canvas 2D 完成。"
  ],
  "src": [
    [
      "crates/simulation/cloth/src/lib.rs",
      ""
    ],
    [
      "www/cloth/index.js",
      ""
    ]
  ]
}
