35 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};
38float dot(
const Vec3&
a,
const Vec3&
b) {
return a.x *
b.x +
a.y *
b.y +
a.z *
b.z; }
45 return feature ==
"arbitrary_topology" || feature ==
"xpbd_material" || feature ==
"tether" ||
46 feature ==
"volume_pressure" || feature ==
"skin_backstop" || feature ==
"attachment" ||
47 feature ==
"world_collision" || feature ==
"collision_filter" || feature ==
"friction" ||
48 feature ==
"self_collision" || feature ==
"aerodynamics" || feature ==
"runtime_tearing";
52 float backstopDistance,
float backstopRadius,
float compliance) {
53 if (!validIndex(
index))
throw Exception(
"Cloth3D.setSkinConstraint: index out of range");
54 const float normalLength = std::sqrt(
nx *
nx +
ny *
ny +
nz *
nz);
55 if (!std::isfinite(normalLength) || normalLength <= 1e-7f)
56 throw Exception(
"Cloth3D.setSkinConstraint: normal must be finite and non-zero");
57 auto found = std::find_if(skinConstraints_.begin(), skinConstraints_.end(),
58 [
index](
const SkinConstraint& item) { return item.particle == index; });
67 std::isfinite(backstopDistance) ? backstopDistance : -1.f,
68 std::isfinite(backstopRadius) ? std::max(0.f, backstopRadius) : 0.f,
69 std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f,
71 if (
found == skinConstraints_.end())
72 skinConstraints_.push_back(
value);
78 auto found = std::find_if(skinConstraints_.begin(), skinConstraints_.end(),
79 [
index](
const SkinConstraint& item) { return item.particle == index; });
80 if (
found == skinConstraints_.end())
throw Exception(
"Cloth3D.updateSkinReference: particle is not constrained");
81 const float normalLength = std::sqrt(
nx *
nx +
ny *
ny +
nz *
nz);
82 if (!std::isfinite(normalLength) || normalLength <= 1e-7f)
83 throw Exception(
"Cloth3D.updateSkinReference: normal must be finite and non-zero");
93 std::erase_if(skinConstraints_, [
index](
const SkinConstraint& item) {
return item.particle ==
index; });
97 return std::any_of(skinConstraints_.begin(), skinConstraints_.end(),
98 [
index](
const SkinConstraint& item) { return item.particle == index; });
102 if (!validIndex(
index))
throw Exception(
"Cloth3D.attachParticle: index out of range");
103 auto found = std::find_if(attachments_.begin(), attachments_.end(),
104 [
index](
const Attachment& item) { return item.particle == index; });
105 const Attachment
value{
index,
x,
y,
z, std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f, 0.f};
106 if (
found == attachments_.end())
107 attachments_.push_back(
value);
113 auto found = std::find_if(attachments_.begin(), attachments_.end(),
114 [
index](
const Attachment& item) { return item.particle == index; });
115 if (
found == attachments_.end())
throw Exception(
"Cloth3D.updateAttachment: particle is not attached");
122 std::erase_if(attachments_, [
index](
const Attachment& item) {
return item.particle ==
index; });
126 return std::any_of(attachments_.begin(), attachments_.end(),
127 [
index](
const Attachment& item) { return item.particle == index; });
133 tetherCompliance_ = std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f;
136void Cloth3D::setPressure(
float pressure) { pressure_ = std::isfinite(pressure) ? std::max(0.f, pressure) : 0.f; }
139 volumeCompliance_ = std::isfinite(compliance) ? std::max(0.f, compliance) : 0.f;
143 if (restVolume_ == 0.f)
return 0.f;
145 for (
const Tri&
triangle : triangles_) {
146 const Particle&
a = particles_[
static_cast<size_t>(
triangle.v[0])];
147 const Particle&
b = particles_[
static_cast<size_t>(
triangle.v[1])];
148 const Particle&
c = particles_[
static_cast<size_t>(
triangle.v[2])];
149 volume += (double(
a.x) * (double(
b.y) *
c.z - double(
b.z) *
c.y) +
150 double(
a.y) * (double(
b.z) *
c.x - double(
b.x) *
c.z) +
151 double(
a.z) * (double(
b.x) *
c.y - double(
b.y) *
c.x)) /
154 return static_cast<float>(
volume);
157void Cloth3D::solveTetherConstraints(
float dt) {
158 if (tetherScale_ <= 0.f || tethers_.empty() || dt <= 0.f)
return;
159 const float alpha = tetherCompliance_ / (dt * dt);
160 for (Tether& tether : tethers_) {
161 Particle& particle = particles_[
static_cast<size_t>(tether.particle)];
162 const Particle&
anchor = particles_[
static_cast<size_t>(tether.anchor)];
163 if (particle.pinned)
continue;
164 const float dx = particle.x -
anchor.x;
165 const float dy = particle.y -
anchor.y;
166 const float dz = particle.z -
anchor.z;
168 const float maximum = tether.maxLength * tetherScale_;
170 const float weight = particle.inverseMass;
172 tether.lambda += deltaLambda;
179void Cloth3D::solveVolumeConstraint(
float dt) {
180 if (pressure_ <= 0.f || restVolume_ == 0.f || dt <= 0.f)
return;
181 std::vector<Vec3> gradients(particles_.size());
182 for (
const Tri&
triangle : triangles_) {
183 const Particle&
a = particles_[
static_cast<size_t>(
triangle.v[0])];
184 const Particle&
b = particles_[
static_cast<size_t>(
triangle.v[1])];
185 const Particle&
c = particles_[
static_cast<size_t>(
triangle.v[2])];
186 const Vec3 av{
a.x,
a.y,
a.z};
192 for (
const auto& [
index, gradient] :
194 gradients[
static_cast<size_t>(
index)].
x += gradient.x / 6.f;
195 gradients[
static_cast<size_t>(
index)].
y += gradient.y / 6.f;
196 gradients[
static_cast<size_t>(
index)].
z += gradient.z / 6.f;
200 float denominator = 0.f;
201 for (
size_t i = 0; i < particles_.size(); ++i)
202 if (!particles_[i].
pinned) denominator += particles_[i].inverseMass *
dot(gradients[i], gradients[i]);
203 const float alpha = volumeCompliance_ / (dt * dt);
204 denominator += alpha;
205 if (denominator <= 1e-10f)
return;
207 const float deltaLambda = (-constraint - alpha * volumeLambda_) / denominator;
208 volumeLambda_ += deltaLambda;
209 for (
size_t i = 0; i < particles_.size(); ++i) {
210 if (particles_[i].
pinned)
continue;
211 particles_[i].x += particles_[i].inverseMass * gradients[i].x * deltaLambda;
212 particles_[i].y += particles_[i].inverseMass * gradients[i].y * deltaLambda;
213 particles_[i].z += particles_[i].inverseMass * gradients[i].z * deltaLambda;
217void Cloth3D::solveSkinConstraints(
float dt) {
218 if (dt <= 0.f)
return;
219 for (SkinConstraint& skin : skinConstraints_) {
220 Particle& particle = particles_[
static_cast<size_t>(skin.particle)];
221 if (particle.pinned)
continue;
222 Vec3 delta{particle.x - skin.x, particle.y - skin.y, particle.z - skin.z};
225 const float alpha = skin.compliance / (dt * dt);
226 const float weight = particle.inverseMass;
227 const float dl = (-(
distance - skin.radius) - alpha * skin.lambda) / (
weight + alpha);
233 if (skin.backstopDistance < 0.f || skin.backstopRadius <= 0.f)
continue;
234 const Vec3 center{skin.x - skin.nx * skin.backstopDistance, skin.y - skin.ny * skin.backstopDistance,
235 skin.z - skin.nz * skin.backstopDistance};
238 if (
distance >= skin.backstopRadius)
continue;
240 :
Vec3{skin.nx, skin.ny, skin.nz};
241 const float correction = skin.backstopRadius -
distance;
248void Cloth3D::solveAttachments(
float dt) {
249 if (dt <= 0.f)
return;
250 for (Attachment& attachment : attachments_) {
251 Particle& particle = particles_[
static_cast<size_t>(attachment.particle)];
252 const Vec3 delta{particle.x - attachment.x, particle.y - attachment.y, particle.z - attachment.z};
255 const float alpha = attachment.compliance / (dt * dt);
256 const float weight = std::max(particle.inverseMass, 1.f);
257 const float dl = (-
distance - alpha * attachment.lambda) / (
weight + alpha);
258 attachment.lambda += dl;
std::array< float, 3 > scale
RoadLaneDirection direction
EVENGINE_API_FOUNDATION public API.
float getBendCompliance() const
Return XPBD distance-bend compliance in m/N.
float getPressure() const
Return the closed-mesh target volume ratio.
uint64_t getCollisionCategoryBits() const
Return cloth collision category bits.
void setPressure(float pressure)
Set closed-mesh target volume ratio; zero disables volume solving.
uint64_t getCollisionMaskBits() const
Return cloth collision mask bits.
bool isAttached(int index) const
Return whether a particle currently has an attachment.
void setTetherScale(float scale)
Set tether maximum-length scale; zero disables tethers, one uses baked lengths.
void updateSkinReference(int index, float x, float y, float z, float nx, float ny, float nz)
Update the animated reference pose of an existing skin constraint.
bool supportsFeature(const std::string &feature) const
Query a stable cloth feature name without assuming CPU/GPU parity.
int getTetherConstraintCount() const
Return baked geodesic tether count.
float getVolumeCompliance() const
Return XPBD closed-volume compliance.
float getTetherScale() const
Return the current tether length scale.
void clearSkinConstraint(int index)
Remove a particle's skin/backstop constraint.
int getDistanceConstraintCount() const
Return the current distance-constraint count.
void detachParticle(int index)
Detach a particle; its simulated velocity remains continuous.
float getShearCompliance() const
Return XPBD shear compliance in m/N.
float getCurrentVolume() const
Return current signed mesh volume, or zero for an open model.
bool hasSkinConstraint(int index) const
Return whether a particle has a skin/backstop constraint.
float getTetherCompliance() const
Return XPBD tether compliance in m/N.
void updateAttachment(int index, float x, float y, float z)
Move an existing attachment target without recreating it.
void setVolumeCompliance(float compliance)
Set XPBD closed-volume compliance in m^3/N.
float getStretchCompliance() const
Return XPBD stretch compliance in m/N.
float getCollisionRestitution() const
Return cloth-side restitution.
int getSkinConstraintCount() const
Return active skin-constraint count.
int getTriangleCount() const
Return the current render/collision triangle count, reduced by tearing.
void setTetherCompliance(float compliance)
Set XPBD tether compliance in m/N.
void attachParticle(int index, float x, float y, float z, float compliance=0.f)
Attach a particle to a world-space target using an XPBD point constraint.
void setSkinConstraint(int index, float x, float y, float z, float nx, float ny, float nz, float radius, float backstopDistance=-1.f, float backstopRadius=0.f, float compliance=0.f)
Constrain a particle around an animated skin reference and optional backstop sphere.
float getCollisionFriction() const
Return cloth-side Coulomb friction.
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.