载入中...
搜索中...
未找到
SoftBody3D.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4
5#include <glm/gtc/quaternion.hpp>
6
7#include <algorithm>
8#include <cmath>
9#include <limits>
10#include <string>
11
12namespace eve::physics {
13namespace {
14
15using V3 = glm::vec3;
16
17float tetraVolume(const V3& a, const V3& b, const V3& c, const V3& d) {
18 return std::fabs(glm::dot(b - a, glm::cross(c - a, d - a))) / 6.f;
19}
20
21glm::quat bestFitRotation(const glm::mat3& covariance) {
22 glm::quat rotation(1.f, 0.f, 0.f, 0.f);
23 for (int i = 0; i < 8; ++i) {
24 const glm::mat3 basis = glm::mat3_cast(rotation);
25 const V3 omega = (glm::cross(basis[0], covariance[0]) + glm::cross(basis[1], covariance[1]) +
26 glm::cross(basis[2], covariance[2])) /
27 (std::fabs(glm::dot(basis[0], covariance[0]) + glm::dot(basis[1], covariance[1]) +
28 glm::dot(basis[2], covariance[2])) +
29 1e-8f);
30 const float magnitude = glm::length(omega);
31 if (magnitude < 1e-6f) break;
32 rotation = glm::normalize(glm::angleAxis(magnitude, omega / magnitude) * rotation);
33 }
34 return rotation;
35}
36
37} // namespace
38
40 float originX, float originY, float originZ) {
41 SoftBody3DDefinition definition;
42 definition.cols = cols;
43 definition.rows = rows;
44 definition.layers = layers;
45 definition.spacing = spacing;
46 definition.originX = originX;
47 definition.originY = originY;
48 definition.originZ = originZ;
49 return create(definition);
50}
51
54 if (!registered) return eve::Result<std::unique_ptr<SoftBody3D>>::failure(registered.status());
55 auto valid = definition.validate();
56 if (!valid) return eve::Result<std::unique_ptr<SoftBody3D>>::failure(valid.status());
57 auto result = std::unique_ptr<SoftBody3D>(new SoftBody3D(definition.cols, definition.rows, definition.layers,
58 definition.spacing, definition.originX, definition.originY,
59 definition.originZ));
60 result->setGravity(definition.gravityX, definition.gravityY, definition.gravityZ);
61 result->setDeformationResistance(definition.deformationResistance);
62 result->setIterations(definition.iterations);
63 result->setDamping(definition.damping);
64 result->setParticleRadius(definition.particleRadius);
65 result->setParticleMass(definition.particleMass);
66 result->setPlasticity(definition.plasticYield, definition.plasticCreep, definition.plasticRecovery,
67 definition.maxDeformation);
68 result->setSelfCollision(definition.selfCollision);
69 return eve::Result<std::unique_ptr<SoftBody3D>>::success(std::move(result));
70}
71
72SoftBody3D::SoftBody3D(int cols, int rows, int layers, float spacing, float originX, float originY, float originZ)
73 : cols_(cols), rows_(rows), layers_(layers), spacing_(spacing) {
74 particles_.reserve(static_cast<size_t>(cols_) * rows_ * layers_);
75 for (int z = 0; z < layers_; ++z) {
76 for (int y = 0; y < rows_; ++y) {
77 for (int x = 0; x < cols_; ++x) {
78 const float px = originX + float(x) * spacing_;
79 const float py = originY + float(y) * spacing_;
80 const float pz = originZ + float(z) * spacing_;
81 particles_.push_back({px, py, pz, px, py, pz, px, py, pz, false});
82 }
83 }
84 }
85 clusters_.reserve(static_cast<size_t>(cols_ - 1) * (rows_ - 1) * (layers_ - 1));
86 for (int z = 0; z + 1 < layers_; ++z) {
87 for (int y = 0; y + 1 < rows_; ++y) {
88 for (int x = 0; x + 1 < cols_; ++x) {
89 Cluster cluster;
90 cluster.indices[0] = indexOf(x, y, z);
91 cluster.indices[1] = indexOf(x + 1, y, z);
92 cluster.indices[2] = indexOf(x, y + 1, z);
93 cluster.indices[3] = indexOf(x + 1, y + 1, z);
94 cluster.indices[4] = indexOf(x, y, z + 1);
95 cluster.indices[5] = indexOf(x + 1, y, z + 1);
96 cluster.indices[6] = indexOf(x, y + 1, z + 1);
97 cluster.indices[7] = indexOf(x + 1, y + 1, z + 1);
98 clusters_.push_back(cluster);
99 }
100 }
101 }
102 particleRadius_ = spacing_ * 0.35f;
103}
104
106
108 destroyed_ = true;
109 world_ = nullptr;
110 collisionWorld_ = nullptr;
111 ownedCollisionWorld_.reset();
112}
113
114bool SoftBody3D::validIndex(int index) const noexcept { return index >= 0 && index < getParticleCount(); }
115
116int SoftBody3D::indexOf(int x, int y, int z) const noexcept { return (z * rows_ + y) * cols_ + x; }
117
118void SoftBody3D::setGravity(float x, float y, float z) {
119 gravityX_ = std::isfinite(x) ? x : 0.f;
120 gravityY_ = std::isfinite(y) ? y : 0.f;
121 gravityZ_ = std::isfinite(z) ? z : 0.f;
122}
123
124void SoftBody3D::setDeformationResistance(float value) { deformationResistance_ = std::clamp(value, 0.f, 1.f); }
125
126float SoftBody3D::getDeformationResistance() const { return deformationResistance_; }
127
128void SoftBody3D::setIterations(int value) { iterations_ = std::clamp(value, 1, 32); }
129void SoftBody3D::setDamping(float value) { damping_ = std::clamp(value, 0.f, 1.f); }
131 if (std::isfinite(value)) particleRadius_ = std::max(0.f, value);
132}
133float SoftBody3D::getParticleRadius() const { return particleRadius_; }
135 if (std::isfinite(value)) particleMass_ = std::max(1e-5f, value);
136}
137
138void SoftBody3D::setPlasticity(float yield, float creep, float recovery, float maxDeformation) {
139 plasticYield_ = std::max(0.f, std::isfinite(yield) ? yield : 0.f);
140 plasticCreep_ = std::clamp(std::isfinite(creep) ? creep : 0.f, 0.f, 1.f);
141 plasticRecovery_ = std::max(0.f, std::isfinite(recovery) ? recovery : 0.f);
142 maxDeformation_ = std::max(0.f, std::isfinite(maxDeformation) ? maxDeformation : 0.f);
143}
144
145float SoftBody3D::getPlasticYield() const { return plasticYield_; }
146float SoftBody3D::getPlasticCreep() const { return plasticCreep_; }
147float SoftBody3D::getPlasticRecovery() const { return plasticRecovery_; }
148float SoftBody3D::getMaxDeformation() const { return maxDeformation_; }
149int SoftBody3D::getLayers() const { return layers_; }
150
151void SoftBody3D::setBounds(float x, float y, float z, float width, float height, float depth) {
152 boundX_ = x;
153 boundY_ = y;
154 boundZ_ = z;
155 boundW_ = std::max(0.f, width);
156 boundH_ = std::max(0.f, height);
157 boundD_ = std::max(0.f, depth);
158 hasBounds_ = std::isfinite(x) && std::isfinite(y) && std::isfinite(z) && std::isfinite(width) &&
159 std::isfinite(height) && std::isfinite(depth);
160}
161
163 if (!validIndex(index)) throw Exception("SoftBody3D.pin: index out of range");
164 particles_[static_cast<size_t>(index)].pinned = true;
165}
167 if (!validIndex(index)) throw Exception("SoftBody3D.unpin: index out of range");
168 particles_[static_cast<size_t>(index)].pinned = false;
169}
171 if (!validIndex(index)) throw Exception("SoftBody3D.isPinned: index out of range");
172 return particles_[static_cast<size_t>(index)].pinned;
173}
174
175int SoftBody3D::grabAt(float x, float y, float z, float radius) {
176 float best = radius * radius;
177 int found = -1;
178 for (int i = 0; i < getParticleCount(); ++i) {
179 const Particle& p = particles_[static_cast<size_t>(i)];
180 const float dx = p.x - x, dy = p.y - y, dz = p.z - z;
181 const float distanceSquared = dx * dx + dy * dy + dz * dz;
182 if (!p.pinned && distanceSquared <= best) {
183 best = distanceSquared;
184 found = i;
185 }
186 }
187 if (found >= 0) {
188 grabIndex_ = found;
189 moveGrab(x, y, z);
190 }
191 return found;
192}
193
194void SoftBody3D::moveGrab(float x, float y, float z) {
195 if (grabIndex_ < 0) return;
196 grabX_ = x;
197 grabY_ = y;
198 grabZ_ = z;
199 Particle& p = particles_[static_cast<size_t>(grabIndex_)];
200 p.x = p.px = x;
201 p.y = p.py = y;
202 p.z = p.pz = z;
203}
204
205void SoftBody3D::applyForce(float x, float y, float z) {
206 if (std::isfinite(x)) forceX_ += x;
207 if (std::isfinite(y)) forceY_ += y;
208 if (std::isfinite(z)) forceZ_ += z;
209}
210
211void SoftBody3D::interactAt(float x, float y, float z, float radius, float strength) {
212 hasInteraction_ = radius > 0.f && std::isfinite(radius) && std::isfinite(strength);
213 interactX_ = x;
214 interactY_ = y;
215 interactZ_ = z;
216 interactRadius_ = radius;
217 interactStrength_ = strength;
218}
219
221 if (!validIndex(index)) throw Exception("SoftBody3D.getParticleX: index out of range");
222 return particles_[static_cast<size_t>(index)].x;
223}
225 if (!validIndex(index)) throw Exception("SoftBody3D.getParticleY: index out of range");
226 return particles_[static_cast<size_t>(index)].y;
227}
229 if (!validIndex(index)) throw Exception("SoftBody3D.getParticleZ: index out of range");
230 return particles_[static_cast<size_t>(index)].z;
231}
232void SoftBody3D::setParticlePosition(int index, float x, float y, float z) {
233 if (!validIndex(index)) throw Exception("SoftBody3D.setParticlePosition: index out of range");
234 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
235 throw Exception("SoftBody3D.setParticlePosition: coordinates must be finite");
236 Particle& p = particles_[static_cast<size_t>(index)];
237 p.x = p.px = x;
238 p.y = p.py = y;
239 p.z = p.pz = z;
240}
241
243 for (Particle& p : particles_) {
244 p.x = p.px = p.rx;
245 p.y = p.py = p.ry;
246 p.z = p.pz = p.rz;
247 p.pinned = false;
248 }
249 for (Cluster& cluster : clusters_)
250 for (auto& offset : cluster.plastic) offset[0] = offset[1] = offset[2] = 0.f;
251 grabIndex_ = -1;
252 forceX_ = forceY_ = forceZ_ = 0.f;
253 hasInteraction_ = false;
254}
255
256void SoftBody3D::integrate(float dt) {
257 const float velocityScale = std::max(0.f, 1.f - damping_);
258 for (int i = 0; i < getParticleCount(); ++i) {
259 Particle& p = particles_[static_cast<size_t>(i)];
260 if (p.pinned || i == grabIndex_) continue;
261 float ax = gravityX_ + forceX_ / particleMass_;
262 float ay = gravityY_ + forceY_ / particleMass_;
263 float az = gravityZ_ + forceZ_ / particleMass_;
264 if (hasInteraction_) {
265 const V3 delta = V3(interactX_ - p.x, interactY_ - p.y, interactZ_ - p.z);
266 const float distance = glm::length(delta);
267 if (distance > 1e-6f && distance < interactRadius_) {
268 const float falloff = 1.f - distance / interactRadius_;
269 const V3 field = delta * (interactStrength_ * falloff / distance);
270 ax += field.x;
271 ay += field.y;
272 az += field.z;
273 }
274 }
275 const float vx = (p.x - p.px) * velocityScale;
276 const float vy = (p.y - p.py) * velocityScale;
277 const float vz = (p.z - p.pz) * velocityScale;
278 p.px = p.x;
279 p.py = p.y;
280 p.pz = p.z;
281 p.x += vx + ax * dt * dt;
282 p.y += vy + ay * dt * dt;
283 p.z += vz + az * dt * dt;
284 }
285}
286
287void SoftBody3D::solveShapeMatching(float dt) {
288 const float stiffness = 1.f - std::pow(1.f - deformationResistance_, 1.f / float(iterations_));
289 for (Cluster& cluster : clusters_) {
290 V3 currentCom(0.f), restCom(0.f);
291 for (int i = 0; i < 8; ++i) {
292 const Particle& p = particles_[static_cast<size_t>(cluster.indices[i])];
293 currentCom += V3(p.x, p.y, p.z);
294 restCom += V3(p.rx + cluster.plastic[i][0], p.ry + cluster.plastic[i][1], p.rz + cluster.plastic[i][2]);
295 }
296 currentCom /= 8.f;
297 restCom /= 8.f;
298 glm::mat3 covariance(0.f);
299 for (int i = 0; i < 8; ++i) {
300 const Particle& p = particles_[static_cast<size_t>(cluster.indices[i])];
301 const V3 current = V3(p.x, p.y, p.z) - currentCom;
302 const V3 rest =
303 V3(p.rx + cluster.plastic[i][0], p.ry + cluster.plastic[i][1], p.rz + cluster.plastic[i][2]) - restCom;
304 covariance += glm::outerProduct(current, rest);
305 }
306 const glm::mat3 rotation = glm::mat3_cast(bestFitRotation(covariance));
307 float deformation = 0.f;
308 V3 restCurrent[8];
309 for (int i = 0; i < 8; ++i) {
310 Particle& p = particles_[static_cast<size_t>(cluster.indices[i])];
311 restCurrent[i] = glm::transpose(rotation) * (V3(p.x, p.y, p.z) - currentCom);
312 const V3 rest =
313 V3(p.rx + cluster.plastic[i][0], p.ry + cluster.plastic[i][1], p.rz + cluster.plastic[i][2]) - restCom;
314 deformation += glm::length(restCurrent[i] - rest) / spacing_;
315 if (!p.pinned && cluster.indices[i] != grabIndex_) {
316 const V3 goal = currentCom + rotation * rest;
317 p.x += (goal.x - p.x) * stiffness;
318 p.y += (goal.y - p.y) * stiffness;
319 p.z += (goal.z - p.z) * stiffness;
320 }
321 }
322 deformation /= 8.f;
323 const float creep = deformation > plasticYield_ ? plasticCreep_ * dt : 0.f;
324 const float recovery = plasticRecovery_ * dt;
325 if (maxDeformation_ > 0.f && (creep > 0.f || recovery > 0.f)) {
326 for (int i = 0; i < 8; ++i) {
327 const Particle& p = particles_[static_cast<size_t>(cluster.indices[i])];
328 const V3 original = V3(p.rx, p.ry, p.rz) - restCom;
329 V3 offset(cluster.plastic[i][0], cluster.plastic[i][1], cluster.plastic[i][2]);
330 offset += (restCurrent[i] - original - offset) * std::min(1.f, creep);
331 offset += (V3(0.f) - offset) * std::min(1.f, recovery);
332 const float limit = maxDeformation_ * spacing_;
333 const float length = glm::length(offset);
334 if (length > limit && length > 1e-6f) offset *= limit / length;
335 cluster.plastic[i][0] = offset.x;
336 cluster.plastic[i][1] = offset.y;
337 cluster.plastic[i][2] = offset.z;
338 }
339 }
340 }
341}
342
343void SoftBody3D::solveSelfCollision() {
344 if (!selfCollision_ || particleRadius_ <= 0.f) return;
345 const float minimum = particleRadius_ * 2.f;
346 const float minimumSquared = minimum * minimum;
347 for (int i = 0; i < getParticleCount(); ++i) {
348 for (int j = i + 1; j < getParticleCount(); ++j) {
349 Particle& a = particles_[static_cast<size_t>(i)];
350 Particle& b = particles_[static_cast<size_t>(j)];
351 V3 delta(b.x - a.x, b.y - a.y, b.z - a.z);
352 const float distanceSquared = glm::dot(delta, delta);
353 if (distanceSquared >= minimumSquared || distanceSquared < 1e-12f) continue;
354 const float distance = std::sqrt(distanceSquared);
355 const V3 correction = delta * ((minimum - distance) / distance * 0.5f);
356 if (!a.pinned && i != grabIndex_) {
357 a.x -= correction.x;
358 a.y -= correction.y;
359 a.z -= correction.z;
360 }
361 if (!b.pinned && j != grabIndex_) {
362 b.x += correction.x;
363 b.y += correction.y;
364 b.z += correction.z;
365 }
366 }
367 }
368}
369
370void SoftBody3D::collideWorld(float dt) {
371 if (!collisionWorld_ || !collisionWorld_->softBodyCollisionAvailable() || particleRadius_ <= 0.f) return;
372 softbody::SoftBodyContact contact;
373 const float inverseDt = dt > 1e-6f ? 1.f / dt : 0.f;
374 for (Particle& p : particles_) {
375 if (p.pinned || collisionWorld_->probeSoftBodyParticle(p.x, p.y, p.z, particleRadius_, contact) !=
377 !contact.hit)
378 continue;
379 const V3 velocity = V3(p.x - p.px, p.y - p.py, p.z - p.pz) * inverseDt;
380 p.x += contact.nx * contact.depth;
381 p.y += contact.ny * contact.depth;
382 p.z += contact.nz * contact.depth;
383 if (contact.dynamicBody) {
384 const float normalVelocity = glm::dot(velocity, V3(contact.nx, contact.ny, contact.nz));
385 if (normalVelocity < 0.f) {
386 const float impulse = std::min(-normalVelocity * particleMass_, particleMass_ * 8.f);
387 collisionWorld_->applySoftBodyImpulse(contact.bodyId, -contact.nx * impulse, -contact.ny * impulse,
388 -contact.nz * impulse);
389 }
390 }
391 }
392}
393
394void SoftBody3D::collideBounds() {
395 if (!hasBounds_) return;
396 const V3 minimum(boundX_, boundY_, boundZ_);
397 const V3 maximum(boundX_ + boundW_, boundY_ + boundH_, boundZ_ + boundD_);
398 for (Particle& p : particles_) {
399 if (p.pinned) continue;
400 p.x = std::clamp(p.x, minimum.x, maximum.x);
401 p.y = std::clamp(p.y, minimum.y, maximum.y);
402 p.z = std::clamp(p.z, minimum.z, maximum.z);
403 }
404}
405
406void SoftBody3D::update(float dt) {
407 if (destroyed_) return;
408 updateSubsteps(std::clamp(dt, 0.f, 0.05f), 2);
409}
410
411void SoftBody3D::updateSubsteps(float dt, int substeps) {
412 if (destroyed_ || substeps < 1) return;
413 const float h = dt / float(substeps);
414 for (int substep = 0; substep < substeps; ++substep) {
415 integrate(h);
416 for (int iteration = 0; iteration < iterations_; ++iteration) {
417 solveShapeMatching(h);
418 solveSelfCollision();
419 collideWorld(h);
420 collideBounds();
421 }
422 if (grabIndex_ >= 0) moveGrab(grabX_, grabY_, grabZ_);
423 }
424 forceX_ = forceY_ = forceZ_ = 0.f;
425 hasInteraction_ = false;
426}
427
429 if (destroyed_)
431 "Cannot step a destroyed soft body",
432 "physics.softbody3d.step"));
433 auto valid = detail::validateSimulationStep(stepValue, settings, observation_);
434 if (!valid) return valid;
435 auto next = detail::advanceSimulationObservation(observation_, stepValue);
436 if (!next) return eve::Result<void>::failure(next.status());
437 updateSubsteps(static_cast<float>(stepValue.delta.seconds()), settings.subStepCount);
438 observation_ = std::move(next).takeValue();
440}
441
443 auto valid = detail::validateSimulationObservation(observation, "physics.softbody3d.restoreObservation");
444 if (!valid) return valid;
445 if (destroyed_)
447 "Cannot restore a destroyed soft body",
448 "physics.softbody3d.restoreObservation"));
449 observation_ = observation;
451}
452
454 if (clusters_.empty()) return 1.f;
455 float volume = 0.f;
456 for (const Cluster& cluster : clusters_) {
457 V3 p[8];
458 for (int i = 0; i < 8; ++i) {
459 const Particle& particle = particles_[static_cast<size_t>(cluster.indices[i])];
460 p[i] = V3(particle.x, particle.y, particle.z);
461 }
462 volume += tetraVolume(p[0], p[1], p[3], p[7]) + tetraVolume(p[0], p[3], p[2], p[7]) +
463 tetraVolume(p[0], p[2], p[6], p[7]) + tetraVolume(p[0], p[6], p[4], p[7]) +
464 tetraVolume(p[0], p[4], p[5], p[7]) + tetraVolume(p[0], p[5], p[1], p[7]);
465 }
466 const float rest = float(clusters_.size()) * spacing_ * spacing_ * spacing_;
467 return rest > 0.f ? volume / rest : 1.f;
468}
469
470} // namespace eve::physics
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
double volume
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
float length
Definition CaveMesh.cpp:94
int az
Definition CaveMesh.cpp:113
float py
float pz
glm::vec4 p[6]
int rows
int cols
float maximum[3]
float minimum[3]
std::int32_t c
eve::ResourcePin pinned
int h
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
size_t offset
std::array< float, 4 > rotation
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
float radius
float d
bool found
double current
float dz
float dy
float dx
std::map< Cell, int > best
TerrainThermalSettings settings
int spacing
int limit
Definition TreeMesh.cpp:164
uint32_t index
std::uint32_t depth
float vz
float vy
float vx
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
Volumetric particle soft body using overlapping shape-matching clusters.
Definition SoftBody3D.h:27
int getParticleCount() const
Definition SoftBody3D.h:174
void applyForce(float x, float y, float z)
Applies force.
void pin(int index)
Pin.
void setParticleMass(float value)
Sets the particle mass.
void update(float dt)
Advances using a legacy variable timestep clamped to 50 ms.
void setBounds(float x, float y, float z, float width, float height, float depth)
Sets the bounds.
SimulationObservation observation() const noexcept override
Returns completed fixed-step metadata by value.
Definition SoftBody3D.h:65
float getParticleZ(int index) const
void setIterations(int value)
Sets the iterations.
void setDeformationResistance(float value)
Sets the deformation resistance.
float getPlasticCreep() const
Returns the plastic creep.
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings) override
Advances through the checked fixed-step backend contract.
~SoftBody3D() override
Soft body 3 d.
float getMaxDeformation() const
Returns the max deformation.
float getVolumeRatio() const
Returns current cell volume divided by undeformed rest volume.
float getPlasticYield() const
Returns the plastic yield.
void moveGrab(float x, float y, float z)
Moves grab.
void setGravity(float x, float y, float z)
Sets the gravity.
void setPlasticity(float yield, float creep, float recovery, float maxDeformation)
Sets the plasticity.
void setParticlePosition(int index, float x, float y, float z)
float getDeformationResistance() const
Returns the deformation resistance.
static eve::Result< std::unique_ptr< SoftBody3D > > create(const SoftBody3DDefinition &definition)
Creates a soft body from the canonical versioned definition.
void interactAt(float x, float y, float z, float radius, float strength)
Interact at.
SoftBody3D(const SoftBody3D &)=delete
float getPlasticRecovery() const
Returns the plastic recovery.
eve::Result< void > restoreObservation(const SimulationObservation &observation) override
Restores checked progress metadata; particle state is unchanged.
void setParticleRadius(float value)
Sets the particle radius.
void setDamping(float value)
Sets the damping.
bool isPinned(int index) const
True when pinned.
float getParticleX(int index) const
float getParticleY(int index) const
int grabAt(float x, float y, float z, float radius)
Grab at.
void unpin(int index)
Unpin.
void destroy()
Destroys .
float getParticleRadius() const
Returns the particle radius.
virtual void applySoftBodyImpulse(int bodyId, float x, float y, float z)=0
Apply a world-space impulse to a previously reported dynamic body id.
virtual SoftBodyContactState probeSoftBodyParticle(float x, float y, float z, float radius, SoftBodyContact &contact) const =0
Probe the deepest non-sensor contact for a spherical particle.
virtual bool softBodyCollisionAvailable() const noexcept=0
Whether the provider can currently answer contacts.
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
float distanceSquared(float ax, float ay, float bx, float by)
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.
Versioned, owning creation definition for one volumetric soft body.
static eve::Result< void > ensureSchemaRegistered()
Idempotently register version 1 in the process schema registry.