载入中...
搜索中...
未找到
Cloth.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "graphics/Graphics.h"
5#include "graphics/Canvas.h"
6#include "physics/Body.h"
7#include "physics/World.h"
8
9#include <algorithm>
10#include <cmath>
11#include <exception>
12#include <utility>
13
14namespace eve::physics {
15
16// Color lives in eve::graphics (see graphics/Canvas.h); keep the unqualified form.
18
19namespace {
20constexpr float kPi = 3.14159265358979323846f;
21} // namespace
22
23Cloth::Cloth(int cols, int rows, float spacing, float originX, float originY)
24 : cols_(cols), rows_(rows), spacing_(spacing), originX_(originX), originY_(originY) {
25 if (cols_ < 2 || rows_ < 2)
26 throw Exception("Cloth: cols and rows must be >= 2");
27 if (spacing_ <= 0.f)
28 throw Exception("Cloth: spacing must be > 0");
29
30 particles_.resize(static_cast<size_t>(cols_ * rows_));
31 for (int r = 0; r < rows_; ++r) {
32 for (int c = 0; c < cols_; ++c) {
33 Particle &p = particles_[static_cast<size_t>(r * cols_ + c)];
34 p.x = p.px = originX_ + float(c) * spacing_;
35 p.y = p.py = originY_ + float(r) * spacing_;
36 p.pinned = false;
37 }
38 }
39 rebuildLinks();
40 pinTopRow();
41}
42
44
45void Cloth::destroy() { destroyed_ = true; }
46
48 if (cols_ < 2 || rows_ < 2) return;
49 particles_.clear();
50 particles_.resize(static_cast<size_t>(cols_ * rows_));
51 for (int r = 0; r < rows_; ++r) {
52 for (int c = 0; c < cols_; ++c) {
53 Particle &p = particles_[static_cast<size_t>(r * cols_ + c)];
54 p.x = p.px = originX_ + float(c) * spacing_;
55 p.y = p.py = originY_ + float(r) * spacing_;
56 p.pinned = false;
57 }
58 }
59 rebuildLinks();
60 buildLinkKeys();
61 pinTopRow();
62 grabIndex_ = -1;
63 forceX_ = forceY_ = 0.f;
64 interactStrength_ = 0.f;
65}
66
67void Cloth::rebuildLinks() {
68 links_.clear();
69 auto add = [&](int a, int b) {
70 if (a < 0 || b < 0 || a >= getParticleCount() || b >= getParticleCount()) return;
71 Link link;
72 link.a = a;
73 link.b = b;
74 const Particle &pa = particles_[static_cast<size_t>(a)];
75 const Particle &pb = particles_[static_cast<size_t>(b)];
76 const float dx = pb.x - pa.x;
77 const float dy = pb.y - pa.y;
78 link.rest = std::sqrt(dx * dx + dy * dy);
79 if (link.rest > 1e-4f) links_.push_back(link);
80 };
81
82 for (int r = 0; r < rows_; ++r) {
83 for (int c = 0; c < cols_; ++c) {
84 const int i = r * cols_ + c;
85 // Structural
86 if (c + 1 < cols_) add(i, i + 1);
87 if (r + 1 < rows_) add(i, i + cols_);
88 // Shear
89 if (c + 1 < cols_ && r + 1 < rows_) add(i, i + cols_ + 1);
90 if (c > 0 && r + 1 < rows_) add(i, i + cols_ - 1);
91 // Bend (every other)
92 if (c + 2 < cols_) add(i, i + 2);
93 if (r + 2 < rows_) add(i, i + cols_ * 2);
94 }
95 }
96 buildLinkKeys();
97}
98
99void Cloth::buildLinkKeys() {
100 linkKeys_.clear();
101 for (const Link &link : links_) {
102 const int a = link.a;
103 const int b = link.b;
104 const int lo = std::min(a, b);
105 const int hi = std::max(a, b);
106 linkKeys_.insert((int64_t(lo) << 32) | int64_t(hi));
107 }
108}
109
110bool Cloth::areLinked(int a, int b) const {
111 if (a == b) return true;
112 const int lo = std::min(a, b);
113 const int hi = std::max(a, b);
114 return linkKeys_.find((int64_t(lo) << 32) | int64_t(hi)) != linkKeys_.end();
115}
116
117bool Cloth::validIndex(int index) const {
118 return index >= 0 && index < getParticleCount();
119}
120
121void Cloth::setGravity(float gx, float gy) {
122 gravityX_ = gx;
123 gravityY_ = gy;
124}
125
126void Cloth::setStiffness(float stiffness) {
127 stiffness_ = std::clamp(stiffness, 0.f, 1.f);
128}
129
131 iterations_ = std::max(1, iterations);
132}
133
134void Cloth::setDamping(float damping) {
135 damping_ = std::clamp(damping, 0.f, 1.f);
136}
137
139 particleSize_ = std::max(1.f, size);
140}
141
142void Cloth::setParticleMass(float mass) {
143 particleMass_ = std::max(1e-4f, mass);
144}
145
146void Cloth::setSelfCollision(bool on) { selfCollision_ = on; }
147
149 foldStiffness_ = std::clamp(k, 0.f, 1.f);
150}
151
153 maxFoldAngle_ = std::clamp(degrees, 0.f, 180.f) * kPi / 180.f;
154}
155
156void Cloth::setBounds(float x, float y, float w, float h) {
157 if (w <= 0.f || h <= 0.f) {
158 clearBounds();
159 return;
160 }
161 hasBounds_ = true;
162 boundX_ = x;
163 boundY_ = y;
164 boundW_ = w;
165 boundH_ = h;
166}
167
168void Cloth::clearBounds() { hasBounds_ = false; }
169
170void Cloth::pin(int index) {
171 if (!validIndex(index)) throw Exception("Cloth.pin: index out of range");
172 particles_[static_cast<size_t>(index)].pinned = true;
173}
174
176 if (!validIndex(index)) throw Exception("Cloth.unpin: index out of range");
177 particles_[static_cast<size_t>(index)].pinned = false;
178}
179
181 for (int c = 0; c < cols_; ++c)
182 particles_[static_cast<size_t>(c)].pinned = true;
183}
184
185bool Cloth::isPinned(int index) const {
186 if (!validIndex(index)) return false;
187 return particles_[static_cast<size_t>(index)].pinned;
188}
189
190int Cloth::grabAt(float x, float y, float radius) {
191 grabIndex_ = -1;
192 float best = radius * radius;
193 for (int i = 0; i < getParticleCount(); ++i) {
194 const Particle &p = particles_[static_cast<size_t>(i)];
195 if (p.pinned) continue;
196 const float dx = p.x - x;
197 const float dy = p.y - y;
198 const float d2 = dx * dx + dy * dy;
199 if (d2 <= best) {
200 best = d2;
201 grabIndex_ = i;
202 }
203 }
204 if (grabIndex_ >= 0) moveGrab(x, y);
205 return grabIndex_;
206}
207
208void Cloth::moveGrab(float x, float y) {
209 grabX_ = x;
210 grabY_ = y;
211 if (!validIndex(grabIndex_)) return;
212 Particle &p = particles_[static_cast<size_t>(grabIndex_)];
213 p.x = x;
214 p.y = y;
215 p.px = x;
216 p.py = y;
217}
218
219void Cloth::releaseGrab() { grabIndex_ = -1; }
220
221void Cloth::applyForce(float fx, float fy) {
222 forceX_ += fx;
223 forceY_ += fy;
224}
225
226void Cloth::interactAt(float x, float y, float radius, float strength) {
227 interactX_ = x;
228 interactY_ = y;
229 interactRadius_ = std::max(0.f, radius);
230 interactStrength_ = strength;
231}
232
234
235int64_t Cloth::cellKey(int cx, int cy) const {
236 return (int64_t(uint32_t(cx)) << 32) | int64_t(uint32_t(cy));
237}
238
239void Cloth::rebuildHash() {
240 hash_.clear();
241 const float cell = std::max(1e-3f, particleSize_ * 2.f);
242 const float inv = 1.f / cell;
243 for (int i = 0; i < getParticleCount(); ++i) {
244 const Particle &p = particles_[static_cast<size_t>(i)];
245 const int cx = int(std::floor(p.x * inv));
246 const int cy = int(std::floor(p.y * inv));
247 hash_[cellKey(cx, cy)].push_back(i);
248 }
249}
250
251void Cloth::solveSelfCollision() {
252 if (!selfCollision_ || getParticleCount() == 0) return;
253 const float minDist = particleSize_ * 2.f;
254 if (minDist <= 0.f) return;
255 rebuildHash();
256 const float cell = std::max(1e-3f, minDist);
257 const float inv = 1.f / cell;
258 for (int i = 0; i < getParticleCount(); ++i) {
259 Particle &pi = particles_[static_cast<size_t>(i)];
260 const int cx = int(std::floor(pi.x * inv));
261 const int cy = int(std::floor(pi.y * inv));
262 for (int oy = -1; oy <= 1; ++oy) {
263 for (int ox = -1; ox <= 1; ++ox) {
264 auto it = hash_.find(cellKey(cx + ox, cy + oy));
265 if (it == hash_.end()) continue;
266 for (int j : it->second) {
267 if (j <= i) continue;
268 if (areLinked(i, j)) continue;
269 Particle &pj = particles_[static_cast<size_t>(j)];
270 float dx = pj.x - pi.x;
271 float dy = pj.y - pi.y;
272 const float d2 = dx * dx + dy * dy;
273 if (d2 >= minDist * minDist || d2 < 1e-8f) continue;
274 const float d = std::sqrt(d2);
275 // Clamp the per-iteration correction so near-coincident
276 // particles cannot explode apart in one substep.
277 const float corr = std::min(0.5f, (minDist - d) / d);
278 if (pi.pinned && pj.pinned) continue;
279 float wa = 0.5f;
280 float wb = 0.5f;
281 // Pinned particles stay fixed; the free partner takes it all.
282 if (pi.pinned && !pj.pinned) { wa = 0.f; wb = 1.f; }
283 else if (pj.pinned && !pi.pinned) { wa = 1.f; wb = 0.f; }
284 pi.x -= dx * corr * wa;
285 pi.y -= dy * corr * wa;
286 pj.x += dx * corr * wb;
287 pj.y += dy * corr * wb;
288 }
289 }
290 }
291 }
292}
293
294void Cloth::solveFoldConstraint() {
295 if (foldStiffness_ <= 0.f || maxFoldAngle_ >= kPi) return;
296 // Bend deviation at each interior vertex along rows and columns.
297 const float maxDev = maxFoldAngle_;
298 const auto straighten = [&](int mid, int a, int b) {
299 if (mid < 0 || a < 0 || b < 0 || mid >= getParticleCount() || a >= getParticleCount() ||
300 b >= getParticleCount()) {
301 return;
302 }
303 Particle &pm = particles_[static_cast<size_t>(mid)];
304 if (pm.pinned) return;
305 const Particle &pa = particles_[static_cast<size_t>(a)];
306 const Particle &pb = particles_[static_cast<size_t>(b)];
307 float v1x = pa.x - pm.x, v1y = pa.y - pm.y;
308 float v2x = pb.x - pm.x, v2y = pb.y - pm.y;
309 const float l1 = std::sqrt(v1x * v1x + v1y * v1y);
310 const float l2 = std::sqrt(v2x * v2x + v2y * v2y);
311 if (l1 < 1e-6f || l2 < 1e-6f) return;
312 v1x /= l1; v1y /= l1;
313 v2x /= l2; v2y /= l2;
314 const float dot = std::clamp(v1x * v2x + v1y * v2y, -1.f, 1.f);
315 const float angle = std::acos(dot); // pi = straight
316 const float dev = kPi - angle; // deviation from straight
317 if (dev <= maxDev) return;
318 // Project the middle particle onto the fold-limit cone: move it toward
319 // the midpoint until the deviation equals maxDev (binary search).
320 const float mx = (pa.x + pb.x) * 0.5f;
321 const float my = (pa.y + pb.y) * 0.5f;
322 float lo = 0.f;
323 float hi = 1.f;
324 for (int it = 0; it < 10; ++it) {
325 const float t = (lo + hi) * 0.5f;
326 const float qx = pm.x + (mx - pm.x) * t;
327 const float qy = pm.y + (my - pm.y) * t;
328 const float d1x = pa.x - qx, d1y = pa.y - qy;
329 const float d2x = pb.x - qx, d2y = pb.y - qy;
330 const float s1 = std::sqrt(d1x * d1x + d1y * d1y);
331 const float s2 = std::sqrt(d2x * d2x + d2y * d2y);
332 if (s1 < 1e-6f || s2 < 1e-6f) break;
333 const float c = std::clamp((d1x * d2x + d1y * d2y) / (s1 * s2), -1.f, 1.f);
334 const float devT = kPi - std::acos(c);
335 if (devT > maxDev)
336 lo = t;
337 else
338 hi = t;
339 }
340 const float f = hi * foldStiffness_;
341 pm.x += (mx - pm.x) * f;
342 pm.y += (my - pm.y) * f;
343 };
344 for (int r = 0; r < rows_; ++r) {
345 for (int c = 1; c + 1 < cols_; ++c) {
346 const int mid = r * cols_ + c;
347 straighten(mid, mid - 1, mid + 1);
348 }
349 }
350 for (int c = 0; c < cols_; ++c) {
351 for (int r = 1; r + 1 < rows_; ++r) {
352 const int mid = r * cols_ + c;
353 straighten(mid, mid - cols_, mid + cols_);
354 }
355 }
356}
357
358void Cloth::collideWorld(float dt) {
359 if (!world_ || !world_->isValid() || particleSize_ <= 0.f) return;
360 const float invDt = dt > 1e-6f ? 1.f / dt : 0.f;
361 World::ClothContact contact;
362 for (int i = 0; i < getParticleCount(); ++i) {
363 Particle &p = particles_[static_cast<size_t>(i)];
364 if (p.pinned) continue;
365 if (!world_->pointProbe(p.x, p.y, particleSize_, &contact) || !contact.hit) continue;
366 // Particle velocity (Verlet) before the push.
367 const float vpx = (p.x - p.px) * invDt;
368 const float vpy = (p.y - p.py) * invDt;
369 p.x += contact.nx * contact.depth;
370 p.y += contact.ny * contact.depth;
371
372 // Mass-proportional momentum exchange along the contact normal. Static and
373 // kinematic bodies act as infinite mass; dynamic bodies take -(1+e)*v_rel
374 // scaled by the reduced mass of the particle/body pair.
375 float vbx = 0.f;
376 float vby = 0.f;
377 float bodyMass = 0.f;
378 const bool dynamic =
379 contact.body != nullptr && contact.body->getType() == "dynamic";
380 if (dynamic) {
381 vbx = contact.body->getLinearVelocityX();
382 vby = contact.body->getLinearVelocityY();
383 bodyMass = contact.body->getMass();
384 }
385 const float vn = (vpx - vbx) * contact.nx + (vpy - vby) * contact.ny;
386 if (vn < 0.f) {
387 constexpr float restitution = 0.15f;
388 const float m = particleMass_;
389 const float reduced = bodyMass > 0.f ? (m * bodyMass) / (m + bodyMass) : m;
390 float j = -(1.f + restitution) * vn * reduced;
391 // Cap the particle velocity kick to keep the solver stable.
392 const float maxKick = 900.f; // pixels/s
393 j = std::min(j, maxKick * m);
394 const float kick = (j / m) * dt;
395 p.x += contact.nx * kick;
396 p.y += contact.ny * kick;
397 if (dynamic && bodyMass > 0.f) {
398 contact.body->applyLinearImpulse(-contact.nx * j, -contact.ny * j);
399 }
400 }
401 }
402}
403
404void Cloth::setColor(float r, float g, float b, float a) {
405 colorR_ = r;
406 colorG_ = g;
407 colorB_ = b;
408 colorA_ = a;
409}
410
411float Cloth::getParticleX(int index) const {
412 if (!validIndex(index)) return 0.f;
413 return particles_[static_cast<size_t>(index)].x;
414}
415
416float Cloth::getParticleY(int index) const {
417 if (!validIndex(index)) return 0.f;
418 return particles_[static_cast<size_t>(index)].y;
419}
420
421void Cloth::setParticlePosition(int index, float x, float y) {
422 if (!validIndex(index)) throw Exception("Cloth.setParticlePosition: index out of range");
423 Particle &p = particles_[static_cast<size_t>(index)];
424 p.x = p.px = x;
425 p.y = p.py = y;
426}
427
428void Cloth::integrate(float dt) {
429 if (dt <= 0.f) return;
430 const float ax = gravityX_ + forceX_;
431 const float ay = gravityY_ + forceY_;
432 const float damp = 1.f - damping_;
433 const bool hasInteract = interactRadius_ > 0.f && interactStrength_ != 0.f;
434
435 for (Particle &p : particles_) {
436 if (p.pinned) {
437 p.px = p.x;
438 p.py = p.y;
439 continue;
440 }
441 const float vx = (p.x - p.px) * damp;
442 const float vy = (p.y - p.py) * damp;
443 p.px = p.x;
444 p.py = p.y;
445 p.x += vx + ax * dt * dt;
446 p.y += vy + ay * dt * dt;
447 if (hasInteract) {
448 const float dx = interactX_ - p.x;
449 const float dy = interactY_ - p.y;
450 const float r2 = dx * dx + dy * dy;
451 const float R2 = interactRadius_ * interactRadius_;
452 if (r2 < R2 && r2 > 1e-6f) {
453 const float r = std::sqrt(r2);
454 const float w = 1.f - r / interactRadius_;
455 p.x += (dx / r) * interactStrength_ * w * dt * dt;
456 p.y += (dy / r) * interactStrength_ * w * dt * dt;
457 }
458 }
459 // Cap the per-substep displacement so dense pile-ups cannot accumulate
460 // unbounded kinetic energy (mirrors Fluid2D::update's maxSpeed clamp).
461 constexpr float maxSpeed = 900.f; // pixels/s
462 const float maxDisp = maxSpeed * dt;
463 const float dvx = p.x - p.px;
464 const float dvy = p.y - p.py;
465 const float v2 = dvx * dvx + dvy * dvy;
466 if (v2 > maxDisp * maxDisp) {
467 const float s = maxDisp / std::sqrt(v2);
468 p.px = p.x - dvx * s;
469 p.py = p.y - dvy * s;
470 }
471 }
472}
473
474void Cloth::solveConstraints() {
475 for (int iter = 0; iter < iterations_; ++iter) {
476 for (const Link &link : links_) {
477 Particle &a = particles_[static_cast<size_t>(link.a)];
478 Particle &b = particles_[static_cast<size_t>(link.b)];
479 if (a.pinned && b.pinned) continue;
480 float dx = b.x - a.x;
481 float dy = b.y - a.y;
482 float dist = std::sqrt(dx * dx + dy * dy);
483 if (dist < 1e-5f) continue;
484 const float diff = (dist - link.rest) / dist * stiffness_;
485 // When one end is pinned, apply the full correction to the free end.
486 if (a.pinned) {
487 b.x -= dx * diff;
488 b.y -= dy * diff;
489 } else if (b.pinned) {
490 a.x += dx * diff;
491 a.y += dy * diff;
492 } else {
493 const float half = diff * 0.5f;
494 a.x += dx * half;
495 a.y += dy * half;
496 b.x -= dx * half;
497 b.y -= dy * half;
498 }
499 }
500 if (validIndex(grabIndex_)) {
501 Particle &g = particles_[static_cast<size_t>(grabIndex_)];
502 g.x = grabX_;
503 g.y = grabY_;
504 g.px = grabX_;
505 g.py = grabY_;
506 }
507 }
508}
509
510void Cloth::collideBounds() {
511 if (!hasBounds_) return;
512 const float minX = boundX_;
513 const float minY = boundY_;
514 const float maxX = boundX_ + boundW_;
515 const float maxY = boundY_ + boundH_;
516 constexpr float bounce = 0.35f;
517
518 for (Particle &p : particles_) {
519 if (p.pinned) continue;
520 if (p.x < minX) {
521 const float vx = p.x - p.px;
522 p.x = minX;
523 p.px = p.x + vx * bounce;
524 } else if (p.x > maxX) {
525 const float vx = p.x - p.px;
526 p.x = maxX;
527 p.px = p.x + vx * bounce;
528 }
529 if (p.y < minY) {
530 const float vy = p.y - p.py;
531 p.y = minY;
532 p.py = p.y + vy * bounce;
533 } else if (p.y > maxY) {
534 const float vy = p.y - p.py;
535 p.y = maxY;
536 p.py = p.y + vy * bounce;
537 }
538 }
539}
540
541void Cloth::update(float dt) {
542 if (destroyed_) return;
543 if (dt < 0.f) dt = 0.f;
544 if (dt > 0.05f) dt = 0.05f;
545
546 updateSubsteps(dt, 2);
547}
548
549void Cloth::updateSubsteps(float dt, int substeps) {
550 if (destroyed_ || substeps < 1) return;
551
552 // Fixed substeps for stability under hitch frames.
553 const float h = dt / float(substeps);
554 for (int s = 0; s < substeps; ++s) {
555 integrate(h);
556 solveConstraints();
557 solveFoldConstraint();
558 solveSelfCollision();
559 collideWorld(h);
560 collideBounds();
561 if (grabIndex_ >= 0) {
562 Particle &p = particles_[static_cast<size_t>(grabIndex_)];
563 // Preserve grab target set by moveGrab (px/py already synced there).
564 p.px = p.x;
565 p.py = p.y;
566 }
567 }
568 forceX_ = 0.f;
569 forceY_ = 0.f;
570 interactStrength_ = 0.f;
571}
572
574 if (destroyed_)
576 eve::DiagnosticCode::PreconditionViolation, "Cannot step a destroyed cloth", "physics.cloth.step"));
577 auto valid = detail::validateSimulationStep(stepValue, settings, observation_);
578 if (!valid) return valid;
579 auto next = detail::advanceSimulationObservation(observation_, stepValue);
580 if (!next) return eve::Result<void>::failure(next.status());
581 try {
582 updateSubsteps(static_cast<float>(stepValue.delta.seconds()), settings.subStepCount);
583 } catch (const std::exception &error) {
585 eve::DiagnosticCode::Failed, std::string("Cloth step failed: ") + error.what(), "physics.cloth.step"));
586 } catch (...) {
588 eve::DiagnosticCode::Failed, "Cloth step failed with an unknown exception", "physics.cloth.step"));
589 }
590 observation_ = std::move(next).takeValue();
592}
593
595 auto valid = detail::validateSimulationObservation(observation, "physics.cloth.restoreObservation");
596 if (!valid) return valid;
597 if (destroyed_)
599 "Cannot restore a destroyed cloth",
600 "physics.cloth.restoreObservation"));
601 observation_ = observation;
603}
604
606 if (!gfx || destroyed_) return;
607 const Color linkColor(colorR_, colorG_, colorB_, colorA_ * 0.75f);
608 const Color nodeColor(colorR_, colorG_, colorB_, colorA_);
609
610 for (const Link &link : links_) {
611 const Particle &a = particles_[static_cast<size_t>(link.a)];
612 const Particle &b = particles_[static_cast<size_t>(link.b)];
613 const float x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y;
614 const float dx = x2 - x1;
615 const float dy = y2 - y1;
616 const float len = std::sqrt(dx * dx + dy * dy);
617 const int steps = std::max(1, int(len / 4.f));
618 for (int i = 0; i <= steps; ++i) {
619 const float t = float(i) / float(steps);
620 gfx->drawSolidRect(x1 + dx * t - 1.f, y1 + dy * t - 1.f, 2.f, 2.f, linkColor);
621 }
622 }
623 for (const Particle &p : particles_) {
624 const float s = p.pinned ? std::max(5.f, particleSize_ + 2.f) : particleSize_;
625 gfx->drawSolidRect(p.x - s * 0.5f, p.y - s * 0.5f, s, s, nodeColor);
626 }
627 if (grabIndex_ >= 0) {
628 const Particle &p = particles_[static_cast<size_t>(grabIndex_)];
629 gfx->drawSolidRect(p.x - 6.f, p.y - 6.f, 12.f, 12.f,
630 Color(1.f, 0.85f, 0.35f, 0.9f));
631 }
632}
633
634} // namespace eve::physics
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int mid
Definition AnimSmr.cpp:120
const std::string & s
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float degrees
Definition CardTypes.cpp:35
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
glm::vec4 p[6]
int rows
int cols
tensor::Graph g
Definition GpuGraph.cpp:7
double r
std::int32_t second
std::int32_t c
eve::ResourcePin pinned
int h
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
float f
World3D * world
float radius
float d
int steps
float t
float dy
float dx
double restitution
Cell cell
std::map< Cell, int > best
TerrainThermalSettings settings
int spacing
int iterations
Definition TreeMesh.cpp:311
float size
Definition TreeMesh.cpp:156
uint32_t index
double oy
double ox
float m[16]
float vy
float qy
float vx
float qx
float angle
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 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 setParticlePosition(int index, float x, float y)
Sets the particle position.
Definition Cloth.cpp:421
void setMaxFoldAngle(float degrees)
Maximum bend deviation from a straight row/column segment in degrees Range is 0..180 degrees; default...
Definition Cloth.cpp:152
eve::Result< void > restoreObservation(const SimulationObservation &observation) override
Restores tick/progress metadata after an owner-level restore.
Definition Cloth.cpp:594
void setBounds(float x, float y, float w, float h)
Axis-aligned walls; particles bounce inside. Disabled if w/h <= 0.
Definition Cloth.cpp:156
void setParticleSize(float size)
Particle radius used for self-collision, draw size and body collision Default is 3....
Definition Cloth.cpp:138
void setStiffness(float stiffness)
Constraint relaxation strength in [0,1] (default 0.85).
Definition Cloth.cpp:126
void clearBounds()
Clears bounds.
Definition Cloth.cpp:168
void setDamping(float damping)
Damping applied to Verlet velocity [0,1] (default 0.01).
Definition Cloth.cpp:134
float getParticleX(int index) const
Returns the particle x.
Definition Cloth.cpp:411
void reset()
Restore the flat grid pose (top row pinned) and clear transient state.
Definition Cloth.cpp:47
void interactAt(float x, float y, float radius, float strength)
Pointer-field interaction like Fluid2D::interactAt: positive strength attracts, negative repels....
Definition Cloth.cpp:226
void setParticleMass(float mass)
Implicit particle mass in kg (default 0.1). Used for mass-proportional momentum exchange when collidi...
Definition Cloth.cpp:142
bool isPinned(int index) const
True when pinned.
Definition Cloth.cpp:185
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings) override
Advances cloth with the shared ticked backend contract.
Definition Cloth.cpp:573
float getParticleY(int index) const
Returns the particle y.
Definition Cloth.cpp:416
void setGravity(float gx, float gy)
Sets the gravity.
Definition Cloth.cpp:121
int getParticleCount() const
Returns the particle count.
Definition Cloth.h:208
void moveGrab(float x, float y)
Moves grab.
Definition Cloth.cpp:208
void update(float dt)
Updates .
Definition Cloth.cpp:541
void setColor(float r, float g, float b, float a=1.f)
Sets the color.
Definition Cloth.cpp:404
Cloth(int cols, int rows, float spacing, float originX, float originY)
Cloth.
Definition Cloth.cpp:23
void setCollideWorld(World *world)
Attach a 2D World so free particles collide with its non-sensor fixtures in pixel space....
Definition Cloth.cpp:233
void pin(int index)
Pin.
Definition Cloth.cpp:170
void destroy()
Destroys .
Definition Cloth.cpp:45
void setSelfCollision(bool on)
Enable proximity-based self-collision between non-adjacent particles Default is true.
Definition Cloth.cpp:146
void applyForce(float fx, float fy)
Applies force.
Definition Cloth.cpp:221
void draw(graphics::Graphics *gfx)
Draws .
Definition Cloth.cpp:605
SimulationObservation observation() const noexcept override
Returns completed tick/time observables.
Definition Cloth.h:52
void setIterations(int iterations)
Constraint solver iterations per substep (default 4).
Definition Cloth.cpp:130
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:190
void releaseGrab()
Release grab.
Definition Cloth.cpp:219
void setFoldStiffness(float k)
Strength of the fold-angle clamp [0,1] (default 0.5).
Definition Cloth.cpp:148
void unpin(int index)
Unpin.
Definition Cloth.cpp:175
void pinTopRow()
Pin top row.
Definition Cloth.cpp:180
Box2D world wrapper (2D physics) with pixel-space coordinates. Handles stepping, gravity,...
Definition World.h:41
bool pointProbe(float x, float y, float radius, ClothContact *out) const
Probe the deepest non-sensor fixture within radius of a pixel point. Returns false when nothing is hi...
Definition World.cpp:308
bool isValid() const
True while the underlying Box2D world is alive.
Definition World.h:306
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
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
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.