载入中...
搜索中...
未找到
Cloth.cpp
浏览该文件的文档.
1#include "physics/Cloth.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
15Cloth::Cloth(int cols, int rows, float spacing, float originX, float originY)
16 : cols_(cols), rows_(rows), spacing_(spacing), originX_(originX), originY_(originY) {
17 if (cols_ < 2 || rows_ < 2)
18 throw Exception("Cloth: cols and rows must be >= 2");
19 if (spacing_ <= 0.f)
20 throw Exception("Cloth: spacing must be > 0");
21
22 particles_.resize(static_cast<size_t>(cols_ * rows_));
23 for (int r = 0; r < rows_; ++r) {
24 for (int c = 0; c < cols_; ++c) {
25 Particle &p = particles_[static_cast<size_t>(r * cols_ + c)];
26 p.x = p.px = originX_ + float(c) * spacing_;
27 p.y = p.py = originY_ + float(r) * spacing_;
28 p.pinned = false;
29 }
30 }
31 rebuildLinks();
32 pinTopRow();
33}
34
36
37void Cloth::destroy() { destroyed_ = true; }
38
39void Cloth::rebuildLinks() {
40 links_.clear();
41 auto add = [&](int a, int b) {
42 if (a < 0 || b < 0 || a >= getParticleCount() || b >= getParticleCount()) return;
43 Link link;
44 link.a = a;
45 link.b = b;
46 const Particle &pa = particles_[static_cast<size_t>(a)];
47 const Particle &pb = particles_[static_cast<size_t>(b)];
48 const float dx = pb.x - pa.x;
49 const float dy = pb.y - pa.y;
50 link.rest = std::sqrt(dx * dx + dy * dy);
51 if (link.rest > 1e-4f) links_.push_back(link);
52 };
53
54 for (int r = 0; r < rows_; ++r) {
55 for (int c = 0; c < cols_; ++c) {
56 const int i = r * cols_ + c;
57 // Structural
58 if (c + 1 < cols_) add(i, i + 1);
59 if (r + 1 < rows_) add(i, i + cols_);
60 // Shear
61 if (c + 1 < cols_ && r + 1 < rows_) add(i, i + cols_ + 1);
62 if (c > 0 && r + 1 < rows_) add(i, i + cols_ - 1);
63 // Bend (every other)
64 if (c + 2 < cols_) add(i, i + 2);
65 if (r + 2 < rows_) add(i, i + cols_ * 2);
66 }
67 }
68}
69
70bool Cloth::validIndex(int index) const {
71 return index >= 0 && index < getParticleCount();
72}
73
74void Cloth::setGravity(float gx, float gy) {
75 gravityX_ = gx;
76 gravityY_ = gy;
77}
78
79void Cloth::setStiffness(float stiffness) {
80 stiffness_ = std::clamp(stiffness, 0.f, 1.f);
81}
82
84 iterations_ = std::max(1, iterations);
85}
86
87void Cloth::setDamping(float damping) {
88 damping_ = std::clamp(damping, 0.f, 1.f);
89}
90
91void Cloth::setBounds(float x, float y, float w, float h) {
92 if (w <= 0.f || h <= 0.f) {
94 return;
95 }
96 hasBounds_ = true;
97 boundX_ = x;
98 boundY_ = y;
99 boundW_ = w;
100 boundH_ = h;
101}
102
103void Cloth::clearBounds() { hasBounds_ = false; }
104
105void Cloth::pin(int index) {
106 if (!validIndex(index)) throw Exception("Cloth.pin: index out of range");
107 particles_[static_cast<size_t>(index)].pinned = true;
108}
109
110void Cloth::unpin(int index) {
111 if (!validIndex(index)) throw Exception("Cloth.unpin: index out of range");
112 particles_[static_cast<size_t>(index)].pinned = false;
113}
114
116 for (int c = 0; c < cols_; ++c)
117 particles_[static_cast<size_t>(c)].pinned = true;
118}
119
120bool Cloth::isPinned(int index) const {
121 if (!validIndex(index)) return false;
122 return particles_[static_cast<size_t>(index)].pinned;
123}
124
125int Cloth::grabAt(float x, float y, float radius) {
126 grabIndex_ = -1;
127 float best = radius * radius;
128 for (int i = 0; i < getParticleCount(); ++i) {
129 const Particle &p = particles_[static_cast<size_t>(i)];
130 if (p.pinned) continue;
131 const float dx = p.x - x;
132 const float dy = p.y - y;
133 const float d2 = dx * dx + dy * dy;
134 if (d2 <= best) {
135 best = d2;
136 grabIndex_ = i;
137 }
138 }
139 if (grabIndex_ >= 0) moveGrab(x, y);
140 return grabIndex_;
141}
142
143void Cloth::moveGrab(float x, float y) {
144 grabX_ = x;
145 grabY_ = y;
146 if (!validIndex(grabIndex_)) return;
147 Particle &p = particles_[static_cast<size_t>(grabIndex_)];
148 p.x = x;
149 p.y = y;
150 p.px = x;
151 p.py = y;
152}
153
154void Cloth::releaseGrab() { grabIndex_ = -1; }
155
156void Cloth::applyForce(float fx, float fy) {
157 forceX_ += fx;
158 forceY_ += fy;
159}
160
161void Cloth::setColor(float r, float g, float b, float a) {
162 colorR_ = r;
163 colorG_ = g;
164 colorB_ = b;
165 colorA_ = a;
166}
167
168float Cloth::getParticleX(int index) const {
169 if (!validIndex(index)) return 0.f;
170 return particles_[static_cast<size_t>(index)].x;
171}
172
173float Cloth::getParticleY(int index) const {
174 if (!validIndex(index)) return 0.f;
175 return particles_[static_cast<size_t>(index)].y;
176}
177
178void Cloth::setParticlePosition(int index, float x, float y) {
179 if (!validIndex(index)) throw Exception("Cloth.setParticlePosition: index out of range");
180 Particle &p = particles_[static_cast<size_t>(index)];
181 p.x = p.px = x;
182 p.y = p.py = y;
183}
184
185void Cloth::integrate(float dt) {
186 if (dt <= 0.f) return;
187 const float ax = gravityX_ + forceX_;
188 const float ay = gravityY_ + forceY_;
189 const float damp = 1.f - damping_;
190
191 for (Particle &p : particles_) {
192 if (p.pinned) {
193 p.px = p.x;
194 p.py = p.y;
195 continue;
196 }
197 const float vx = (p.x - p.px) * damp;
198 const float vy = (p.y - p.py) * damp;
199 p.px = p.x;
200 p.py = p.y;
201 p.x += vx + ax * dt * dt;
202 p.y += vy + ay * dt * dt;
203 }
204}
205
206void Cloth::solveConstraints() {
207 for (int iter = 0; iter < iterations_; ++iter) {
208 for (const Link &link : links_) {
209 Particle &a = particles_[static_cast<size_t>(link.a)];
210 Particle &b = particles_[static_cast<size_t>(link.b)];
211 if (a.pinned && b.pinned) continue;
212 float dx = b.x - a.x;
213 float dy = b.y - a.y;
214 float dist = std::sqrt(dx * dx + dy * dy);
215 if (dist < 1e-5f) continue;
216 const float diff = (dist - link.rest) / dist * stiffness_;
217 // When one end is pinned, apply the full correction to the free end.
218 if (a.pinned) {
219 b.x -= dx * diff;
220 b.y -= dy * diff;
221 } else if (b.pinned) {
222 a.x += dx * diff;
223 a.y += dy * diff;
224 } else {
225 const float half = diff * 0.5f;
226 a.x += dx * half;
227 a.y += dy * half;
228 b.x -= dx * half;
229 b.y -= dy * half;
230 }
231 }
232 if (validIndex(grabIndex_)) {
233 Particle &g = particles_[static_cast<size_t>(grabIndex_)];
234 g.x = grabX_;
235 g.y = grabY_;
236 g.px = grabX_;
237 g.py = grabY_;
238 }
239 }
240}
241
242void Cloth::collideBounds() {
243 if (!hasBounds_) return;
244 const float minX = boundX_;
245 const float minY = boundY_;
246 const float maxX = boundX_ + boundW_;
247 const float maxY = boundY_ + boundH_;
248 constexpr float bounce = 0.35f;
249
250 for (Particle &p : particles_) {
251 if (p.pinned) continue;
252 if (p.x < minX) {
253 const float vx = p.x - p.px;
254 p.x = minX;
255 p.px = p.x + vx * bounce;
256 } else if (p.x > maxX) {
257 const float vx = p.x - p.px;
258 p.x = maxX;
259 p.px = p.x + vx * bounce;
260 }
261 if (p.y < minY) {
262 const float vy = p.y - p.py;
263 p.y = minY;
264 p.py = p.y + vy * bounce;
265 } else if (p.y > maxY) {
266 const float vy = p.y - p.py;
267 p.y = maxY;
268 p.py = p.y + vy * bounce;
269 }
270 }
271}
272
273void Cloth::update(float dt) {
274 if (destroyed_) return;
275 if (dt < 0.f) dt = 0.f;
276 if (dt > 0.05f) dt = 0.05f;
277
278 // Fixed substeps for stability under hitch frames.
279 const int substeps = 2;
280 const float h = dt / float(substeps);
281 for (int s = 0; s < substeps; ++s) {
282 integrate(h);
283 solveConstraints();
284 collideBounds();
285 if (grabIndex_ >= 0) {
286 Particle &p = particles_[static_cast<size_t>(grabIndex_)];
287 // Preserve grab target set by moveGrab (px/py already synced there).
288 p.px = p.x;
289 p.py = p.y;
290 }
291 }
292 forceX_ = 0.f;
293 forceY_ = 0.f;
294}
295
297 if (!gfx || destroyed_) return;
298 const Color linkColor(colorR_, colorG_, colorB_, colorA_ * 0.75f);
299 const Color nodeColor(colorR_, colorG_, colorB_, colorA_);
300
301 for (const Link &link : links_) {
302 const Particle &a = particles_[static_cast<size_t>(link.a)];
303 const Particle &b = particles_[static_cast<size_t>(link.b)];
304 const float x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y;
305 const float dx = x2 - x1;
306 const float dy = y2 - y1;
307 const float len = std::sqrt(dx * dx + dy * dy);
308 const int steps = std::max(1, int(len / 4.f));
309 for (int i = 0; i <= steps; ++i) {
310 const float t = float(i) / float(steps);
311 gfx->drawSolidRect(x1 + dx * t - 1.f, y1 + dy * t - 1.f, 2.f, 2.f, linkColor);
312 }
313 }
314 for (const Particle &p : particles_) {
315 const float s = p.pinned ? 5.f : 3.f;
316 gfx->drawSolidRect(p.x - s * 0.5f, p.y - s * 0.5f, s, s, nodeColor);
317 }
318 if (grabIndex_ >= 0) {
319 const Particle &p = particles_[static_cast<size_t>(grabIndex_)];
320 gfx->drawSolidRect(p.x - 6.f, p.y - 6.f, 12.f, 12.f,
321 Color(1.f, 0.85f, 0.35f, 0.9f));
322 }
323}
324
325} // namespace eve::physics
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
int h
int w
uint32_t a
uint32_t b
uint32_t c
glm::vec4 p[6]
int spacing
int iterations
Definition TreeMesh.cpp:193
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.
void setParticlePosition(int index, float x, float y)
Definition Cloth.cpp:178
void setBounds(float x, float y, float w, float h)
Axis-aligned walls; particles bounce inside. Disabled if w/h <= 0.
Definition Cloth.cpp:91
void setStiffness(float stiffness)
Constraint relaxation strength in [0,1] (default 0.85).
Definition Cloth.cpp:79
void setDamping(float damping)
Damping applied to Verlet velocity [0,1] (default 0.01).
Definition Cloth.cpp:87
float getParticleX(int index) const
Definition Cloth.cpp:168
bool isPinned(int index) const
Definition Cloth.cpp:120
float getParticleY(int index) const
Definition Cloth.cpp:173
void setGravity(float gx, float gy)
Definition Cloth.cpp:74
int getParticleCount() const
Definition Cloth.h:82
void moveGrab(float x, float y)
Definition Cloth.cpp:143
void update(float dt)
Definition Cloth.cpp:273
void setColor(float r, float g, float b, float a=1.f)
Definition Cloth.cpp:161
Cloth(int cols, int rows, float spacing, float originX, float originY)
Definition Cloth.cpp:15
void pin(int index)
Definition Cloth.cpp:105
void applyForce(float fx, float fy)
Definition Cloth.cpp:156
void draw(graphics::Graphics *gfx)
Definition Cloth.cpp:296
void setIterations(int iterations)
Constraint solver iterations per substep (default 4).
Definition Cloth.cpp:83
int grabAt(float x, float y, float radius=24.f)
Grab nearest free particle within radius (pixels). Returns particle index, or -1 if none.
Definition Cloth.cpp:125
void unpin(int index)
Definition Cloth.cpp:110
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13