载入中...
搜索中...
未找到
ClothGPU.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
5#include "gpgpu/Gpgpu.h"
6#include "gpgpu/GpuBuffer.h"
7#include "gpgpu/Sequence.h"
8#include "graphics/Canvas.h"
9#include "graphics/Graphics.h"
10
11#include <algorithm>
12#include <cmath>
13#include <exception>
14#include <utility>
15
16namespace eve::physics {
17
18bool ClothGPU::supportsFeature(const std::string &feature) const {
19 return feature == "grid_topology" || feature == "distance_constraints" || feature == "self_collision" ||
20 feature == "bounds_collision" || feature == "interaction_force";
21}
22
25
26namespace {
27
28// One thread owns one particle: integrate, then relax its fixed-size link list.
29// Push constant layout (float indices):
30// 0 dt | 1 gx | 2 gy | 3 stiffness | 4 iterations | 5 count | 6 damping
31// 7 boundX | 8 boundY | 9 boundW | 10 boundH | 11 hasBounds
32// 12 forceX | 13 forceY | 14 interactX | 15 interactY
33// 16 interactRadius | 17 interactStrength | 18 links-per-particle
34// 19 mode (0 = integrate, 1 = constraints + bounds, 2 = build hash,
35// 3 = self-collision, 4 = clear hash counters)
36// 20 self-collision min distance | 21 self-collision on/off
37// 22 hash nx | 23 hash ny | 24 cell count
38const char *kClothKernel = R"glsl(#version 450
39layout(local_size_x = 64) in;
40
41layout(set = 0, binding = 0) buffer Pos {
42 vec4 posIn[]; // x, y, prevX, prevY (read)
43};
44layout(set = 0, binding = 1) buffer PosOut {
45 vec4 posOut[]; // x, y, prevX, prevY (write)
46};
47layout(set = 0, binding = 2) buffer Links {
48 vec4 links[]; // otherIndex, restLen, 0, 0 per particle slot
49};
50layout(set = 0, binding = 3) buffer Flags {
51 vec4 flags[]; // x = 1.0 when pinned
52};
53layout(set = 0, binding = 4) buffer CellHead {
54 uint cellHead[]; // per-cell linked-list head; 0xFFFFFFFF = empty
55};
56layout(set = 0, binding = 5) buffer CellNext {
57 uint cellNext[]; // slot -> next slot in the same cell
58};
59layout(set = 0, binding = 6) buffer CellItems {
60 uint cellItems[]; // slot -> particle index
61};
62layout(set = 0, binding = 7) buffer CellSlot {
63 uint cellSlot[1]; // global slot counter for hash building
64};
65
66layout(push_constant) uniform PC {
67 float data[32];
68} pc;
69
70// Keep two non-adjacent particles at least minDist apart. Pinned endpoints are
71// handled by their own thread being skipped: the free side takes the full
72// correction; two free sides split it.
73void separatePair(uint i, uint j, inout vec4 p) {
74 if (j == i) return;
75 const uint K = uint(pc.data[18]);
76 bool linked = false;
77 for (uint k = 0u; k < K; ++k) {
78 vec4 l = links[i * K + k];
79 if (int(l.x) < 0) break;
80 if (int(l.x) == int(j)) {
81 linked = true;
82 break;
83 }
84 }
85 if (linked) return;
86 bool jPinned = flags[j].x > 0.5;
87 vec4 q = posIn[j];
88 float dx = q.x - p.x;
89 float dy = q.y - p.y;
90 float d2 = dx * dx + dy * dy;
91 float minDist = pc.data[20];
92 if (d2 >= minDist * minDist || d2 < 1e-8) return;
93 float d = sqrt(d2);
94 float corr = min(2.0, (minDist - d) / d);
95 float w = jPinned ? 1.0 : 0.5;
96 p.x -= dx * corr * w;
97 p.y -= dy * corr * w;
98}
99
100void main() {
101 // Reset the hash: every cell head becomes empty, the global slot counter
102 // goes back to zero. Runs once per substep before building.
103 if (int(pc.data[19]) == 4) {
104 uint cid = gl_GlobalInvocationID.x;
105 if (cid < uint(pc.data[24])) cellHead[cid] = 0xFFFFFFFFu;
106 if (cid == 0u) cellSlot[0] = 0u;
107 return;
108 }
109
110 uint i = gl_GlobalInvocationID.x;
111 uint n = uint(pc.data[5]);
112 if (i >= n) return;
113
114 const float dt = pc.data[0];
115 const float damp = 1.0 - pc.data[6];
116 const bool pinned = flags[i].x > 0.5;
117 const uint K = uint(pc.data[18]);
118 const int mode = int(pc.data[19]);
119 vec4 p = posIn[i];
120
121 if (mode == 0 && !pinned) {
122 // Verlet integrate.
123 float vx = (p.x - p.z) * damp;
124 float vy = (p.y - p.w) * damp;
125 p.z = p.x;
126 p.w = p.y;
127 float ax = pc.data[1] + pc.data[12];
128 float ay = pc.data[2] + pc.data[13];
129 p.x += vx + ax * dt * dt;
130 p.y += vy + ay * dt * dt;
131
132 // Pointer field (Fluid2D-style attract/repel).
133 float ir = pc.data[16];
134 if (ir > 0.0 && abs(pc.data[17]) > 1e-9) {
135 float dx = pc.data[14] - p.x;
136 float dy = pc.data[15] - p.y;
137 float r2 = dx * dx + dy * dy;
138 if (r2 < ir * ir && r2 > 1e-6) {
139 float r = sqrt(r2);
140 float w = 1.0 - r / ir;
141 p.x += (dx / r) * pc.data[17] * w * dt * dt;
142 p.y += (dy / r) * pc.data[17] * w * dt * dt;
143 }
144 }
145 }
146
147 // Distance-constraint relaxation over this particle's link slots. The
148 // owning thread never moves a pinned particle: a pinned endpoint leaves
149 // the full correction to the free partner's thread. Runs in a separate
150 // dispatch so integration results are visible before solving.
151 if (mode == 1 && !pinned) {
152 // One Jacobi pass per dispatch: each thread reads the previous pass's
153 // positions (posIn) and writes its own corrected position (posOut).
154 // The outer loop in update() repeats the dispatch to converge.
155 for (uint k = 0u; k < K; ++k) {
156 vec4 l = links[i * K + k];
157 int other = int(l.x);
158 if (other < 0) break;
159 vec4 q = posIn[uint(other)];
160 bool otherPinned = flags[uint(other)].x > 0.5;
161 float dx = q.x - p.x;
162 float dy = q.y - p.y;
163 float dist = sqrt(dx * dx + dy * dy);
164 if (dist < 1e-5) continue;
165 float diff = (dist - l.y) / dist * pc.data[3];
166 if (otherPinned) {
167 p.x += dx * diff;
168 p.y += dy * diff;
169 } else {
170 p.x += dx * diff * 0.5;
171 p.y += dy * diff * 0.5;
172 }
173 }
174 }
175
176 // Build the spatial hash: each particle atomically claims a global slot and
177 // prepends it to its cell's linked list (overflow-free).
178 if (mode == 2 && pc.data[21] > 0.5) {
179 int nx = int(pc.data[22]);
180 int ny = int(pc.data[23]);
181 if (nx > 0 && ny > 0) {
182 float cellSize = pc.data[20];
183 float bx = pc.data[7];
184 float by = pc.data[8];
185 int cx = clamp(int(floor((p.x - bx) / cellSize)), 0, nx - 1);
186 int cy = clamp(int(floor((p.y - by) / cellSize)), 0, ny - 1);
187 uint cid = uint(cy * nx + cx);
188 uint slot = atomicAdd(cellSlot[0], 1u);
189 if (slot >= n) return;
190 cellNext[slot] = atomicExchange(cellHead[cid], slot);
191 cellItems[slot] = i;
192 }
193 }
194
195 // Particle-level self-collision via the hash (3x3 neighbor cells) or an
196 // O(n) scan fallback when no bounds define a hash grid.
197 if (mode == 3 && pc.data[21] > 0.5 && !pinned) {
198 int nx = int(pc.data[22]);
199 int ny = int(pc.data[23]);
200 if (nx > 0 && ny > 0 && pc.data[20] > 0.0) {
201 float cellSize = pc.data[20];
202 float bx = pc.data[7];
203 float by = pc.data[8];
204 int cx = clamp(int(floor((p.x - bx) / cellSize)), 0, nx - 1);
205 int cy = clamp(int(floor((p.y - by) / cellSize)), 0, ny - 1);
206 for (int oy = -1; oy <= 1; ++oy) {
207 for (int ox = -1; ox <= 1; ++ox) {
208 int ncx = clamp(cx + ox, 0, nx - 1);
209 int ncy = clamp(cy + oy, 0, ny - 1);
210 uint cid = uint(ncy * nx + ncx);
211 uint s = cellHead[cid];
212 while (s != 0xFFFFFFFFu) {
213 separatePair(i, cellItems[s], p);
214 s = cellNext[s];
215 }
216 }
217 }
218 } else {
219 for (uint j = 0u; j < n; ++j) separatePair(i, j, p);
220 }
221 }
222
223 // Axis-aligned bounds with a soft bounce (mirrors the CPU cloth).
224 if (mode == 1 && pc.data[11] > 0.5 && !pinned) {
225 float bx = pc.data[7];
226 float by = pc.data[8];
227 float bw = pc.data[9];
228 float bh = pc.data[10];
229 if (p.x < bx) {
230 float vx = p.x - p.z;
231 p.x = bx;
232 p.z = p.x + vx * 0.35;
233 } else if (p.x > bx + bw) {
234 float vx = p.x - p.z;
235 p.x = bx + bw;
236 p.z = p.x + vx * 0.35;
237 }
238 if (p.y < by) {
239 float vy = p.y - p.w;
240 p.y = by;
241 p.w = p.y + vy * 0.35;
242 } else if (p.y > by + bh) {
243 float vy = p.y - p.w;
244 p.y = by + bh;
245 p.w = p.y + vy * 0.35;
246 }
247 }
248
249 posOut[i] = p;
250}
251)glsl";
252
253} // namespace
254
255ClothGPU::ClothGPU(eve::gpgpu::Gpgpu *gpgpu, int cols, int rows, float spacing, float originX,
256 float originY)
257 : gpgpu_(gpgpu), cols_(cols), rows_(rows), spacing_(spacing), originX_(originX),
258 originY_(originY) {
259 if (!gpgpu_) throw Exception("ClothGPU: Gpgpu module required");
260 if (cols_ < 2 || rows_ < 2) throw Exception("ClothGPU: cols and rows must be >= 2");
261 if (spacing_ <= 0.f) throw Exception("ClothGPU: spacing must be > 0");
262
263 const int count = getParticleCount();
264 posCpu_.assign(static_cast<size_t>(count) * 4, 0.f);
265 flagsCpu_.assign(static_cast<size_t>(count), 0.f);
266 for (int r = 0; r < rows_; ++r) {
267 for (int c = 0; c < cols_; ++c) {
268 const size_t i = static_cast<size_t>(r * cols_ + c);
269 posCpu_[i * 4 + 0] = originX_ + float(c) * spacing_;
270 posCpu_[i * 4 + 1] = originY_ + float(r) * spacing_;
271 posCpu_[i * 4 + 2] = posCpu_[i * 4 + 0];
272 posCpu_[i * 4 + 3] = posCpu_[i * 4 + 1];
273 }
274 }
275 rebuildLinks();
276 // Pin the top row in the CPU flag mirror only; buffers are created next and
277 // uploadInitialState() uploads the flags together with positions/links.
278 for (int c = 0; c < cols_; ++c) flagsCpu_[static_cast<size_t>(c)] = 1.f;
279
280 posBuf_ = gpgpu_->newBuffer(count * int(4 * sizeof(float)), "storage");
281 posBufB_ = gpgpu_->newBuffer(count * int(4 * sizeof(float)), "storage");
282 linkBuf_ = gpgpu_->newBuffer(count * kMaxLinksPerParticle * int(4 * sizeof(float)),
283 "storage");
284 flagBuf_ = gpgpu_->newBuffer(count * int(4 * sizeof(float)), "storage");
285 staging_ = gpgpu_->newBuffer(count * int(4 * sizeof(float)), "staging");
286 if (!posBuf_ || !posBufB_ || !linkBuf_ || !flagBuf_ || !staging_)
287 throw Exception("ClothGPU: failed to allocate storage buffers");
288 uploadInitialState();
289
290 shader_ = gpgpu_->newShader(kClothKernel);
291 if (!shader_) throw Exception("ClothGPU: compute shader compile failed");
292 seq_ = gpgpu_->newSequence();
293 if (!seq_ || !seq_->isAvailable())
294 throw Exception("ClothGPU: command sequence unavailable");
295}
296
298
300 if (destroyed_) return;
301 destroyed_ = true;
302 delete seq_;
303 seq_ = nullptr;
304 delete shader_;
305 shader_ = nullptr;
306 delete posBuf_;
307 posBuf_ = nullptr;
308 delete posBufB_;
309 posBufB_ = nullptr;
310 delete linkBuf_;
311 linkBuf_ = nullptr;
312 delete flagBuf_;
313 flagBuf_ = nullptr;
314 delete staging_;
315 staging_ = nullptr;
316 delete cellHeadBuf_;
317 cellHeadBuf_ = nullptr;
318 delete cellNextBuf_;
319 cellNextBuf_ = nullptr;
320 delete cellItemsBuf_;
321 cellItemsBuf_ = nullptr;
322 delete cellSlotBuf_;
323 cellSlotBuf_ = nullptr;
324}
325
326void ClothGPU::rebuildLinks() {
327 links_.assign(static_cast<size_t>(getParticleCount()) * kMaxLinksPerParticle, Link{});
328 linkCpu_.assign(static_cast<size_t>(getParticleCount()) * kMaxLinksPerParticle * 4, 0.f);
329
330 const auto findFreeSlot = [&](int i) {
331 for (int k = 0; k < kMaxLinksPerParticle; ++k) {
332 Link &s = links_[static_cast<size_t>(i) * kMaxLinksPerParticle + k];
333 if (s.other < 0) return k;
334 }
335 return kMaxLinksPerParticle - 1;
336 };
337 auto add = [&](int r, int c, int nr, int nc) {
338 if (nr < 0 || nc < 0 || nr >= rows_ || nc >= cols_) return;
339 const int i = r * cols_ + c;
340 const int j = nr * cols_ + nc;
341 Link &slot = links_[static_cast<size_t>(i) * kMaxLinksPerParticle +
342 findFreeSlot(i)];
343 slot.other = j;
344 const float dx = (float(nc) - float(c)) * spacing_;
345 const float dy = (float(nr) - float(r)) * spacing_;
346 slot.rest = std::sqrt(dx * dx + dy * dy);
347 };
348
349 for (int r = 0; r < rows_; ++r) {
350 for (int c = 0; c < cols_; ++c) {
351 add(r, c, r, c + 1); // right
352 add(r, c, r, c - 1); // left
353 add(r, c, r + 1, c); // down
354 add(r, c, r - 1, c); // up
355 add(r, c, r + 1, c + 1);
356 add(r, c, r + 1, c - 1);
357 add(r, c, r - 1, c + 1);
358 add(r, c, r - 1, c - 1);
359 add(r, c, r, c + 2); // bend
360 add(r, c, r, c - 2);
361 add(r, c, r + 2, c);
362 add(r, c, r - 2, c);
363 }
364 }
365
366 for (size_t i = 0; i < links_.size(); ++i) {
367 const Link &link = links_[i];
368 linkCpu_[i * 4 + 0] = float(link.other);
369 linkCpu_[i * 4 + 1] = link.rest;
370 linkCpu_[i * 4 + 2] = 0.f;
371 linkCpu_[i * 4 + 3] = 0.f;
372 }
373}
374
375void ClothGPU::uploadInitialState() {
376 const int count = getParticleCount();
377 posBuf_->writeFloat32s(posCpu_.data(), count * 4, 0);
378 posBufB_->writeFloat32s(posCpu_.data(), count * 4, 0);
379 linkBuf_->writeFloat32s(linkCpu_.data(), count * kMaxLinksPerParticle * 4, 0);
380 uploadPinned();
381}
382
383void ClothGPU::uploadPinned() {
384 const int count = getParticleCount();
385 std::vector<float> buf(static_cast<size_t>(count) * 4, 0.f);
386 for (int i = 0; i < count; ++i) buf[static_cast<size_t>(i) * 4] = flagsCpu_[static_cast<size_t>(i)];
387 flagBuf_->writeFloat32s(buf.data(), count * 4, 0);
388}
389
390void ClothGPU::ensureHashBuffers() {
391 const float minDist = std::max(1e-3f, particleSize_ * 2.f);
392 if (!selfCollision_ || !hasBounds_) {
393 if (hashNx_ != 0 || hashNy_ != 0) {
394 delete cellHeadBuf_;
395 cellHeadBuf_ = nullptr;
396 delete cellNextBuf_;
397 cellNextBuf_ = nullptr;
398 delete cellItemsBuf_;
399 cellItemsBuf_ = nullptr;
400 delete cellSlotBuf_;
401 cellSlotBuf_ = nullptr;
402 hashNx_ = hashNy_ = 0;
403 }
404 return;
405 }
406 const int nx = std::max(1, int(std::ceil(boundW_ / minDist)));
407 const int ny = std::max(1, int(std::ceil(boundH_ / minDist)));
408 const int nCells = nx * ny;
409 const int count = getParticleCount();
410 if (nx == hashNx_ && ny == hashNy_) return;
411 delete cellHeadBuf_;
412 cellHeadBuf_ = nullptr;
413 delete cellNextBuf_;
414 cellNextBuf_ = nullptr;
415 delete cellItemsBuf_;
416 cellItemsBuf_ = nullptr;
417 delete cellSlotBuf_;
418 cellSlotBuf_ = nullptr;
419 cellHeadBuf_ = gpgpu_->newBuffer(nCells * int(sizeof(uint32_t)), "storage");
420 cellNextBuf_ = gpgpu_->newBuffer(count * int(sizeof(uint32_t)), "storage");
421 cellItemsBuf_ = gpgpu_->newBuffer(count * int(sizeof(uint32_t)), "storage");
422 cellSlotBuf_ = gpgpu_->newBuffer(int(sizeof(uint32_t)), "storage");
423 hashNx_ = nx;
424 hashNy_ = ny;
425}
426
427void ClothGPU::setGravity(float gx, float gy) {
428 gravityX_ = gx;
429 gravityY_ = gy;
430}
431
432void ClothGPU::setStiffness(float stiffness) {
433 stiffness_ = std::clamp(stiffness, 0.f, 1.f);
434}
435
437 iterations_ = std::max(1, iterations);
438}
439
440void ClothGPU::setDamping(float damping) {
441 damping_ = std::clamp(damping, 0.f, 1.f);
442}
443
445 particleSize_ = std::max(1.f, size);
446}
447
448void ClothGPU::setSelfCollision(bool on) { selfCollision_ = on; }
449
450void ClothGPU::setBounds(float x, float y, float w, float h) {
451 if (w <= 0.f || h <= 0.f) {
452 clearBounds();
453 return;
454 }
455 hasBounds_ = true;
456 boundX_ = x;
457 boundY_ = y;
458 boundW_ = w;
459 boundH_ = h;
460}
461
462void ClothGPU::clearBounds() { hasBounds_ = false; }
463
465 if (index < 0 || index >= getParticleCount())
466 throw Exception("ClothGPU.pin: index out of range");
467 flagsCpu_[static_cast<size_t>(index)] = 1.f;
468 uploadPinned();
469}
470
472 if (index < 0 || index >= getParticleCount())
473 throw Exception("ClothGPU.unpin: index out of range");
474 flagsCpu_[static_cast<size_t>(index)] = 0.f;
475 uploadPinned();
476}
477
479 for (int c = 0; c < cols_; ++c) flagsCpu_[static_cast<size_t>(c)] = 1.f;
480 uploadPinned();
481}
482
483bool ClothGPU::isPinned(int index) const {
484 if (index < 0 || index >= getParticleCount()) return false;
485 return flagsCpu_[static_cast<size_t>(index)] > 0.5f;
486}
487
488void ClothGPU::applyForce(float fx, float fy) {
489 forceX_ += fx;
490 forceY_ += fy;
491}
492
493void ClothGPU::interactAt(float x, float y, float radius, float strength) {
494 interactX_ = x;
495 interactY_ = y;
496 interactRadius_ = std::max(0.f, radius);
497 interactStrength_ = strength;
498}
499
500void ClothGPU::setColor(float r, float g, float b, float a) {
501 colorR_ = r;
502 colorG_ = g;
503 colorB_ = b;
504 colorA_ = a;
505}
506
508 if (index < 0 || index >= getParticleCount()) return 0.f;
509 return posCpu_[static_cast<size_t>(index) * 4 + 0];
510}
511
513 if (index < 0 || index >= getParticleCount()) return 0.f;
514 return posCpu_[static_cast<size_t>(index) * 4 + 1];
515}
516
518 for (int r = 0; r < rows_; ++r) {
519 for (int c = 0; c < cols_; ++c) {
520 const size_t i = static_cast<size_t>(r * cols_ + c);
521 posCpu_[i * 4 + 0] = originX_ + float(c) * spacing_;
522 posCpu_[i * 4 + 1] = originY_ + float(r) * spacing_;
523 posCpu_[i * 4 + 2] = posCpu_[i * 4 + 0];
524 posCpu_[i * 4 + 3] = posCpu_[i * 4 + 1];
525 }
526 }
527 std::fill(flagsCpu_.begin(), flagsCpu_.end(), 0.f);
528 pinTopRow();
529 forceX_ = forceY_ = 0.f;
530 interactStrength_ = 0.f;
531 uploadInitialState();
532}
533
534void ClothGPU::update(float dt) {
535 if (destroyed_) return;
536 if (dt < 0.f) dt = 0.f;
537 if (dt > 0.05f) dt = 0.05f;
538 auto result = stepGpu(dt, 2);
539 result.ignore("legacy ClothGPU::update cannot return a structured result");
540}
541
542eve::Result<void> ClothGPU::stepGpu(float dt, int substeps) {
543 if (destroyed_ || !shader_ || !gpgpu_ || !seq_)
545 "GPU cloth resources are not available",
546 "physics.clothGpu.step"));
547 if (substeps < 1 || substeps > 1024)
549 "GPU cloth substep count must be in [1, 1024]",
550 "physics.clothGpu.step.substeps"));
551
552 const int count = getParticleCount();
553 ensureHashBuffers();
554 const float h = dt / float(substeps);
555 shader_->bindBuffer(2, linkBuf_);
556 shader_->bindBuffer(3, flagBuf_);
557 if (cellHeadBuf_ && cellNextBuf_ && cellItemsBuf_ && cellSlotBuf_) {
558 shader_->bindBuffer(4, cellHeadBuf_);
559 shader_->bindBuffer(5, cellNextBuf_);
560 shader_->bindBuffer(6, cellItemsBuf_);
561 shader_->bindBuffer(7, cellSlotBuf_);
562 }
563 shader_->setFloat(0, h);
564 shader_->setFloat(1, gravityX_);
565 shader_->setFloat(2, gravityY_);
566 shader_->setFloat(3, stiffness_);
567 shader_->setFloat(4, float(iterations_));
568 shader_->setFloat(5, float(count));
569 shader_->setFloat(6, damping_);
570 shader_->setFloat(7, boundX_);
571 shader_->setFloat(8, boundY_);
572 shader_->setFloat(9, boundW_);
573 shader_->setFloat(10, boundH_);
574 shader_->setFloat(11, hasBounds_ ? 1.f : 0.f);
575 shader_->setFloat(12, forceX_);
576 shader_->setFloat(13, forceY_);
577 shader_->setFloat(14, interactX_);
578 shader_->setFloat(15, interactY_);
579 shader_->setFloat(16, interactRadius_);
580 shader_->setFloat(17, interactStrength_);
581 shader_->setFloat(18, float(kMaxLinksPerParticle));
582 shader_->setFloat(20, particleSize_ * 2.f);
583 shader_->setFloat(21, selfCollision_ ? 1.f : 0.f);
584 shader_->setFloat(22, float(hashNx_));
585 shader_->setFloat(23, float(hashNy_));
586 const int nCells = hashNx_ > 0 && hashNy_ > 0 ? hashNx_ * hashNy_ : 0;
587 shader_->setFloat(24, float(nCells));
588
589 const int groups = (count + 63) / 64;
590 // Double-buffered passes: every pass reads posIn and writes posOut, then the
591 // roles swap. Keeps constraint iterations Jacobi-stable (no in-place
592 // feedback between threads). The whole frame is recorded into one Sequence
593 // submission (the Sequence inserts memory barriers between dispatches).
594 GpuBuffer *in = posBuf_;
595 GpuBuffer *out = posBufB_;
596 const auto pass = [&](float mode) {
597 shader_->bindBuffer(0, in);
598 shader_->bindBuffer(1, out);
599 shader_->setFloat(19, mode);
600 seq_->recordDispatch(shader_, groups, 1, 1);
601 std::swap(in, out);
602 };
603 seq_->begin();
604 for (int s = 0; s < substeps; ++s) {
605 pass(0.f); // integrate
606 if (selfCollision_ && nCells > 0) {
607 // Clear counters, build the hash from the freshly integrated
608 // positions, then constraints, then hash-based self-collision.
609 shader_->setFloat(19, 4.f);
610 seq_->recordDispatch(shader_, (nCells + 63) / 64, 1, 1);
611 pass(2.f);
612 }
613 for (int it = 0; it < iterations_; ++it) pass(1.f); // constraints + bounds
614 if (selfCollision_) {
615 for (int sc = 0; sc < 2; ++sc)
616 pass(3.f); // self-collision (hash or O(n²) fallback)
617 }
618 }
619
620 // The pass count is even in both branches, so the result lands in posBuf_.
621 seq_->recordDownload(posBuf_, staging_, uint64_t(count) * 4 * sizeof(float));
622 seq_->submit();
623 staging_->downloadBytes(posCpu_.data(), uint64_t(count) * 4 * sizeof(float));
624 forceX_ = 0.f;
625 forceY_ = 0.f;
626 interactStrength_ = 0.f;
628}
629
631 if (destroyed_)
633 eve::DiagnosticCode::PreconditionViolation, "Cannot step a destroyed GPU cloth", "physics.clothGpu.step"));
634 auto valid = detail::validateSimulationStep(stepValue, settings, observation_);
635 if (!valid) return valid;
636 auto next = detail::advanceSimulationObservation(observation_, stepValue);
637 if (!next) return eve::Result<void>::failure(next.status());
638 try {
639 auto applied = stepGpu(static_cast<float>(stepValue.delta.seconds()), settings.subStepCount);
640 if (!applied) return applied;
641 } catch (const std::exception &error) {
643 std::string("GPU cloth step failed: ") + error.what(),
644 "physics.clothGpu.step"));
645 } catch (...) {
647 eve::DiagnosticCode::Failed, "GPU cloth step failed with an unknown exception", "physics.clothGpu.step"));
648 }
649 observation_ = std::move(next).takeValue();
651}
652
654 auto valid = detail::validateSimulationObservation(observation, "physics.clothGpu.restoreObservation");
655 if (!valid) return valid;
656 if (destroyed_)
658 "Cannot restore a destroyed GPU cloth",
659 "physics.clothGpu.restoreObservation"));
660 observation_ = observation;
662}
663
665 if (!gfx || destroyed_) return;
666 const Color linkColor(colorR_, colorG_, colorB_, colorA_ * 0.75f);
667 const Color nodeColor(colorR_, colorG_, colorB_, colorA_);
668 const int count = getParticleCount();
669 for (int i = 0; i < count; ++i) {
670 for (int k = 0; k < kMaxLinksPerParticle; ++k) {
671 const Link &link = links_[static_cast<size_t>(i) * kMaxLinksPerParticle + k];
672 if (link.other < 0) break;
673 const float x1 = posCpu_[static_cast<size_t>(i) * 4 + 0];
674 const float y1 = posCpu_[static_cast<size_t>(i) * 4 + 1];
675 const float x2 = posCpu_[static_cast<size_t>(link.other) * 4 + 0];
676 const float y2 = posCpu_[static_cast<size_t>(link.other) * 4 + 1];
677 const float dx = x2 - x1;
678 const float dy = y2 - y1;
679 const float len = std::sqrt(dx * dx + dy * dy);
680 const int steps = std::max(1, int(len / 4.f));
681 for (int s = 0; s <= steps; ++s) {
682 const float t = float(s) / float(steps);
683 gfx->drawSolidRect(x1 + dx * t - 1.f, y1 + dy * t - 1.f, 2.f, 2.f, linkColor);
684 }
685 }
686 }
687 for (int i = 0; i < count; ++i) {
688 const float s = flagsCpu_[static_cast<size_t>(i)] > 0.5f
689 ? std::max(5.f, particleSize_ + 2.f)
690 : particleSize_;
691 gfx->drawSolidRect(posCpu_[static_cast<size_t>(i) * 4 + 0] - s * 0.5f,
692 posCpu_[static_cast<size_t>(i) * 4 + 1] - s * 0.5f, s, s,
693 nodeColor);
694 }
695}
696
697} // namespace eve::physics
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
float nx
float ny
int rows
int cols
tensor::Graph g
Definition GpuGraph.cpp:7
double r
std::int32_t c
int h
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
uint32_t groups
Definition OnnxGpgpu.cpp:39
std::string error
Definition Package.cpp:60
float radius
int steps
float t
float dy
float dx
std::uint32_t count
TerrainThermalSettings settings
int spacing
int iterations
Definition TreeMesh.cpp:311
float size
Definition TreeMesh.cpp:156
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Definition Time.cpp:28
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
virtual void setFloat(int index, float value)=0
Sets the float.
virtual void bindBuffer(int binding, GpuBuffer *buffer)=0
Bind a storage buffer to set=0 binding. binding in [0, kMaxBindings).
GPGPU module — compute shaders + storage buffers via the active Graphics backend. Uses the graphics q...
Definition Gpgpu.h:23
ComputeShader * newShader(const std::string &source)
Compatibility-only raw-owning shader factory (Vulkan: GLSL; WebGPU: WGSL). Vulkan delegates to the ch...
Definition Gpgpu.cpp:324
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Definition Gpgpu.cpp:372
Sequence * newSequence()
Create a Kompute-style command Sequence: record buffer transfers and compute dispatches into one comm...
Definition Gpgpu.cpp:384
Backend-agnostic GPU buffer for compute (storage) or CPU staging transfers. Squirrel-owned; derived c...
Definition GpuBuffer.h:18
virtual void writeFloat32s(const float *data, int count, int startIndex=0)=0
Bulk float upload/download (one transfer). startIndex is in floats.
virtual void downloadBytes(void *dst, uint64_t nbytes, uint64_t srcOffset=0) const =0
Downloads bytes.
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Record download.
Definition Sequence.cpp:82
void submit()
Submit.
Definition Sequence.cpp:92
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Record dispatch.
Definition Sequence.cpp:87
void begin()
Begins begin.
Definition Sequence.cpp:75
bool isAvailable() const
True when available.
Definition Sequence.cpp:66
virtual void drawSolidRect(float x, float y, float w, float h, float r, float g, float b, float a=1.f)
RGBA-float overload matching the script-facing drawSolidRect name.
void draw(graphics::Graphics *gfx)
Draw links + particles from the latest GPU readback.
Definition ClothGPU.cpp:664
void unpin(int index)
Unpin.
Definition ClothGPU.cpp:471
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings) override
Advances the production GPU cloth with the shared ticked contract.
Definition ClothGPU.cpp:630
float getParticleY(int index) const
Returns the particle y.
Definition ClothGPU.cpp:512
void pinTopRow()
Pin top row.
Definition ClothGPU.cpp:478
void setColor(float r, float g, float b, float a=1.f)
Sets the color.
Definition ClothGPU.cpp:500
int getParticleCount() const
Returns the particle count.
Definition ClothGPU.h:155
void setSelfCollision(bool on)
Enable proximity-based self-collision between non-adjacent particles Default is false....
Definition ClothGPU.cpp:448
eve::Result< void > restoreObservation(const SimulationObservation &observation) override
Restores tick/progress metadata after an owner-level restore.
Definition ClothGPU.cpp:653
void setDamping(float damping)
Damping applied to Verlet velocity [0,1] (default 0.01).
Definition ClothGPU.cpp:440
void update(float dt)
Updates .
Definition ClothGPU.cpp:534
void setBounds(float x, float y, float w, float h)
Axis-aligned walls; particles are clamped (with a small bounce).
Definition ClothGPU.cpp:450
void pin(int index)
Pin.
Definition ClothGPU.cpp:464
void setParticleSize(float size)
Particle draw size in pixels (default 3).
Definition ClothGPU.cpp:444
bool supportsFeature(const std::string &feature) const
Query a stable cloth feature name; unsupported features never silently fall back.
Definition ClothGPU.cpp:18
static constexpr int kMaxLinksPerParticle
Definition ClothGPU.h:33
void setIterations(int iterations)
Constraint solver iterations per substep (default 4).
Definition ClothGPU.cpp:436
void setGravity(float gx, float gy)
Sets the gravity.
Definition ClothGPU.cpp:427
void applyForce(float fx, float fy)
Applies force.
Definition ClothGPU.cpp:488
bool isPinned(int index) const
True when pinned.
Definition ClothGPU.cpp:483
void reset()
Restore the flat grid pose (top row pinned) and re-upload state.
Definition ClothGPU.cpp:517
float getParticleX(int index) const
Returns the particle x.
Definition ClothGPU.cpp:507
void destroy()
Destroys .
Definition ClothGPU.cpp:299
void clearBounds()
Clears bounds.
Definition ClothGPU.cpp:462
void interactAt(float x, float y, float radius, float strength)
Pointer-field interaction like Fluid2D::interactAt: positive strength attracts, negative repels withi...
Definition ClothGPU.cpp:493
ClothGPU(eve::gpgpu::Gpgpu *gpgpu, int cols, int rows, float spacing, float originX, float originY)
Cloth gpu.
Definition ClothGPU.cpp:255
SimulationObservation observation() const noexcept override
Returns completed tick/time observables.
Definition ClothGPU.h:59
void setStiffness(float stiffness)
Constraint relaxation strength in [0,1] (default 0.85).
Definition ClothGPU.cpp:432
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
glm::vec4 Color
Render-neutral RGBA color shared by graphics-facing modules.
Definition RenderTypes.h:8
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Duration delta
Fixed simulation duration for this step.
Definition Time.h:162
Observable backend progress shared by CPU and accelerator providers.
Validated solver policy for one simulation step.
gpgpu::Gpgpu * gpgpu