8#include <box3d/box3d.h>
12#include <unordered_set>
17b3BodyType parseBodyType(
const std::string &
type) {
18 if (
type ==
"static")
return b3_staticBody;
19 if (
type ==
"kinematic")
return b3_kinematicBody;
20 if (
type ==
"dynamic")
return b3_dynamicBody;
21 throw eve::Exception(
"World3D.newBody: unknown body type '%s' (use static|kinematic|dynamic)",
25Body3D *bodyFromShape(b3ShapeId shapeId) {
26 if (!b3Shape_IsValid(shapeId))
return nullptr;
27 return static_cast<Body3D *
>(b3Body_GetUserData(b3Shape_GetBody(shapeId)));
33 b3WorldDef def = b3DefaultWorldDef();
34 def.gravity = b3Vec3{gravityX, gravityY, gravityZ};
35 def.enableSleep = sleep;
36 worldId_ = b3CreateWorld(&def);
44 if (destroyed_)
return;
47 std::vector<Body3D *> bodies(bodies_.begin(), bodies_.end());
49 if (
b)
b->invalidate();
53 std::vector<Shape3D *> shapes(shapes_.begin(), shapes_.end());
55 if (
s)
s->invalidate();
59 if (b3World_IsValid(worldId_)) {
60 b3DestroyWorld(worldId_);
69 if (dt < 0.f) dt = 0.f;
70 if (dt > 0.05f) dt = 0.05f;
71 if (subStepCount < 1) subStepCount = 1;
72 b3World_Step(worldId_, dt, subStepCount);
78 b3World_SetGravity(worldId_, b3Vec3{gx, gy, gz});
83 return b3World_GetGravity(worldId_).x;
88 return b3World_GetGravity(worldId_).y;
93 return b3World_GetGravity(worldId_).z;
101 b3BodyDef def = b3DefaultBodyDef();
102 def.type = parseBodyType(bodyType);
103 def.position = b3Pos{
x,
y,
z};
104 def.rotation = b3Quat_identity;
106 b3BodyId
raw = b3CreateBody(worldId_, &def);
109 bodies_.insert(
body);
124 auto *ev = eve::ModuleManager::getInstance<eve::event::Event>(
"Event");
127 b3ContactEvents contacts = b3World_GetContactEvents(worldId_);
128 for (
int i = 0; i < contacts.beginCount; ++i) {
129 Body3D *
a = bodyFromShape(contacts.beginEvents[i].shapeIdA);
130 Body3D *
b = bodyFromShape(contacts.beginEvents[i].shapeIdB);
131 if (!
a || !
b)
continue;
136 for (
int i = 0; i < contacts.endCount; ++i) {
137 Body3D *
a = bodyFromShape(contacts.endEvents[i].shapeIdA);
138 Body3D *
b = bodyFromShape(contacts.endEvents[i].shapeIdB);
139 if (!
a || !
b)
continue;
151 rayHitNormalX_ = 0.f;
152 rayHitNormalY_ = 0.f;
153 rayHitNormalZ_ = 0.f;
154 rayHitFraction_ = 0.f;
157 b3Pos origin{x1, y1, z1};
158 b3Vec3 translation{x2 - x1, y2 - y1, z2 - z1};
159 b3QueryFilter
filter = b3DefaultQueryFilter();
160 b3RayResult hit = b3World_CastRayClosest(worldId_, origin, translation,
filter);
161 if (!hit.hit)
return -1;
164 if (!
body)
return -1;
166 rayHitBodyId_ =
body->getId();
167 rayHitX_ =
static_cast<float>(hit.point.x);
168 rayHitY_ =
static_cast<float>(hit.point.y);
169 rayHitZ_ =
static_cast<float>(hit.point.z);
170 rayHitNormalX_ = hit.normal.x;
171 rayHitNormalY_ = hit.normal.y;
172 rayHitNormalZ_ = hit.normal.z;
173 rayHitFraction_ = hit.fraction;
174 return rayHitBodyId_;
178 queryBodyIds_.clear();
182 std::vector<int> *ids =
nullptr;
183 std::unordered_set<int> seen;
185 static bool callback(b3ShapeId shapeId,
void *context) {
186 auto *self =
static_cast<Collector *
>(context);
187 Body3D *
b = bodyFromShape(shapeId);
190 if (self->seen.insert(
id).second) self->ids->push_back(
id);
194 cb.ids = &queryBodyIds_;
197 aabb.lowerBound = b3Vec3{std::min(minX, maxX), std::min(minY, maxY), std::min(minZ, maxZ)};
198 aabb.upperBound = b3Vec3{std::max(minX, maxX), std::max(minY, maxY), std::max(minZ, maxZ)};
199 b3QueryFilter
filter = b3DefaultQueryFilter();
200 b3World_OverlapAABB(worldId_, aabb,
filter, &Collector::callback, &cb);
201 return static_cast<int>(queryBodyIds_.size());
205 if (index < 0 || index >=
static_cast<int>(queryBodyIds_.size()))
206 throw eve::Exception(
"World3D.getQueryBodyId: index out of range");
207 return queryBodyIds_[
static_cast<size_t>(index)];
A named event carrying an ordered list of Variant payloads. Pushed messages are heap-allocated; the q...
3D rigid body (Box3D) in meter-space coordinates (+Y up by convention). Owned by a World3D; create sh...
3D shape (box/sphere/capsule) attached to a Body3D with material settings. Created via Body3D::new*Sh...
void setGravity(float gx, float gy, float gz)
Gravity in m/s².
int getQueryBodyId(int index) const
Body id of the i-th query hit.
void update(float dt)
Steps the simulation by dt seconds (default substep count).
void forgetBody(Body3D *body)
Internal: wrapper teardown bookkeeping.
float getGravityX() const
int nextBodyId()
Internal: next stable body id.
float getGravityY() const
int rayCast(float x1, float y1, float z1, float x2, float y2, float z2)
Closest raycast from (x1,y1,z1) to (x2,y2,z2) in meters. Returns hit body id, or -1....
b3WorldId raw() const
Raw Box3D world id.
bool isValid() const
True while the underlying Box3D world is alive.
int queryAABB(float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
Query shapes overlapping an AABB in meters (min/max corners). Returns match count; read ids with getQ...
void updateFull(float dt, int subStepCount)
Steps with an explicit substep count.
void destroyBody(Body3D *body)
Destroys a body (null is ignored).
void forgetShape(Shape3D *shape)
float getGravityZ() const
void destroy()
Destroys the world and invalidates all wrappers.
World3D(float gravityX, float gravityY, float gravityZ, bool sleep)
void emitContactEvents()
Internal: collects Box3D contact events into the event buffers.
Body3D * newBody(const std::string &bodyType, float x, float y, float z)
bodyType: "static" | "kinematic" | "dynamic". Position in meters.
static Variant makeInt(int64_t v)
Constructs an integer variant.