24constexpr float kPi = 3.14159265358979323846f;
26int64_t pairKey(
int lo,
int hi) {
return (int64_t(lo) << 32) | int64_t(hi); }
30 float x = 0.f,
y = 0.f,
z = 0.f;
33V3 sub(
const V3&
a,
const V3&
b) {
return {
a.x -
b.x,
a.y -
b.y,
a.z -
b.z}; }
34V3 add(
const V3&
a,
const V3&
b) {
return {
a.x +
b.x,
a.y +
b.y,
a.z +
b.z}; }
35V3 mul(
float s,
const V3&
a) {
return {
s *
a.x,
s *
a.y,
s *
a.z}; }
36V3 fma(
const V3&
a,
float s,
const V3&
b) {
return {
a.x *
s +
b.x,
a.y *
s +
b.y,
a.z *
s +
b.z}; }
37float dot(
const V3&
a,
const V3&
b) {
return a.x *
b.x +
a.y *
b.y +
a.z *
b.z; }
38V3
cross(
const V3&
a,
const V3&
b) {
return {
a.y *
b.z -
a.z *
b.y,
a.z *
b.x -
a.x *
b.z,
a.x *
b.y -
a.y *
b.x}; }
39float vlen(
const V3&
a) {
return std::sqrt(
dot(
a,
a)); }
40V3 vnorm(
const V3&
a) {
41 const float l = vlen(
a);
42 return l > 1e-8f ? mul(1.f / l,
a) : V3{0.f, 1.f, 0.f};
46V3 rotateAround(
const V3&
v,
const V3& axis,
float angle) {
47 const float c = std::cos(
angle);
48 const float sn = std::sin(
angle);
49 const V3 cv =
cross(axis,
v);
50 const float d =
dot(axis,
v);
51 return add(add(mul(
c,
v), mul(sn, cv)), mul(
d * (1.f -
c), axis));
55float signedAngle(
const V3&
a,
const V3&
b,
const V3&
u) {
return std::atan2(
dot(
cross(
a,
b),
u),
dot(
a,
b)); }
58V3 closestPointSegment(
const V3&
a,
const V3&
b,
const V3&
p) {
59 const V3
ab = sub(
b,
a);
61 if (len2 < 1e-12f)
return a;
62 const float t = std::clamp(
dot(sub(
p,
a),
ab) / len2, 0.f, 1.f);
67V3 closestPointTriangle(
const V3&
a,
const V3&
b,
const V3&
c,
const V3&
p) {
68 const V3
ab = sub(
b,
a);
69 const V3
ac = sub(
c,
a);
70 const V3 ap = sub(
p,
a);
71 const float d1 =
dot(
ab, ap);
72 const float d2 =
dot(
ac, ap);
73 if (d1 <= 0.f && d2 <= 0.f)
return a;
75 const V3 bp = sub(
p,
b);
76 const float d3 =
dot(
ab, bp);
77 const float d4 =
dot(
ac, bp);
78 if (d3 >= 0.f && d4 <= d3)
return b;
80 const float vc = d1 * d4 - d3 * d2;
81 if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f) {
82 const float t = d1 / (d1 - d3);
86 const V3 cp = sub(
p,
c);
87 const float d5 =
dot(
ab, cp);
88 const float d6 =
dot(
ac, cp);
89 if (d6 >= 0.f && d5 <= d6)
return c;
91 const float vb = d5 * d2 - d1 * d6;
92 if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f) {
93 const float t = d2 / (d2 - d6);
97 const float va = d3 * d6 - d5 * d4;
98 if (va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) {
99 const float t = (d4 - d3) / ((d4 - d3) + (d5 - d6));
100 return fma(sub(
c,
b),
t,
b);
103 const float denom = 1.f / (va + vb + vc);
104 const float v = vb * denom;
105 const float w = vc * denom;
106 return add(fma(
ab,
v,
a), mul(
w,
ac));
110float closestPointSegments(
const V3& p1,
const V3& p2,
const V3& q1,
const V3& q2, V3& cp1, V3& cp2) {
111 const V3 d1 = sub(p2, p1);
112 const V3 d2 = sub(q2, q1);
113 const V3
r = sub(p1, q1);
114 const float a =
dot(d1, d1);
115 const float e =
dot(d2, d2);
116 const float f =
dot(d2,
r);
119 if (
a <= 1e-12f && e <= 1e-12f) {
121 }
else if (
a <= 1e-12f) {
123 t = std::clamp(
f / e, 0.f, 1.f);
125 const float c =
dot(d1,
r);
128 s = std::clamp(-
c /
a, 0.f, 1.f);
130 const float b =
dot(d1, d2);
131 const float denom =
a * e -
b *
b;
132 s = denom > 1e-12f ? std::clamp((
b *
f -
c * e) / denom, 0.f, 1.f) : 0.f;
136 s = std::clamp(-
c /
a, 0.f, 1.f);
137 }
else if (
t > 1.f) {
139 s = std::clamp((
b -
c) /
a, 0.f, 1.f);
143 cp1 = fma(d1,
s, p1);
144 cp2 = fma(d2,
t, q1);
145 return vlen(sub(cp1, cp2));
148bool pointInTriangle(
const V3&
p,
const V3&
a,
const V3&
b,
const V3&
c) {
150 const V3 pa =
cross(sub(
b,
a), sub(
p,
a));
151 const V3 pb =
cross(sub(
c,
b), sub(
p,
b));
152 const V3 pc =
cross(sub(
a,
c), sub(
p,
c));
153 return dot(pa,
n) >= -1e-8f &&
dot(pb,
n) >= -1e-8f &&
dot(pc,
n) >= -1e-8f;
158float triangleDistance(
const V3* A,
const V3* B,
int featA[3],
int featB[3],
int& countA,
int& countB, V3& cpA,
160 float best = std::numeric_limits<float>::max();
162 for (
int i = 0; i < 3; ++i) {
163 const V3 cp = closestPointTriangle(B[0], B[1], B[2], A[i]);
164 const float d = vlen(sub(A[i], cp));
177 for (
int j = 0; j < 3; ++j) {
178 const V3 cp = closestPointTriangle(A[0], A[1], A[2], B[j]);
179 const float d = vlen(sub(B[j], cp));
192 const int aEdges[3][2] = {{0, 1}, {1, 2}, {2, 0}};
193 const int bEdges[3][2] = {{0, 1}, {1, 2}, {2, 0}};
194 for (
const auto& ea : aEdges) {
195 for (
const auto& eb : bEdges) {
198 const float d = closestPointSegments(A[ea[0]], A[ea[1]], B[eb[0]], B[eb[1]], cp1, cp2);
217 : cols_(
cols), rows_(
rows), spacing_(
spacing), originX_(originX), originY_(originY), originZ_(originZ) {
220 throw Exception(
"%s",
model.error() ?
model.error()->message().c_str() :
"Cloth3D grid model creation failed");
221 initializeFromModel(
model.value());
231 particles_ = restParticles_;
233 forceX_ = forceY_ = forceZ_ = 0.f;
234 interactStrength_ = 0.f;
237void Cloth3D::rebuildLinks() {
239 auto add = [&](
int a,
int b) {
244 const Particle& pa = particles_[
static_cast<size_t>(
a)];
245 const Particle& pb = particles_[
static_cast<size_t>(
b)];
246 const float dx = pb.x - pa.x;
247 const float dy = pb.y - pa.y;
248 const float dz = pb.z - pa.z;
250 if (link.rest > 1e-4f) links_.push_back(link);
253 for (
int r = 0;
r < rows_; ++
r) {
254 for (
int c = 0;
c < cols_; ++
c) {
255 const int i =
r * cols_ +
c;
257 if (
c + 1 < cols_) add(i, i + 1);
258 if (
r + 1 < rows_) add(i, i + cols_);
260 if (
c + 1 < cols_ &&
r + 1 < rows_) add(i, i + cols_ + 1);
261 if (
c > 0 &&
r + 1 < rows_) add(i, i + cols_ - 1);
263 if (
c + 2 < cols_) add(i, i + 2);
264 if (
r + 2 < rows_) add(i, i + cols_ * 2);
270void Cloth3D::buildLinkKeys() {
272 for (
const Link& link : links_) {
273 const int lo = std::min(link.a, link.b);
274 const int hi = std::max(link.a, link.b);
275 linkKeys_.insert(pairKey(lo, hi));
279bool Cloth3D::areLinked(
int a,
int b)
const {
280 if (
a ==
b)
return true;
281 const int lo = std::min(
a,
b);
282 const int hi = std::max(
a,
b);
283 return linkKeys_.find(pairKey(lo, hi)) != linkKeys_.end();
286void Cloth3D::rebuildTriangles() {
291 std::unordered_map<int64_t, std::vector<int>> edgeTris;
292 auto addTri = [&](
int a,
int b,
int c, std::unordered_map<int64_t, std::vector<int>>& map) {
297 const int idx =
static_cast<int>(triangles_.size());
299 for (
const auto& e :
edges) {
300 map[pairKey(std::min(e[0], e[1]), std::max(e[0], e[1]))].push_back(
idx);
302 triangles_.push_back(tri);
305 for (
int r = 0;
r + 1 < rows_; ++
r) {
306 for (
int c = 0;
c + 1 < cols_; ++
c) {
307 const int a =
r * cols_ +
c;
308 const int b =
r * cols_ +
c + 1;
309 const int cc = (
r + 1) * cols_ +
c + 1;
310 const int d = (
r + 1) * cols_ +
c;
311 addTri(
a,
d,
b, edgeTris);
312 addTri(
d, cc,
b, edgeTris);
316 for (
const auto& entry : edgeTris) {
317 if (entry.second.size() != 2)
continue;
318 const Tri& t0 = triangles_[
static_cast<size_t>(entry.second[0])];
319 const Tri& t1 = triangles_[
static_cast<size_t>(entry.second[1])];
320 const int e0 = int(entry.first >> 32);
321 const int e1 =
static_cast<int>(
static_cast<uint32_t
>(entry.first));
325 if (
v != e0 &&
v != e1) {
332 if (
v != e0 &&
v != e1) {
337 if (k < 0 || l < 0)
continue;
343 foldPairs_.push_back(pair);
358 stretchCompliance_ = std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f;
362 shearCompliance_ = std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f;
366 bendCompliance_ = std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f;
378 cols_ =
model.gridCols();
379 rows_ =
model.gridRows();
380 spacing_ =
model.gridSpacing();
381 if (!
model.particles().empty()) {
382 originX_ =
model.particles().front().x;
383 originY_ =
model.particles().front().y;
384 originZ_ =
model.particles().front().z;
387 particles_.reserve(
model.particles().size());
388 for (
const ClothModelParticle&
source :
model.particles()) {
390 particle.x = particle.px =
source.x;
391 particle.y = particle.py =
source.y;
392 particle.z = particle.pz =
source.z;
393 particle.inverseMass =
source.inverseMass;
394 particle.pinned =
source.inverseMass == 0.f;
395 particles_.push_back(particle);
397 restParticles_ = particles_;
400 links_.reserve(
model.distanceConstraints().size());
401 for (
const ClothModelDistanceConstraint&
source :
model.distanceConstraints())
405 triangles_.reserve(
model.triangles().size());
410 foldPairs_.reserve(
model.foldConstraints().size());
411 for (
const ClothModelFoldConstraint&
source :
model.foldConstraints())
414 tethers_.reserve(
model.tetherConstraints().size());
415 for (
const ClothModelTetherConstraint&
source :
model.tetherConstraints())
417 restVolume_ =
model.restVolume();
434 airDensity_ = std::max(0.f, airDensity);
435 dragCoefficient_ = std::max(0.f, dragCoefficient);
436 liftCoefficient_ = std::max(0.f, liftCoefficient);
452 if (
w <= 0.f ||
h <= 0.f ||
d <= 0.f) {
468 collisionFriction_ = std::isfinite(friction) ? std::clamp(friction, 0.f, 1.f) : 0.f;
473 collisionCategoryBits_ = categoryBits;
474 collisionMaskBits_ = maskBits;
478 if (!validIndex(
index))
throw Exception(
"Cloth3D.pin: index out of range");
479 Particle& particle = particles_[
static_cast<size_t>(
index)];
480 particle.pinned =
true;
481 particle.inverseMass = 0.f;
485 if (!validIndex(
index))
throw Exception(
"Cloth3D.unpin: index out of range");
486 Particle& particle = particles_[
static_cast<size_t>(
index)];
487 particle.pinned =
false;
488 particle.inverseMass = 1.f;
492 for (
int c = 0;
c < cols_; ++
c)
pin(
c);
496 if (!validIndex(
index))
return false;
497 return particles_[
static_cast<size_t>(
index)].
pinned;
501 if (!validIndex(
index))
throw Exception(
"Cloth3D.setParticleInverseMass: index out of range");
502 Particle& particle = particles_[
static_cast<size_t>(
index)];
503 particle.inverseMass = std::isfinite(inverseMass) ? std::max(0.f, inverseMass) : 0.f;
504 particle.pinned = particle.inverseMass == 0.f;
508 return validIndex(
index) ? particles_[
static_cast<size_t>(
index)].inverseMass : 0.f;
515 const Particle&
p = particles_[
static_cast<size_t>(i)];
516 if (
p.pinned)
continue;
517 const float dx =
p.x -
x;
518 const float dy =
p.y -
y;
519 const float dz =
p.z -
z;
534 if (!validIndex(grabIndex_))
return;
535 Particle&
p = particles_[
static_cast<size_t>(grabIndex_)];
556 interactRadius_ = std::max(0.f,
radius);
570 if (!validIndex(
index))
return 0.f;
571 return particles_[
static_cast<size_t>(
index)].
x;
575 if (!validIndex(
index))
return 0.f;
576 return particles_[
static_cast<size_t>(
index)].
y;
580 if (!validIndex(
index))
return 0.f;
581 return particles_[
static_cast<size_t>(
index)].
z;
585 if (!validIndex(
index))
throw Exception(
"Cloth3D.setParticlePosition: index out of range");
586 Particle&
p = particles_[
static_cast<size_t>(
index)];
592int64_t Cloth3D::cellKey(
int cx,
int cy,
int cz)
const {
594 return int64_t(uint32_t(
cx) & 0x1fffffu) | (int64_t(uint32_t(
cy) & 0x1fffffu) << 21) |
595 (int64_t(uint32_t(cz) & 0x1fffffu) << 42);
598void Cloth3D::rebuildHash() {
600 const float cell = std::max(1e-3f, particleSize_ * 2.f);
601 const float inv = 1.f /
cell;
603 const Particle&
p = particles_[
static_cast<size_t>(i)];
604 const int cx = int(std::floor(
p.x * inv));
605 const int cy = int(std::floor(
p.y * inv));
606 const int cz = int(std::floor(
p.z * inv));
607 hash_[cellKey(
cx,
cy, cz)].push_back(i);
611void Cloth3D::integrate(
float dt) {
612 if (dt <= 0.f)
return;
613 const float ax = gravityX_ + forceX_;
614 const float ay = gravityY_ + forceY_;
615 const float az = gravityZ_ + forceZ_;
616 const float damp = 1.f - damping_;
617 const bool hasInteract = interactRadius_ > 0.f && interactStrength_ != 0.f;
619 std::vector<float> aerodynamicAcceleration;
620 if (airDensity_ > 0.f && (dragCoefficient_ > 0.f || liftCoefficient_ > 0.f)) {
621 aerodynamicAcceleration.assign(particles_.size() * 3, 0.f);
622 accumulateAerodynamicAcceleration(dt, aerodynamicAcceleration);
625 for (
size_t particleIndex = 0; particleIndex < particles_.size(); ++particleIndex) {
626 Particle&
p = particles_[particleIndex];
633 const float vx = (
p.x -
p.px) * damp;
634 const float vy = (
p.y -
p.py) * damp;
635 const float vz = (
p.z -
p.pz) * damp;
639 const float aeroX = aerodynamicAcceleration.empty() ? 0.f : aerodynamicAcceleration[particleIndex * 3];
640 const float aeroY = aerodynamicAcceleration.empty() ? 0.f : aerodynamicAcceleration[particleIndex * 3 + 1];
641 const float aeroZ = aerodynamicAcceleration.empty() ? 0.f : aerodynamicAcceleration[particleIndex * 3 + 2];
642 p.x +=
vx + (
ax + aeroX) * dt * dt;
643 p.y +=
vy + (
ay + aeroY) * dt * dt;
644 p.z +=
vz + (
az + aeroZ) * dt * dt;
646 const float dx = interactX_ -
p.x;
647 const float dy = interactY_ -
p.y;
648 const float dz = interactZ_ -
p.z;
650 const float R2 = interactRadius_ * interactRadius_;
651 if (r2 < R2 && r2 > 1e-6f) {
652 const float r = std::sqrt(r2);
653 const float w = 1.f -
r / interactRadius_;
654 const float a = interactStrength_ *
w * dt * dt;
660 constexpr float maxSpeed = 9.f;
661 const float maxDisp = maxSpeed * dt;
662 const float dvx =
p.x -
p.px;
663 const float dvy =
p.y -
p.py;
664 const float dvz =
p.z -
p.pz;
665 const float v2 = dvx * dvx + dvy * dvy + dvz * dvz;
666 if (v2 > maxDisp * maxDisp) {
667 const float s = maxDisp / std::sqrt(v2);
668 p.px =
p.x - dvx *
s;
669 p.py =
p.y - dvy *
s;
670 p.pz =
p.z - dvz *
s;
675void Cloth3D::accumulateAerodynamicAcceleration(
float dt, std::vector<float>& accelerations)
const {
676 const float invDt = dt > 1e-6f ? 1.f / dt : 0.f;
677 const float invMass = 1.f / particleMass_;
678 for (
const Tri& tri : triangles_) {
679 const Particle&
a = particles_[
static_cast<size_t>(tri.v[0])];
680 const Particle&
b = particles_[
static_cast<size_t>(tri.v[1])];
681 const Particle&
c = particles_[
static_cast<size_t>(tri.v[2])];
683 const V3
ab{
b.x -
a.x,
b.y -
a.y,
b.z -
a.z};
684 const V3
ac{
c.x -
a.x,
c.y -
a.y,
c.z -
a.z};
686 const float twiceArea = vlen(areaNormal);
687 if (twiceArea < 1e-8f)
continue;
688 V3
normal = mul(1.f / twiceArea, areaNormal);
690 const V3 clothVelocity{((
a.x -
a.px) + (
b.x -
b.px) + (
c.x -
c.px)) * (invDt / 3.f),
691 ((
a.y -
a.py) + (
b.y -
b.py) + (
c.y -
c.py)) * (invDt / 3.f),
692 ((
a.z -
a.pz) + (
b.z -
b.pz) + (
c.z -
c.pz)) * (invDt / 3.f)};
693 const V3 relativeWind{windVelocityX_ - clothVelocity.x, windVelocityY_ - clothVelocity.y,
694 windVelocityZ_ - clothVelocity.z};
695 const float speed = vlen(relativeWind);
696 if (speed < 1e-5f)
continue;
699 if (incidence < 0.f) {
701 incidence = -incidence;
703 if (incidence < 1e-5f)
continue;
705 const float dynamicPressureArea = 0.5f * airDensity_ * speed * speed * (twiceArea * 0.5f);
706 const V3 drag = mul(dragCoefficient_ * incidence * dynamicPressureArea,
windDirection);
708 const float liftLength = vlen(liftDirection);
709 if (liftLength > 1e-6f)
710 liftDirection = mul(1.f / liftLength, liftDirection);
712 liftDirection = V3{0.f, 0.f, 0.f};
713 const V3 lift = mul(liftCoefficient_ * incidence * dynamicPressureArea, liftDirection);
714 const V3 acceleration = mul(invMass / 3.f, add(drag, lift));
716 for (
int vertex : tri.
v) {
717 if (particles_[
static_cast<size_t>(vertex)].
pinned)
continue;
718 accelerations[
static_cast<size_t>(vertex) * 3] += acceleration.x;
719 accelerations[
static_cast<size_t>(vertex) * 3 + 1] += acceleration.y;
720 accelerations[
static_cast<size_t>(vertex) * 3 + 2] += acceleration.z;
725void Cloth3D::solveConstraints(
float dt) {
726 for (Link& link : links_) link.lambda = 0.f;
727 for (Tether& tether : tethers_) tether.lambda = 0.f;
728 for (SkinConstraint& skin : skinConstraints_) skin.lambda = 0.f;
729 for (Attachment& attachment : attachments_) attachment.lambda = 0.f;
731 const float invDtSquared = dt > 1e-8f ? 1.f / (dt * dt) : 0.f;
732 for (
int iter = 0; iter < iterations_; ++iter) {
733 for (Link& link : links_) {
734 Particle&
a = particles_[
static_cast<size_t>(link.a)];
735 Particle&
b = particles_[
static_cast<size_t>(link.b)];
736 if (
a.pinned &&
b.pinned)
continue;
737 float dx =
b.x -
a.x;
738 float dy =
b.y -
a.y;
739 float dz =
b.z -
a.z;
740 const float dist = std::sqrt(
dx *
dx +
dy *
dy +
dz *
dz);
741 if (dist < 1e-5f)
continue;
742 float compliance = stretchCompliance_;
744 compliance = shearCompliance_;
746 compliance = bendCompliance_;
747 if (compliance > 0.f) {
748 const float weightA =
a.pinned ? 0.f :
a.inverseMass;
749 const float weightB =
b.pinned ? 0.f :
b.inverseMass;
750 const float alpha = compliance * invDtSquared;
751 const float deltaLambda = (-(dist - link.rest) - alpha * link.lambda) / (weightA + weightB + alpha);
752 link.lambda += deltaLambda;
753 const float nx =
dx / dist;
754 const float ny =
dy / dist;
755 const float nz =
dz / dist;
756 a.x -= weightA *
nx * deltaLambda;
757 a.y -= weightA *
ny * deltaLambda;
758 a.z -= weightA *
nz * deltaLambda;
759 b.x += weightB *
nx * deltaLambda;
760 b.y += weightB *
ny * deltaLambda;
761 b.z += weightB *
nz * deltaLambda;
764 const float diff = (dist - link.rest) / dist * stiffness_;
769 }
else if (
b.pinned) {
774 const float half = diff * 0.5f;
783 solveTetherConstraints(dt);
784 solveVolumeConstraint(dt);
785 solveSkinConstraints(dt);
786 solveAttachments(dt);
787 if (validIndex(grabIndex_)) {
788 Particle&
g = particles_[
static_cast<size_t>(grabIndex_)];
799void Cloth3D::solveFoldConstraint() {
800 if (foldStiffness_ <= 0.f || maxFoldAngle_ >= kPi || foldPairs_.empty())
return;
806 const float thetaMin = kPi - maxFoldAngle_;
807 const float s = foldStiffness_;
808 for (
const FoldPair& pair : foldPairs_) {
809 Particle& pa = particles_[
static_cast<size_t>(pair.a)];
810 Particle& pb = particles_[
static_cast<size_t>(pair.b)];
811 Particle& pk = particles_[
static_cast<size_t>(pair.k)];
812 Particle& pl = particles_[
static_cast<size_t>(pair.l)];
813 if (pa.pinned && pb.pinned && pk.pinned && pl.pinned)
continue;
815 const V3 paV{pa.x, pa.y, pa.z};
816 const V3 pbV{pb.x, pb.y, pb.z};
817 V3 pkV{pk.x, pk.y, pk.z};
818 V3 plV{pl.x, pl.y, pl.z};
820 const V3
u = vnorm(sub(pbV, paV));
821 const V3 vk = sub(pkV, paV);
822 const V3 vl = sub(plV, paV);
823 const V3 h1 = sub(vk, mul(
dot(vk,
u),
u));
824 const V3 h2 = sub(vl, mul(
dot(vl,
u),
u));
825 const float b1 = vlen(h1);
826 const float b2 = vlen(h2);
827 if (b1 < 1e-6f || b2 < 1e-6f)
continue;
828 const V3 m1 = mul(1.f / b1, h1);
829 const V3 m2 = mul(1.f / b2, h2);
830 const float theta = std::acos(std::clamp(
dot(m1, m2), -1.f, 1.f));
831 if (theta >= thetaMin)
continue;
836 const float phi = signedAngle(m1, m2,
u);
837 const float sign =
phi >= 0.f ? 1.f : -1.f;
838 const float delta = thetaMin - theta;
839 const float a1 = -sign * delta * 0.5f *
s;
840 const float a2 = sign * delta * 0.5f *
s;
842 pkV = add(paV, rotateAround(vk,
u, a1 *
s));
848 plV = add(paV, rotateAround(vl,
u, a2 *
s));
856void Cloth3D::solveSelfCollision() {
858 const float minDist = particleSize_ * 2.f;
859 if (minDist <= 0.f)
return;
861 const float cell = std::max(1e-3f, minDist);
862 const float inv = 1.f /
cell;
864 Particle& pi = particles_[
static_cast<size_t>(i)];
865 const int cx = int(std::floor(pi.x * inv));
866 const int cy = int(std::floor(pi.y * inv));
867 const int cz = int(std::floor(pi.z * inv));
868 for (
int oz = -1; oz <= 1; ++oz) {
869 for (
int oy = -1;
oy <= 1; ++
oy) {
870 for (
int ox = -1;
ox <= 1; ++
ox) {
871 auto it = hash_.find(cellKey(
cx +
ox,
cy +
oy, cz + oz));
872 if (it == hash_.end())
continue;
873 for (
int j : it->
second) {
874 if (j <= i)
continue;
875 if (areLinked(i, j))
continue;
876 Particle& pj = particles_[
static_cast<size_t>(j)];
877 float dx = pj.x - pi.x;
878 float dy = pj.y - pi.y;
879 float dz = pj.z - pi.z;
881 if (d2 >= minDist * minDist || d2 < 1e-8f)
continue;
882 if (pi.pinned && pj.pinned)
continue;
883 const float d = std::sqrt(d2);
884 const float corr = std::min(0.5f, (minDist -
d) /
d);
887 if (pi.pinned && !pj.pinned) {
890 }
else if (pj.pinned && !pi.pinned) {
894 pi.x -=
dx * corr * wa;
895 pi.y -=
dy * corr * wa;
896 pi.z -=
dz * corr * wa;
897 pj.x +=
dx * corr * wb;
898 pj.y +=
dy * corr * wb;
899 pj.z +=
dz * corr * wb;
907void Cloth3D::solveSelfCollisionTriangles() {
908 if (!selfCollision_ || triangles_.size() < 2)
return;
909 const float thickness = std::max(1e-3f, particleSize_ * 2.f);
910 const int triCount =
static_cast<int>(triangles_.size());
912 std::vector<V3>
pos(particles_.size());
913 for (
size_t i = 0; i < particles_.size(); ++i) {
914 pos[i] = {particles_[i].x, particles_[i].y, particles_[i].z};
918 const float inv = 1.f /
cell;
919 std::unordered_map<int64_t, std::vector<int>> triHash;
920 auto triCellKey = [](
int cx,
int cy,
int cz) -> int64_t {
921 return int64_t(uint32_t(
cx) & 0x1fffffu) | (int64_t(uint32_t(
cy) & 0x1fffffu) << 21) |
922 (int64_t(uint32_t(cz) & 0x1fffffu) << 42);
924 const auto insertTri = [&](
int ti) {
925 const Tri& tri = triangles_[
static_cast<size_t>(ti)];
926 V3 mn =
pos[
static_cast<size_t>(tri.v[0])];
928 for (
int k = 1; k < 3; ++k) {
929 const V3&
p =
pos[
static_cast<size_t>(tri.v[k])];
930 mn.x = std::min(mn.x,
p.x);
931 mn.y = std::min(mn.y,
p.y);
932 mn.z = std::min(mn.z,
p.z);
933 mx.x = std::max(mx.x,
p.x);
934 mx.y = std::max(mx.y,
p.y);
935 mx.z = std::max(mx.z,
p.z);
940 const int x0 = int(std::floor(mn.x * inv));
941 const int y0 = int(std::floor(mn.y * inv));
942 const int z0 = int(std::floor(mn.z * inv));
943 const int x1 = int(std::floor(mx.x * inv));
944 const int y1 = int(std::floor(mx.y * inv));
945 const int z1 = int(std::floor(mx.z * inv));
946 for (
int cz = z0; cz <= z1; ++cz)
947 for (
int cy = y0;
cy <= y1; ++
cy)
948 for (
int cx = x0;
cx <= x1; ++
cx) triHash[triCellKey(
cx,
cy, cz)].push_back(ti);
950 for (
int ti = 0; ti < triCount; ++ti) insertTri(ti);
952 const auto sharesVertex = [&](
const Tri&
a,
const Tri&
b) {
953 for (
int i = 0; i < 3; ++i)
954 for (
int j = 0; j < 3; ++j)
955 if (
a.v[i] ==
b.v[j])
return true;
959 std::unordered_set<int64_t> processed;
960 std::vector<int>
cells;
964 for (
int i = 0; i < triCount; ++i) {
965 const Tri& triI = triangles_[
static_cast<size_t>(i)];
966 const V3& pa0 =
pos[
static_cast<size_t>(triI.v[0])];
969 for (
int k = 1; k < 3; ++k) {
970 const V3&
p =
pos[
static_cast<size_t>(triI.v[k])];
971 mn.x = std::min(mn.x,
p.x);
972 mn.y = std::min(mn.y,
p.y);
973 mn.z = std::min(mn.z,
p.z);
974 mx.x = std::max(mx.x,
p.x);
975 mx.y = std::max(mx.y,
p.y);
976 mx.z = std::max(mx.z,
p.z);
981 const int x0 = int(std::floor(mn.x * inv));
982 const int y0 = int(std::floor(mn.y * inv));
983 const int z0 = int(std::floor(mn.z * inv));
984 const int x1 = int(std::floor(mx.x * inv));
985 const int y1 = int(std::floor(mx.y * inv));
986 const int z1 = int(std::floor(mx.z * inv));
988 for (
int cz = z0; cz <= z1; ++cz)
989 for (
int cy = y0;
cy <= y1; ++
cy)
990 for (
int cx = x0;
cx <= x1; ++
cx) {
991 auto it = triHash.find(triCellKey(
cx,
cy, cz));
992 if (it != triHash.end())
cells.insert(
cells.end(), it->second.begin(), it->second.end());
994 for (
int j :
cells) {
995 if (j <= i)
continue;
996 const int64_t
key = pairKey(i, j);
997 if (!processed.insert(
key).second)
continue;
998 const Tri& triJ = triangles_[
static_cast<size_t>(j)];
999 if (sharesVertex(triI, triJ))
continue;
1001 const V3
A[3] = {
pos[
static_cast<size_t>(triI.v[0])],
pos[
static_cast<size_t>(triI.v[1])],
1002 pos[
static_cast<size_t>(triI.v[2])]};
1003 const V3
B[3] = {
pos[
static_cast<size_t>(triJ.v[0])],
pos[
static_cast<size_t>(triJ.v[1])],
1004 pos[
static_cast<size_t>(triJ.v[2])]};
1009 const float d = triangleDistance(A, B, featA, featB, countA, countB, cpA, cpB);
1012 V3
n = vnorm(sub(cpB, cpA));
1013 if (vlen(sub(cpB, cpA)) < 1e-6f) {
1014 n = vnorm(
cross(sub(B[1], B[0]), sub(B[2], B[0])));
1016 const float corr = (
thickness -
d) * stiffness_;
1017 if (corr <= 0.f)
continue;
1019 const auto freeCount = [&](
const int* feat,
int count) {
1021 for (
int k = 0; k <
count; ++k)
1022 if (!particles_[
static_cast<size_t>(feat[k])].pinned) ++free;
1025 const int freeA = freeCount(featA, countA);
1026 const int freeB = freeCount(featB, countB);
1027 if (freeA == 0 && freeB == 0)
continue;
1028 const float wA = freeA > 0 ? (freeB > 0 ? 0.5f : 1.f) : 0.f;
1029 const float wB = freeB > 0 ? (freeA > 0 ? 0.5f : 1.f) : 0.f;
1030 const auto pushFeature = [&](
const int* feat,
int count,
const V3&
dir) {
1031 for (
int k = 0; k <
count; ++k) {
1032 Particle& pv = particles_[
static_cast<size_t>(feat[k])];
1033 if (pv.pinned)
continue;
1040 const V3
dir = mul(-corr * wA /
float(freeA),
n);
1041 pushFeature(featA, countA,
dir);
1044 const V3
dir = mul(corr * wB /
float(freeB),
n);
1045 pushFeature(featB, countB,
dir);
1052 for (
int i = 0; i < triCount; ++i) {
1053 const Tri& triI = triangles_[
static_cast<size_t>(i)];
1054 const V3
A[3] = {
pos[
static_cast<size_t>(triI.v[0])],
pos[
static_cast<size_t>(triI.v[1])],
1055 pos[
static_cast<size_t>(triI.v[2])]};
1056 const V3 nI = vnorm(
cross(sub(A[1], A[0]), sub(A[2], A[0])));
1057 for (
int j = i + 1; j < triCount; ++j) {
1058 const Tri& triJ = triangles_[
static_cast<size_t>(j)];
1059 if (sharesVertex(triI, triJ))
continue;
1060 const V3
B[3] = {
pos[
static_cast<size_t>(triJ.v[0])],
pos[
static_cast<size_t>(triJ.v[1])],
1061 pos[
static_cast<size_t>(triJ.v[2])]};
1062 const V3 nJ = vnorm(
cross(sub(B[1], B[0]), sub(B[2], B[0])));
1064 for (
int k = 0; k < 3; ++k) {
1065 Particle& pv = particles_[
static_cast<size_t>(triI.v[k])];
1066 if (pv.pinned)
continue;
1068 const float d =
dot(sub(va, B[0]), nJ);
1070 const V3
q = add(va, mul(-
d, nJ));
1071 if (!pointInTriangle(
q, B[0], B[1], B[2]))
continue;
1072 const float push = (
thickness - std::fabs(
d)) * stiffness_ * 0.5f;
1073 const V3
dir = mul(
d >= 0.f ? push : -push, nJ);
1079 for (
int k = 0; k < 3; ++k) {
1080 Particle& pv = particles_[
static_cast<size_t>(triJ.v[k])];
1081 if (pv.pinned)
continue;
1083 const float d =
dot(sub(vb, A[0]), nI);
1085 const V3
q = add(vb, mul(-
d, nI));
1086 if (!pointInTriangle(
q, A[0], A[1], A[2]))
continue;
1087 const float push = (
thickness - std::fabs(
d)) * stiffness_ * 0.5f;
1088 const V3
dir = mul(
d >= 0.f ? push : -push, nI);
1097void Cloth3D::collideWorld(
float dt) {
1098 if (!world_ || !world_->
isValid() || particleSize_ <= 0.f)
return;
1099 const float invDt = dt > 1e-6f ? 1.f / dt : 0.f;
1100 ClothContact3D contact;
1102 Particle&
p = particles_[
static_cast<size_t>(i)];
1103 if (
p.pinned)
continue;
1108 const float vpx = (
p.x -
p.px) * invDt;
1109 const float vpy = (
p.y -
p.py) * invDt;
1110 const float vpz = (
p.z -
p.pz) * invDt;
1111 p.x += contact.nx * contact.depth;
1112 p.y += contact.ny * contact.depth;
1113 p.z += contact.nz * contact.depth;
1118 float bodyMass = 0.f;
1119 const bool dynamic = contact.body !=
nullptr && contact.body->getType() ==
"dynamic";
1121 vbx = contact.body->getLinearVelocityX();
1122 vby = contact.body->getLinearVelocityY();
1123 vbz = contact.body->getLinearVelocityZ();
1124 bodyMass = contact.body->getMass();
1126 const float vn = (vpx - vbx) * contact.nx + (vpy - vby) * contact.ny + (vpz - vbz) * contact.nz;
1128 const float restitution = std::max(collisionRestitution_, contact.restitution);
1129 const float friction = std::clamp(std::sqrt(collisionFriction_ * std::max(0.f, contact.friction)), 0.f, 1.f);
1130 const float relativeX = vpx - vbx;
1131 const float relativeY = vpy - vby;
1132 const float relativeZ = vpz - vbz;
1133 const float tangentX = relativeX - vn * contact.nx;
1134 const float tangentY = relativeY - vn * contact.ny;
1135 const float tangentZ = relativeZ - vn * contact.nz;
1137 const float nextVx = vbx + tangentX * (1.f - friction) + contact.nx * nextNormal;
1138 const float nextVy = vby + tangentY * (1.f - friction) + contact.ny * nextNormal;
1139 const float nextVz = vbz + tangentZ * (1.f - friction) + contact.nz * nextNormal;
1140 p.px =
p.x - nextVx * dt;
1141 p.py =
p.y - nextVy * dt;
1142 p.pz =
p.z - nextVz * dt;
1143 const float m = particleMass_;
1144 const float reduced = bodyMass > 0.f ? (
m * bodyMass) / (
m + bodyMass) :
m;
1146 const float maxKick = 9.f;
1147 j = std::min(j, maxKick *
m);
1148 const float kick = (j /
m) * dt;
1149 p.x += contact.nx * kick;
1150 p.y += contact.ny * kick;
1151 p.z += contact.nz * kick;
1152 if (dynamic && bodyMass > 0.f) {
1153 contact.body->applyLinearImpulse(-contact.nx * j, -contact.ny * j, -contact.nz * j);
1159void Cloth3D::collideBounds() {
1160 if (!hasBounds_)
return;
1161 const float minX = boundX_;
1162 const float minY = boundY_;
1163 const float minZ = boundZ_;
1164 const float maxX = boundX_ + boundW_;
1165 const float maxY = boundY_ + boundH_;
1166 const float maxZ = boundZ_ + boundD_;
1167 const float bounce = collisionRestitution_;
1169 for (Particle&
p : particles_) {
1170 if (
p.pinned)
continue;
1172 const float vx =
p.x -
p.px;
1175 }
else if (
p.x > maxX) {
1176 const float vx =
p.x -
p.px;
1181 const float vy =
p.y -
p.py;
1184 }
else if (
p.y > maxY) {
1185 const float vy =
p.y -
p.py;
1190 const float vz =
p.z -
p.pz;
1193 }
else if (
p.z > maxZ) {
1194 const float vz =
p.z -
p.pz;
1202 if (destroyed_)
return;
1203 if (dt < 0.f) dt = 0.f;
1204 if (dt > 0.05f) dt = 0.05f;
1206 updateSubsteps(dt, 2);
1209void Cloth3D::updateSubsteps(
float dt,
int substeps) {
1210 if (destroyed_ || substeps < 1)
return;
1212 const float h = dt / float(substeps);
1213 for (
int s = 0;
s < substeps; ++
s) {
1215 applyAutomaticTearing();
1216 solveConstraints(
h);
1217 solveFoldConstraint();
1218 solveSelfCollision();
1219 solveSelfCollisionTriangles();
1222 if (grabIndex_ >= 0) {
1223 Particle&
p = particles_[
static_cast<size_t>(grabIndex_)];
1232 interactStrength_ = 0.f;
1239 auto valid = detail::validateSimulationStep(stepValue,
settings, observation_);
1241 auto next = detail::advanceSimulationObservation(observation_, stepValue);
1245 }
catch (
const std::exception&
error) {
1252 observation_ = std::move(next).takeValue();
1257 auto valid = detail::validateSimulationObservation(
observation,
"physics.cloth3d.restoreObservation");
1261 "Cannot restore a destroyed cloth",
1262 "physics.cloth3d.restoreObservation"));
1272 const int gridVertices = cols_ * rows_;
1274 const int quadCount = (cols_ - 1) * (rows_ - 1);
1278 std::vector<float> nrm(
static_cast<size_t>(
vertexCount) * 3, 0.f);
1279 std::vector<float>
uv(
static_cast<size_t>(
vertexCount) * 2);
1280 for (
int r = 0;
r < rows_; ++
r) {
1281 for (
int c = 0;
c < cols_; ++
c) {
1282 const size_t i =
static_cast<size_t>(
r * cols_ +
c);
1283 const Particle&
p = particles_[i];
1284 const float u = cols_ > 1 ? float(
c) / float(cols_ - 1) : 0.f;
1285 const float v = rows_ > 1 ? float(
r) / float(rows_ - 1) : 0.f;
1287 const size_t vi = i +
static_cast<size_t>(
side) * gridVertices;
1288 pos[vi * 3 + 0] =
p.x;
1289 pos[vi * 3 + 1] =
p.y;
1290 pos[vi * 3 + 2] =
p.z;
1298 for (
const Tri& tri : triangles_) {
1299 const float* pa = &
pos[
static_cast<size_t>(tri.v[0]) * 3];
1300 const float* pb = &
pos[
static_cast<size_t>(tri.v[1]) * 3];
1301 const float* pc = &
pos[
static_cast<size_t>(tri.v[2]) * 3];
1302 float e1x = pb[0] - pa[0], e1y = pb[1] - pa[1], e1z = pb[2] - pa[2];
1303 float e2x = pc[0] - pa[0], e2y = pc[1] - pa[1], e2z = pc[2] - pa[2];
1304 float nx = e1y * e2z - e1z * e2y;
1305 float ny = e1z * e2x - e1x * e2z;
1306 float nz = e1x * e2y - e1y * e2x;
1307 const float len = std::sqrt(
nx *
nx +
ny *
ny +
nz *
nz);
1317 for (
int v : tri.v) {
1318 nrm[
static_cast<size_t>(
v) * 3 + 0] +=
nx;
1319 nrm[
static_cast<size_t>(
v) * 3 + 1] +=
ny;
1320 nrm[
static_cast<size_t>(
v) * 3 + 2] +=
nz;
1321 nrm[(
static_cast<size_t>(
v) + gridVertices) * 3 + 0] -=
nx;
1322 nrm[(
static_cast<size_t>(
v) + gridVertices) * 3 + 1] -=
ny;
1323 nrm[(
static_cast<size_t>(
v) + gridVertices) * 3 + 2] -=
nz;
1326 for (
int i = 0; i < gridVertices; ++i) {
1327 const float nx = nrm[
static_cast<size_t>(i) * 3 + 0];
1328 const float ny = nrm[
static_cast<size_t>(i) * 3 + 1];
1329 const float nz = nrm[
static_cast<size_t>(i) * 3 + 2];
1330 const float len = std::sqrt(
nx *
nx +
ny *
ny +
nz *
nz);
1332 const size_t vi =
static_cast<size_t>(i) +
static_cast<size_t>(
side) * gridVertices;
1333 const float sign =
side == 0 ? 1.f : -1.f;
1335 nrm[vi * 3 + 0] = sign *
nx / len;
1336 nrm[vi * 3 + 1] = sign *
ny / len;
1337 nrm[vi * 3 + 2] = sign *
nz / len;
1339 nrm[vi * 3 + 1] = sign;
1347 for (
int r = 0;
r + 1 < rows_; ++
r) {
1348 for (
int c = 0;
c + 1 < cols_; ++
c) {
1349 const uint32_t
a =
static_cast<uint32_t
>(
r * cols_ +
c);
1350 const uint32_t
b =
static_cast<uint32_t
>(
r * cols_ +
c + 1);
1351 const uint32_t cc =
static_cast<uint32_t
>((
r + 1) * cols_ +
c + 1);
1352 const uint32_t
d =
static_cast<uint32_t
>((
r + 1) * cols_ +
c);
1354 indices[
static_cast<size_t>(out) + 0] =
a;
1355 indices[
static_cast<size_t>(out) + 1] =
d;
1356 indices[
static_cast<size_t>(out) + 2] =
b;
1357 indices[
static_cast<size_t>(out) + 3] =
d;
1358 indices[
static_cast<size_t>(out) + 4] = cc;
1359 indices[
static_cast<size_t>(out) + 5] =
b;
1361 const uint32_t oa =
a +
static_cast<uint32_t
>(gridVertices);
1362 const uint32_t ob =
b +
static_cast<uint32_t
>(gridVertices);
1363 const uint32_t occ = cc +
static_cast<uint32_t
>(gridVertices);
1364 const uint32_t od =
d +
static_cast<uint32_t
>(gridVertices);
1365 indices[
static_cast<size_t>(out) + 6] = ob;
1366 indices[
static_cast<size_t>(out) + 7] = od;
1367 indices[
static_cast<size_t>(out) + 8] = oa;
1368 indices[
static_cast<size_t>(out) + 9] = ob;
1369 indices[
static_cast<size_t>(out) + 10] = occ;
1370 indices[
static_cast<size_t>(out) + 11] = od;
1382 gfx->
drawMesh(mesh_, glm::mat4(1.f),
nullptr,
Color(colorR_, colorG_, colorB_, colorA_));
std::uint32_t vertexCount
std::array< double, 10 > q
std::vector< std::uint32_t > indices
HexDirection windDirection
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
std::map< Cell, int > best
TerrainThermalSettings settings
const UnitySourceAsset & source
std::vector< double > phi
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.
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
virtual void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint)=0
Draw one mesh with model matrix. Requires begin3DFrame() (or an open swapchain pass).
virtual bool updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
In-place update of a mesh's vertex/index data (CPU -> host-visible VBO). Mirrors bakeMeshMorph: the u...
virtual Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
Upload a triangle mesh from packed CPU arrays. Owned by Graphics. posXYZ required (vertexCount*3)....
GPU mesh handle (+ optional CPU morph targets).
void clearBounds()
Clears bounds.
float getWindVelocityZ() const
Return configured air velocity along Z in m/s.
void setGravity(float gx, float gy, float gz)
Sets the gravity.
void setCollisionMaterial(float friction, float restitution)
Configure cloth contact friction and restitution in [0,1].
void setParticleInverseMass(int index, float inverseMass)
Set per-particle inverse mass; zero makes the particle kinematic/pinned.
void interactAt(float x, float y, float z, float radius, float strength)
Pointer-field interaction like Fluid2D::interactAt (3D): positive strength attracts,...
Cloth3D(int cols, int rows, float spacing, float originX, float originY, float originZ)
Cloth 3 d.
void setShearCompliance(float compliance)
Set XPBD shear compliance in m/N; zero preserves legacy PBD.
float getDragCoefficient() const
Return the aerodynamic drag coefficient.
void moveGrab(float x, float y, float z)
Moves grab.
void setCollideWorld(World3D *world)
Attach a World3D so free particles collide with its non-sensor shapes in meter space....
void draw(graphics::Graphics *gfx)
Draw the cloth as a triangle mesh (requires an open 3D frame).
float getLiftCoefficient() const
Return the aerodynamic lift coefficient.
void setWindVelocity(float vx, float vy, float vz)
Set world-space air velocity used by the aerodynamic model (m/s).
void setStretchCompliance(float compliance)
Set XPBD stretch compliance in m/N; zero preserves legacy PBD.
void setSelfCollision(bool on)
Enable proximity-based self-collision between non-adjacent particles and triangles (default true)....
int grabAt(float x, float y, float z, float radius=0.3f)
Grab nearest free particle within radius (meters). Returns particle index, or -1 if none.
bool isPinned(int index) const
True when pinned.
void pinTopRow()
Pin top row.
void setColor(float r, float g, float b, float a=1.f)
Sets the color.
void setStiffness(float stiffness)
Constraint relaxation strength in [0,1] (default 0.85).
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings) override
Advances cloth with the shared ticked backend contract.
void setCollisionFilter(uint64_t categoryBits, uint64_t maskBits)
Configure symmetric category/mask filtering for World3D contacts.
float getParticleX(int index) const
void setBendCompliance(float compliance)
Set XPBD distance-bend compliance in m/N; zero preserves legacy PBD.
void unpin(int index)
Unpin.
eve::Result< void > restoreObservation(const SimulationObservation &observation) override
Restores tick/progress metadata after an owner-level restore.
void setBounds(float x, float y, float z, float w, float h, float d)
Axis-aligned box (origin + extents, meters); particles bounce inside.
void reset()
Restore the flat grid pose (top row pinned) and clear transient state.
void releaseGrab()
Release grab.
SimulationObservation observation() const noexcept override
Returns completed tick/time observables.
void setParticleSize(float size)
Particle radius in meters (default 0.1): self-collision separation, draw scale and rigid-body collisi...
void setIterations(int iterations)
Constraint solver iterations per substep (default 4).
void update(float dt)
Updates .
void setDamping(float damping)
Damping applied to Verlet velocity [0,1] (default 0.01).
float getWindVelocityY() const
Return configured air velocity along Y in m/s.
float getParticleInverseMass(int index) const
Return per-particle inverse mass, or zero for an invalid index.
void setParticleMass(float mass)
Implicit particle mass in kg (default 0.1). Used for mass-proportional momentum exchange when collidi...
void applyForce(float fx, float fy, float fz)
Applies force.
float getParticleZ(int index) const
Returns the particle z.
void setParticlePosition(int index, float x, float y, float z)
Sets the particle position.
void setMaxFoldAngle(float degrees)
Maximum fold angle between adjacent triangles in degrees Range is 0..180 degrees; default is 120....
int getParticleCount() const
Returns the particle count.
float getParticleY(int index) const
Returns the particle y.
float getAirDensity() const
Return aerodynamic fluid density in kg/m³.
float getWindVelocityX() const
Return configured air velocity along X in m/s.
void setFoldStiffness(float k)
Strength of the dihedral fold clamp [0,1] (default 0.5).
void setAerodynamics(float airDensity, float dragCoefficient, float liftCoefficient)
Configure two-sided triangle aerodynamics.
Reusable, validated cloth simulation asset.
static eve::Result< ClothModel > grid(int cols, int rows, float spacing, float originX, float originY, float originZ)
Bake a regular XZ cloth grid with its top row pinned.
Box3D rigid-body world. Script coordinates are meters (Box3D native), unlike 2D World which uses pixe...
bool isValid() const
True while the underlying Box3D world is alive.
ClothProbeStatus pointProbeFiltered(float x, float y, float z, float radius, ClothContact3D *out, uint64_t categoryBits, uint64_t maskBits) const
Probe a cloth particle using symmetric category/mask filtering.
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Optional physics backend for vehicle mobility and body attach.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
double cross(const Vec2 &a, const Vec2 &b)
Cross.
glm::vec4 Color
Render-neutral RGBA color shared by graphics-facing modules.
One deterministic fixed-step emitted by SimulationClock.
Duration delta
Fixed simulation duration for this step.
Observable backend progress shared by CPU and accelerator providers.
Validated solver policy for one simulation step.