{
  "lead": "N 体引力模拟：画布上的每个粒子都受到其他所有粒子的万有引力，粒子会自发聚集成团、互相环绕、合并。点击画布可以投放新的粒子团。",
  "formulas": [
    [
      "Δv = G · dt / (r² + ε²)",
      "每对粒子，ε = 20"
    ]
  ],
  "terms": [
    [
      "两两计算引力",
      "对每一对粒子计算引力 <code>F ∝ G / r²</code>，复杂度是 O(n²)。利用牛顿第三定律（作用力与反作用力大小相等、方向相反），每对粒子只算一次，同时更新双方，计算量减半。"
    ],
    [
      "软化因子",
      "距离平方加上一个常数 <code>r² + ε²</code>（ε = 20）。否则两个粒子靠得太近时引力趋于无穷大，粒子会被瞬间弹飞。"
    ],
    [
      "数值积分",
      "采用半隐式欧拉法，先用引力更新速度，再用新速度更新位置，时间步长 <code>dt = 0.016s</code>（约一帧）。引力大于 1500 时每帧拆成 3 个子步，提高稳定性。"
    ],
    [
      "阻尼与边界",
      "每步速度乘以阻尼系数（例如 0.99），模拟能量损耗；粒子飞出边界后从对边回来。"
    ]
  ],
  "flow": [
    [
      "rust",
      "<code>init_particles(300, 900, 600)</code>：Rust 随机生成 300 个粒子，保存为全局状态。"
    ],
    [
      "rust",
      "每帧 JS 调用 <code>step_particles(dt, 引力, 阻尼)</code>，由 Rust 完成全部物理计算。"
    ],
    [
      "js",
      "JS 调用 <code>get_particles()</code>，取回扁平数组 <code>[x1, y1, x2, y2, …]</code>，用径向渐变画出发光粒子，并用半透明覆盖层产生拖尾效果。"
    ],
    [
      "rust",
      "点击画布时调用 <code>add_cluster(x, y, 50, 30)</code>，在点击位置附近加入 50 个粒子。"
    ]
  ],
  "perf": [
    "300 个粒子每帧约 4.5 万对引力计算。粒子数翻倍，计算量变成 4 倍。点击几次后可以在底部看到物理计算的耗时明显上升。",
    "这正是 CPU 方案的上限。可以对比「WebGPU N 体模拟」：同样的算法放到 GPU 上并行，可以跑上万个粒子。"
  ],
  "src": [
    [
      "crates/simulation/particles/src/lib.rs",
      ""
    ],
    [
      "www/particles/index.js",
      ""
    ]
  ]
}
