12#include <Box2D/Box2D.h>
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)",
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());
37Fixture *fixtureFromRaw(b2Fixture *
f) {
38 return f ?
static_cast<Fixture *
>(
f->GetUserData()) : nullptr;
43bool shapePointDistance(
const b2Shape *
shape,
const b2Transform &xf,
const b2Vec2 &
p,
44 float &dist, b2Vec2 &
normal) {
47 if (!
shape)
return false;
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);
53 const float r = circle->m_radius;
55 const float len =
d.Length();
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;
72 float minSide = std::numeric_limits<float>::max();
73 float bestEdge = std::numeric_limits<float>::max();
74 b2Vec2 bestN(0.f, 1.f);
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]);
84 bestN = poly->m_normals[i];
87 const b2Vec2
ab =
b -
a;
88 const float len2 = b2Dot(
ab,
ab);
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);
100 normal = b2Mul(xf.q, bestN);
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);
107 normal = b2Mul(xf.q, bestN);
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);
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();
125 normal = (1.f / dist) * delta;
126 }
else if (len2 > 1e-12f) {
127 normal = (1.f / std::sqrt(len2)) * b2Vec2(-
ab.y,
ab.x);
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);
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();
150 : (len2 > 1e-12f ? (1.f /
std::sqrt(len2)) * b2Vec2(-
ab.
y,
ab.
x)
154 if (
best < std::numeric_limits<float>::max()) {
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());
169 out.bodyAId = fa->getBodyId();
170 out.fixtureATag = fa->getTag();
173 out.bodyBId = fb->getBodyId();
174 out.fixtureBTag = fb->getTag();
180 if (!std::isfinite(dt) || dt < 0.f) {
183 "World update dt must be finite and non-negative",
"physics.world.update.dt"));
185 const float normalized = std::min(dt, 0.05f);
190 "World simulation tick cannot be incremented",
"physics.world.simulationTick"));
206 void PreSolve(b2Contact *contact,
const b2Manifold *oldManifold)
override {
209 void PostSolve(b2Contact *contact,
const b2ContactImpulse *impulse)
override {
218 : instanceId_(instanceId), meter_(meter) {
219 if (meter_ <= 0.f) meter_ = 30.f;
223 world_->SetAllowSleeping(sleep);
225 world_->SetContactListener(relay_);
227 detail::makeBox2DSimulationBackend(world_), world_,
true);
230 throw eve::Exception(
"World: cannot select a simulation backend: %s",
status.describe().c_str());
232 backendSelectionStatus_ = selection.status();
233 auto selected = std::move(selection).takeValue();
234 backendFallback_ = selected.usedFallback;
235 simulation_ = std::move(selected.backend);
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_);
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;
254 world_->SetContactListener(relay_);
255 prepared.relay_->
setWorld(&prepared);
256 prepared.world_->SetContactListener(prepared.relay_);
260 if (destroyed_)
return;
264 std::vector<Mechanism2D *> mechanisms(mechanisms_.begin(), mechanisms_.end());
266 if (mechanism) mechanism->invalidate();
270 std::vector<Joint2D *> joints(joints_.begin(), joints_.end());
271 for (
Joint2D *joint : joints) {
272 if (joint) joint->invalidate();
275 jointHandles_.clear();
278 std::vector<Body *> bodies(bodies_.begin(), bodies_.end());
279 for (
Body *
b : bodies) {
289 std::vector<Fixture *>
fixtures(fixtures_.begin(), fixtures_.end());
291 if (
f)
f->invalidate();
299 world_->SetContactListener(
nullptr);
315 struct Probe : b2QueryCallback {
321 bool ReportFixture(b2Fixture *fixture)
override {
322 if (!fixture || fixture->IsSensor())
return true;
323 const b2Shape *
shape = fixture->GetShape();
324 if (!
shape)
return true;
327 if (!shapePointDistance(
shape, fixture->GetBody()->GetTransform(),
center, dist,
331 const float depth = radiusM - dist;
337 best->body =
static_cast<Body *
>(fixture->GetBody()->GetUserData());
344 probe.center = centerM;
348 aabb.lowerBound = centerM - b2Vec2(rM, rM);
349 aabb.upperBound = centerM + b2Vec2(rM, rM);
350 world_->QueryAABB(&probe, aabb);
358 auto legacyStep = makeLegacyStep(dt, simulationTick_);
360 legacyStep.ignore(
"legacy World::updateFull cannot return a structured error");
365 result.ignore(
"legacy World::updateFull cannot return a structured result");
369 if (!
isValid() || !simulation_) {
372 "Cannot step a destroyed or uninitialized physics world",
"physics.world.step"));
374 auto valid = detail::validateSimulationStep(stepValue,
settings, simulation_->observation());
377 if (mechanism) mechanism->syncBeforeStep();
379 auto result = simulation_->step(stepValue,
settings);
380 if (!result)
return result;
381 simulationTick_ = stepValue.
tick;
405 if (!world_)
return 0.f;
406 return toPixels(world_->GetGravity().x);
410 if (!world_)
return 0.f;
411 return toPixels(world_->GetGravity().y);
415 if (pixelsPerMeter <= 0.f)
416 throw eve::Exception(
"World.setMeter: pixelsPerMeter must be > 0");
417 meter_ = pixelsPerMeter;
427 throw eve::Exception(
"World.newBody: process-local body handle space exhausted");
432 if (!world_ || destroyed_)
throw eve::Exception(
"World.newBody: world destroyed");
435 def.type = parseBodyType(bodyType);
439 b2Body *
raw = world_->CreateBody(&def);
442 bodies_.insert(
body);
455 if (!
isValid() || bodyId < 0)
return nullptr;
469 const int id =
body->getId();
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),
476 impacts_.erase(std::remove_if(impacts_.begin(), impacts_.end(), touches), impacts_.end());
479 if (!fixture)
return;
481 const std::string
tag = fixture->
getTag();
483 return (e.bodyAId == bodyId && e.fixtureATag ==
tag) ||
484 (e.bodyBId == bodyId && e.fixtureBTag ==
tag);
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),
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);
499 fixtures_.erase(fixture);
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;
509 beginContacts_.push_back(contactEventFrom(contact));
511 auto *ev = eve::ModuleManager::getInstance<eve::platform_event::PlatformEvent>(
"PlatformEvent");
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;
526 endContacts_.push_back(contactEventFrom(contact));
528 auto *ev = eve::ModuleManager::getInstance<eve::platform_event::PlatformEvent>(
"PlatformEvent");
536 if (!contact || !world_)
return;
537 b2WorldManifold manifold;
538 contact->GetWorldManifold(&manifold);
539 const b2Manifold *
local = contact->GetManifold();
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);
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;
559 if (!contact || !impulse)
return;
560 auto found = preSolve_.find(contact);
561 if (
found == preSolve_.end())
return;
564 static_cast<ContactEvent &
>(out) = contactEventFrom(contact);
570 const int count = contact->GetManifold() ? contact->GetManifold()->pointCount : 0;
571 for (
int i = 0; i <
count; ++i) {
575 if (out.
normalImpulse > 0.f) impacts_.push_back(std::move(out));
579template <
typename Event>
580const Event *eventAt(
const std::vector<Event> &
events,
int index) {
586 auto *e = eventAt(beginContacts_,
index);
return e ? e->bodyAId : 0;
589 auto *e = eventAt(beginContacts_,
index);
return e ? e->bodyBId : 0;
592 auto *e = eventAt(beginContacts_,
index);
return e ? e->fixtureATag : std::string();
595 auto *e = eventAt(beginContacts_,
index);
return e ? e->fixtureBTag : std::string();
598 auto *e = eventAt(endContacts_,
index);
return e ? e->bodyAId : 0;
601 auto *e = eventAt(endContacts_,
index);
return e ? e->bodyBId : 0;
604 auto *e = eventAt(endContacts_,
index);
return e ? e->fixtureATag : std::string();
607 auto *e = eventAt(endContacts_,
index);
return e ? e->fixtureBTag : std::string();
610 auto *e = eventAt(impacts_,
index);
return e ? e->bodyAId : 0;
613 auto *e = eventAt(impacts_,
index);
return e ? e->bodyBId : 0;
616 auto *e = eventAt(impacts_,
index);
return e ? e->fixtureATag : std::string();
619 auto *e = eventAt(impacts_,
index);
return e ? e->fixtureBTag : std::string();
622 auto *e = eventAt(impacts_,
index);
return e ? e->pointX : 0.f;
625 auto *e = eventAt(impacts_,
index);
return e ? e->pointY : 0.f;
628 auto *e = eventAt(impacts_,
index);
return e ? e->normalX : 0.f;
631 auto *e = eventAt(impacts_,
index);
return e ? e->normalY : 0.f;
634 auto *e = eventAt(impacts_,
index);
return e ? e->relativeNormalSpeed : 0.f;
637 auto *e = eventAt(impacts_,
index);
return e ? e->normalImpulse : 0.f;
640 auto *e = eventAt(impacts_,
index);
return e ? e->tangentImpulse : 0.f;
644 beginContacts_.clear();
645 endContacts_.clear();
654 rayHitNormalX_ = 0.f;
655 rayHitNormalY_ = 0.f;
656 rayHitFraction_ = 0.f;
657 if (!world_ || destroyed_)
return -1;
659 struct Closest :
public b2RayCastCallback {
666 float32 ReportFixture(b2Fixture *fixture,
const b2Vec2 &pointIn,
const b2Vec2 &normalIn,
667 float32 fraction)
override {
668 Body *
b = bodyFromFixture(fixture);
670 if (fraction <
best) {
683 world_->RayCast(&cb, p1, p2);
685 if (!cb.hit)
return -1;
686 rayHitBodyId_ = cb.hit->getId();
689 rayHitNormalX_ = cb.normal.x;
690 rayHitNormalY_ = cb.normal.y;
691 rayHitFraction_ = cb.best;
692 return rayHitBodyId_;
696 queryBodyIds_.clear();
697 if (!world_ || destroyed_)
return 0;
699 struct Collector :
public b2QueryCallback {
701 std::vector<int> *
ids =
nullptr;
702 std::unordered_set<int> seen;
704 bool ReportFixture(b2Fixture *fixture)
override {
705 Body *
b = bodyFromFixture(fixture);
708 if (seen.insert(
id).second)
ids->push_back(
id);
713 cb.ids = &queryBodyIds_;
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());
727 if (index < 0 || index >=
static_cast<int>(queryBodyIds_.size()))
729 return queryBodyIds_[
static_cast<size_t>(
index)];
wgpu::PopErrorScopeStatus status
std::array< double, 10 > q
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
Backend-neutral, observable fixed-step contract for physics domains.
std::map< Cell, int > best
TerrainThermalSettings settings
std::vector< char > inside
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.
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
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.
constexpr bool isNil() const noexcept
Returns whether this value is the all-zero nil ID.
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...
void destroy()
Destroys the body inside its world.
2D fixture: a shape attached to a Body with material + filter settings. Also carries a string tag use...
b2Fixture * raw()
Exposes the underlying Box2D fixture for tightly-scoped backend integration.
int getBodyId() const
Id of the owning body.
const std::string & getTag() const
Returns the tag.
Script-facing Box2D joint owned by a World.
Composed 2D mechanical assembly owned by a World.
Box2D world wrapper (2D physics) with pixel-space coordinates. Handles stepping, gravity,...
void setMeter(float pixelsPerMeter)
Changes the pixels-per-meter conversion.
void update(float dt)
Steps the simulation by dt seconds (5 velocity / 2 position iterations).
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...
Body * findBodyById(int bodyId) const
Resolves a live body by its world-local stable event/query id.
float getImpactNormalY(int index) const
SimulationObservation simulationObservation() const noexcept
Snapshot of completed backend steps and logical simulation time.
void destroyBody(Body *body)
Destroys a body (null is ignored).
void onBeginContact(b2Contact *contact)
std::string getBeginContactFixtureATag(int index) const
void forgetBody(Body *body)
void onEndContact(b2Contact *contact)
void forgetFixture(Fixture *fixture)
std::string getEndContactFixtureATag(int index) const
float getImpactPointX(int index) const
int getQueryBodyId(int index) const
eve::Status backendSelectionStatus() const
Returns the selection outcome, including an absent-capability warning.
int getEndContactBodyBId(int index) const
int getBeginContactBodyBId(int index) const
void destroy()
Destroys the underlying Box2D world and resets event buffers.
void setGravity(float gx, float gy)
Sets the world gravity vector in pixels/s^2.
void onPostSolve(b2Contact *contact, const b2ContactImpulse *impulse)
int getEndContactBodyAId(int index) const
float getGravityX() const
float toPixels(float meters) const
Converts a meter-space length to pixels.
float getImpactNormalX(int index) const
float getImpactRelativeNormalSpeed(int index) const
b2World * raw()
Exposes the underlying Box2D world for tightly-scoped backend integration.
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...
Body * newBody(const std::string &bodyType, float x, float y)
Creates a body in pixel-space units.
int getBeginContactBodyAId(int index) const
int getImpactBodyAId(int index) const
PhysicsWorldHandle runtimeHandle() const noexcept
Process-local identity used by PhysicsLink; invalid after destruction.
int getImpactBodyBId(int index) const
PhysicsBodyHandle nextBodyRuntimeHandle()
World(float gravityX, float gravityY, bool sleep, float meter, eve::PersistentId instanceId=eve::PersistentId::nil())
Creates a physics world.
float getImpactNormalImpulse(int index) const
SimulationDeterminism backendDeterminism() const noexcept
Replay/numeric guarantee declared by the selected backend.
std::string getEndContactFixtureBTag(int index) const
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....
bool isValid() const
True while the underlying Box2D world is alive.
float getImpactTangentImpulse(int index) const
float getGravityY() const
std::string getBeginContactFixtureBTag(int index) const
Body * findBody(PhysicsBodyHandle handle) const
Resolves a live body handle; returns null for a stale or foreign handle.
eve::Result< void > step(const eve::SimulationStep &step, const SimulationSettings &settings={})
Advances the domain with an injected deterministic simulation step.
void updateFull(float dt, int velocityIterations, int positionIterations)
Steps with explicit iteration counts.
void clearContactEvents()
Clears collected begin/end contact and impact event buffers.
void onPreSolve(b2Contact *contact, const b2Manifold *oldManifold)
float toMeters(float pixels) const
Converts a pixel-space length to meters.
std::string getImpactFixtureATag(int index) const
std::string getImpactFixtureBTag(int index) const
SimulationBackendKind backendKind() const noexcept
Selected CPU/GPU/mock backend family.
float getImpactPointY(int index) const
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.
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.
SimulationTick tick
Tick reached after this step is applied.
Observable backend progress shared by CPU and accelerator providers.
Validated solver policy for one simulation step.
float relativeNormalSpeed