12template<
typename Scalar>
21 template<
typename PosAccessor>
23 starts_.resize(n + 1);
29 for (
int i = 0; i < n; ++i) {
30 auto [xi, yi] = get_pos(i);
34 cl.
query(xi, yi, [&](
int j) {
37 auto [xj, yj] = get_pos(j);
38 const Scalar dx = xi - xj, dy = yi - yj;
39 if (dx * dx + dy * dy < ext_sq_)
43 starts_[i + 1] =
static_cast<int>(flat_.size());
48 template<
typename PosAccessor>
52 const Scalar half_skin_sq = (skin_ * Scalar(0.5)) * (skin_ * Scalar(0.5));
53 for (
int i = 0; i < n; ++i) {
54 auto [xi, yi] = get_pos(i);
55 const Scalar dx = xi - ref_x_[i];
56 const Scalar dy = yi - ref_y_[i];
57 if (dx * dx + dy * dy > half_skin_sq)
65 return {flat_.data() + starts_[i], flat_.data() + starts_[i + 1]};
68 Scalar
cutoff() const noexcept {
return cutoff_; }
69 Scalar
skin() const noexcept {
return skin_; }
70 Scalar
ext_cutoff() const noexcept {
return cutoff_ + skin_; }
72 return starts_.empty() ? 0 :
static_cast<int>(starts_.size()) - 1;
76 Scalar cutoff_, skin_, ext_sq_;
77 std::vector<int> flat_;
78 std::vector<int> starts_;
79 std::vector<Scalar> ref_x_;
80 std::vector<Scalar> ref_y_;
Cache-coherent 2D cell list for O(1) amortized neighbour queries.
void query(Scalar px, Scalar py, F &&f) const
Call f(j) for candidate particles near (px, py).
Scalar ext_cutoff() const noexcept
void build(PosAccessor &&get_pos, int n, const CellList2D< Scalar > &cl)
Build the neighbour list using a pre-built CellList2D.
Scalar skin() const noexcept
bool needs_rebuild(PosAccessor &&get_pos, int n) const
Return true if a particle moved more than half the skin.
int n_particles() const noexcept
VerletList2D(Scalar cutoff, Scalar skin)
Scalar cutoff() const noexcept
IntRange neighbors(int i) const noexcept
Cached neighbors of particle i.