载入中...
搜索中...
未找到
Rope3D.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8
9namespace eve::physics {
10namespace {
11using V = Rope3D::Vec3;
12V add(V a, V b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
13V sub(V a, V b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
14V mul(V a, float s) { return {a.x * s, a.y * s, a.z * s}; }
15float dot(V a, V b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
16float length(V v) { return std::sqrt(dot(v, v)); }
17bool finite(V v) { return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); }
18} // namespace
19
20Rope3D::Rope3D(int count, float sx, float sy, float sz, float ex, float ey, float ez) {
21 const Vec3 start{sx, sy, sz};
22 const Vec3 end{ex, ey, ez};
23 if (count < 2 || !finite(start) || !finite(end) || length(sub(end, start)) <= 1e-6f)
24 throw Exception("Rope3D: requires at least two particles and distinct finite endpoints");
25 particles_.resize(static_cast<std::size_t>(count));
26 elements_.resize(static_cast<std::size_t>(count - 1));
27 for (int i = 0; i < count; ++i) {
28 const float t = static_cast<float>(i) / static_cast<float>(count - 1);
29 auto& p = particles_[static_cast<std::size_t>(i)];
30 p.position = p.previous = add(start, mul(sub(end, start), t));
31 if (i + 1 < count) {
32 auto& e = elements_[static_cast<std::size_t>(i)];
33 e.a = i;
34 e.b = i + 1;
35 e.restLength = length(sub(end, start)) / static_cast<float>(count - 1);
36 }
37 }
38 syncBendState();
39}
40
41void Rope3D::update(float dt) {
42 if (!std::isfinite(dt) || dt < 0.f) throw Exception("Rope3D: dt must be finite and non-negative");
43 if (dt == 0.f) return;
44 lastTornElements_.clear();
45 dt = std::clamp(dt, 0.f, 0.05f);
46 constexpr int substeps = 4;
47 for (int substep = 0; substep < substeps; ++substep) {
48 const float h = dt / static_cast<float>(substeps);
49 for (auto& element : elements_) element.lambda = 0.f;
50 integrate(h);
51 collideBorrowedSurfaces(h);
52 if (bendConstraintsEnabled_) updatePlasticity(h);
53 for (int iteration = 0; iteration < 3; ++iteration) {
54 if (distanceConstraintsEnabled_) solveStretch(h);
55 if (bendConstraintsEnabled_) solveBend(h);
56 if (selfCollision_) solveSelfCollision();
57 solveExternalCollisions();
58 collideBounds();
59 applyAttachments();
60 }
61 }
62 applyTearing();
63 force_ = {};
64}
65
67 auto validation = detail::validateSimulationStep(tick, settings, observation_);
68 if (!validation) return validation;
69 const float dt = static_cast<float>(tick.delta.seconds());
70 const auto oldParticles = particles_;
71 const auto oldElements = elements_;
72 const auto oldBendPlasticity = bendPlasticity_;
73 const auto oldTopologyRevision = topologyRevision_;
74 const auto oldTornElements = lastTornElements_;
75 lastTornElements_.clear();
76 for (int substep = 0; substep < settings.subStepCount; ++substep) {
77 const float h = dt / static_cast<float>(settings.subStepCount);
78 for (auto& element : elements_) element.lambda = 0.f;
79 integrate(h);
80 collideBorrowedSurfaces(h);
81 if (bendConstraintsEnabled_) updatePlasticity(h);
82 for (int iteration = 0; iteration < settings.positionIterations; ++iteration) {
83 if (distanceConstraintsEnabled_) solveStretch(h);
84 if (bendConstraintsEnabled_) solveBend(h);
85 if (selfCollision_) solveSelfCollision();
86 solveExternalCollisions();
87 collideBounds();
88 applyAttachments();
89 }
90 }
91 applyTearing();
92 force_ = {};
93 auto next = detail::advanceSimulationObservation(observation_, tick);
94 if (!next) {
95 particles_ = oldParticles;
96 elements_ = oldElements;
97 bendPlasticity_ = oldBendPlasticity;
98 topologyRevision_ = oldTopologyRevision;
99 lastTornElements_ = oldTornElements;
100 return eve::Result<void>::failure(next.status());
101 }
102 observation_ = std::move(next).takeValue();
104}
105
107 auto result = detail::validateSimulationObservation(observation, "physics.rope3d.observation");
108 if (!result) return result;
109 observation_ = observation;
111}
112
113void Rope3D::setGravity(float x, float y, float z) {
114 if (!finite({x, y, z})) throw Exception("Rope3D: gravity must be finite");
115 gravity_ = {x, y, z};
116}
118 if (!std::isfinite(value) || value < 0.f)
119 throw Exception("Rope3D: stretch compliance must be finite and non-negative");
120 stretchCompliance_ = value;
121}
123 if (!std::isfinite(value) || value < 0.f)
124 throw Exception("Rope3D: bend compliance must be finite and non-negative");
125 bendCompliance_ = value;
126}
128 if (!std::isfinite(value) || value < 0.f || value > 0.5f) throw Exception("Rope3D: max bending must be in [0,0.5]");
129 maxBending_ = value;
130}
131void Rope3D::setPlasticity(float yield, float creep) {
132 if (!std::isfinite(yield) || yield < 0.f || yield > 0.5f || !std::isfinite(creep) || creep < 0.f)
133 throw Exception("Rope3D: plastic yield must be in [0,0.5] and creep must be non-negative");
134 plasticYield_ = yield;
135 plasticCreep_ = creep;
136}
138 return index >= 0 && static_cast<std::size_t>(index) < bendPlasticity_.size()
139 ? bendPlasticity_[static_cast<std::size_t>(index)]
140 : 0.f;
141}
143 if (!std::isfinite(value) || value < 0.f || value > 1.f)
144 throw Exception("Rope3D: max compression must be in [0,1]");
145 maxCompression_ = value;
146}
148 if (!std::isfinite(value) || value < 0.f || value > 1.f) throw Exception("Rope3D: damping must be in [0,1]");
149 damping_ = value;
150}
152 if (!std::isfinite(value) || value <= 0.f) throw Exception("Rope3D: particle mass must be positive");
153 particleMass_ = value;
154}
156 if (!std::isfinite(value) || value <= 0.f) throw Exception("Rope3D: radius must be positive");
157 radius_ = value;
158}
159void Rope3D::setBounds(float x, float y, float z, float w, float h, float d) {
160 if (!finite({x, y, z}) || !finite({w, h, d}) || w <= 0.f || h <= 0.f || d <= 0.f)
161 throw Exception("Rope3D: bounds extents must be finite and positive");
162 boundsMin_ = {x, y, z};
163 boundsMax_ = {x + w, y + h, z + d};
164 hasBounds_ = true;
165}
166
168 if (!validParticle(i))
170 eve::DiagnosticCode::InvalidArgument, "Particle index is out of range", "physics.rope3d.pin"));
171 const auto& p = particles_[static_cast<std::size_t>(i)];
172 return attach(i, p.position.x, p.position.y, p.position.z);
173}
175 if (!validParticle(i) || !finite({x, y, z}))
178 "Invalid particle index or attachment position", "physics.rope3d.attach"));
179 auto& p = particles_[static_cast<std::size_t>(i)];
180 const bool changed = !p.attached || p.attachment.x != x || p.attachment.y != y || p.attachment.z != z;
181 p.attached = true;
182 p.attachment = p.position = p.previous = {x, y, z};
185}
187 if (!validParticle(i) || !particles_[static_cast<std::size_t>(i)].attached || !finite({x, y, z}))
190 "Attachment does not exist or target is invalid", "physics.rope3d.moveAttachment"));
191 return attach(i, x, y, z);
192}
194 if (!validParticle(i))
196 eve::DiagnosticCode::InvalidArgument, "Particle index is out of range", "physics.rope3d.detach"));
197 auto& p = particles_[static_cast<std::size_t>(i)];
199 p.attached = false;
200 p.previous = p.position;
202}
203bool Rope3D::isAttached(int i) const { return validParticle(i) && particles_[static_cast<std::size_t>(i)].attached; }
204void Rope3D::applyForce(float x, float y, float z) {
205 if (!finite({x, y, z})) throw Exception("Rope3D: force must be finite");
206 force_ = add(force_, {x, y, z});
207}
208
210 const float current = getRestLength();
211 if (!std::isfinite(requested) || requested <= 0.f || current <= 0.f)
213 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "Rest length must be finite and positive",
214 "physics.rope3d.setRestLength"));
215 if (std::abs(requested - current) <= 1e-6f)
217 const float scale = requested / current;
218 for (auto& e : elements_)
219 if (e.active) e.restLength *= scale;
220 ++topologyRevision_;
222}
224 float sum = 0.f;
225 for (const auto& e : elements_)
226 if (e.active) sum += e.restLength;
227 return sum;
228}
230 float sum = 0.f;
231 for (const auto& e : elements_)
232 if (e.active) sum += length(sub(particles_[e.b].position, particles_[e.a].position));
233 return sum;
234}
235
237 if (!validElement(i))
239 eve::DiagnosticCode::InvalidArgument, "Element index is out of range", "physics.rope3d.cut"));
240 auto& e = elements_[static_cast<std::size_t>(i)];
242 e.active = false;
243 e.lambda = 0.f;
244 e.force = 0.f;
245 ++topologyRevision_;
247}
249 if (!validElement(i))
251 eve::DiagnosticCode::InvalidArgument, "Element index is out of range", "physics.rope3d.repair"));
252 auto& e = elements_[static_cast<std::size_t>(i)];
254 e.restLength = std::max(length(sub(particles_[e.b].position, particles_[e.a].position)), 1e-5f);
255 e.active = true;
256 ++topologyRevision_;
258}
259bool Rope3D::isElementActive(int i) const { return validElement(i) && elements_[static_cast<std::size_t>(i)].active; }
260float Rope3D::getElementForce(int i) const {
261 return validElement(i) ? elements_[static_cast<std::size_t>(i)].force : 0.f;
262}
264 if (!validElement(i) || !std::isfinite(multiplier) || multiplier <= 0.f)
266 eve::DiagnosticCode::InvalidArgument, "Element index and tearing multiplier must be valid",
267 "physics.rope3d.setElementTearResistance"));
268 auto& element = elements_[static_cast<std::size_t>(i)];
269 if (element.tearResistance == multiplier)
271 element.tearResistance = multiplier;
273}
274void Rope3D::setTearing(float resistance, int maxTears) {
275 if (!std::isfinite(resistance) || resistance <= 0.f || maxTears < 1)
276 throw Exception("Rope3D: tearing requires positive resistance and tear count");
277 tearingEnabled_ = true;
278 tearResistance_ = resistance;
279 maxTearsPerStep_ = maxTears;
280}
281
282float Rope3D::getParticleX(int i) const { return validParticle(i) ? particles_[i].position.x : 0.f; }
283float Rope3D::getParticleY(int i) const { return validParticle(i) ? particles_[i].position.y : 0.f; }
284float Rope3D::getParticleZ(int i) const { return validParticle(i) ? particles_[i].position.z : 0.f; }
285float Rope3D::getParticleVelocityX(int i, float dt) const {
286 return validParticle(i) && dt > 0 ? (particles_[i].position.x - particles_[i].previous.x) / dt : 0.f;
287}
288float Rope3D::getParticleVelocityY(int i, float dt) const {
289 return validParticle(i) && dt > 0 ? (particles_[i].position.y - particles_[i].previous.y) / dt : 0.f;
290}
291float Rope3D::getParticleVelocityZ(int i, float dt) const {
292 return validParticle(i) && dt > 0 ? (particles_[i].position.z - particles_[i].previous.z) / dt : 0.f;
293}
294bool Rope3D::validParticle(int i) const noexcept { return i >= 0 && i < getParticleCount(); }
295bool Rope3D::validElement(int i) const noexcept { return i >= 0 && i < getElementCount(); }
296
297void Rope3D::integrate(float dt) {
298 const Vec3 accel = add(gravity_, mul(force_, 1.f / particleMass_));
299 for (auto& p : particles_) {
300 if (p.attached) continue;
301 const Vec3 velocity = mul(sub(p.position, p.previous), 1.f - damping_);
302 p.previous = p.position;
303 p.position = add(add(p.position, velocity), mul(accel, dt * dt));
304 }
305}
306void Rope3D::solveStretch(float dt) {
307 const float invMass = 1.f / particleMass_;
308 for (auto& e : elements_) {
309 if (!e.active) continue;
310 auto& a = particles_[e.a];
311 auto& b = particles_[e.b];
312 const Vec3 delta = sub(b.position, a.position);
313 const float l = length(delta);
314 if (l < 1e-7f) continue;
315 const float target = std::max(e.restLength * (1.f - maxCompression_), std::min(l, e.restLength));
316 const float c = l - target;
317 const float wa = a.attached ? 0.f : invMass, wb = b.attached ? 0.f : invMass;
318 const float alpha = stretchCompliance_ / (dt * dt);
319 const float dl = (-c - alpha * e.lambda) / (wa + wb + alpha);
320 e.lambda += dl;
321 const Vec3 correction = mul(delta, dl / l);
322 if (!a.attached) a.position = sub(a.position, mul(correction, wa));
323 if (!b.attached) b.position = add(b.position, mul(correction, wb));
324 e.force = std::abs(e.lambda) / (dt * dt);
325 }
326}
327void Rope3D::solveBend(float dt) {
328 const float invMass = 1.f / particleMass_, alpha = bendCompliance_ / (dt * dt);
329 for (std::size_t i = 1; i < particles_.size() - 1; ++i) {
330 if (!elements_[i - 1].active || !elements_[i].active) continue;
331 auto& a = particles_[i - 1];
332 auto& c = particles_[i + 1];
333 const Vec3 delta = sub(c.position, a.position);
334 const float l = length(delta);
335 const float baseSpan = elements_[i - 1].restLength + elements_[i].restLength;
336 const float rest = baseSpan * (1.f - std::max(maxBending_, bendPlasticity_[i - 1]));
337 if (l < 1e-7f) continue;
338 if (l >= rest) continue;
339 const float wa = a.attached ? 0.f : invMass, wc = c.attached ? 0.f : invMass;
340 const float dl = -(l - rest) / (wa + wc + alpha);
341 const Vec3 corr = mul(delta, dl / l);
342 if (!a.attached) a.position = sub(a.position, mul(corr, wa));
343 if (!c.attached) c.position = add(c.position, mul(corr, wc));
344 }
345}
346void Rope3D::updatePlasticity(float dt) {
347 if (plasticCreep_ <= 0.f) return;
348 for (std::size_t i = 1; i + 1 < particles_.size(); ++i) {
349 if (!elements_[i - 1].active || !elements_[i].active) continue;
350 const float baseSpan = elements_[i - 1].restLength + elements_[i].restLength;
351 const float currentSpan = length(sub(particles_[i + 1].position, particles_[i - 1].position));
352 const float deformation = std::clamp(1.f - currentSpan / baseSpan, 0.f, 0.5f);
353 if (deformation > plasticYield_)
354 bendPlasticity_[i - 1] = std::min(deformation, bendPlasticity_[i - 1] + plasticCreep_ * dt);
355 }
356}
357void Rope3D::syncBendState() { bendPlasticity_.resize(particles_.size() > 2 ? particles_.size() - 2 : 0, 0.f); }
358void Rope3D::solveSelfCollision() {
359 const float minDist = 2.f * radius_;
360 for (std::size_t i = 0; i < particles_.size(); ++i)
361 for (std::size_t j = i + 2; j < particles_.size(); ++j) {
362 auto& a = particles_[i];
363 auto& b = particles_[j];
364 Vec3 d = sub(b.position, a.position);
365 float l = length(d);
366 if (l >= minDist || l < 1e-7f) continue;
367 const float wa = a.attached ? 0.f : 1.f, wb = b.attached ? 0.f : 1.f, total = wa + wb;
368 if (total == 0) continue;
369 Vec3 corr = mul(d, (minDist - l) / (l * total));
370 if (!a.attached) a.position = sub(a.position, mul(corr, wa));
371 if (!b.attached) b.position = add(b.position, mul(corr, wb));
372 }
373}
374void Rope3D::collideBounds() {
375 if (!hasBounds_) return;
376 for (auto& p : particles_)
377 if (!p.attached) {
378 p.position.x = std::clamp(p.position.x, boundsMin_.x + radius_, boundsMax_.x - radius_);
379 p.position.y = std::clamp(p.position.y, boundsMin_.y + radius_, boundsMax_.y - radius_);
380 p.position.z = std::clamp(p.position.z, boundsMin_.z + radius_, boundsMax_.z - radius_);
381 }
382}
383void Rope3D::applyAttachments() {
384 for (auto& p : particles_)
385 if (p.attached) p.position = p.previous = p.attachment;
386}
387void Rope3D::applyTearing() {
388 if (!tearingEnabled_) return;
389 std::vector<std::size_t> candidates;
390 candidates.reserve(elements_.size());
391 for (std::size_t i = 0; i < elements_.size(); ++i) {
392 const auto& element = elements_[i];
393 if (element.active && element.force > tearResistance_ * element.tearResistance) candidates.push_back(i);
394 }
395 std::stable_sort(candidates.begin(), candidates.end(),
396 [&](std::size_t lhs, std::size_t rhs) { return elements_[lhs].force > elements_[rhs].force; });
397 int tears = 0;
398 for (std::size_t index : candidates) {
399 auto& element = elements_[index];
400 element.active = false;
401 element.lambda = 0.f;
402 element.force = 0.f;
403 lastTornElements_.push_back(static_cast<int>(index));
404 ++topologyRevision_;
405 if (++tears >= maxTearsPerStep_) break;
406 }
407}
408} // namespace eve::physics
LogicalId target
double value
Duration start
bool & active
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
const std::string & s
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
float v
std::int32_t c
int h
std::array< float, 3 > position
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
graphics::Canvas * previous
bool finite
float d
float t
double current
std::uint32_t count
std::string element
SimulationTick tick
TerrainThermalSettings settings
uint32_t index
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
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
eve::Result< void > restoreObservation(const SimulationObservation &observation) override
Restores progress metadata after the owner restores its state.
Definition Rope3D.cpp:106
void update(float dt)
Advances using a compatibility-generated monotonic fixed tick.
Definition Rope3D.cpp:41
eve::Result< RopeTopologyChange > cut(int elementIndex)
Deactivates one structural element and splits the simulated chain.
Definition Rope3D.cpp:236
float getParticleVelocityY(int index, float dt) const
Definition Rope3D.cpp:288
float getBendPlasticity(int bendIndex) const
Returns absorbed local bend fraction, or zero for an invalid bend index.
Definition Rope3D.cpp:137
void setBendCompliance(float compliance)
Sets bend compliance; larger values make the rope easier to bend.
Definition Rope3D.cpp:122
void applyForce(float x, float y, float z)
Applies a one-frame acceleration to every free particle.
Definition Rope3D.cpp:204
void setDamping(float damping)
Sets Verlet velocity damping in [0,1].
Definition Rope3D.cpp:147
bool isElementActive(int elementIndex) const
Definition Rope3D.cpp:259
SimulationObservation observation() const noexcept override
Returns completed-step observables by value.
Definition Rope3D.h:63
eve::Result< RopeTopologyChange > pin(int particleIndex)
Pins a particle at its current position.
Definition Rope3D.cpp:167
void setMaxBending(float fraction)
Sets fractional bend slack in [0,0.5] before bend resistance activates.
Definition Rope3D.cpp:127
void setParticleMass(float mass)
Sets each particle's mass in kilograms.
Definition Rope3D.cpp:151
eve::Result< RopeTopologyChange > attach(int particleIndex, float x, float y, float z)
Pins a particle at an explicit animated target.
Definition Rope3D.cpp:174
void setStretchCompliance(float compliance)
Sets stretch compliance in inverse newtons; zero is inextensible.
Definition Rope3D.cpp:117
float getParticleZ(int index) const
Definition Rope3D.cpp:284
eve::Result< RopeTopologyChange > moveAttachment(int particleIndex, float x, float y, float z)
Moves an existing attachment target.
Definition Rope3D.cpp:186
void setMaxCompression(float fraction)
Sets the maximum fractional compression in [0,1].
Definition Rope3D.cpp:142
float getRestLength() const
Definition Rope3D.cpp:223
float getParticleVelocityX(int index, float dt) const
Definition Rope3D.cpp:285
float calculateLength() const
Definition Rope3D.cpp:229
void setPlasticity(float yield, float creep)
Configures local permanent bend absorption; zero creep disables plasticity.
Definition Rope3D.cpp:131
float getParticleVelocityZ(int index, float dt) const
Definition Rope3D.cpp:291
void setBounds(float x, float y, float z, float width, float height, float depth)
Constrains particles to an axis-aligned box (origin plus positive extents).
Definition Rope3D.cpp:159
float getParticleX(int index) const
Definition Rope3D.cpp:282
float getElementForce(int elementIndex) const
Definition Rope3D.cpp:260
Rope3D(int particleCount, float startX, float startY, float startZ, float endX, float endY, float endZ)
Creates a straight rope including both endpoints.
Definition Rope3D.cpp:20
eve::Result< RopeTopologyChange > setRestLength(float length)
Scales all active element rest lengths, supporting reel/winch behavior.
Definition Rope3D.cpp:209
bool isAttached(int particleIndex) const
Definition Rope3D.cpp:203
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings) override
Advances the rope atomically under the shared simulation contract.
Definition Rope3D.cpp:66
void setTearing(float resistance, int maxTearsPerStep)
Enables automatic tearing above a tensile-force threshold in newtons.
Definition Rope3D.cpp:274
float getParticleY(int index) const
Definition Rope3D.cpp:283
eve::Result< RopeTopologyChange > detach(int particleIndex)
Releases a pinned particle.
Definition Rope3D.cpp:193
eve::Result< RopeTopologyChange > setElementTearResistance(int elementIndex, float multiplier)
Sets a positive per-element multiplier for the global tearing threshold.
Definition Rope3D.cpp:263
eve::Result< RopeTopologyChange > repair(int elementIndex)
Re-enables a cut element using its current endpoint distance.
Definition Rope3D.cpp:248
void setRadius(float radius)
Sets collision radius in meters.
Definition Rope3D.cpp:155
void setGravity(float x, float y, float z)
Sets uniform acceleration in meters per second squared.
Definition Rope3D.cpp:113
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
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Compact solver vector exposed only as a value type.
Definition Rope3D.h:41
Observable backend progress shared by CPU and accelerator providers.
Validated solver policy for one simulation step.