10#include <Box2D/Box2D.h>
21b2BodyType parseBodyType(
const std::string &
type) {
22 if (
type ==
"static")
return b2_staticBody;
23 if (
type ==
"kinematic")
return b2_kinematicBody;
24 if (
type ==
"dynamic")
return b2_dynamicBody;
25 throw eve::Exception(
"World.newBody: unknown body type '%s' (use static|kinematic|dynamic)",
29Body *bodyFromFixture(b2Fixture *
f) {
30 if (!
f)
return nullptr;
31 b2Body *
b =
f->GetBody();
32 if (!
b)
return nullptr;
33 return static_cast<Body *
>(
b->GetUserData());
36Fixture *fixtureFromRaw(b2Fixture *
f) {
37 return f ?
static_cast<Fixture *
>(
f->GetUserData()) : nullptr;
40World::ContactEvent contactEventFrom(b2Contact *contact) {
41 World::ContactEvent out;
42 if (!contact)
return out;
43 Fixture *fa = fixtureFromRaw(contact->GetFixtureA());
44 Fixture *fb = fixtureFromRaw(contact->GetFixtureB());
46 out.bodyAId = fa->getBodyId();
47 out.fixtureATag = fa->getTag();
50 out.bodyBId = fb->getBodyId();
51 out.fixtureBTag = fb->getTag();
64 void PreSolve(b2Contact *contact,
const b2Manifold *oldManifold)
override {
67 void PostSolve(b2Contact *contact,
const b2ContactImpulse *impulse)
override {
77 DebugDraw() { SetFlags(e_shapeBit | e_jointBit | e_aabbBit); }
83 void end() { gfx_ =
nullptr; }
85 void DrawPolygon(
const b2Vec2 *vertices, int32 vertexCount,
const b2Color &
color)
override {
86 drawPoly(vertices, vertexCount,
color,
false);
89 drawPoly(vertices, vertexCount,
color,
true);
91 void DrawCircle(
const b2Vec2 ¢er, float32 radius,
const b2Color &
color)
override {
92 drawCircle(center, radius,
color,
false);
95 const b2Color &
color)
override {
96 drawCircle(center, radius,
color,
true);
100 float x1 = p1.x * meter_, y1 = p1.y * meter_;
101 float x2 = p2.x * meter_, y2 = p2.y * meter_;
102 float minx = std::min(x1, x2), miny = std::min(y1, y2);
103 float w = std::max(1.f, std::fabs(x2 - x1));
104 float h = std::max(1.f, std::fabs(y2 - y1));
108 DrawSegment(xf.p, xf.p + 0.5f * xf.q.GetXAxis(), b2Color(1, 0, 0));
109 DrawSegment(xf.p, xf.p + 0.5f * xf.q.GetYAxis(), b2Color(0, 1, 0));
113 void drawPoly(
const b2Vec2 *vertices, int32 vertexCount,
const b2Color &
color,
bool solid) {
114 if (!gfx_ || vertexCount < 2)
return;
115 for (int32 i = 0; i < vertexCount; ++i) {
116 const b2Vec2 &
a = vertices[i];
117 const b2Vec2 &
b = vertices[(i + 1) % vertexCount];
120 if (solid && vertexCount >= 3) {
121 float minx = vertices[0].x, maxx = vertices[0].x;
122 float miny = vertices[0].y, maxy = vertices[0].y;
123 for (int32 i = 1; i < vertexCount; ++i) {
124 minx = std::min(minx, vertices[i].
x);
125 maxx = std::max(maxx, vertices[i].
x);
126 miny = std::min(miny, vertices[i].
y);
127 maxy = std::max(maxy, vertices[i].
y);
129 gfx_->
drawSolidRect(minx * meter_, miny * meter_, (maxx - minx) * meter_,
130 (maxy - miny) * meter_,
134 void drawCircle(
const b2Vec2 ¢er, float32 radius,
const b2Color &
color,
bool solid) {
136 float px = (center.x - radius) * meter_;
137 float py = (center.y - radius) * meter_;
138 float d = radius * 2.f * meter_;
139 float a = solid ?
color.a * 0.35f :
color.a * 0.7f;
143 graphics::Graphics *gfx_ =
nullptr;
147World::World(
float gravityX,
float gravityY,
bool sleep,
float meter) : meter_(meter) {
148 if (meter_ <= 0.f) meter_ = 30.f;
150 world_->SetAllowSleeping(sleep);
152 world_->SetContactListener(relay_);
154 world_->SetDebugDraw(draw_);
160 if (destroyed_)
return;
164 std::vector<Body *> bodies(bodies_.begin(), bodies_.end());
165 for (
Body *
b : bodies) {
175 std::vector<Fixture *> fixtures(fixtures_.begin(), fixtures_.end());
177 if (
f)
f->invalidate();
183 world_->SetContactListener(
nullptr);
184 world_->SetDebugDraw(
nullptr);
197 if (!world_ || destroyed_)
return;
198 if (dt < 0.f) dt = 0.f;
200 if (dt > 0.05f) dt = 0.05f;
201 world_->Step(dt, velocityIterations, positionIterations);
210 if (!world_)
return 0.f;
211 return toPixels(world_->GetGravity().x);
215 if (!world_)
return 0.f;
216 return toPixels(world_->GetGravity().y);
220 if (pixelsPerMeter <= 0.f)
221 throw eve::Exception(
"World.setMeter: pixelsPerMeter must be > 0");
222 meter_ = pixelsPerMeter;
231 if (!world_ || destroyed_)
throw eve::Exception(
"World.newBody: world destroyed");
234 def.type = parseBodyType(bodyType);
237 b2Body *
raw = world_->CreateBody(&def);
240 bodies_.insert(
body);
251 const int id =
body->getId();
253 auto touches = [
id](
const ContactEvent &e) {
return e.bodyAId ==
id || e.bodyBId ==
id; };
254 beginContacts_.erase(std::remove_if(beginContacts_.begin(), beginContacts_.end(), touches),
255 beginContacts_.end());
256 endContacts_.erase(std::remove_if(endContacts_.begin(), endContacts_.end(), touches),
258 impacts_.erase(std::remove_if(impacts_.begin(), impacts_.end(), touches), impacts_.end());
261 if (!fixture)
return;
263 const std::string tag = fixture->
getTag();
265 return (e.bodyAId == bodyId && e.fixtureATag == tag) ||
266 (e.bodyBId == bodyId && e.fixtureBTag == tag);
268 beginContacts_.erase(std::remove_if(beginContacts_.begin(), beginContacts_.end(), touches),
269 beginContacts_.end());
270 endContacts_.erase(std::remove_if(endContacts_.begin(), endContacts_.end(), touches),
272 impacts_.erase(std::remove_if(impacts_.begin(), impacts_.end(), touches), impacts_.end());
273 b2Fixture *
raw = fixture->
raw();
274 for (
auto it = preSolve_.begin(); it != preSolve_.end();) {
275 b2Contact *contact = it->first;
276 if (contact && (contact->GetFixtureA() ==
raw || contact->GetFixtureB() ==
raw))
277 it = preSolve_.erase(it);
281 fixtures_.erase(fixture);
285 if (!contact || !fixtureFromRaw(contact->GetFixtureA()) ||
286 !fixtureFromRaw(contact->GetFixtureB()))
return;
287 Body *
a = bodyFromFixture(contact->GetFixtureA());
288 Body *
b = bodyFromFixture(contact->GetFixtureB());
289 if (!
a || !
b)
return;
291 beginContacts_.push_back(contactEventFrom(contact));
293 auto *ev = eve::ModuleManager::getInstance<eve::event::Event>(
"Event");
301 preSolve_.erase(contact);
302 if (!contact || !fixtureFromRaw(contact->GetFixtureA()) ||
303 !fixtureFromRaw(contact->GetFixtureB()))
return;
304 Body *
a = bodyFromFixture(contact->GetFixtureA());
305 Body *
b = bodyFromFixture(contact->GetFixtureB());
306 if (!
a || !
b)
return;
308 endContacts_.push_back(contactEventFrom(contact));
310 auto *ev = eve::ModuleManager::getInstance<eve::event::Event>(
"Event");
318 if (!contact || !world_)
return;
319 b2WorldManifold manifold;
320 contact->GetWorldManifold(&manifold);
321 const b2Manifold *local = contact->GetManifold();
322 if (!local || local->pointCount <= 0)
return;
324 b2Body *
a = contact->GetFixtureA()->GetBody();
325 b2Body *
b = contact->GetFixtureB()->GetBody();
326 if (!
a || !
b)
return;
327 const b2Vec2 point = manifold.points[0];
328 const b2Vec2 va =
a->GetLinearVelocityFromWorldPoint(point);
329 const b2Vec2 vb =
b->GetLinearVelocityFromWorldPoint(point);
334 data.normalX = manifold.normal.x;
335 data.normalY = manifold.normal.y;
336 data.relativeNormalSpeed =
toPixels(std::max(0.f, b2Dot(va - vb, manifold.normal)));
337 preSolve_[contact] =
data;
341 if (!contact || !impulse)
return;
342 auto found = preSolve_.find(contact);
343 if (found == preSolve_.end())
return;
346 static_cast<ContactEvent &
>(out) = contactEventFrom(contact);
347 out.
pointX = found->second.pointX;
348 out.
pointY = found->second.pointY;
349 out.
normalX = found->second.normalX;
350 out.
normalY = found->second.normalY;
352 const int count = contact->GetManifold() ? contact->GetManifold()->pointCount : 0;
353 for (
int i = 0; i < count; ++i) {
357 if (out.
normalImpulse > 0.f) impacts_.push_back(std::move(out));
361template <
typename Event>
362const Event *eventAt(
const std::vector<Event> &
events,
int index) {
363 return index >= 0 && index < int(
events.size()) ? &
events[size_t(index)] :
nullptr;
368 auto *e = eventAt(beginContacts_, index);
return e ? e->bodyAId : 0;
371 auto *e = eventAt(beginContacts_, index);
return e ? e->bodyBId : 0;
374 auto *e = eventAt(beginContacts_, index);
return e ? e->fixtureATag : std::string();
377 auto *e = eventAt(beginContacts_, index);
return e ? e->fixtureBTag : std::string();
380 auto *e = eventAt(endContacts_, index);
return e ? e->bodyAId : 0;
383 auto *e = eventAt(endContacts_, index);
return e ? e->bodyBId : 0;
386 auto *e = eventAt(endContacts_, index);
return e ? e->fixtureATag : std::string();
389 auto *e = eventAt(endContacts_, index);
return e ? e->fixtureBTag : std::string();
392 auto *e = eventAt(impacts_, index);
return e ? e->bodyAId : 0;
395 auto *e = eventAt(impacts_, index);
return e ? e->bodyBId : 0;
398 auto *e = eventAt(impacts_, index);
return e ? e->fixtureATag : std::string();
401 auto *e = eventAt(impacts_, index);
return e ? e->fixtureBTag : std::string();
404 auto *e = eventAt(impacts_, index);
return e ? e->pointX : 0.f;
407 auto *e = eventAt(impacts_, index);
return e ? e->pointY : 0.f;
410 auto *e = eventAt(impacts_, index);
return e ? e->normalX : 0.f;
413 auto *e = eventAt(impacts_, index);
return e ? e->normalY : 0.f;
416 auto *e = eventAt(impacts_, index);
return e ? e->relativeNormalSpeed : 0.f;
419 auto *e = eventAt(impacts_, index);
return e ? e->normalImpulse : 0.f;
422 auto *e = eventAt(impacts_, index);
return e ? e->tangentImpulse : 0.f;
426 beginContacts_.clear();
427 endContacts_.clear();
433 if (!world_ || !draw_ || !gfx)
return;
434 draw_->
begin(gfx, meter_);
435 world_->DrawDebugData();
443 rayHitNormalX_ = 0.f;
444 rayHitNormalY_ = 0.f;
445 rayHitFraction_ = 0.f;
446 if (!world_ || destroyed_)
return -1;
448 struct Closest :
public b2RayCastCallback {
449 World *world =
nullptr;
455 float32 ReportFixture(b2Fixture *fixture,
const b2Vec2 &pointIn,
const b2Vec2 &normalIn,
456 float32 fraction)
override {
457 Body *
b = bodyFromFixture(fixture);
459 if (fraction < best) {
472 world_->RayCast(&cb, p1, p2);
474 if (!cb.hit)
return -1;
475 rayHitBodyId_ = cb.hit->getId();
478 rayHitNormalX_ = cb.normal.x;
479 rayHitNormalY_ = cb.normal.y;
480 rayHitFraction_ = cb.best;
481 return rayHitBodyId_;
485 queryBodyIds_.clear();
486 if (!world_ || destroyed_)
return 0;
488 struct Collector :
public b2QueryCallback {
489 World *world =
nullptr;
490 std::vector<int> *ids =
nullptr;
491 std::unordered_set<int> seen;
493 bool ReportFixture(b2Fixture *fixture)
override {
494 Body *
b = bodyFromFixture(fixture);
497 if (seen.insert(
id).second) ids->push_back(
id);
502 cb.ids = &queryBodyIds_;
509 aabb.lowerBound = b2Vec2(std::min(x0, x1), std::min(y0, y1));
510 aabb.upperBound = b2Vec2(std::max(x0, x1), std::max(y0, y1));
511 world_->QueryAABB(&cb, aabb);
512 return static_cast<int>(queryBodyIds_.size());
516 if (index < 0 || index >=
static_cast<int>(queryBodyIds_.size()))
518 return queryBodyIds_[
static_cast<size_t>(index)];
std::vector< HostEvent > events
image::ImageData::Colorf color
A named event carrying an ordered list of Variant payloads. Pushed messages are heap-allocated; the q...
virtual void drawSolidRect(float x, float y, float w, float h, const Color &color, BlendMode blend=BlendMode::Alpha)=0
Internal immediate-mode helper used by RenderSystem / Batcher.
2D rigid body (Box2D) in pixel-space coordinates. Owned by a World; create shapes with newRectangleFi...
void begin(graphics::Graphics *gfx, float meter)
void DrawSolidCircle(const b2Vec2 ¢er, float32 radius, const b2Vec2 &, const b2Color &color) override
void DrawCircle(const b2Vec2 ¢er, float32 radius, const b2Color &color) override
void DrawTransform(const b2Transform &xf) override
void DrawSegment(const b2Vec2 &p1, const b2Vec2 &p2, const b2Color &color) override
void DrawPolygon(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color) override
void DrawSolidPolygon(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color) override
2D fixture: a shape attached to a Body with material + filter settings. Also carries a string tag use...
b2Fixture * raw()
Raw Box2D fixture.
int getBodyId() const
Id of the owning body.
const std::string & getTag() const
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...
float getImpactNormalY(int index) const
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
void drawDebug(graphics::Graphics *gfx)
Optional: draw fixture AABBs via Graphics::drawSolidRect.
int getQueryBodyId(int index) const
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()
The underlying Box2D world (advanced use).
Body * newBody(const std::string &bodyType, float x, float y)
bodyType: "static" | "kinematic" | "dynamic". x/y in pixels.
int getBeginContactBodyAId(int index) const
int getImpactBodyAId(int index) const
int getImpactBodyBId(int index) const
float getImpactNormalImpulse(int index) const
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....
float getImpactTangentImpulse(int index) const
float getGravityY() const
std::string getBeginContactFixtureBTag(int index) const
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
World(float gravityX, float gravityY, bool sleep, float meter)
Creates a physics world.
float getImpactPointY(int index) const
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
static Variant makeInt(int64_t v)
Constructs an integer variant.
float relativeNormalSpeed