载入中...
搜索中...
未找到
GeometryCollectionInstance.cpp
浏览该文件的文档.
2
3#include "physics/Body3D.h"
4#include "physics/World3D.h"
6
7#include <algorithm>
8#include <cmath>
9#include <queue>
10#include <utility>
11
12namespace eve::physics {
13namespace {
14
15constexpr float kSleepSpeed = 0.05f;
16
17void setBoneBodyType(Body3D& body, BoneRuntimeState state) {
18 switch (state) {
21 body.setType("static");
22 break;
24 body.setType("dynamic");
25 body.setAwake(true);
26 break;
27 }
28}
29
30} // namespace
31
33 float originZ)
34 : worldHandle_(worldHandle), originX_(originX), originY_(originY), originZ_(originZ) {}
35
36GeometryCollectionInstance::~GeometryCollectionInstance() { releaseBodies(); }
37
39 World3D& world, const GeometryCollectionAsset& asset, float originX, float originY, float originZ) {
40 auto valid = asset.validate();
42 if (!world.isValid())
44 eve::DiagnosticCode::StaleHandle, "geometry-collection create requires a live World3D", "world"));
45
46 auto instance = std::unique_ptr<GeometryCollectionInstance>(
47 new GeometryCollectionInstance(world.runtimeHandle(), originX, originY, originZ));
48 instance->bones_.reserve(asset.bones.size());
49 instance->edges_.reserve(asset.edges.size());
50
51 std::vector<Body3D*> createdBodies;
52 createdBodies.reserve(asset.bones.size());
53 auto rollback = [&]() {
54 for (Body3D* body : createdBodies) {
55 if (body && body->isValid()) body->destroy();
56 }
57 createdBodies.clear();
58 instance->bones_.clear();
59 };
60
61 for (const auto& boneDef : asset.bones) {
62 const float x = originX + boneDef.localX;
63 const float y = originY + boneDef.localY;
64 const float z = originZ + boneDef.localZ;
65 // Intact Attached bones stay static so connection-graph topology (not
66 // independent dynamic bodies) keeps the collection from falling apart
67 // before any edge breaks. Detach sync promotes free islands to dynamic.
68 Body3D* body = world.newBody("static", x, y, z);
69 if (!body || !body->isValid()) {
70 rollback();
72 eve::DiagnosticCode::Failed, "geometry-collection failed to create a bone body", "body"));
73 }
74 body->newBoxShape(boneDef.halfExtentX * 2.f, boneDef.halfExtentY * 2.f, boneDef.halfExtentZ * 2.f,
75 boneDef.density, boneDef.friction, boneDef.restitution);
76 auto link = PhysicsLink::fromBody(*body);
77 if (!link) {
78 rollback();
79 if (body->isValid()) body->destroy();
80 return eve::Result<std::unique_ptr<GeometryCollectionInstance>>::failure(link.status());
81 }
82 BoneRuntime bone;
83 bone.state = BoneRuntimeState::Attached;
84 bone.anchored = boneDef.anchoredDefault;
85 bone.link = link.value();
86 bone.halfExtentX = boneDef.halfExtentX;
87 bone.halfExtentY = boneDef.halfExtentY;
88 bone.halfExtentZ = boneDef.halfExtentZ;
89 bone.clusterId = boneDef.clusterId;
90 bone.fractureLevel = boneDef.fractureLevel;
91 instance->bones_.push_back(bone);
92 createdBodies.push_back(body);
93 }
94
95 for (const auto& edgeDef : asset.edges) {
96 EdgeRuntime edge;
97 edge.boneA = edgeDef.boneA;
98 edge.boneB = edgeDef.boneB;
99 edge.strainThreshold = edgeDef.strainThreshold;
100 instance->edges_.push_back(edge);
101 }
102
103 return eve::Result<std::unique_ptr<GeometryCollectionInstance>>::success(std::move(instance));
104}
105
106bool GeometryCollectionInstance::hasLiveWorld() const noexcept { return resolveWorld() != nullptr; }
107
108World3D* GeometryCollectionInstance::resolveWorld() const noexcept {
109 World3D* world = World3D::findWorld(worldHandle_);
110 if (!world || !world->isValid()) return nullptr;
111 return world;
112}
113
114eve::Result<Body3D*> GeometryCollectionInstance::resolveBoneBody(int boneIndex) const {
115 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size()))
117 eve::DiagnosticCode::InvalidArgument, "geometry-collection bone index is out of range", "bone"));
118 World3D* world = resolveWorld();
119 if (!world)
121 eve::DiagnosticCode::StaleHandle, "geometry-collection world is gone", "world"));
122 return bones_[static_cast<std::size_t>(boneIndex)].link.resolve(*world);
123}
124
125float GeometryCollectionInstance::falloffAt(const DestructionField& field, float x, float y, float z) const {
126 const float dx = x - field.centerX;
127 const float dy = y - field.centerY;
128 const float dz = z - field.centerZ;
129 const float dist = std::sqrt(dx * dx + dy * dy + dz * dz);
130 if (!(field.radius > 0.f) || !std::isfinite(field.radius)) return 0.f;
131 if (dist > field.radius) return 0.f;
132 if (field.falloff == DestructionFieldFalloff::None) return 1.f;
133 return 1.f - dist / field.radius;
134}
135
136eve::Result<FieldApplicationReceipt> GeometryCollectionInstance::applyField(const DestructionField& field) {
137 if (!std::isfinite(field.centerX) || !std::isfinite(field.centerY) || !std::isfinite(field.centerZ) ||
138 !std::isfinite(field.radius) || field.radius < 0.f || !std::isfinite(field.magnitude))
140 eve::DiagnosticCode::InvalidArgument, "destruction field parameters must be finite", "field"));
141 if (field.kind == DestructionFieldKind::Impulse &&
142 (!std::isfinite(field.dirX) || !std::isfinite(field.dirY) || !std::isfinite(field.dirZ)))
144 eve::DiagnosticCode::InvalidArgument, "impulse field direction must be finite", "field"));
145 World3D* world = resolveWorld();
146 if (!world) {
147 orphanedPhysics_ = true;
149 eve::DiagnosticCode::StaleHandle, "geometry-collection world is gone", "world"));
150 }
151
153 int sleepsThisCall = 0;
154 bool needGraphSync = false;
155 for (std::size_t i = 0; i < bones_.size(); ++i) {
156 auto bodyResult = bones_[i].link.resolve(*world);
157 if (!bodyResult) continue;
158 Body3D* body = bodyResult.value();
159 const float weight = falloffAt(field, body->getX(), body->getY(), body->getZ());
160 if (weight <= 0.f) continue;
161 switch (field.kind) {
162 case DestructionFieldKind::Anchor: {
163 const bool wasSleeping = bones_[i].state == BoneRuntimeState::Sleeping;
164 bones_[i].anchored = true;
165 bones_[i].state = BoneRuntimeState::Attached;
166 body->setType("static");
167 if (wasSleeping) ++sleepBatchRevision_;
168 ++receipt.bonesAffected;
169 needGraphSync = true;
170 break;
171 }
172 case DestructionFieldKind::Impulse: {
173 // Anchored foundations and still-Attached graph members stay put.
174 if (bones_[i].anchored || bones_[i].state == BoneRuntimeState::Attached) break;
175 float dx = field.dirX, dy = field.dirY, dz = field.dirZ;
176 const float len = std::sqrt(dx * dx + dy * dy + dz * dz);
177 if (len <= 1e-6f) {
178 dx = 0.f;
179 dy = 1.f;
180 dz = 0.f;
181 } else {
182 dx /= len;
183 dy /= len;
184 dz /= len;
185 }
186 const float impulse = field.magnitude * weight;
187 const bool wasSleeping = bones_[i].state == BoneRuntimeState::Sleeping;
188 body->setType("dynamic");
189 body->applyLinearImpulse(dx * impulse, dy * impulse, dz * impulse);
190 bones_[i].state = BoneRuntimeState::Detached;
191 if (wasSleeping) ++sleepBatchRevision_;
192 ++receipt.bonesAffected;
193 break;
194 }
195 case DestructionFieldKind::Sleep: {
196 if (bones_[i].state != BoneRuntimeState::Detached) break;
197 const float vx = body->getLinearVelocityX();
198 const float vy = body->getLinearVelocityY();
199 const float vz = body->getLinearVelocityZ();
200 const float speed = std::sqrt(vx * vx + vy * vy + vz * vz);
201 if (speed > kSleepSpeed) break;
202 if (budget_.maxSleepsPerStep > 0 && sleepsThisCall >= budget_.maxSleepsPerStep) {
203 ++receipt.sleepsDeferred;
204 break;
205 }
206 body->setType("static");
207 if (bones_[i].state != BoneRuntimeState::Sleeping) ++sleepBatchRevision_;
208 bones_[i].state = BoneRuntimeState::Sleeping;
209 ++receipt.bonesAffected;
210 ++sleepsThisCall;
211 break;
212 }
213 case DestructionFieldKind::Strain:
214 break;
215 }
216 }
217 if (needGraphSync) syncBoneActivationFromGraph();
218
219 if (field.kind == DestructionFieldKind::Strain) {
220 for (auto& edge : edges_) {
221 if (edge.broken) continue;
222 if (edge.boneA < 0 || edge.boneB < 0 || edge.boneA >= static_cast<int>(bones_.size()) ||
223 edge.boneB >= static_cast<int>(bones_.size()))
224 continue;
225 auto bodyA = bones_[static_cast<std::size_t>(edge.boneA)].link.resolve(*world);
226 auto bodyB = bones_[static_cast<std::size_t>(edge.boneB)].link.resolve(*world);
227 if (!bodyA || !bodyB) continue;
228 const float midX = 0.5f * (bodyA.value()->getX() + bodyB.value()->getX());
229 const float midY = 0.5f * (bodyA.value()->getY() + bodyB.value()->getY());
230 const float midZ = 0.5f * (bodyA.value()->getZ() + bodyB.value()->getZ());
231 const float weight = falloffAt(field, midX, midY, midZ);
232 if (weight <= 0.f) continue;
233 edge.accumulatedStrain += field.magnitude * weight;
234 ++receipt.edgesAffected;
235 }
236 }
238}
239
240void GeometryCollectionInstance::breakEdge(int edgeIndex, std::uint64_t tick, DestructionStepReceipt& receipt) {
241 auto& edge = edges_[static_cast<std::size_t>(edgeIndex)];
242 if (edge.broken) return;
243 edge.broken = true;
244 ++receipt.edgesBroken;
245
246 BoneDetachEvent event;
247 event.boneA = edge.boneA;
248 event.boneB = edge.boneB;
249 event.tick = static_cast<std::int64_t>(tick);
250 detachEvents_.push_back(event);
251
252 if (edge.boneA >= 0 && edge.boneB >= 0 && edge.boneA < static_cast<int>(bones_.size()) &&
253 edge.boneB < static_cast<int>(bones_.size())) {
254 const int clusterA = bones_[static_cast<std::size_t>(edge.boneA)].clusterId;
255 const int clusterB = bones_[static_cast<std::size_t>(edge.boneB)].clusterId;
256 if (clusterA != clusterB) {
257 ClusterBreakEvent clusterEvent;
258 clusterEvent.clusterA = clusterA;
259 clusterEvent.clusterB = clusterB;
260 clusterEvent.boneA = edge.boneA;
261 clusterEvent.boneB = edge.boneB;
262 clusterEvent.tick = static_cast<std::int64_t>(tick);
263 clusterBreakEvents_.push_back(clusterEvent);
264 ++receipt.clusterBreaks;
265 }
266 }
267}
268
269void GeometryCollectionInstance::syncBoneActivationFromGraph() {
270 const int boneCount = static_cast<int>(bones_.size());
271 if (boneCount == 0) return;
272
273 std::vector<std::vector<int>> adjacency(static_cast<std::size_t>(boneCount));
274 for (const auto& edge : edges_) {
275 if (edge.broken) continue;
276 if (edge.boneA < 0 || edge.boneB < 0 || edge.boneA >= boneCount || edge.boneB >= boneCount) continue;
277 adjacency[static_cast<std::size_t>(edge.boneA)].push_back(edge.boneB);
278 adjacency[static_cast<std::size_t>(edge.boneB)].push_back(edge.boneA);
279 }
280
281 std::vector<int> component(static_cast<std::size_t>(boneCount), -1);
282 int componentCount = 0;
283 bool anyAnchored = false;
284 for (int i = 0; i < boneCount; ++i) {
285 if (bones_[static_cast<std::size_t>(i)].anchored) anyAnchored = true;
286 if (component[static_cast<std::size_t>(i)] >= 0) continue;
287 std::queue<int> queue;
288 queue.push(i);
289 component[static_cast<std::size_t>(i)] = componentCount;
290 while (!queue.empty()) {
291 const int cur = queue.front();
292 queue.pop();
293 for (int next : adjacency[static_cast<std::size_t>(cur)]) {
294 if (component[static_cast<std::size_t>(next)] >= 0) continue;
295 component[static_cast<std::size_t>(next)] = componentCount;
296 queue.push(next);
297 }
298 }
299 ++componentCount;
300 }
301
302 std::vector<bool> componentHasAnchor(static_cast<std::size_t>(componentCount), false);
303 for (int i = 0; i < boneCount; ++i) {
304 if (bones_[static_cast<std::size_t>(i)].anchored)
305 componentHasAnchor[static_cast<std::size_t>(component[static_cast<std::size_t>(i)])] = true;
306 }
307
308 World3D* world = resolveWorld();
309 for (int i = 0; i < boneCount; ++i) {
310 auto& bone = bones_[static_cast<std::size_t>(i)];
311 if (bone.state == BoneRuntimeState::Sleeping) continue; // Sleep is sticky until Impulse/Anchor
312 const bool stayAttached =
313 bone.anchored ||
314 componentHasAnchor[static_cast<std::size_t>(component[static_cast<std::size_t>(i)])] ||
315 (!anyAnchored && componentCount == 1);
316 const BoneRuntimeState desired =
317 stayAttached ? BoneRuntimeState::Attached : BoneRuntimeState::Detached;
318 if (bone.state == desired) {
319 // Ensure Attached bones remain static even if a prior path made them dynamic.
320 if (desired == BoneRuntimeState::Attached && world) {
321 auto body = bone.link.resolve(*world);
322 if (body) setBoneBodyType(*body.value(), desired);
323 }
324 continue;
325 }
326 bone.state = desired;
327 if (!world) continue;
328 auto body = bone.link.resolve(*world);
329 if (body) setBoneBodyType(*body.value(), desired);
330 }
331}
332
333eve::Result<DestructionStepReceipt> GeometryCollectionInstance::step(SimulationStep step) {
335 if (step.delta.nanoseconds() < 0)
337 eve::DiagnosticCode::InvalidArgument, "geometry-collection step delta must be non-negative", "step"));
338 const std::uint64_t tick = step.tick.value();
339 if (lastTick_ != 0 && tick <= lastTick_)
341 eve::DiagnosticCode::InvalidArgument, "geometry-collection step tick must advance", "step"));
342 World3D* world = resolveWorld();
343 if (!world) {
344 orphanedPhysics_ = true;
346 eve::DiagnosticCode::StaleHandle, "geometry-collection world is gone", "world"));
347 }
348 orphanedPhysics_ = false;
349 detachEvents_.clear();
350 clusterBreakEvents_.clear();
351
352 std::vector<int> toBreak = pendingEdgeBreaks_;
353 pendingEdgeBreaks_.clear();
354 for (std::size_t i = 0; i < edges_.size(); ++i) {
355 const auto& edge = edges_[i];
356 if (edge.broken) continue;
357 if (edge.accumulatedStrain >= edge.strainThreshold) toBreak.push_back(static_cast<int>(i));
358 }
359 std::sort(toBreak.begin(), toBreak.end());
360 toBreak.erase(std::unique(toBreak.begin(), toBreak.end()), toBreak.end());
361
362 const int budget =
363 budget_.maxEdgeBreaksPerStep > 0 ? budget_.maxEdgeBreaksPerStep : static_cast<int>(toBreak.size());
364 int brokenThisStep = 0;
365 for (int edgeIndex : toBreak) {
366 if (edgeIndex < 0 || edgeIndex >= static_cast<int>(edges_.size())) continue;
367 if (edges_[static_cast<std::size_t>(edgeIndex)].broken) continue;
368 if (brokenThisStep >= budget) {
369 pendingEdgeBreaks_.push_back(edgeIndex);
370 ++receipt.edgesDeferred;
371 continue;
372 }
373 breakEdge(edgeIndex, tick, receipt);
374 ++brokenThisStep;
375 }
376 if (receipt.edgesBroken > 0) syncBoneActivationFromGraph();
377 lastTick_ = tick;
379}
380
381BoneRuntimeState GeometryCollectionInstance::boneState(int boneIndex) const {
382 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size())) return BoneRuntimeState::Attached;
383 return bones_[static_cast<std::size_t>(boneIndex)].state;
384}
385
386int GeometryCollectionInstance::boneClusterId(int boneIndex) const {
387 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size())) return -1;
388 return bones_[static_cast<std::size_t>(boneIndex)].clusterId;
389}
390
391float GeometryCollectionInstance::edgeStrain(int edgeIndex) const {
392 if (edgeIndex < 0 || edgeIndex >= static_cast<int>(edges_.size())) return 0.f;
393 return edges_[static_cast<std::size_t>(edgeIndex)].accumulatedStrain;
394}
395
396bool GeometryCollectionInstance::isEdgeBroken(int edgeIndex) const {
397 if (edgeIndex < 0 || edgeIndex >= static_cast<int>(edges_.size())) return false;
398 return edges_[static_cast<std::size_t>(edgeIndex)].broken;
399}
400
401BoneDetachEvent GeometryCollectionInstance::detachEventAt(int index) const {
402 if (index < 0 || index >= static_cast<int>(detachEvents_.size())) return {};
403 return detachEvents_[static_cast<std::size_t>(index)];
404}
405
406ClusterBreakEvent GeometryCollectionInstance::clusterBreakEventAt(int index) const {
407 if (index < 0 || index >= static_cast<int>(clusterBreakEvents_.size())) return {};
408 return clusterBreakEvents_[static_cast<std::size_t>(index)];
409}
410
411eve::Result<PhysicsLink> GeometryCollectionInstance::boneLink(int boneIndex) const {
412 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size()))
414 eve::DiagnosticCode::InvalidArgument, "geometry-collection bone index is out of range", "bone"));
415 World3D* world = resolveWorld();
416 if (!world)
418 eve::DiagnosticCode::StaleHandle, "geometry-collection world is gone", "world"));
419 auto body = bones_[static_cast<std::size_t>(boneIndex)].link.resolve(*world);
420 if (!body) return eve::Result<PhysicsLink>::failure(body.status());
421 return eve::Result<PhysicsLink>::success(bones_[static_cast<std::size_t>(boneIndex)].link);
422}
423
424eve::Result<BonePresentation> GeometryCollectionInstance::bonePresentation(int boneIndex) const {
425 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size()))
427 eve::DiagnosticCode::InvalidArgument, "geometry-collection bone index is out of range", "bone"));
428 World3D* world = resolveWorld();
429 if (!world)
431 eve::DiagnosticCode::StaleHandle, "geometry-collection world is gone", "world"));
432 const auto& bone = bones_[static_cast<std::size_t>(boneIndex)];
433 auto body = bone.link.resolve(*world);
434 if (!body) return eve::Result<BonePresentation>::failure(body.status());
435 BonePresentation presentation;
436 presentation.state = bone.state;
437 presentation.halfExtentX = bone.halfExtentX;
438 presentation.halfExtentY = bone.halfExtentY;
439 presentation.halfExtentZ = bone.halfExtentZ;
440 presentation.worldX = body.value()->getX();
441 presentation.worldY = body.value()->getY();
442 presentation.worldZ = body.value()->getZ();
443 presentation.rotX = body.value()->getRotX();
444 presentation.rotY = body.value()->getRotY();
445 presentation.rotZ = body.value()->getRotZ();
446 presentation.rotW = body.value()->getRotW();
447 presentation.clusterId = bone.clusterId;
448 presentation.fractureLevel = bone.fractureLevel;
449 return eve::Result<BonePresentation>::success(presentation);
450}
451
452eve::Result<GeometryCollectionInstanceSnapshot> GeometryCollectionInstance::captureSnapshot() const {
454 snapshot.originX = originX_;
455 snapshot.originY = originY_;
456 snapshot.originZ = originZ_;
457 snapshot.lastTick = static_cast<std::int64_t>(lastTick_);
458 snapshot.sleepBatchRevision = sleepBatchRevision_;
459 snapshot.bones.reserve(bones_.size());
460 for (const auto& bone : bones_) {
462 boneSnap.state = static_cast<std::uint8_t>(bone.state);
463 boneSnap.anchored = bone.anchored;
464 boneSnap.clusterId = bone.clusterId;
465 boneSnap.fractureLevel = bone.fractureLevel;
466 boneSnap.halfExtentX = bone.halfExtentX;
467 boneSnap.halfExtentY = bone.halfExtentY;
468 boneSnap.halfExtentZ = bone.halfExtentZ;
469 snapshot.bones.push_back(boneSnap);
470 }
471 snapshot.edges.reserve(edges_.size());
472 for (const auto& edge : edges_) {
474 edgeSnap.boneA = edge.boneA;
475 edgeSnap.boneB = edge.boneB;
476 edgeSnap.strainThreshold = edge.strainThreshold;
477 edgeSnap.accumulatedStrain = edge.accumulatedStrain;
478 edgeSnap.broken = edge.broken;
479 snapshot.edges.push_back(edgeSnap);
480 }
481 auto valid = snapshot.validate();
484}
485
486eve::Result<void> GeometryCollectionInstance::restoreSnapshot(const GeometryCollectionInstanceSnapshot& snapshot) {
487 auto valid = snapshot.validate();
488 if (!valid) return eve::Result<void>::failure(valid.status());
489 if (snapshot.bones.size() != bones_.size() || snapshot.edges.size() != edges_.size())
492 "instance-snapshot bone/edge counts must match the live instance", "snapshot"));
493 World3D* world = resolveWorld();
494 if (!world) {
495 orphanedPhysics_ = true;
497 eve::DiagnosticCode::StaleHandle, "geometry-collection world is gone", "world"));
498 }
499
500 // Stage + resolve every body before mutating World3D so failures leave both
501 // the instance topology and body types unchanged.
502 std::vector<BoneRuntime> stagedBones = bones_;
503 std::vector<EdgeRuntime> stagedEdges = edges_;
504 std::vector<Body3D*> bodies(snapshot.bones.size(), nullptr);
505 for (std::size_t i = 0; i < snapshot.bones.size(); ++i) {
506 const auto& src = snapshot.bones[i];
507 stagedBones[i].state = static_cast<BoneRuntimeState>(src.state);
508 stagedBones[i].anchored = src.anchored;
509 stagedBones[i].clusterId = src.clusterId;
510 stagedBones[i].fractureLevel = src.fractureLevel;
511 stagedBones[i].halfExtentX = src.halfExtentX;
512 stagedBones[i].halfExtentY = src.halfExtentY;
513 stagedBones[i].halfExtentZ = src.halfExtentZ;
514 auto body = stagedBones[i].link.resolve(*world);
515 if (!body) return eve::Result<void>::failure(body.status());
516 bodies[i] = body.value();
517 }
518 for (std::size_t i = 0; i < snapshot.edges.size(); ++i) {
519 const auto& src = snapshot.edges[i];
520 if (src.boneA != stagedEdges[i].boneA || src.boneB != stagedEdges[i].boneB)
522 eve::DiagnosticCode::InvalidArgument, "instance-snapshot edge topology mismatch", "edges"));
523 stagedEdges[i].strainThreshold = src.strainThreshold;
524 stagedEdges[i].accumulatedStrain = src.accumulatedStrain;
525 stagedEdges[i].broken = src.broken;
526 }
527
528 for (std::size_t i = 0; i < stagedBones.size(); ++i) {
529 Body3D* body = bodies[i];
530 switch (stagedBones[i].state) {
531 case BoneRuntimeState::Attached:
532 body->setType(stagedBones[i].anchored ? "static" : "dynamic");
533 break;
534 case BoneRuntimeState::Detached:
535 body->setType("dynamic");
536 body->setAwake(true);
537 break;
538 case BoneRuntimeState::Sleeping:
539 body->setType("static");
540 break;
541 }
542 }
543
544 bones_ = std::move(stagedBones);
545 edges_ = std::move(stagedEdges);
546 originX_ = snapshot.originX;
547 originY_ = snapshot.originY;
548 originZ_ = snapshot.originZ;
549 lastTick_ = static_cast<std::uint64_t>(snapshot.lastTick);
550 sleepBatchRevision_ = snapshot.sleepBatchRevision;
551 detachEvents_.clear();
552 clusterBreakEvents_.clear();
553 pendingEdgeBreaks_.clear();
554 orphanedPhysics_ = false;
556}
557
558void GeometryCollectionInstance::destroyOwnedBodies(World3D& world) {
559 for (auto& bone : bones_) {
560 auto body = bone.link.resolve(world);
561 if (body && body.value() && body.value()->isValid()) body.value()->destroy();
562 bone.link = {};
563 }
564}
565
566void GeometryCollectionInstance::releaseBodies() {
567 if (World3D* world = resolveWorld()) {
568 destroyOwnedBodies(*world);
569 } else {
570 for (auto& bone : bones_) bone.link = {};
571 orphanedPhysics_ = true;
572 }
573}
574
575} // namespace eve::physics
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::uint32_t bone
bool valid
World3D * world
const RoadEdge * edge
float dz
float dy
float dx
SimulationTick tick
float step
Definition TreeMesh.cpp:314
std::string body
uint32_t index
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
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
3D rigid body (Box3D) in meter-space coordinates (+Y up by convention). Owned by a World3D; create pr...
Definition Body3D.h:24
void setType(const std::string &bodyType)
"static" | "kinematic" | "dynamic".
Definition Body3D.cpp:656
Runtime owner of bone state, connection strain, and PhysicsLink bindings.
GeometryCollectionInstance(const GeometryCollectionInstance &)=delete
Box3D rigid-body world. Script coordinates are meters (Box3D native), unlike 2D World which uses pixe...
Definition World3D.h:63
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
BoneRuntimeState
Runtime lifecycle for one bone inside a geometry-collection instance.
eve::RuntimeHandle< PhysicsWorldHandleTag > PhysicsWorldHandle
Generation-qualified runtime identity for a physics world.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
One ordered detach event emitted during step.
Presentation snapshot for one bone (world pose + proxy extents).
Emitted when a broken edge separates two different cook clusters.
One volumetric influence applied to a geometry-collection instance.
Observable outcome of one checked step.
Observable receipt for one checked field application.
int sleepsDeferred
Sleep conversions skipped because of the per-step sleep budget.
Immutable cooked/authored geometry-collection asset.
std::vector< GeometryCollectionEdge > edges
std::vector< GeometryCollectionBone > bones
eve::Result< void > validate() const
Validate bone/edge topology and numeric ranges.
Persistent runtime snapshot for one geometry-collection instance.
eve::Result< void > validate() const
Validate counts and numeric ranges.