载入中...
搜索中...
未找到
World.cpp
浏览该文件的文档.
1#include "physics/World.h"
2#include "physics/Body.h"
3#include "physics/Fixture.h"
4#include "physics/Joint2D.h"
7
8#include "common/Exception.h"
9#include "common/Profile.h"
11
12#include <Box2D/Box2D.h>
13
14#include <algorithm>
15#include <cmath>
16#include <cstring>
17#include <limits>
18#include <utility>
19
20namespace eve::physics {
21namespace {
22b2BodyType parseBodyType(const std::string &type) {
23 if (type == "static") return b2_staticBody;
24 if (type == "kinematic") return b2_kinematicBody;
25 if (type == "dynamic") return b2_dynamicBody;
26 throw eve::Exception("World.newBody: unknown body type '%s' (use static|kinematic|dynamic)",
27 type.c_str());
28}
29
30Body *bodyFromFixture(b2Fixture *f) {
31 if (!f) return nullptr;
32 b2Body *b = f->GetBody();
33 if (!b) return nullptr;
34 return static_cast<Body *>(b->GetUserData());
35}
36
37Fixture *fixtureFromRaw(b2Fixture *f) {
38 return f ? static_cast<Fixture *>(f->GetUserData()) : nullptr;
39}
40
41// Signed distance from a world point to a shape (meters). Negative when inside.
42// `normal` points from the shape toward the point (world space, unit length).
43bool shapePointDistance(const b2Shape *shape, const b2Transform &xf, const b2Vec2 &p,
44 float &dist, b2Vec2 &normal) {
45 dist = 0.f;
46 normal = b2Vec2(0.f, 1.f);
47 if (!shape) return false;
48
49 if (shape->GetType() == b2Shape::e_circle) {
50 const auto *circle = static_cast<const b2CircleShape *>(shape);
51 const b2Vec2 center = b2Mul(xf, circle->m_p);
52 const b2Vec2 d = p - center;
53 const float r = circle->m_radius;
54 dist = b2Distance(p, center) - r;
55 const float len = d.Length();
56 if (len > 1e-6f) {
57 normal = (1.f / len) * d;
58 } else if (r > 0.f) {
59 normal = b2Vec2(0.f, 1.f);
60 }
61 return true;
62 }
63
64 if (shape->GetType() == b2Shape::e_polygon) {
65 const auto *poly = static_cast<const b2PolygonShape *>(shape);
66 const b2Vec2 local = b2MulT(xf, p);
67 const int n = poly->m_count;
68 if (n < 3) return false;
69
70 // Signed distance to each edge: positive outside for Box2D CCW polygons
71 // (m_normals point outward). Inside when every side <= 0.
72 float minSide = std::numeric_limits<float>::max();
73 float bestEdge = std::numeric_limits<float>::max();
74 b2Vec2 bestN(0.f, 1.f);
75 b2Vec2 bestQ;
76 bool inside = true;
77 for (int i = 0; i < n; ++i) {
78 const b2Vec2 &a = poly->m_vertices[i];
79 const b2Vec2 &b = poly->m_vertices[(i + 1) % n];
80 const float side = b2Dot(local - a, poly->m_normals[i]);
81 if (side > 0.f) inside = false;
82 if (side < minSide) {
83 minSide = side;
84 bestN = poly->m_normals[i];
85 }
86 // Closest point on the edge segment.
87 const b2Vec2 ab = b - a;
88 const float len2 = b2Dot(ab, ab);
89 float t = 0.f;
90 if (len2 > 1e-12f) t = b2Clamp(b2Dot(local - a, ab) / len2, 0.f, 1.f);
91 const b2Vec2 q = a + t * ab;
92 const float d2 = b2DistanceSquared(local, q);
93 if (d2 < bestEdge) {
94 bestEdge = d2;
95 bestQ = q;
96 }
97 }
98 if (inside) {
99 dist = minSide; // negative penetration
100 normal = b2Mul(xf.q, bestN);
101 } else {
102 dist = std::sqrt(bestEdge);
103 const b2Vec2 delta = local - bestQ;
104 if (delta.LengthSquared() > 1e-8f) {
105 normal = b2Mul(xf.q, (1.f / delta.Length()) * delta);
106 } else {
107 normal = b2Mul(xf.q, bestN);
108 }
109 }
110 return true;
111 }
112
113 if (shape->GetType() == b2Shape::e_edge) {
114 const auto *edge = static_cast<const b2EdgeShape *>(shape);
115 const b2Vec2 a = b2Mul(xf, edge->m_vertex1);
116 const b2Vec2 b = b2Mul(xf, edge->m_vertex2);
117 const b2Vec2 ab = b - a;
118 const float len2 = b2Dot(ab, ab);
119 float t = 0.f;
120 if (len2 > 1e-12f) t = b2Clamp(b2Dot(p - a, ab) / len2, 0.f, 1.f);
121 const b2Vec2 q = a + t * ab;
122 const b2Vec2 delta = p - q;
123 dist = delta.Length();
124 if (dist > 1e-6f) {
125 normal = (1.f / dist) * delta;
126 } else if (len2 > 1e-12f) {
127 normal = (1.f / std::sqrt(len2)) * b2Vec2(-ab.y, ab.x);
128 }
129 return true;
130 }
131
132 if (shape->GetType() == b2Shape::e_chain) {
133 const auto *chain = static_cast<const b2ChainShape *>(shape);
134 float best = std::numeric_limits<float>::max();
135 b2Vec2 bestN(0.f, 1.f);
136 for (int i = 0; i + 1 < chain->m_count; ++i) {
137 const b2Vec2 a = b2Mul(xf, chain->m_vertices[i]);
138 const b2Vec2 b = b2Mul(xf, chain->m_vertices[i + 1]);
139 const b2Vec2 ab = b - a;
140 const float len2 = b2Dot(ab, ab);
141 float t = 0.f;
142 if (len2 > 1e-12f) t = b2Clamp(b2Dot(p - a, ab) / len2, 0.f, 1.f);
143 const b2Vec2 q = a + t * ab;
144 const b2Vec2 delta = p - q;
145 const float d = delta.Length();
146 if (d < best) {
147 best = d;
148 bestN = d > 1e-6f
149 ? (1.f / d) * delta
150 : (len2 > 1e-12f ? (1.f / std::sqrt(len2)) * b2Vec2(-ab.y, ab.x)
151 : b2Vec2(0.f, 1.f));
152 }
153 }
154 if (best < std::numeric_limits<float>::max()) {
155 dist = best;
156 normal = bestN;
157 return true;
158 }
159 }
160 return false;
161}
162
163World::ContactEvent contactEventFrom(b2Contact *contact) {
164 World::ContactEvent out;
165 if (!contact) return out;
166 Fixture *fa = fixtureFromRaw(contact->GetFixtureA());
167 Fixture *fb = fixtureFromRaw(contact->GetFixtureB());
168 if (fa) {
169 out.bodyAId = fa->getBodyId();
170 out.fixtureATag = fa->getTag();
171 }
172 if (fb) {
173 out.bodyBId = fb->getBodyId();
174 out.fixtureBTag = fb->getTag();
175 }
176 return out;
177}
178
179eve::Result<eve::SimulationStep> makeLegacyStep(float dt, eve::SimulationTick currentTick) {
180 if (!std::isfinite(dt) || dt < 0.f) {
183 "World update dt must be finite and non-negative", "physics.world.update.dt"));
184 }
185 const float normalized = std::min(dt, 0.05f);
186 const auto nextTick = currentTick.incremented();
187 if (!nextTick) {
190 "World simulation tick cannot be incremented", "physics.world.simulationTick"));
191 }
192 auto duration = eve::Duration::fromSeconds(static_cast<double>(normalized));
194 return eve::Result<eve::SimulationStep>::success({*nextTick, std::move(duration).takeValue()});
195}
196
197} // namespace
198
199class ContactRelay : public b2ContactListener {
200public:
201 explicit ContactRelay(World *world) : world_(world) {}
202 void setWorld(World *world) noexcept { world_ = world; }
203
204 void BeginContact(b2Contact *contact) override { world_->onBeginContact(contact); }
205 void EndContact(b2Contact *contact) override { world_->onEndContact(contact); }
206 void PreSolve(b2Contact *contact, const b2Manifold *oldManifold) override {
207 world_->onPreSolve(contact, oldManifold);
208 }
209 void PostSolve(b2Contact *contact, const b2ContactImpulse *impulse) override {
210 world_->onPostSolve(contact, impulse);
211 }
212
213private:
214 World *world_;
215};
216
217World::World(float gravityX, float gravityY, bool sleep, float meter, eve::PersistentId instanceId)
218 : instanceId_(instanceId), meter_(meter) {
219 if (meter_ <= 0.f) meter_ = 30.f;
220 runtimeHandle_ = detail::allocatePhysicsWorldHandle();
221 if (instanceId_.isNil()) instanceId_ = detail::makePhysicsWorldPersistentId(runtimeHandle_);
222 world_ = new b2World(b2Vec2(toMeters(gravityX), toMeters(gravityY)));
223 world_->SetAllowSleeping(sleep);
224 relay_ = new ContactRelay(this);
225 world_->SetContactListener(relay_);
226 auto selection = detail::selectSimulationBackend(SimulationBackendDomain::World2D,
227 detail::makeBox2DSimulationBackend(world_), world_, true);
228 if (!selection) {
229 const eve::Status status = selection.status();
230 throw eve::Exception("World: cannot select a simulation backend: %s", status.describe().c_str());
231 }
232 backendSelectionStatus_ = selection.status();
233 auto selected = std::move(selection).takeValue();
234 backendFallback_ = selected.usedFallback;
235 simulation_ = std::move(selected.backend);
236}
237
239
240void World::adoptPreparedTopology(World &prepared) {
241 std::swap(world_, prepared.world_);
242 std::swap(relay_, prepared.relay_);
243 std::swap(simulation_, prepared.simulation_);
244 std::swap(meter_, prepared.meter_);
245 std::swap(nextId_, prepared.nextId_);
246 std::swap(bodies_, prepared.bodies_);
247 std::swap(fixtures_, prepared.fixtures_);
248 std::swap(simulationTick_, prepared.simulationTick_);
249 for (Body *body : bodies_) body->world_ = this;
250 for (Fixture *fixture : fixtures_) fixture->world_ = this;
251 for (Body *body : prepared.bodies_) body->world_ = &prepared;
252 for (Fixture *fixture : prepared.fixtures_) fixture->world_ = &prepared;
253 relay_->setWorld(this);
254 world_->SetContactListener(relay_);
255 prepared.relay_->setWorld(&prepared);
256 prepared.world_->SetContactListener(prepared.relay_);
257}
258
260 if (destroyed_) return;
261 destroyed_ = true;
262 lifetime_.reset();
263
264 std::vector<Mechanism2D *> mechanisms(mechanisms_.begin(), mechanisms_.end());
265 for (Mechanism2D *mechanism : mechanisms) {
266 if (mechanism) mechanism->invalidate();
267 }
268 mechanisms_.clear();
269
270 std::vector<Joint2D *> joints(joints_.begin(), joints_.end());
271 for (Joint2D *joint : joints) {
272 if (joint) joint->invalidate();
273 }
274 joints_.clear();
275 jointHandles_.clear();
276
277 // Copy sets — Body/Fixture destructors erase from them.
278 std::vector<Body *> bodies(bodies_.begin(), bodies_.end());
279 for (Body *b : bodies) {
280 if (b) {
281 b->invalidate();
282 // Script may still hold Body*; leave object but null raw pointer.
283 // If World is script-owned and Body is also script-owned, Body::~Body
284 // will see null body_ and skip DestroyBody.
285 }
286 }
287 bodies_.clear();
288
289 std::vector<Fixture *> fixtures(fixtures_.begin(), fixtures_.end());
290 for (Fixture *f : fixtures) {
291 if (f) f->invalidate();
292 }
293 fixtures_.clear();
295
296 simulation_.reset();
297
298 if (world_) {
299 world_->SetContactListener(nullptr);
300 delete world_;
301 world_ = nullptr;
302 }
303 delete relay_;
304 relay_ = nullptr;
305 runtimeHandle_ = PhysicsWorldHandle::invalid();
306}
307
308bool World::pointProbe(float x, float y, float radius, ClothContact *out) const {
309 if (out) *out = ClothContact{};
310 if (!isValid() || radius <= 0.f) return false;
311
312 const float rM = toMeters(radius);
313 const b2Vec2 centerM(toMeters(x), toMeters(y));
314
315 struct Probe : b2QueryCallback {
316 const World *world = nullptr;
317 ClothContact *best = nullptr;
318 b2Vec2 center;
319 float radiusM = 0.f;
320
321 bool ReportFixture(b2Fixture *fixture) override {
322 if (!fixture || fixture->IsSensor()) return true;
323 const b2Shape *shape = fixture->GetShape();
324 if (!shape) return true;
325 float dist;
326 b2Vec2 normal;
327 if (!shapePointDistance(shape, fixture->GetBody()->GetTransform(), center, dist,
328 normal)) {
329 return true;
330 }
331 const float depth = radiusM - dist;
332 if (depth > 0.f && depth > best->depth) {
333 best->hit = true;
334 best->depth = world->toPixels(depth);
335 best->nx = normal.x;
336 best->ny = normal.y;
337 best->body = static_cast<Body *>(fixture->GetBody()->GetUserData());
338 }
339 return true;
340 }
341 } probe;
342 probe.world = this;
343 probe.best = out;
344 probe.center = centerM;
345 probe.radiusM = rM;
346
347 b2AABB aabb;
348 aabb.lowerBound = centerM - b2Vec2(rM, rM);
349 aabb.upperBound = centerM + b2Vec2(rM, rM);
350 world_->QueryAABB(&probe, aabb);
351 return out->hit;
352}
353
354void World::update(float dt) { updateFull(dt, 8, 3); }
355
356void World::updateFull(float dt, int velocityIterations, int positionIterations) {
357 EV_PROFILE_MODULE("physics", "World::update");
358 auto legacyStep = makeLegacyStep(dt, simulationTick_);
359 if (!legacyStep) {
360 legacyStep.ignore("legacy World::updateFull cannot return a structured error");
361 return;
362 }
363 auto result =
364 step(std::move(legacyStep).takeValue(), SimulationSettings{velocityIterations, positionIterations, 4});
365 result.ignore("legacy World::updateFull cannot return a structured result");
366}
367
369 if (!isValid() || !simulation_) {
372 "Cannot step a destroyed or uninitialized physics world", "physics.world.step"));
373 }
374 auto valid = detail::validateSimulationStep(stepValue, settings, simulation_->observation());
375 if (!valid) return valid;
376 for (Mechanism2D *mechanism : mechanisms_) {
377 if (mechanism) mechanism->syncBeforeStep();
378 }
379 auto result = simulation_->step(stepValue, settings);
380 if (!result) return result;
381 simulationTick_ = stepValue.tick;
382 return result;
383}
384
386 return simulation_ ? simulation_->observation() : SimulationObservation{};
387}
388
390 return simulation_ ? simulation_->kind() : SimulationBackendKind::Cpu;
391}
392
394 return simulation_ ? simulation_->determinism() : SimulationDeterminism::ToleranceBounded;
395}
396
397eve::Status World::backendSelectionStatus() const { return backendSelectionStatus_; }
398
399void World::setGravity(float gx, float gy) {
400 if (!world_) return;
401 world_->SetGravity(b2Vec2(toMeters(gx), toMeters(gy)));
402}
403
404float World::getGravityX() const {
405 if (!world_) return 0.f;
406 return toPixels(world_->GetGravity().x);
407}
408
409float World::getGravityY() const {
410 if (!world_) return 0.f;
411 return toPixels(world_->GetGravity().y);
412}
413
414void World::setMeter(float pixelsPerMeter) {
415 if (pixelsPerMeter <= 0.f)
416 throw eve::Exception("World.setMeter: pixelsPerMeter must be > 0");
417 meter_ = pixelsPerMeter;
418}
419
420float World::toMeters(float pixels) const { return pixels / meter_; }
421float World::toPixels(float meters) const { return meters * meter_; }
422
423int World::nextBodyId() { return nextId_++; }
424
426 if (nextBodyHandleIndex_ == PhysicsBodyHandle::invalidIndex)
427 throw eve::Exception("World.newBody: process-local body handle space exhausted");
428 return PhysicsBodyHandle(nextBodyHandleIndex_++, 1u);
429}
430
431Body *World::newBody(const std::string &bodyType, float x, float y) {
432 if (!world_ || destroyed_) throw eve::Exception("World.newBody: world destroyed");
433
434 b2BodyDef def;
435 def.type = parseBodyType(bodyType);
436 def.position = b2Vec2(toMeters(x), toMeters(y));
437
439 b2Body *raw = world_->CreateBody(&def);
440 Body *body = new Body(this, raw, nextBodyId(), runtimeHandle);
441 raw->SetUserData(body);
442 bodies_.insert(body);
443 return body;
444}
445
447 if (!isValid() || handle.isInvalid()) return nullptr;
448 for (Body *body : bodies_) {
449 if (body && body->isValid() && body->runtimeHandle() == handle) return body;
450 }
451 return nullptr;
452}
453
454Body *World::findBodyById(int bodyId) const {
455 if (!isValid() || bodyId < 0) return nullptr;
456 for (Body *body : bodies_) {
457 if (body && body->isValid() && body->getId() == bodyId) return body;
458 }
459 return nullptr;
460}
461
463 if (!body) return;
464 body->destroy();
465}
466
468 if (!body) return;
469 const int id = body->getId();
470 bodies_.erase(body);
471 auto touches = [id](const ContactEvent &e) { return e.bodyAId == id || e.bodyBId == id; };
472 beginContacts_.erase(std::remove_if(beginContacts_.begin(), beginContacts_.end(), touches),
473 beginContacts_.end());
474 endContacts_.erase(std::remove_if(endContacts_.begin(), endContacts_.end(), touches),
475 endContacts_.end());
476 impacts_.erase(std::remove_if(impacts_.begin(), impacts_.end(), touches), impacts_.end());
477}
479 if (!fixture) return;
480 const int bodyId = fixture->getBodyId();
481 const std::string tag = fixture->getTag();
482 auto touches = [&](const ContactEvent &e) {
483 return (e.bodyAId == bodyId && e.fixtureATag == tag) ||
484 (e.bodyBId == bodyId && e.fixtureBTag == tag);
485 };
486 beginContacts_.erase(std::remove_if(beginContacts_.begin(), beginContacts_.end(), touches),
487 beginContacts_.end());
488 endContacts_.erase(std::remove_if(endContacts_.begin(), endContacts_.end(), touches),
489 endContacts_.end());
490 impacts_.erase(std::remove_if(impacts_.begin(), impacts_.end(), touches), impacts_.end());
491 b2Fixture *raw = fixture->raw();
492 for (auto it = preSolve_.begin(); it != preSolve_.end();) {
493 b2Contact *contact = it->first;
494 if (contact && (contact->GetFixtureA() == raw || contact->GetFixtureB() == raw))
495 it = preSolve_.erase(it);
496 else
497 ++it;
498 }
499 fixtures_.erase(fixture);
500}
501
502void World::onBeginContact(b2Contact *contact) {
503 if (!contact || !fixtureFromRaw(contact->GetFixtureA()) ||
504 !fixtureFromRaw(contact->GetFixtureB())) return;
505 Body *a = bodyFromFixture(contact->GetFixtureA());
506 Body *b = bodyFromFixture(contact->GetFixtureB());
507 if (!a || !b) return;
508
509 beginContacts_.push_back(contactEventFrom(contact));
510
511 auto *ev = eve::ModuleManager::getInstance<eve::platform_event::PlatformEvent>("PlatformEvent");
512 if (!ev) return;
513 std::vector<eve::platform_event::Variant> args = {eve::platform_event::Variant::makeInt(a->getId()),
515 ev->push(new eve::platform_event::Message("begincontact", args));
516}
517
518void World::onEndContact(b2Contact *contact) {
519 preSolve_.erase(contact);
520 if (!contact || !fixtureFromRaw(contact->GetFixtureA()) ||
521 !fixtureFromRaw(contact->GetFixtureB())) return;
522 Body *a = bodyFromFixture(contact->GetFixtureA());
523 Body *b = bodyFromFixture(contact->GetFixtureB());
524 if (!a || !b) return;
525
526 endContacts_.push_back(contactEventFrom(contact));
527
528 auto *ev = eve::ModuleManager::getInstance<eve::platform_event::PlatformEvent>("PlatformEvent");
529 if (!ev) return;
530 std::vector<eve::platform_event::Variant> args = {eve::platform_event::Variant::makeInt(a->getId()),
532 ev->push(new eve::platform_event::Message("endcontact", args));
533}
534
535void World::onPreSolve(b2Contact *contact, const b2Manifold * /*oldManifold*/) {
536 if (!contact || !world_) return;
537 b2WorldManifold manifold;
538 contact->GetWorldManifold(&manifold);
539 const b2Manifold *local = contact->GetManifold();
540 if (!local || local->pointCount <= 0) return;
541
542 b2Body *a = contact->GetFixtureA()->GetBody();
543 b2Body *b = contact->GetFixtureB()->GetBody();
544 if (!a || !b) return;
545 const b2Vec2 point = manifold.points[0];
546 const b2Vec2 va = a->GetLinearVelocityFromWorldPoint(point);
547 const b2Vec2 vb = b->GetLinearVelocityFromWorldPoint(point);
548
549 PreSolveData data;
550 data.pointX = toPixels(point.x);
551 data.pointY = toPixels(point.y);
552 data.normalX = manifold.normal.x;
553 data.normalY = manifold.normal.y;
554 data.relativeNormalSpeed = toPixels(std::max(0.f, b2Dot(va - vb, manifold.normal)));
555 preSolve_[contact] = data;
556}
557
558void World::onPostSolve(b2Contact *contact, const b2ContactImpulse *impulse) {
559 if (!contact || !impulse) return;
560 auto found = preSolve_.find(contact);
561 if (found == preSolve_.end()) return;
562
563 ImpactEvent out;
564 static_cast<ContactEvent &>(out) = contactEventFrom(contact);
565 out.pointX = found->second.pointX;
566 out.pointY = found->second.pointY;
567 out.normalX = found->second.normalX;
568 out.normalY = found->second.normalY;
569 out.relativeNormalSpeed = found->second.relativeNormalSpeed;
570 const int count = contact->GetManifold() ? contact->GetManifold()->pointCount : 0;
571 for (int i = 0; i < count; ++i) {
572 out.normalImpulse += toPixels(impulse->normalImpulses[i]);
573 out.tangentImpulse += toPixels(std::fabs(impulse->tangentImpulses[i]));
574 }
575 if (out.normalImpulse > 0.f) impacts_.push_back(std::move(out));
576}
577
578namespace {
579template <typename Event>
580const Event *eventAt(const std::vector<Event> &events, int index) {
581 return index >= 0 && index < int(events.size()) ? &events[size_t(index)] : nullptr;
582}
583} // namespace
584
586 auto *e = eventAt(beginContacts_, index); return e ? e->bodyAId : 0;
587}
589 auto *e = eventAt(beginContacts_, index); return e ? e->bodyBId : 0;
590}
592 auto *e = eventAt(beginContacts_, index); return e ? e->fixtureATag : std::string();
593}
595 auto *e = eventAt(beginContacts_, index); return e ? e->fixtureBTag : std::string();
596}
598 auto *e = eventAt(endContacts_, index); return e ? e->bodyAId : 0;
599}
601 auto *e = eventAt(endContacts_, index); return e ? e->bodyBId : 0;
602}
604 auto *e = eventAt(endContacts_, index); return e ? e->fixtureATag : std::string();
605}
607 auto *e = eventAt(endContacts_, index); return e ? e->fixtureBTag : std::string();
608}
610 auto *e = eventAt(impacts_, index); return e ? e->bodyAId : 0;
611}
613 auto *e = eventAt(impacts_, index); return e ? e->bodyBId : 0;
614}
615std::string World::getImpactFixtureATag(int index) const {
616 auto *e = eventAt(impacts_, index); return e ? e->fixtureATag : std::string();
617}
618std::string World::getImpactFixtureBTag(int index) const {
619 auto *e = eventAt(impacts_, index); return e ? e->fixtureBTag : std::string();
620}
622 auto *e = eventAt(impacts_, index); return e ? e->pointX : 0.f;
623}
625 auto *e = eventAt(impacts_, index); return e ? e->pointY : 0.f;
626}
628 auto *e = eventAt(impacts_, index); return e ? e->normalX : 0.f;
629}
631 auto *e = eventAt(impacts_, index); return e ? e->normalY : 0.f;
632}
634 auto *e = eventAt(impacts_, index); return e ? e->relativeNormalSpeed : 0.f;
635}
637 auto *e = eventAt(impacts_, index); return e ? e->normalImpulse : 0.f;
638}
640 auto *e = eventAt(impacts_, index); return e ? e->tangentImpulse : 0.f;
641}
642
644 beginContacts_.clear();
645 endContacts_.clear();
646 impacts_.clear();
647 preSolve_.clear();
648}
649
650int World::rayCast(float x1, float y1, float x2, float y2) {
651 rayHitBodyId_ = -1;
652 rayHitX_ = 0.f;
653 rayHitY_ = 0.f;
654 rayHitNormalX_ = 0.f;
655 rayHitNormalY_ = 0.f;
656 rayHitFraction_ = 0.f;
657 if (!world_ || destroyed_) return -1;
658
659 struct Closest : public b2RayCastCallback {
660 World *world = nullptr;
661 float best = 1.f;
662 Body *hit = nullptr;
663 b2Vec2 point{};
664 b2Vec2 normal{};
665
666 float32 ReportFixture(b2Fixture *fixture, const b2Vec2 &pointIn, const b2Vec2 &normalIn,
667 float32 fraction) override {
668 Body *b = bodyFromFixture(fixture);
669 if (!b) return -1.f;
670 if (fraction < best) {
671 best = fraction;
672 hit = b;
673 point = pointIn;
674 normal = normalIn;
675 }
676 return fraction;
677 }
678 } cb;
679 cb.world = this;
680
681 b2Vec2 p1(toMeters(x1), toMeters(y1));
682 b2Vec2 p2(toMeters(x2), toMeters(y2));
683 world_->RayCast(&cb, p1, p2);
684
685 if (!cb.hit) return -1;
686 rayHitBodyId_ = cb.hit->getId();
687 rayHitX_ = toPixels(cb.point.x);
688 rayHitY_ = toPixels(cb.point.y);
689 rayHitNormalX_ = cb.normal.x;
690 rayHitNormalY_ = cb.normal.y;
691 rayHitFraction_ = cb.best;
692 return rayHitBodyId_;
693}
694
695int World::queryAABB(float x, float y, float w, float h) {
696 queryBodyIds_.clear();
697 if (!world_ || destroyed_) return 0;
698
699 struct Collector : public b2QueryCallback {
700 World *world = nullptr;
701 std::vector<int> *ids = nullptr;
702 std::unordered_set<int> seen;
703
704 bool ReportFixture(b2Fixture *fixture) override {
705 Body *b = bodyFromFixture(fixture);
706 if (!b) return true;
707 int id = b->getId();
708 if (seen.insert(id).second) ids->push_back(id);
709 return true;
710 }
711 } cb;
712 cb.world = this;
713 cb.ids = &queryBodyIds_;
714
715 b2AABB aabb;
716 float x0 = toMeters(x);
717 float y0 = toMeters(y);
718 float x1 = toMeters(x + w);
719 float y1 = toMeters(y + h);
720 aabb.lowerBound = b2Vec2(std::min(x0, x1), std::min(y0, y1));
721 aabb.upperBound = b2Vec2(std::max(x0, x1), std::max(y0, y1));
722 world_->QueryAABB(&cb, aabb);
723 return static_cast<int>(queryBodyIds_.size());
724}
725
727 if (index < 0 || index >= static_cast<int>(queryBodyIds_.size()))
728 throw eve::Exception("World.getQueryBodyId: index out of range");
729 return queryBodyIds_[static_cast<size_t>(index)];
730}
731
732} // namespace eve::physics
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float duration
glm::vec4 p[6]
ShaderImageInput shape
wgpu::PopErrorScopeStatus status
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
std::uint32_t ab
double r
int h
std::string local
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
Texture * normal
const std::string * tag
float f
World3D * world
float radius
std::string id
Definition PlayHost.cpp:108
bool hit
PrimitiveHandle handle
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
Definition Profile.h:140
float d
float t
uint8_t * pixels
const RoadEdge * edge
bool found
Backend-neutral, observable fixed-step contract for physics domains.
std::uint32_t count
std::map< Cell, int > best
Battle::Events events
ecs::EntityHandle side
TerrainThermalSettings settings
float step
Definition TreeMesh.cpp:314
std::string body
uint32_t index
std::uint32_t depth
std::vector< char > inside
glm::vec3 point
eve::Value fixtures
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
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
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 constexpr index_type invalidIndex
Reserved index value shared by all invalid handles.
static constexpr RuntimeHandle invalid() noexcept
Returns the canonical invalid handle.
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
Id128 public API.
Definition Identity.h:112
constexpr bool isNil() const noexcept
Returns whether this value is the all-zero nil ID.
Definition Identity.h:176
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
2D rigid body (Box2D) in pixel-space coordinates. Owned by a World; create shapes with newRectangleFi...
Definition Body.h:21
void destroy()
Destroys the body inside its world.
Definition Body.cpp:72
ContactRelay(World *world)
Definition World.cpp:201
void PreSolve(b2Contact *contact, const b2Manifold *oldManifold) override
Definition World.cpp:206
void setWorld(World *world) noexcept
Definition World.cpp:202
void BeginContact(b2Contact *contact) override
Definition World.cpp:204
void PostSolve(b2Contact *contact, const b2ContactImpulse *impulse) override
Definition World.cpp:209
void EndContact(b2Contact *contact) override
Definition World.cpp:205
2D fixture: a shape attached to a Body with material + filter settings. Also carries a string tag use...
Definition Fixture.h:18
b2Fixture * raw()
Exposes the underlying Box2D fixture for tightly-scoped backend integration.
Definition Fixture.h:93
int getBodyId() const
Id of the owning body.
Definition Fixture.cpp:103
const std::string & getTag() const
Returns the tag.
Definition Fixture.h:51
Script-facing Box2D joint owned by a World.
Definition Joint2D.h:21
Composed 2D mechanical assembly owned by a World.
Definition Mechanism2D.h:25
Box2D world wrapper (2D physics) with pixel-space coordinates. Handles stepping, gravity,...
Definition World.h:41
void setMeter(float pixelsPerMeter)
Changes the pixels-per-meter conversion.
Definition World.cpp:414
void update(float dt)
Steps the simulation by dt seconds (5 velocity / 2 position iterations).
Definition World.cpp:354
int queryAABB(float x, float y, float w, float h)
Query fixtures overlapping an axis-aligned box in pixel space (x,y,w,h). Returns match count; read id...
Definition World.cpp:695
Body * findBodyById(int bodyId) const
Resolves a live body by its world-local stable event/query id.
Definition World.cpp:454
float getImpactNormalY(int index) const
Definition World.cpp:630
SimulationObservation simulationObservation() const noexcept
Snapshot of completed backend steps and logical simulation time.
Definition World.cpp:385
void destroyBody(Body *body)
Destroys a body (null is ignored).
Definition World.cpp:462
void onBeginContact(b2Contact *contact)
Definition World.cpp:502
std::string getBeginContactFixtureATag(int index) const
Definition World.cpp:591
void forgetBody(Body *body)
Definition World.cpp:467
void onEndContact(b2Contact *contact)
Definition World.cpp:518
void forgetFixture(Fixture *fixture)
Definition World.cpp:478
std::string getEndContactFixtureATag(int index) const
Definition World.cpp:603
float getImpactPointX(int index) const
Definition World.cpp:621
int getQueryBodyId(int index) const
Definition World.cpp:726
eve::Status backendSelectionStatus() const
Returns the selection outcome, including an absent-capability warning.
Definition World.cpp:397
int getEndContactBodyBId(int index) const
Definition World.cpp:600
int getBeginContactBodyBId(int index) const
Definition World.cpp:588
void destroy()
Destroys the underlying Box2D world and resets event buffers.
Definition World.cpp:259
void setGravity(float gx, float gy)
Sets the world gravity vector in pixels/s^2.
Definition World.cpp:399
void onPostSolve(b2Contact *contact, const b2ContactImpulse *impulse)
Definition World.cpp:558
int getEndContactBodyAId(int index) const
Definition World.cpp:597
float getGravityX() const
Definition World.cpp:404
float toPixels(float meters) const
Converts a meter-space length to pixels.
Definition World.cpp:421
friend class Body
Definition World.h:412
float getImpactNormalX(int index) const
Definition World.cpp:627
float getImpactRelativeNormalSpeed(int index) const
Definition World.cpp:633
b2World * raw()
Exposes the underlying Box2D world for tightly-scoped backend integration.
Definition World.h:378
bool pointProbe(float x, float y, float radius, ClothContact *out) const
Probe the deepest non-sensor fixture within radius of a pixel point. Returns false when nothing is hi...
Definition World.cpp:308
Body * newBody(const std::string &bodyType, float x, float y)
Creates a body in pixel-space units.
Definition World.cpp:431
int getBeginContactBodyAId(int index) const
Definition World.cpp:585
int getImpactBodyAId(int index) const
Definition World.cpp:609
PhysicsWorldHandle runtimeHandle() const noexcept
Process-local identity used by PhysicsLink; invalid after destruction.
Definition World.h:111
int getImpactBodyBId(int index) const
Definition World.cpp:612
PhysicsBodyHandle nextBodyRuntimeHandle()
Definition World.cpp:425
World(float gravityX, float gravityY, bool sleep, float meter, eve::PersistentId instanceId=eve::PersistentId::nil())
Creates a physics world.
Definition World.cpp:217
float getImpactNormalImpulse(int index) const
Definition World.cpp:636
SimulationDeterminism backendDeterminism() const noexcept
Replay/numeric guarantee declared by the selected backend.
Definition World.cpp:393
std::string getEndContactFixtureBTag(int index) const
Definition World.cpp:606
int rayCast(float x1, float y1, float x2, float y2)
Closest raycast in pixel space from (x1,y1) to (x2,y2). Returns hit body id, or -1....
Definition World.cpp:650
bool isValid() const
True while the underlying Box2D world is alive.
Definition World.h:306
float getImpactTangentImpulse(int index) const
Definition World.cpp:639
float getGravityY() const
Definition World.cpp:409
std::string getBeginContactFixtureBTag(int index) const
Definition World.cpp:594
Body * findBody(PhysicsBodyHandle handle) const
Resolves a live body handle; returns null for a stale or foreign handle.
Definition World.cpp:446
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings={})
Advances the domain with an injected deterministic simulation step.
Definition World.cpp:368
void updateFull(float dt, int velocityIterations, int positionIterations)
Steps with explicit iteration counts.
Definition World.cpp:356
void clearContactEvents()
Clears collected begin/end contact and impact event buffers.
Definition World.cpp:643
void onPreSolve(b2Contact *contact, const b2Manifold *oldManifold)
Definition World.cpp:535
float toMeters(float pixels) const
Converts a pixel-space length to meters.
Definition World.cpp:420
std::string getImpactFixtureATag(int index) const
Definition World.cpp:615
std::string getImpactFixtureBTag(int index) const
Definition World.cpp:618
SimulationBackendKind backendKind() const noexcept
Selected CPU/GPU/mock backend family.
Definition World.cpp:389
float getImpactPointY(int index) const
Definition World.cpp:624
A named event carrying an ordered list of Variant payloads. Pushed messages are heap-allocated; the q...
PhysicsWorldHandle allocatePhysicsWorldHandle()
Allocates a process-local world handle from the physics owner.
eve::PersistentId makePhysicsWorldPersistentId(PhysicsWorldHandle runtimeHandle)
Creates a non-nil process-local generated identity for a world without an injected persistent ID.
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
SimulationBackendKind
Kind of implementation that owns a simulation step.
SimulationDeterminism
Determinism guarantee made by a simulation backend.
@ ToleranceBounded
Results are equivalent within a documented numeric tolerance.
eve::RuntimeHandle< PhysicsBodyHandleTag > PhysicsBodyHandle
Generation-qualified runtime identity for a physics body.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
SimulationTick tick
Tick reached after this step is applied.
Definition Time.h:160
Observable backend progress shared by CPU and accelerator providers.
Validated solver policy for one simulation step.
Result of a point probe: deepest non-sensor fixture within radius. Normal points from the shape towar...
Definition World.h:65
ContactEvent public API.
Definition World.h:44
static Variant makeInt(int64_t v)
Constructs an integer variant.