载入中...
搜索中...
未找到
Fluid.cpp
浏览该文件的文档.
1#include "physics/Fluid.h"
2
3#include "common/Exception.h"
4#include "graphics/Graphics.h"
5#include "graphics/Canvas.h"
6
7#include <algorithm>
8#include <cmath>
9
10namespace eve::physics {
11
12// Color lives in eve::graphics (see graphics/Canvas.h); keep the unqualified form.
14
15Fluid::Fluid(int capacity) : capacity_(capacity) {
16 if (capacity_ < 1) throw Exception("Fluid: capacity must be >= 1");
17 particles_.reserve(static_cast<size_t>(capacity_));
18}
19
21
23 destroyed_ = true;
24 particles_.clear();
25 hash_.clear();
26}
27
28void Fluid::setGravity(float gx, float gy) {
29 gravityX_ = gx;
30 gravityY_ = gy;
31}
32
33void Fluid::setSmoothingRadius(float radius) {
34 if (radius <= 0.f) throw Exception("Fluid.setSmoothingRadius: radius must be > 0");
35 h_ = radius;
36}
37
38void Fluid::setRestDensity(float density) {
39 if (density <= 0.f) throw Exception("Fluid.setRestDensity: density must be > 0");
40 restDensity_ = density;
41}
42
43void Fluid::setPressureStiffness(float k) { pressureK_ = std::max(0.f, k); }
44
45void Fluid::setNearPressureStiffness(float k) { nearPressureK_ = std::max(0.f, k); }
46
47void Fluid::setViscosity(float viscosity) { viscosity_ = std::clamp(viscosity, 0.f, 1.f); }
48
49void Fluid::setIterations(int iterations) { iterations_ = std::max(1, iterations); }
50
51void Fluid::setBounds(float x, float y, float w, float h) {
52 if (w <= 0.f || h <= 0.f) {
54 return;
55 }
56 hasBounds_ = true;
57 boundX_ = x;
58 boundY_ = y;
59 boundW_ = w;
60 boundH_ = h;
61}
62
63void Fluid::clearBounds() { hasBounds_ = false; }
64
65int Fluid::emit(float x, float y, int count, float vx, float vy) {
66 if (destroyed_ || count <= 0) return 0;
67 int added = 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)))));
70 for (int i = 0; i < count && getParticleCount() < capacity_; ++i) {
71 Particle p;
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;
76 p.vx = vx;
77 p.vy = vy;
78 particles_.push_back(p);
79 ++added;
80 }
81 return added;
82}
83
85 particles_.clear();
86 hash_.clear();
87}
88
89void Fluid::interactAt(float x, float y, float radius, float strength) {
90 interactX_ = x;
91 interactY_ = y;
92 interactRadius_ = std::max(0.f, radius);
93 interactStrength_ = strength;
94}
95
96void Fluid::setColor(float r, float g, float b, float a) {
97 colorR_ = r;
98 colorG_ = g;
99 colorB_ = b;
100 colorA_ = a;
101}
102
103void Fluid::setParticleSize(float size) { particleSize_ = std::max(1.f, size); }
104
105bool Fluid::validIndex(int index) const {
106 return index >= 0 && index < getParticleCount();
107}
108
109float Fluid::getParticleX(int index) const {
110 if (!validIndex(index)) return 0.f;
111 return particles_[static_cast<size_t>(index)].x;
112}
113
114float Fluid::getParticleY(int index) const {
115 if (!validIndex(index)) return 0.f;
116 return particles_[static_cast<size_t>(index)].y;
117}
118
119float Fluid::getParticleVx(int index) const {
120 if (!validIndex(index)) return 0.f;
121 return particles_[static_cast<size_t>(index)].vx;
122}
123
124float Fluid::getParticleVy(int index) const {
125 if (!validIndex(index)) return 0.f;
126 return particles_[static_cast<size_t>(index)].vy;
127}
128
129int64_t Fluid::cellKey(int cx, int cy) const {
130 return (int64_t(uint32_t(cx)) << 32) | int64_t(uint32_t(cy));
131}
132
133void Fluid::rebuildHash() {
134 hash_.clear();
135 const float inv = 1.f / h_;
136 for (int i = 0; i < getParticleCount(); ++i) {
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);
141 }
142}
143
144void Fluid::applyViscosity(float dt) {
145 if (viscosity_ <= 0.f || getParticleCount() == 0) return;
146 const float inv = 1.f / h_;
147 for (int i = 0; i < getParticleCount(); ++i) {
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;
171 }
172 }
173 }
174 }
175}
176
177void Fluid::doubleDensityRelaxation() {
178 const float inv = 1.f / h_;
179 for (int i = 0; i < getParticleCount(); ++i) {
180 Particle &pi = particles_[static_cast<size_t>(i)];
181 float density = 0.f;
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));
185
186 struct Neighbor {
187 int j;
188 float r;
189 float dx;
190 float dy;
191 float q;
192 };
193 Neighbor neighbors[64];
194 int nCount = 0;
195
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_;
209 density += q * q;
210 nearDensity += q * q * q;
211 if (nCount < 64) neighbors[nCount++] = {j, r, dx, dy, q};
212 }
213 }
214 }
215
216 pi.density = density;
217 const float pressure = pressureK_ * (density - restDensity_);
218 const float nearPressure = nearPressureK_ * nearDensity;
219
220 float dxSum = 0.f;
221 float dySum = 0.f;
222 for (int n = 0; n < nCount; ++n) {
223 const Neighbor &nb = neighbors[n];
224 const float mag =
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)];
229 pj.x += dispX;
230 pj.y += dispY;
231 dxSum -= dispX;
232 dySum -= dispY;
233 }
234 pi.x += dxSum;
235 pi.y += dySum;
236 }
237}
238
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;
247
248 for (Particle &p : particles_) {
249 if (p.x < minX) {
250 p.x = minX;
251 p.vx = std::fabs(p.vx) * damp;
252 } else if (p.x > maxX) {
253 p.x = maxX;
254 p.vx = -std::fabs(p.vx) * damp;
255 }
256 if (p.y < minY) {
257 p.y = minY;
258 p.vy = std::fabs(p.vy) * damp;
259 } else if (p.y > maxY) {
260 p.y = maxY;
261 p.vy = -std::fabs(p.vy) * damp;
262 }
263 }
264}
265
266void Fluid::update(float dt) {
267 if (destroyed_) return;
268 if (getParticleCount() == 0) {
269 interactStrength_ = 0.f;
270 return;
271 }
272 if (dt < 0.f) dt = 0.f;
273 if (dt > 0.05f) dt = 0.05f;
274
275 // External accelerations.
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;
289 }
290 }
291 }
292
293 rebuildHash();
294 applyViscosity(dt);
295
296 // Predict positions, then relax density.
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;
303 }
304
305 for (int iter = 0; iter < iterations_; ++iter) {
306 rebuildHash();
307 doubleDensityRelaxation();
308 collideBounds();
309 }
310
311 // Update velocities from position delta.
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;
316 const float speed2 =
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;
323 }
324 }
325
326 interactStrength_ = 0.f;
327}
328
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);
334 gfx->drawSolidRect(p.x - s * 0.5f, p.y - s * 0.5f, s, s,
335 Color(colorR_ * (0.55f + 0.45f * t), colorG_ * (0.65f + 0.35f * t),
336 colorB_, colorA_));
337 }
338}
339
340} // namespace eve::physics
float cx
Definition CardTypes.cpp:31
float cy
Definition CardTypes.cpp:32
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
uint32_t a
uint32_t b
glm::vec4 p[6]
int iterations
Definition TreeMesh.cpp:193
float step
Definition TreeMesh.cpp:196
uint32_t s
Definition Weather.cpp:28
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.
Definition Fluid.cpp:65
float getParticleVx(int index) const
Definition Fluid.cpp:119
float getParticleVy(int index) const
Definition Fluid.cpp:124
void setRestDensity(float density)
Target rest density for the relaxation solver (default 4).
Definition Fluid.cpp:38
void setColor(float r, float g, float b, float a=1.f)
Definition Fluid.cpp:96
int getParticleCount() const
Definition Fluid.h:86
void setIterations(int iterations)
Solver iterations per frame (default 3).
Definition Fluid.cpp:49
void setParticleSize(float size)
Particle draw size in pixels (default 5).
Definition Fluid.cpp:103
void setBounds(float x, float y, float w, float h)
Axis-aligned container; particles bounce inside.
Definition Fluid.cpp:51
void setViscosity(float viscosity)
Definition Fluid.cpp:47
void update(float dt)
Definition Fluid.cpp:266
float getParticleY(int index) const
Definition Fluid.cpp:114
void draw(graphics::Graphics *gfx)
Definition Fluid.cpp:329
Fluid(int capacity=512)
Definition Fluid.cpp:15
void setPressureStiffness(float k)
Pressure stiffness (default 0.5).
Definition Fluid.cpp:43
void setSmoothingRadius(float radius)
Interaction / neighbor radius in pixels (default 18).
Definition Fluid.cpp:33
void clear()
Clear all particles.
Definition Fluid.cpp:84
void setNearPressureStiffness(float k)
Near-pressure (anti-clustering) stiffness (default 0.5).
Definition Fluid.cpp:45
void interactAt(float x, float y, float radius, float strength)
Mouse / pointer interaction: positive strength attracts, negative repels. Applied as acceleration wit...
Definition Fluid.cpp:89
float getParticleX(int index) const
Definition Fluid.cpp:109
void setGravity(float gx, float gy)
Definition Fluid.cpp:28
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13