diff --git a/3rd/box2d/CMakeLists.txt b/3rd/box2d/CMakeLists.txt index 0599ee8e..eddff030 100644 --- a/3rd/box2d/CMakeLists.txt +++ b/3rd/box2d/CMakeLists.txt @@ -12,6 +12,8 @@ aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/common BOX2D_SRC_1) aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/dynamics BOX2D_SRC_2) aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/rope BOX2D_SRC_3) +aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src BOX2D_BINDINGS_SRC) + set(CMAKE_CXX_FLAGS_RELEASE "-O2") set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -20,5 +22,5 @@ add_library( box2d STATIC ${BOX2D_SRC_0} ${BOX2D_SRC_1} ${BOX2D_SRC_2} ${BOX2D_SRC_3} - ${CMAKE_CURRENT_LIST_DIR}/src/box2d_bindings.cpp + ${BOX2D_BINDINGS_SRC} ) \ No newline at end of file diff --git a/3rd/box2d/include/box2d_bindings.hpp b/3rd/box2d/include/box2d_bindings.hpp index cb7cb12d..a9deafd8 100644 --- a/3rd/box2d/include/box2d_bindings.hpp +++ b/3rd/box2d/include/box2d_bindings.hpp @@ -1,5 +1,6 @@ #pragma once +#include "box2d/b2_world.h" #include "box2d/box2d.h" #include "pocketpy/pocketpy.h" #include @@ -24,6 +25,7 @@ inline PyObject* py_var(VM* vm, b2Vec2 v){ namespace imbox2d{ + struct Body final{ b2Body* body; b2Fixture* fixture; @@ -133,12 +135,7 @@ struct PyBody: OpaquePointer{ static void _register(VM* vm, PyObject* mod, PyObject* type); }; -struct PyWorld: OpaquePointer{ - PY_CLASS(PyWorld, box2d, World) - using OpaquePointer::OpaquePointer; - static void _register(VM* vm, PyObject* mod, PyObject* type); -}; } // namespace imbox2d diff --git a/3rd/box2d/src/box2d_Body.cpp b/3rd/box2d/src/box2d_Body.cpp new file mode 100644 index 00000000..e69de29b diff --git a/3rd/box2d/src/box2d_World.cpp b/3rd/box2d/src/box2d_World.cpp new file mode 100644 index 00000000..feea3320 --- /dev/null +++ b/3rd/box2d/src/box2d_World.cpp @@ -0,0 +1,194 @@ +#include "box2d/b2_world.h" +#include "box2d/b2_world_callbacks.h" +#include "box2d_bindings.hpp" + +namespace pkpy{ + namespace imbox2d{ + +// This class captures the closest hit shape. +class MyRayCastCallback : public b2RayCastCallback +{ + VM* vm; +public: + List result; + MyRayCastCallback(VM* vm): vm(vm) {} + + float ReportFixture(b2Fixture* fixture, const b2Vec2& point, + const b2Vec2& normal, float fraction) + { + auto userdata = fixture->GetBody()->GetUserData().pointer; + Body* body = reinterpret_cast(userdata); + result.push_back(VAR_T(PyBody, body)); + // if(only_one) return 0; + return fraction; + } +}; + +class MyBoxCastCallback: public b2QueryCallback{ + VM* vm; +public: + List result; + MyBoxCastCallback(VM* vm): vm(vm) {} + + bool ReportFixture(b2Fixture* fixture) override{ + auto userdata = fixture->GetBody()->GetUserData().pointer; + Body* body = reinterpret_cast(userdata); + result.push_back(VAR_T(PyBody, body)); + return true; + } +}; + +// maybe we will use this class later +class PyDebugDraw: public b2Draw{ + VM* vm; +public: + PyDebugDraw(VM* vm): vm(vm){} + + void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ + } + + void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ + } + + void DrawCircle(const b2Vec2& center, float radius, const b2Color& color) override{ + } + + void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color) override{ + } + + void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) override{ + } + + void DrawTransform(const b2Transform& xf) override{ + } + + void DrawPoint(const b2Vec2& p, float size, const b2Color& color) override{ + } +}; + +class PyContactListener : public b2ContactListener{ + VM* vm; +public: + PyContactListener(VM* vm): vm(vm){} + + void _contact_f(b2Contact* contact, StrName name){ + auto a = contact->GetFixtureA()->GetBody()->GetUserData().pointer; + auto b = contact->GetFixtureB()->GetBody()->GetUserData().pointer; + Body* bodyA = reinterpret_cast(a); + Body* bodyB = reinterpret_cast(b); + PyObject* self; + PyObject* f; + f = vm->get_unbound_method(bodyA->obj, name, &self, false); + if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyB)); + f = vm->get_unbound_method(bodyB->obj, name, &self, false); + if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyA)); + } + + void BeginContact(b2Contact* contact) override { + DEF_SNAME(on_contact_begin); + _contact_f(contact, on_contact_begin); + } + + void EndContact(b2Contact* contact) override { + DEF_SNAME(on_contact_end); + _contact_f(contact, on_contact_end); + } +}; + +// implement placement VAR_T...!!!! +struct PyWorld { + PY_CLASS(PyWorld, box2d, World) + + // this object is too large, so we use unique_ptr + std::unique_ptr world; + std::unique_ptr _contact_listener; + std::unique_ptr _debug_draw; + + PyWorld(VM* vm): + world(new b2World(b2Vec2(0, 0))), + _contact_listener(new PyContactListener(vm)), + _debug_draw(new PyDebugDraw(vm)){ + world->SetAllowSleeping(true); + world->SetAutoClearForces(true); + world->SetContactListener(_contact_listener.get()); + world->SetDebugDraw(_debug_draw.get()); + } + + PyWorld(const PyWorld&) = delete; + PyWorld& operator=(const PyWorld&) = delete; + + static void _register(VM* vm, PyObject* mod, PyObject* type){ + vm->bind(type, "__new__(cls)", [](VM* vm, ArgsView args){ + return VAR_T(PyWorld, PyWorld(vm)); + }); + + // gravity + vm->bind_property(type, "gravity", "vec2", [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + return VAR(self.world->GetGravity()); + }, [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + self.world->SetGravity(CAST(b2Vec2, args[1])); + return vm->None; + }); + + vm->bind(type, "get_bodies(self) -> list[Body]", [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + List list; + b2Body* p = self.world->GetBodyList(); + while(p != nullptr){ + Body* body = (Body*)p->GetUserData().pointer; + list.push_back(VAR_T(PyBody, body)); + p = p->GetNext(); + } + return VAR(std::move(list)); + }); + + vm->bind(type, "ray_cast(self, start: vec2, end: vec2) -> list[Body]", [](VM* vm, ArgsView args){ + auto _lock = vm->heap.gc_scope_lock(); + PyWorld& self = _CAST(PyWorld&, args[0]); + b2Vec2 start = CAST(b2Vec2, args[1]); + b2Vec2 end = CAST(b2Vec2, args[2]); + MyRayCastCallback callback(vm); + self.world->RayCast(&callback, start, end); + return VAR(std::move(callback.result)); + }); + + vm->bind(type, "box_cast(self, lower: vec2, upper: vec2) -> list[Body]", [](VM* vm, ArgsView args){ + auto _lock = vm->heap.gc_scope_lock(); + PyWorld& self = _CAST(PyWorld&, args[0]); + b2AABB aabb; + aabb.lowerBound = CAST(b2Vec2, args[1]); + aabb.upperBound = CAST(b2Vec2, args[2]); + MyBoxCastCallback callback(vm); + self.world->QueryAABB(&callback, aabb); + return VAR(std::move(callback.result)); + }); + + vm->bind(type, "step(self, dt: float, velocity_iterations: int, position_iterations: int)", + [](VM* vm, ArgsView args){ + PyWorld& self = _CAST(PyWorld&, args[0]); + float dt = CAST(float, args[1]); + int velocity_iterations = CAST(int, args[2]); + int position_iterations = CAST(int, args[3]); + + auto f = [](VM* vm, b2Body* p, StrName name){ + while(p != nullptr){ + Body* body = (Body*)p->GetUserData().pointer; + vm->call_method(body->obj, name); + p = p->GetNext(); + } + }; + + DEF_SNAME(on_box2d_pre_step); + DEF_SNAME(on_box2d_post_step); + f(vm, self.world->GetBodyList(), on_box2d_pre_step); + self.world->Step(dt, velocity_iterations, position_iterations); + f(vm, self.world->GetBodyList(), on_box2d_post_step); + return vm->None; + }); + } +}; + + } // namespace imbox2d +} // namespace pkpy \ No newline at end of file diff --git a/3rd/box2d/src/box2d_bindings.cpp b/3rd/box2d/src/box2d_bindings.cpp index 6b646fdd..d8b74d5c 100644 --- a/3rd/box2d/src/box2d_bindings.cpp +++ b/3rd/box2d/src/box2d_bindings.cpp @@ -10,62 +10,7 @@ void add_module_box2d(VM *vm){ namespace imbox2d{ -// maybe we will use this class later -class PyDebugDraw: public b2Draw{ - VM* vm; -public: - PyDebugDraw(VM* vm): vm(vm){} - void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ - } - - void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) override{ - } - - void DrawCircle(const b2Vec2& center, float radius, const b2Color& color) override{ - } - - void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color) override{ - } - - void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) override{ - } - - void DrawTransform(const b2Transform& xf) override{ - } - - void DrawPoint(const b2Vec2& p, float size, const b2Color& color) override{ - } -}; - -class PyContactListener : public b2ContactListener{ - VM* vm; -public: - PyContactListener(VM* vm): vm(vm){} - - void _contact_f(b2Contact* contact, StrName name){ - auto a = contact->GetFixtureA()->GetBody()->GetUserData().pointer; - auto b = contact->GetFixtureB()->GetBody()->GetUserData().pointer; - Body* bodyA = reinterpret_cast(a); - Body* bodyB = reinterpret_cast(b); - PyObject* self; - PyObject* f; - f = vm->get_unbound_method(bodyA->obj, name, &self, false); - if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyB)); - f = vm->get_unbound_method(bodyB->obj, name, &self, false); - if(f != nullptr) vm->call_method(self, f, VAR_T(PyBody, bodyA)); - } - - void BeginContact(b2Contact* contact) override { - DEF_SNAME(on_contact_begin); - _contact_f(contact, on_contact_begin); - } - - void EndContact(b2Contact* contact) override { - DEF_SNAME(on_contact_end); - _contact_f(contact, on_contact_end); - } -}; void PyBody::_register(VM* vm, PyObject* mod, PyObject* type){ vm->bind_notimplemented_constructor(type); @@ -221,100 +166,10 @@ public: }); } -// This class captures the closest hit shape. -class MyRayCastCallback : public b2RayCastCallback -{ - VM* vm; -public: - List result; - MyRayCastCallback(VM* vm): vm(vm) {} - - float ReportFixture(b2Fixture* fixture, const b2Vec2& point, - const b2Vec2& normal, float fraction) - { - auto userdata = fixture->GetBody()->GetUserData().pointer; - Body* body = reinterpret_cast(userdata); - result.push_back(VAR_T(PyBody, body)); - // if(only_one) return 0; - return fraction; - } -}; - void PyWorld::_register(VM* vm, PyObject* mod, PyObject* type){ - vm->bind(type, "__new__(cls)", [](VM* vm, ArgsView args){ - b2World* w = new b2World(b2Vec2(0, 0)); - w->SetAllowSleeping(true); - w->SetAutoClearForces(true); - // the contact listener will leak memory after the world is destroyed - // but it's not a big deal since the world is only destroyed when the game exits - w->SetContactListener(new PyContactListener(vm)); - w->SetDebugDraw(new PyDebugDraw(vm)); - return VAR_T(PyWorld, w); - }); - // gravity - vm->bind_property(type, "gravity", "vec2", [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - return VAR(self->GetGravity()); - }, [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - self->SetGravity(CAST(b2Vec2, args[1])); - return vm->None; - }); - // body - vm->bind(type, "create_body(self, obj) -> Body", [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - return VAR_T(PyBody, new Body(self.ptr, args[1])); - }); - vm->bind(type, "get_bodies(self) -> list[Body]", [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - List list; - b2Body* p = self->GetBodyList(); - while(p != nullptr){ - Body* body = (Body*)p->GetUserData().pointer; - list.push_back(VAR_T(PyBody, body)); - p = p->GetNext(); - } - return VAR(std::move(list)); - }); - - // step - vm->bind(type, "step(self, dt: float, velocity_iterations: int, position_iterations: int)", - [](VM* vm, ArgsView args){ - PyWorld& self = _CAST(PyWorld&, args[0]); - float dt = CAST(float, args[1]); - int velocity_iterations = CAST(int, args[2]); - int position_iterations = CAST(int, args[3]); - - auto f = [](VM* vm, b2Body* p, StrName name){ - while(p != nullptr){ - Body* body = (Body*)p->GetUserData().pointer; - vm->call_method(body->obj, name); - p = p->GetNext(); - } - }; - - DEF_SNAME(on_box2d_pre_step); - DEF_SNAME(on_box2d_post_step); - f(vm, self->GetBodyList(), on_box2d_pre_step); - self->Step(dt, velocity_iterations, position_iterations); - f(vm, self->GetBodyList(), on_box2d_post_step); - return vm->None; - }); - - // raycast - vm->bind(type, "raycast(self, start: vec2, end: vec2) -> list[Body]", [](VM* vm, ArgsView args){ - auto _lock = vm->heap.gc_scope_lock(); - PyWorld& self = _CAST(PyWorld&, args[0]); - b2Vec2 start = CAST(b2Vec2, args[1]); - b2Vec2 end = CAST(b2Vec2, args[2]); - MyRayCastCallback callback(vm); - self->RayCast(&callback, start, end); - return VAR(std::move(callback.result)); - }); - } } diff --git a/docs/modules/box2d.md b/docs/modules/box2d.md index 78ce6eac..06340152 100644 --- a/docs/modules/box2d.md +++ b/docs/modules/box2d.md @@ -68,15 +68,12 @@ class World: def ray_cast(self, start: vec2, end: vec2) -> list['Body']: """raycast from start to end""" - def box_cast(self, p0: vec2, p1: vec2) -> list['Body']: + def box_cast(self, lower: vec2, upper: vec2) -> list['Body']: """query bodies in the AABB region.""" def step(self, dt: float, velocity_iterations: int, position_iterations: int) -> None: """step the simulation, e.g. world.step(1/60, 8, 3)""" - def destroy(self): - """destroy this world.""" - # enum # { # e_shapeBit = 0x0001, ///< draw shapes