16 : cols_(cols), rows_(rows), spacing_(
spacing), originX_(originX), originY_(originY) {
17 if (cols_ < 2 || rows_ < 2)
18 throw Exception(
"Cloth: cols and rows must be >= 2");
20 throw Exception(
"Cloth: spacing must be > 0");
22 particles_.resize(
static_cast<size_t>(cols_ * rows_));
23 for (
int r = 0; r < rows_; ++r) {
24 for (
int c = 0;
c < cols_; ++
c) {
25 Particle &
p = particles_[
static_cast<size_t>(r * cols_ +
c)];
26 p.x =
p.px = originX_ + float(
c) * spacing_;
27 p.y =
p.py = originY_ + float(r) * spacing_;
39void Cloth::rebuildLinks() {
41 auto add = [&](
int a,
int b) {
46 const Particle &pa = particles_[
static_cast<size_t>(
a)];
47 const Particle &pb = particles_[
static_cast<size_t>(
b)];
48 const float dx = pb.x - pa.x;
49 const float dy = pb.y - pa.y;
50 link.rest = std::sqrt(dx * dx + dy * dy);
51 if (link.rest > 1e-4f) links_.push_back(link);
54 for (
int r = 0; r < rows_; ++r) {
55 for (
int c = 0;
c < cols_; ++
c) {
56 const int i = r * cols_ +
c;
58 if (
c + 1 < cols_) add(i, i + 1);
59 if (r + 1 < rows_) add(i, i + cols_);
61 if (
c + 1 < cols_ && r + 1 < rows_) add(i, i + cols_ + 1);
62 if (
c > 0 && r + 1 < rows_) add(i, i + cols_ - 1);
64 if (
c + 2 < cols_) add(i, i + 2);
65 if (r + 2 < rows_) add(i, i + cols_ * 2);
70bool Cloth::validIndex(
int index)
const {
80 stiffness_ = std::clamp(stiffness, 0.f, 1.f);
88 damping_ = std::clamp(damping, 0.f, 1.f);
92 if (
w <= 0.f ||
h <= 0.f) {
106 if (!validIndex(index))
throw Exception(
"Cloth.pin: index out of range");
107 particles_[
static_cast<size_t>(index)].pinned =
true;
111 if (!validIndex(index))
throw Exception(
"Cloth.unpin: index out of range");
112 particles_[
static_cast<size_t>(index)].pinned =
false;
116 for (
int c = 0;
c < cols_; ++
c)
117 particles_[
static_cast<size_t>(
c)].pinned =
true;
121 if (!validIndex(index))
return false;
122 return particles_[
static_cast<size_t>(index)].pinned;
127 float best = radius * radius;
129 const Particle &
p = particles_[
static_cast<size_t>(i)];
130 if (
p.pinned)
continue;
131 const float dx =
p.x -
x;
132 const float dy =
p.y -
y;
133 const float d2 = dx * dx + dy * dy;
146 if (!validIndex(grabIndex_))
return;
147 Particle &
p = particles_[
static_cast<size_t>(grabIndex_)];
169 if (!validIndex(index))
return 0.f;
170 return particles_[
static_cast<size_t>(index)].
x;
174 if (!validIndex(index))
return 0.f;
175 return particles_[
static_cast<size_t>(index)].
y;
179 if (!validIndex(index))
throw Exception(
"Cloth.setParticlePosition: index out of range");
180 Particle &
p = particles_[
static_cast<size_t>(index)];
185void Cloth::integrate(
float dt) {
186 if (dt <= 0.f)
return;
187 const float ax = gravityX_ + forceX_;
188 const float ay = gravityY_ + forceY_;
189 const float damp = 1.f - damping_;
191 for (Particle &
p : particles_) {
197 const float vx = (
p.x -
p.px) * damp;
198 const float vy = (
p.y -
p.py) * damp;
201 p.x += vx + ax * dt * dt;
202 p.y += vy + ay * dt * dt;
206void Cloth::solveConstraints() {
207 for (
int iter = 0; iter < iterations_; ++iter) {
208 for (
const Link &link : links_) {
209 Particle &
a = particles_[
static_cast<size_t>(link.a)];
210 Particle &
b = particles_[
static_cast<size_t>(link.b)];
211 if (
a.pinned &&
b.pinned)
continue;
212 float dx =
b.x -
a.x;
213 float dy =
b.y -
a.y;
214 float dist = std::sqrt(dx * dx + dy * dy);
215 if (dist < 1e-5f)
continue;
216 const float diff = (dist - link.rest) / dist * stiffness_;
221 }
else if (
b.pinned) {
225 const float half = diff * 0.5f;
232 if (validIndex(grabIndex_)) {
233 Particle &g = particles_[
static_cast<size_t>(grabIndex_)];
242void Cloth::collideBounds() {
243 if (!hasBounds_)
return;
244 const float minX = boundX_;
245 const float minY = boundY_;
246 const float maxX = boundX_ + boundW_;
247 const float maxY = boundY_ + boundH_;
248 constexpr float bounce = 0.35f;
250 for (Particle &
p : particles_) {
251 if (
p.pinned)
continue;
253 const float vx =
p.x -
p.px;
255 p.px =
p.x + vx * bounce;
256 }
else if (
p.x > maxX) {
257 const float vx =
p.x -
p.px;
259 p.px =
p.x + vx * bounce;
262 const float vy =
p.y -
p.py;
264 p.py =
p.y + vy * bounce;
265 }
else if (
p.y > maxY) {
266 const float vy =
p.y -
p.py;
268 p.py =
p.y + vy * bounce;
274 if (destroyed_)
return;
275 if (dt < 0.f) dt = 0.f;
276 if (dt > 0.05f) dt = 0.05f;
279 const int substeps = 2;
280 const float h = dt / float(substeps);
281 for (
int s = 0;
s < substeps; ++
s) {
285 if (grabIndex_ >= 0) {
286 Particle &
p = particles_[
static_cast<size_t>(grabIndex_)];
297 if (!gfx || destroyed_)
return;
298 const Color linkColor(colorR_, colorG_, colorB_, colorA_ * 0.75f);
299 const Color nodeColor(colorR_, colorG_, colorB_, colorA_);
301 for (
const Link &link : links_) {
302 const Particle &
a = particles_[
static_cast<size_t>(link.a)];
303 const Particle &
b = particles_[
static_cast<size_t>(link.b)];
304 const float x1 =
a.x, y1 =
a.y, x2 =
b.x, y2 =
b.y;
305 const float dx = x2 - x1;
306 const float dy = y2 - y1;
307 const float len = std::sqrt(dx * dx + dy * dy);
308 const int steps = std::max(1,
int(len / 4.f));
309 for (
int i = 0; i <= steps; ++i) {
310 const float t = float(i) / float(steps);
311 gfx->
drawSolidRect(x1 + dx * t - 1.f, y1 + dy * t - 1.f, 2.f, 2.f, linkColor);
314 for (
const Particle &
p : particles_) {
315 const float s =
p.pinned ? 5.f : 3.f;
318 if (grabIndex_ >= 0) {
319 const Particle &
p = particles_[
static_cast<size_t>(grabIndex_)];
321 Color(1.f, 0.85f, 0.35f, 0.9f));
virtual void drawSolidRect(float x, float y, float w, float h, const Color &color, BlendMode blend=BlendMode::Alpha)=0
Internal immediate-mode helper used by RenderSystem / Batcher.
void setParticlePosition(int index, float x, float y)
void setBounds(float x, float y, float w, float h)
Axis-aligned walls; particles bounce inside. Disabled if w/h <= 0.
void setStiffness(float stiffness)
Constraint relaxation strength in [0,1] (default 0.85).
void setDamping(float damping)
Damping applied to Verlet velocity [0,1] (default 0.01).
float getParticleX(int index) const
bool isPinned(int index) const
float getParticleY(int index) const
void setGravity(float gx, float gy)
int getParticleCount() const
void moveGrab(float x, float y)
void setColor(float r, float g, float b, float a=1.f)
Cloth(int cols, int rows, float spacing, float originX, float originY)
void applyForce(float fx, float fy)
void draw(graphics::Graphics *gfx)
void setIterations(int iterations)
Constraint solver iterations per substep (default 4).
int grabAt(float x, float y, float radius=24.f)
Grab nearest free particle within radius (pixels). Returns particle index, or -1 if none.
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...