16 if (capacity_ < 1)
throw Exception(
"Fluid: capacity must be >= 1");
17 particles_.reserve(
static_cast<size_t>(capacity_));
34 if (radius <= 0.f)
throw Exception(
"Fluid.setSmoothingRadius: radius must be > 0");
39 if (density <= 0.f)
throw Exception(
"Fluid.setRestDensity: density must be > 0");
40 restDensity_ = density;
52 if (
w <= 0.f ||
h <= 0.f) {
66 if (destroyed_ || count <= 0)
return 0;
68 const float step = std::max(2.5f, h_ * 0.35f);
69 const int side = std::max(1,
int(std::ceil(std::sqrt(
float(count)))));
72 const int cx = i % side;
73 const int cy = i / side;
74 p.x =
x + (float(
cx) - float(side) * 0.5f) *
step;
75 p.y =
y + (float(
cy) - float(side) * 0.5f) *
step;
78 particles_.push_back(
p);
92 interactRadius_ = std::max(0.f, radius);
93 interactStrength_ = strength;
105bool Fluid::validIndex(
int index)
const {
110 if (!validIndex(index))
return 0.f;
111 return particles_[
static_cast<size_t>(index)].
x;
115 if (!validIndex(index))
return 0.f;
116 return particles_[
static_cast<size_t>(index)].
y;
120 if (!validIndex(index))
return 0.f;
121 return particles_[
static_cast<size_t>(index)].vx;
125 if (!validIndex(index))
return 0.f;
126 return particles_[
static_cast<size_t>(index)].vy;
129int64_t Fluid::cellKey(
int cx,
int cy)
const {
130 return (int64_t(uint32_t(
cx)) << 32) | int64_t(uint32_t(
cy));
133void Fluid::rebuildHash() {
135 const float inv = 1.f / h_;
137 const Particle &
p = particles_[
static_cast<size_t>(i)];
138 const int cx = int(std::floor(
p.x * inv));
139 const int cy = int(std::floor(
p.y * inv));
140 hash_[cellKey(
cx,
cy)].push_back(i);
144void Fluid::applyViscosity(
float dt) {
146 const float inv = 1.f / h_;
148 Particle &pi = particles_[
static_cast<size_t>(i)];
149 const int cx = int(std::floor(pi.x * inv));
150 const int cy = int(std::floor(pi.y * inv));
151 for (
int oy = -1; oy <= 1; ++oy) {
152 for (
int ox = -1; ox <= 1; ++ox) {
153 auto it = hash_.find(cellKey(
cx + ox,
cy + oy));
154 if (it == hash_.end())
continue;
155 for (
int j : it->second) {
156 if (j <= i)
continue;
157 Particle &pj = particles_[
static_cast<size_t>(j)];
158 float dx = pj.x - pi.x;
159 float dy = pj.y - pi.y;
160 float r2 = dx * dx + dy * dy;
161 if (r2 >= h_ * h_ || r2 < 1e-8f)
continue;
162 const float r = std::sqrt(r2);
163 const float q = 1.f - r / h_;
164 float dvx = pj.vx - pi.vx;
165 float dvy = pj.vy - pi.vy;
166 const float impulse = viscosity_ * q * dt;
167 pi.vx += dvx * impulse * 0.5f;
168 pi.vy += dvy * impulse * 0.5f;
169 pj.vx -= dvx * impulse * 0.5f;
170 pj.vy -= dvy * impulse * 0.5f;
177void Fluid::doubleDensityRelaxation() {
178 const float inv = 1.f / h_;
180 Particle &pi = particles_[
static_cast<size_t>(i)];
182 float nearDensity = 0.f;
183 const int cx = int(std::floor(pi.x * inv));
184 const int cy = int(std::floor(pi.y * inv));
193 Neighbor neighbors[64];
196 for (
int oy = -1; oy <= 1; ++oy) {
197 for (
int ox = -1; ox <= 1; ++ox) {
198 auto it = hash_.find(cellKey(
cx + ox,
cy + oy));
199 if (it == hash_.end())
continue;
200 for (
int j : it->second) {
201 if (j == i)
continue;
202 const Particle &pj = particles_[
static_cast<size_t>(j)];
203 float dx = pj.x - pi.x;
204 float dy = pj.y - pi.y;
205 float r2 = dx * dx + dy * dy;
206 if (r2 >= h_ * h_ || r2 < 1e-10f)
continue;
207 const float r = std::sqrt(r2);
208 const float q = 1.f - r / h_;
210 nearDensity += q * q * q;
211 if (nCount < 64) neighbors[nCount++] = {j, r, dx, dy, q};
216 pi.density = density;
217 const float pressure = pressureK_ * (density - restDensity_);
218 const float nearPressure = nearPressureK_ * nearDensity;
222 for (
int n = 0;
n < nCount; ++
n) {
223 const Neighbor &nb = neighbors[
n];
225 (pressure * nb.q + nearPressure * nb.q * nb.q) * (1.f / (nb.r + 1e-6f));
226 const float dispX = nb.dx * mag * 0.5f;
227 const float dispY = nb.dy * mag * 0.5f;
228 Particle &pj = particles_[
static_cast<size_t>(nb.j)];
239void Fluid::collideBounds() {
240 if (!hasBounds_)
return;
241 const float pad = particleSize_ * 0.5f;
242 const float minX = boundX_ + pad;
243 const float minY = boundY_ + pad;
244 const float maxX = boundX_ + boundW_ - pad;
245 const float maxY = boundY_ + boundH_ - pad;
246 constexpr float damp = 0.35f;
248 for (Particle &
p : particles_) {
251 p.vx = std::fabs(
p.vx) * damp;
252 }
else if (
p.x > maxX) {
254 p.vx = -std::fabs(
p.vx) * damp;
258 p.vy = std::fabs(
p.vy) * damp;
259 }
else if (
p.y > maxY) {
261 p.vy = -std::fabs(
p.vy) * damp;
267 if (destroyed_)
return;
269 interactStrength_ = 0.f;
272 if (dt < 0.f) dt = 0.f;
273 if (dt > 0.05f) dt = 0.05f;
276 for (Particle &
p : particles_) {
277 p.vx += gravityX_ * dt;
278 p.vy += gravityY_ * dt;
279 if (interactRadius_ > 0.f && interactStrength_ != 0.f) {
280 const float dx = interactX_ -
p.x;
281 const float dy = interactY_ -
p.y;
282 const float r2 = dx * dx + dy * dy;
283 const float R2 = interactRadius_ * interactRadius_;
284 if (r2 < R2 && r2 > 1e-6f) {
285 const float r = std::sqrt(r2);
286 const float w = 1.f - r / interactRadius_;
287 p.vx += (dx / r) * interactStrength_ *
w * dt;
288 p.vy += (dy / r) * interactStrength_ *
w * dt;
297 std::vector<float> prevX(particles_.size()), prevY(particles_.size());
298 for (
size_t i = 0; i < particles_.size(); ++i) {
299 prevX[i] = particles_[i].x;
300 prevY[i] = particles_[i].y;
301 particles_[i].x += particles_[i].vx * dt;
302 particles_[i].y += particles_[i].vy * dt;
305 for (
int iter = 0; iter < iterations_; ++iter) {
307 doubleDensityRelaxation();
312 const float invDt = dt > 1e-6f ? 1.f / dt : 0.f;
313 for (
size_t i = 0; i < particles_.size(); ++i) {
314 particles_[i].vx = (particles_[i].x - prevX[i]) * invDt;
315 particles_[i].vy = (particles_[i].y - prevY[i]) * invDt;
317 particles_[i].vx * particles_[i].vx + particles_[i].vy * particles_[i].vy;
318 const float maxSpeed = 1600.f;
319 if (speed2 > maxSpeed * maxSpeed) {
320 const float s = maxSpeed / std::sqrt(speed2);
321 particles_[i].vx *=
s;
322 particles_[i].vy *=
s;
326 interactStrength_ = 0.f;
330 if (!gfx || destroyed_)
return;
331 const float s = particleSize_;
332 for (
const Particle &
p : particles_) {
333 const float t = std::clamp(
p.density / (restDensity_ * 1.8f), 0.25f, 1.f);
335 Color(colorR_ * (0.55f + 0.45f * t), colorG_ * (0.65f + 0.35f * t),
virtual void drawSolidRect(float x, float y, float w, float h, const Color &color, BlendMode blend=BlendMode::Alpha)=0
Internal immediate-mode helper used by RenderSystem / Batcher.
int emit(float x, float y, int count, float vx=0.f, float vy=0.f)
Spawn up to count particles at (x,y) with initial velocity. Returns number actually added.
float getParticleVx(int index) const
float getParticleVy(int index) const
void setRestDensity(float density)
Target rest density for the relaxation solver (default 4).
void setColor(float r, float g, float b, float a=1.f)
int getParticleCount() const
void setIterations(int iterations)
Solver iterations per frame (default 3).
void setParticleSize(float size)
Particle draw size in pixels (default 5).
void setBounds(float x, float y, float w, float h)
Axis-aligned container; particles bounce inside.
void setViscosity(float viscosity)
float getParticleY(int index) const
void draw(graphics::Graphics *gfx)
void setPressureStiffness(float k)
Pressure stiffness (default 0.5).
void setSmoothingRadius(float radius)
Interaction / neighbor radius in pixels (default 18).
void clear()
Clear all particles.
void setNearPressureStiffness(float k)
Near-pressure (anti-clustering) stiffness (default 0.5).
void interactAt(float x, float y, float radius, float strength)
Mouse / pointer interaction: positive strength attracts, negative repels. Applied as acceleration wit...
float getParticleX(int index) const
void setGravity(float gx, float gy)
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...