48template<
typename Scalar>
67 template<
typename PosAccessor>
69 starts_.resize(n + 1);
75 for (
int i = 0; i < n; ++i) {
76 auto [xi, yi] = get_pos(i);
80 cl.
query(xi, yi, [&](
int j) {
83 auto [xj, yj] = get_pos(j);
84 const Scalar dx = xi - xj, dy = yi - yj;
85 if (dx * dx + dy * dy < ext_sq_)
89 starts_[i + 1] =
static_cast<int>(flat_.size());
99 template<
typename PosAccessor>
103 const Scalar half_skin_sq = (skin_ * Scalar(0.5))
104 * (skin_ * Scalar(0.5));
105 for (
int i = 0; i < n; ++i) {
106 auto [xi, yi] = get_pos(i);
107 const Scalar dx = xi - ref_x_[i];
108 const Scalar dy = yi - ref_y_[i];
109 if (dx * dx + dy * dy > half_skin_sq)
119 return {flat_.data() + starts_[i], flat_.data() + starts_[i + 1]};
129 return cutoff_ + skin_;
132 return starts_.empty() ? 0 :
static_cast<int>(starts_.size()) - 1;
136 Scalar cutoff_, skin_, ext_sq_;
137 std::vector<int> flat_;
138 std::vector<int> starts_;
141 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
Point query: calls f(int j) for every particle in the 3x3 cell neighbourhood of (px,...
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
Check whether any particle has moved far enough to invalidate the cached list. O(n),...
int n_particles() const noexcept
VerletList2D(Scalar cutoff, Scalar skin)
Scalar cutoff() const noexcept
IntRange neighbors(int i) const noexcept
Cached neighbours of particle i (within cutoff + skin).
Lightweight read-only range over a contiguous int array (C++17-safe span).