Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
4e1d3d1
added initial version of imu plugin
marbosjo Jan 4, 2018
3b48d67
publish twist with covariance
marbosjo May 21, 2018
dc96bbf
diff_drive: added parameter broadcast_tf to enable tf broadcast from …
marbosjo May 21, 2018
4756763
Merge remote-tracking branch 'upstream/master'
marbosjo May 21, 2018
66a1e0a
imu: now reads parameter for broadcasting tf
marbosjo May 28, 2018
09c62e1
diff_drive: added flatland tf resolving for odom frame
marbosjo May 28, 2018
2df4f9f
Merge remote-tracking branch 'upstream/master'
marbosjo Jun 5, 2018
7601315
diff_drive: added parameter to publish twist in robot frame
marbosjo Jun 5, 2018
6eda932
Forced RelWithDebInfo for flatland_server and flatland_plugins. Simul…
josephduchesne Sep 10, 2021
b80d5cb
Fixed incorrect logic on legacy tricycle_drive parameter loading
josephduchesne Sep 10, 2021
26ce985
Cherry picked Rapyuta robotics smart optimization to the laser scan p…
josephduchesne Sep 10, 2021
113af7c
Moved map_to_lines.py into flatland_server program install space
josephduchesne Sep 10, 2021
5bcd916
Fix ground truth Tf changing parent frame
eborghi10 Dec 17, 2021
60485c8
removed changes to tf namespacing
josephduchesne May 3, 2022
5ea5b73
added warning skip for eigen third party library on clang source check
josephduchesne May 3, 2022
c0826a4
Merge pull request #79 from avidbots/laser-scan-cherry-pick
josephduchesne May 3, 2022
c8e8747
Merge pull request #81 from eborghi10/master
josephduchesne May 3, 2022
7d755f5
Version bump of flatland metapackage
josephduchesne May 4, 2022
8595ff4
Merge pull request #82 from avidbots/version-bump
josephduchesne May 4, 2022
4f91dd8
reorder include dirs to fix overlay builds
josephduchesne May 4, 2022
4deeb97
Merge pull request #83 from avidbots/fix-overlay-build
josephduchesne May 4, 2022
5ac1a48
Removed unused legacy variables, and fixed uninitialized acceleration…
josephduchesne May 9, 2022
f686dcf
Added several unit tests to the tricycle drive plugin
josephduchesne May 9, 2022
e25af5b
added ground truth frame id to tricycle drive plugin and updated docu…
josephduchesne May 9, 2022
d8dce46
Fixed a variety of typos
josephduchesne May 10, 2022
77fe3c0
Removed final todo
josephduchesne May 10, 2022
e972680
fixed one more typo
josephduchesne May 10, 2022
d4b1cfb
Merge pull request #84 from avidbots/fix-uninitialized-acceleration-l…
josephduchesne Jan 17, 2023
b61c941
Update package.xml
josephduchesne Jan 17, 2023
a0ceb25
version bump
josephduchesne Jan 17, 2023
59a0f62
yaml_preprocessor: add support for $include
pbelanger-avid Jan 18, 2023
deaed56
yaml_preprocessor: Add sequence include $[include]
pbelanger-avid Jan 18, 2023
631168f
Merge pull request #93 from pbelanger-avid/add-yaml-include-support
josephduchesne Jan 18, 2023
02d5c13
improved wording on scan direction laser plugin option. Added documen…
josephduchesne Jan 18, 2023
2267d30
added ros industrial ci test runner github action
josephduchesne Jan 18, 2023
b72df50
only kinetic ci for now
josephduchesne Jan 18, 2023
6e84710
only kinetic ci for now
josephduchesne Jan 19, 2023
b5dc140
Update industrial_ci_action.yml
josephduchesne Jan 19, 2023
b1e0408
Update industrial_ci_action.yml
josephduchesne Jan 19, 2023
83d1616
Update industrial_ci_action.yml
josephduchesne Jan 19, 2023
59097fa
yaml_preprocessor: fix processing empty sequences to null
pbelanger-avid Jan 19, 2023
80e8dfe
Merge remote-tracking branch 'origin/master' into clean-up-lidar-flip…
josephduchesne Jan 19, 2023
3b92feb
Merge branch 'clean-up-lidar-flip-param' of github.com:avidbots/flatl…
josephduchesne Jan 19, 2023
50b1f40
added extra time for services to start
josephduchesne Jan 19, 2023
8874c0a
Update laser.rst
josephduchesne Jan 19, 2023
c4e16b7
Version bump to 1.3.1
josephduchesne Jan 25, 2023
ddfdbf2
fixed lidar scan data direction, which apparently was backwards forever
josephduchesne Jan 25, 2023
d947f97
fixed incorrect lidar flip code
josephduchesne Jan 25, 2023
d0acfbe
reverted unintentional launchfile change
josephduchesne Jan 25, 2023
60e4b52
laser plugin: support clockwise-turning lidars
pbelanger-avid Jan 25, 2023
713a8fe
Update laser.rst
josephduchesne Jan 26, 2023
4c71e1a
Merge pull request #94 from avidbots/clean-up-lidar-flip-param
josephduchesne Jan 27, 2023
3915458
Added per package license files
josephduchesne Feb 2, 2023
8214028
1.3.2
josephduchesne Feb 2, 2023
b2f7792
Merge pull request #95 from avidbots/per-package-licenses
josephduchesne Feb 3, 2023
a88c12a
added missing std_srvs dep
josephduchesne Feb 6, 2023
1aac2be
added changelogs
josephduchesne Feb 6, 2023
b65c96b
1.3.3
josephduchesne Feb 6, 2023
93096a2
Merge pull request #96 from avidbots/add-missing-std_srvs
josephduchesne Feb 6, 2023
614dc24
Make diff drive plugin tf broadcasting a configurable option
avidbots-kenneth Jul 5, 2023
69f5a1a
Merge pull request #98 from avidbots/diff-drive-plugin-tf-broadcast-o…
avidbots-kenneth Aug 29, 2023
820990b
Merge remote-tracking branch 'marbosjo/master' into imu
avidbots-marc Oct 24, 2023
27773a6
Adding missing header.
avidbots-marc Nov 21, 2023
89af75f
Fixed documentation and comment errors.
avidbots-marc Nov 22, 2023
e795432
Added documentation for imu plugin.
avidbots-marc Nov 22, 2023
91a6bfa
Fixed formatting error in IMU documentation.
avidbots-marc Nov 22, 2023
cba1e4e
Merge pull request #103 from avidbots/imu
kam3k Nov 22, 2023
df9118e
version bumped to 1.4.0 for new IMU plugin
josephduchesne Nov 22, 2023
432bad1
Merge pull request #104 from avidbots/version-bump-1.4.0
josephduchesne Nov 22, 2023
e6aac15
Fixed cmake warning on ros build farm, bumped version to 1.4.1
josephduchesne Nov 24, 2023
b6cece7
Merge pull request #105 from avidbots/1.4.1-fixes
josephduchesne Nov 24, 2023
67d87c1
Added a lightweight profiling printout on the laser plugin
josephduchesne May 7, 2024
238bc03
Swapped out vectorization for opencv::findContours, and box2d line se…
josephduchesne May 7, 2024
6601c65
Added simplify_map feature, as well as benchmark. New simplify_map ca…
josephduchesne May 7, 2024
af437a9
feat(rr): squash-merge rr-devel changes onto upstream/master
sabarish-prasannna Mar 26, 2026
0f3f346
feat(box2d-v3): squash-merge feature/box2d-v3 onto rr-devel-on-upstream
sabarish-prasannna Mar 26, 2026
ce05cc3
fix: restore models_path/world_plugins_path and diff_drive RR flags
sabarish-prasannna Mar 26, 2026
2131941
merge: resolve conflict with master (namespace odom_frame_id already …
sabarish-prasannna Mar 26, 2026
fe17806
fix: rename SpawnModel.srv yaml_name to yaml_path
sabarish-prasannna Mar 26, 2026
2e88ce7
fix: add missing profiler.h include and physics_position_iterations_ …
sabarish-prasannna Mar 26, 2026
3556cb5
fix: pass World* to MakeModel in LoadModel to prevent null dereferenc…
sabarish-prasannna Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/industrial_ci_action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: CI

on: [push, pull_request]

jobs:
industrial_ci:
strategy:
matrix:
env:
- {ROS_DISTRO: noetic, ROS_REPO: main}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: 'ros-industrial/industrial_ci@master'
env: ${{matrix.env}}
6 changes: 4 additions & 2 deletions docs/core_functions/ros_launch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ Here are the full list of parameters with the default values
step_size:=0.005 \
show_viz:=true \
viz_pub_rate:=30.0 \
use_rviz:=false
use_rviz:=false \
simplify_map:=2

* **world_path**: path to world.yaml
* **update_rate**: the real time rate to run the simulation loop in Hz
* **step_size**: amount of time to step each loop in seconds
* **show_viz**: show visualization, pops the flatland_viz window and publishes
visualization messages, either true or false
* **viz_pub_rate**: rate to publish visualization in Hz, works only when show_viz=true
* **use_rviz**: works only when show_viz=true, set this to disable flatland_viz popup
* **use_rviz**: works only when show_viz=true, set this to disable flatland_viz popup
* **simplify_map**: Simpify map during vector tracing: 0=None (default), 1=moderately, 2=significantly
53 changes: 52 additions & 1 deletion docs/core_functions/yaml_preprocessor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,60 @@ Yaml Preprocessor
Flatland Server has a lua preprocessor for YAML with simple bindings for the environment variables and rosparam.
The intent is to be able to build parametric models that are defined by dimensions and flags found in either environment variables or rosparam, in a similar way to xacro+urdf. Because this is parsed at model load time, any roslaunch rosparam loading will have completed and those parameters will be available.

Including additional YAML files
-------------------------------
The YAML preprocessor allows for including of other YAML files using the `$include` keyword. When this string is encountered, the following filename is parsed as a YAML file and substituted for the `$include`-string. The file path bay be absolute or relative. Relative file paths are relative to the YAML file currently being processed.

.. code-block:: yaml

# project/param/parent.yaml
foo: 123
bar: $include child.yaml # Looks in current directory (project/param/) for relative filenames
baz: $include /absolute/path/to/project/param/child.yaml # or an absolute path can be specified

# project/param/child.yaml
a: 1
b: 2

# result of YAML preprocessing parent.yaml:
foo: 123
bar:
a: 1
b: 2
baz:
a: 1
b: 2

There is also a special form of `$include`, `$[include]`, that can be used to populate a sequence with an arbitrary number of items. Each individual document in the specified YAML file (separated by `---` as is standard for multi-document YAML files) becomes its own element in the sequence. For example:

.. code-block:: yaml

# parent.yaml
foo:
- first
- $[include] child.yaml
- last

#child.yaml
one
---
a: foo
b: baz
---
three

# result of YAML preprocessing of parent.yaml:
foo:
- first
- one
- a: foo
b: baz
- three
- last

body/joint/plugin enabled flag
------------------------------
Model bodies, plugins and joints now have a new flag `enabled` which can be set to true or false either directly in the yaml, or based on more complex logic from a lua `$eval` string that returns "true" or "false". Disabled bodies, plugins and joints are skipped during yaml loading, and as a result are never instantiated. From Flatland's perspective `enabled: false` causes the affected body/plugin/joint to be deleted.
Model bodies, plugins and joints now have a new flag `enabled` which can be set to true or false either directly in the yaml, or based on more complex logic from a lua `$eval` string that returns "true" or "false". Disabled bodies, plugins and joints are skipped during yaml loading, and as a result are never instantiated. From Flatland's perspective `enabled: false` causes the affected body/plugin/joint to be deleted.

bindings for env and param
-------------------------------
Expand Down
184 changes: 184 additions & 0 deletions docs/design/2026-03-24-box2d-v3-migration/agent.spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Box2D v3 Migration — Agent-Facing Spec

> **For Claude:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task.

## Repo & Branch

- **Repo:** `C:\Users\Rapyuta Robotics\Desktop\flatland` (rapyuta-robotics/flatland fork)
- **Branch to create:** `feature/box2d-v3`
- **Box2D v3 version:** `v3.1.0` from `https://github.com/erincatto/box2d`
- **enkiTS version:** `v2.10` from `https://github.com/dougbinks/enkiTS`

## Build System

- ROS1 Catkin workspace
- `flatland_server/CMakeLists.txt` — main package, builds `flatland_server` node and library
- `flatland_plugins/CMakeLists.txt` — builds plugin shared libraries
- Box2D 2.3.2 is vendored at `flatland_server/thirdparty/Box2D/` and added via `add_subdirectory` as target `flatland_Box2D`
- ThreadPool header at `flatland_plugins/thirdparty/ThreadPool.h` — used only in `laser.h`, to be removed

## File Inventory (all files that need changes)

### Layer 0 — CMake
- `flatland_server/CMakeLists.txt` — remove Box2D 2.x, add Box2D v3 + enkiTS via FetchContent

### Layer 1 — Core Lifecycle
- `flatland_server/include/flatland_server/world.h` — `b2World*`→`b2WorldId`, remove `b2ContactListener` base
- `flatland_server/src/world.cpp` — `new b2World`→`b2CreateWorld`, `Step`→`b2World_Step`
- `flatland_server/include/flatland_server/body.h` — `b2Body*`→`b2BodyId`
- `flatland_server/src/body.cpp` — `CreateBody`→`b2CreateBody`, `SetUserData`, `DestroyBody`
- `flatland_server/include/flatland_server/joint.h` — `b2Joint*`→`b2JointId`
- `flatland_server/src/joint.cpp` — `CreateJoint`→`b2CreateRevoluteJoint`/`b2CreateWeldJoint`, `frequencyHz`→`stiffness`
- `flatland_server/include/flatland_server/layer.h` — `b2World*`→`b2WorldId`
- `flatland_server/src/layer.cpp` — body/fixture creation, `b2EdgeShape::Set`→`b2Segment`
- `flatland_server/src/model_body.cpp` — `b2FixtureDef`→`b2ShapeDef`, shape structs
- `flatland_server/include/flatland_server/model_body.h` — `b2FixtureDef`→`b2ShapeDef`
- `flatland_server/include/flatland_server/entity.h` — may store `b2World*`, update to `b2WorldId`

### Layer 2 — Contact System
- `flatland_server/src/world.cpp` — poll `b2World_GetContactEvents()` after Step
- `flatland_server/include/flatland_server/flatland_plugin.h` — callback signatures `(b2Contact*)` → `(b2ShapeId, b2ShapeId)`
- `flatland_server/src/plugin_manager.cpp` — dispatch with new signatures
- `flatland_server/include/flatland_server/plugin_manager.h` — same
- `flatland_server/include/flatland_server/model_plugin.h` — same
- `flatland_server/src/model_plugin.cpp` — `fixture->GetBody()->GetUserData()` → `b2Shape_GetBody` + `b2Body_GetUserData`
- `flatland_plugins/src/bumper.cpp` — rewrite BeginContact/EndContact/PostSolve
- `flatland_plugins/include/flatland_plugins/bumper.h` — signature updates
- `flatland_plugins/src/bool_sensor.cpp` — rewrite BeginContact/EndContact

### Layer 3 — Ray Casts
- `flatland_plugins/include/flatland_plugins/laser.h` — delete `LaserCallback` class, `ThreadPool pool_`
- `flatland_plugins/src/laser.cpp` — replace parallel pool with sequential `b2World_CastRay` + free function
- `flatland_plugins/include/flatland_plugins/world_modifier.h` — delete `RayTrace` class
- `flatland_plugins/src/world_modifier.cpp` — replace with free function

### Layer 4 — Remaining Plugins
- `flatland_plugins/src/diff_drive.cpp` — body accessor API updates
- `flatland_plugins/src/tricycle_drive.cpp` — body accessors + revolute joint accessor
- `flatland_plugins/src/gps.cpp` — `GetTransform`→`b2Body_GetTransform`
- `flatland_plugins/src/model_tf_publisher.cpp` — same
- `flatland_plugins/src/tween.cpp` — `SetTransform`→`b2Body_SetTransform`
- `flatland_server/src/debug_visualization.cpp` — shape type enum + field accessor updates
- `flatland_plugins/src/world_random_wall.cpp` — `b2MulT`→`b2InvTransformPoint`

### Layer 5 — Threading
- `flatland_server/src/world.cpp` — add enkiTS scheduler, wire `b2WorldDef` task callbacks
- `flatland_server/include/flatland_server/world.h` — add `enkiTaskScheduler*` member

## Critical v2→v3 API Mapping

### World
| v2 | v3 |
|---|---|
| `new b2World(gravity)` | `b2CreateWorld(&b2DefaultWorldDef())` |
| `delete physics_world_` | `b2DestroyWorld(worldId_)` |
| `physics_world_->Step(dt, vIter, pIter)` | `b2World_Step(worldId_, dt, subSteps)` (note: `position_iterations` deprecated — use `subSteps=4`) |
| `physics_world_->SetContactListener(this)` | removed — poll events instead |
| `physics_world_->RayCast(&cb, p1, p2)` | `b2World_CastRay(worldId_, origin, translation, filter, fcn, ctx)` |

### Body
| v2 | v3 |
|---|---|
| `b2Body* body = world->CreateBody(&def)` | `b2BodyId bodyId = b2CreateBody(worldId, &def)` |
| `body->SetUserData(ptr)` | `b2Body_SetUserData(bodyId, ptr)` |
| `body->GetUserData()` | `b2Body_GetUserData(bodyId)` |
| `world->DestroyBody(body)` | `b2DestroyBody(bodyId)` |
| `body->CreateFixture(&fixtureDef)` | `b2CreatePolygonShape(bodyId, &shapeDef, &polygon)` etc. |
| `body->GetPosition()` | `b2Body_GetPosition(bodyId)` |
| `body->GetAngle()` | `b2Body_GetRotation(bodyId)` → `.angle` or `b2Rot_GetAngle(rot)` |
| `body->GetTransform()` | `b2Body_GetTransform(bodyId)` |
| `body->SetTransform(pos, angle)` | `b2Body_SetTransform(bodyId, pos, b2MakeRot(angle))` |
| `body->GetLinearVelocity()` | `b2Body_GetLinearVelocity(bodyId)` |
| `body->SetLinearVelocity(v)` | `b2Body_SetLinearVelocity(bodyId, v)` |
| `body->GetAngularVelocity()` | `b2Body_GetAngularVelocity(bodyId)` |
| `body->SetAngularVelocity(w)` | `b2Body_SetAngularVelocity(bodyId, w)` |
| `body->GetWorldCenter()` | `b2Body_GetWorldCenterOfMass(bodyId)` |
| `body->GetWorldVector(v)` | `b2Body_GetWorldVector(bodyId, v)` |
| `body->GetWorldPoint(p)` | `b2Body_GetWorldPoint(bodyId, p)` |
| `body->GetLinearVelocityFromLocalPoint(p)` | `b2Body_GetLinearVelocityFromLocalPoint(bodyId, p)` (check v3 API — may need manual: vel + cross(omega, r)) |

### Fixture → Shape
| v2 | v3 |
|---|---|
| `b2FixtureDef` | `b2ShapeDef` |
| `fixtureDef.filter.categoryBits` | `shapeDef.filter.categoryBits` |
| `fixtureDef.isSensor` | `shapeDef.isSensor` |
| `fixtureDef.friction` | `shapeDef.friction` |
| `fixtureDef.restitution` | `shapeDef.restitution` |
| `b2CircleShape shape; shape.m_p = center; shape.m_radius = r;` | `b2Circle circle = {center, r};` then `b2CreateCircleShape(bodyId, &shapeDef, &circle)` |
| `b2PolygonShape shape; shape.Set(pts, n);` | `b2Polygon polygon = b2MakePolygon(&hull, 0);` then `b2CreatePolygonShape(bodyId, &shapeDef, &polygon)` |
| `b2EdgeShape edge; edge.Set(v1, v2);` | `b2Segment seg = {v1, v2};` then `b2CreateSegmentShape(bodyId, &shapeDef, &seg)` |
| `fixture->GetFilterData().categoryBits` | `b2Shape_GetFilter(shapeId).categoryBits` |
| `fixture->IsSensor()` | `b2Shape_IsSensor(shapeId)` |
| `fixture->GetBody()` | `b2Shape_GetBody(shapeId)` |
| `fixture->GetNext()` | `b2Body_GetShapes(bodyId, shapes, capacity)` (batch query) |
| `fixture->GetShape()->GetType()` | `b2Shape_GetType(shapeId)` |

### Joint
| v2 | v3 |
|---|---|
| `b2RevoluteJointDef def; def.Initialize(bA,bB,anchor);` | `b2RevoluteJointDef def = b2DefaultRevoluteJointDef();` then set `bodyIdA/B`, `localAnchorA/B` |
| `world->CreateJoint(&def)` → `b2Joint*` | `b2CreateRevoluteJoint(worldId, &def)` → `b2JointId` |
| `b2WeldJointDef.frequencyHz` | `b2WeldJointDef.angularHertz` (or `linearHertz`) |
| `b2WeldJointDef.dampingRatio` | `b2WeldJointDef.angularDampingRatio` |
| `dynamic_cast<b2RevoluteJoint*>(joint)` | Direct `b2JointId` typed calls `b2RevoluteJoint_GetAngle(jId)` |
| `joint->SetMotorSpeed(s)` | `b2RevoluteJoint_SetMotorSpeed(jId, s)` |
| `joint->GetBodyA()` | `b2Joint_GetBodyA(jId)` |
| `joint->GetAnchorA()` | `b2Joint_GetConstraintForce(jId, dt)` etc. / `b2Body_GetWorldPoint(b2Joint_GetBodyA(jId), localAnchor)` |

### Contact Events (v3 only — replaces b2ContactListener)
```cpp
// After b2World_Step():
b2ContactEvents events = b2World_GetContactEvents(worldId_);
for (int i = 0; i < events.beginCount; i++) {
b2ContactBeginTouchEvent* e = &events.beginEvents[i];
// e->shapeIdA, e->shapeIdB
// To get Body*: static_cast<Body*>(b2Body_GetUserData(b2Shape_GetBody(e->shapeIdA)))
}
for (int i = 0; i < events.endCount; i++) {
b2ContactEndTouchEvent* e = &events.endEvents[i];
}
// Hit events replace PostSolve:
b2ContactHitEvent* hitEvents = events.hitEvents; // events.hitCount
```

### Ray Cast (v3)
```cpp
// Free function signature:
float LaserRayCastFcn(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal,
float fraction, void* context) {
auto* ctx = static_cast<LaserRayContext*>(context);
// check filter: b2Shape_GetFilter(shapeId).categoryBits
if (b2Shape_IsSensor(shapeId)) return -1.0f; // ignore sensors
ctx->hit = true;
ctx->fraction = fraction;
ctx->point = point;
ctx->normal = normal;
return fraction; // return fraction to find closest hit
}
// Dispatch:
b2QueryFilter filter = b2DefaultQueryFilter();
filter.maskBits = laser_layers_mask;
b2World_CastRay(worldId_, origin, translation, filter, LaserRayCastFcn, &context);
```

### Transform
| v2 | v3 |
|---|---|
| `b2Transform t; t.p.x; t.p.y; t.q.c; t.q.s;` | `b2Transform t; t.p.x; t.p.y; t.q.c; t.q.s;` (same struct layout) |
| `b2MulT(transform, point)` | `b2InvTransformPoint(transform, point)` |

### Math
| v2 | v3 |
|---|---|
| `b2Vec2` | `b2Vec2` (unchanged) |
| `b2_staticBody`, `b2_dynamicBody`, `b2_kinematicBody` | `b2_staticBody`, `b2_dynamicBody`, `b2_kinematicBody` (same names in v3) |
| `b2_maxPolygonVertices` | `B2_MAX_POLYGON_VERTICES` (renamed) |

## Test Files
- `flatland_server/test/` — gtest suite, run with `catkin run_tests flatland_server`
- `flatland_plugins/test/` — gtest suite, run with `catkin run_tests flatland_plugins`

## Includes
- v2: `#include <Box2D/Box2D.h>`
- v3: `#include <box2d/box2d.h>`
Loading
Loading