boxdd - Rust bindings for Box2D v3 (C API)

Crates
boxdd-sys: low-level FFI for the official Box2D v3 C API (vendored)boxdd: safe layer (world, bodies, shapes, joints, queries, events, debug draw)
0.4.0 Highlights
0.4.0realignsboxdd-syswith the official upstream Box2D submodule again, so repository checkouts and CI no longer depend on a local-only Box2D patch commit.- Hot-path APIs are first-class: keep the simple
Vec-returning calls for one-off use, or move per-frame code to*_intoandvisit_*. boxdd::collisionnow exposes standalone distance, shape-cast, TOI, manifold, andAabb::ray_casthelpers without dropping to rawffi.- Character-mover support is complete on the safe surface:
cast_mover,collide_mover,solve_planes, andclip_vector. WorldHandleis now a practical read-only follow-up surface for body, shape, joint, query, and owned-event inspection.- Raw FFI boundaries are explicit and easier to reason about: use crate-owned ids/value types plus named
from_raw(...),into_raw(), and*_rawescape hatches. - Open-chain runtime material access still uses visible live-segment indexing on the safe API, but that normalization now lives in Rust instead of a custom Box2D patch.
- The examples and testbed were reorganized around the intended current workflows instead of a loose grab bag of demos.
Detailed Highlights
- Safe, ergonomic Rust wrapper over the official Box2D v3 C API.
- Math interop (features:
mint/cgmath/nalgebra/glam): anyInto<Vec2>accepts the corresponding 2D vector/point types, plus arrays/tuples. - Two error-handling styles: panic-on-misuse by default, plus
try_*APIs returningApiResult<T>for recoverable errors. - Explicit threading model:
worker_countremains part ofWorldDef, whileWorldand owned handles stay pinned to one thread/task; actual Box2D worker-thread stepping still requires an explicit raw task-system configuration path. - Hot-path query, debug-draw collection, and state-extraction APIs expose
*_intobuffer-reuse variants, and overlap queries also exposevisit_*forms for zero result-container allocation. - Character mover helpers cover the full safe workflow:
cast_mover,collide_mover,solve_planes, andclip_vector. - World runtime helpers cover counters, per-stage
Profiletimings, speculative-collision toggles, and safe explosion control. - Core math types (
Vec2,Rot,Transform) now cross the Box2D raw boundary explicitly throughfrom_raw(...)/into_raw()instead of implicit conversions. - Global foundation helpers now cover allocated-byte inspection, timing ticks/millisecond helpers, thread yielding, and deterministic hashing without dropping to raw
ffi. - Standalone collision geometry helpers cover shape proxies, GJK distance, segment distance, contact manifolds, chain-segment manifolds, shape cast, TOI, and
Aabb::is_valid/Aabb::ray_castwithout rawffi, with matching recoverabletry_*entrypoints for malformed input. - Shape creation and editing now use crate-owned geometry values, and chain segments can be inspected through the crate-owned
ChainSegmenttype. - Chain runtime material access now uses visible segment indexing on open chains instead of leaking Box2D's ghost-placeholder storage layout.
- Safe shape/joint mutators now front-load obvious Box2D assert preconditions such as non-negative material scalars and ordered joint limits.
- Safe world/body/joint creation now validates obvious Box2D definition preconditions before entering native code, and definition value objects expose
validate()helpers for preflight checks. - Pointer-bearing config wrappers now keep their raw re-entry explicit:
BodyDef::from_raw(...)andWorldDef::from_raw(...)areunsafebecause raw names/task callbacks can otherwise punch through later safe creation paths. - Live shape runtime helpers now cover
aabb,test_point, directray_cast, computedmass_data, and runtime event toggles without rawffi. - Body runtime helpers now cover
rotation, sleep/awake/enabled/bullet/name controls, attachedshapes/jointsenumeration, and body-level contact/hit event toggles. - Joint runtime helpers now cover both common metadata/control and type-specific distance/prismatic/revolute/weld/wheel/motor state across owned/scoped/id-style APIs.
- Opaque ids (
BodyId,ShapeId,JointId,ChainId,ContactId) are now crate-owned value types; raw interop is explicit throughfrom_raw(...)/into_raw(). ContactIdnow exposes directis_valid/data/data_rawandtry_*helpers as inherent methods; no extension-trait import is required to inspect contact ids from events or snapshots.- Shape classification, mass properties, and contact extraction now use crate-owned value types such as
ShapeType,MassData,ContactData, andManifold. - Body motion constraints use the crate-owned
MotionLockstype instead of raw Box2D flags. - Crate-owned
MassDataandMotionLockscross the FFI boundary explicitly viafrom_raw(...)/into_raw()when raw interop is still needed. - Debug draw callbacks and collected commands use the crate-owned
HexColortype instead of rawfficolors. - Typed world-level friction and restitution mixing callbacks expose
user_material_idwithout dropping to rawffi.
Quickstart
use ;
let def = builder.gravity.build;
let mut world = new.unwrap;
let mut body = world.create_body_owned;
let poly = box_polygon;
let _shape = body.create_polygon_shape;
world.step;
Features (optional)
serde: serialization for core value/config types (Vec2,Rot,Transform,Aabb,QueryFilter, etc.).serialize: snapshot helpers (save/apply world config; take/rebuild minimal full-scene snapshot).mint: lightweight math interop types (mint::Vector2,mint::Point2, bidirectionalmint::RowMatrix2/mint::ColumnMatrix2forRot, and row/column-major 2D affine matrices forTransform).cgmath,nalgebra,glam: conversions with their 2D types (e.g.Vector2/Point2,UnitComplex/Isometry2,glam::Vec2).bytemuck: enablePod/Zeroablefor core math types (Vec2,Rot,Transform,Aabb) for zero-copy interop.unchecked: exposes extraunsafeunchecked APIs for hot paths (skips id validity checks; you must guarantee ids are valid).
Math Interop
Vec2always accepts[f32; 2]and(f32, f32)anywhereInto<Vec2>is used.mintnow coversVec2,Aabb,Rot, andTransform, including row- and column-major 2D matrix forms and recoverableTryFromvalidation forRot/Transform.cgmath,nalgebra, andglamremain first-class interop options for projects that already standardize on those math crates.
Threading and Async
WorldDef::builder().worker_count(n)stores the desired upstream worker count, but Box2D only uses worker threads when task callbacks are also installed throughunsafe WorldBuilder::task_system_raw(...),WorldDef::set_task_system_raw(...), or a fully rawWorldDefpath. It does not makeWorld,WorldHandle, or owned handlesSend/Sync.- Keep physics ownership on one thread/task. In async runtimes prefer
spawn_local/LocalSet; in multi-threaded engines prefer a dedicated physics thread and communicate with channels. set_custom_filter*,set_pre_solve*,set_friction_callback, andset_restitution_callbackmay run on Box2D worker threads, so those closures must staySend + Syncand should be treated as pure callbacks.- See
examples/physics_thread.rsfor a minimal dedicated-thread pattern.
Error Handling
- The default safe APIs panic on misuse such as stale ids or calling Box2D while the world is locked in a callback. This keeps the common path terse and avoids Rust-level UB.
- At engine/runtime boundaries, prefer
try_*APIs and handleApiErrorexplicitly. ApiErrorcovers stale ids, callback-locked access, invalid arguments, out-of-range runtime indices, invalid typed-joint family use, invalid chain defs, interior NUL strings, typed user-data mismatches, and material-callback slot exhaustion.WorldDef,BodyDef,ShapeDef,SurfaceMaterial,JointBase, and concrete*JointDefvalues exposevalidate()so tooling and editor flows can reject invalid config before callingcreate_*.- World-level runtime tuning and explosion helpers now also expose
try_*variants when callback locking should be handled recoverably.
World Runtime Extras
world.counters()andworld.profile()expose simulation size counters and last-step timing breakdowns without dropping to rawffi.ExplosionDefandworld.explode(...)/world.try_explode(...)now expose Box2D's explosion API directly on the main safe surface.- Runtime tuning controls such as sleeping, continuous collision, warm starting, speculative collision, restitution threshold, hit threshold, contact tuning, and maximum linear speed now have matching
try_*coverage. BodyBuilder::allow_fast_rotation(...), computed body AABB helpers (Body::aabb(),OwnedBody::aabb(),World::body_aabb(...)), and read-onlyWorldHandleruntime getters for world diagnostics plus body-by-id, shape-by-id, and joint-by-id queries keep more of the upstream runtime surface on the main safe API.
Snapshots
- Enable
serializeand see exampleexamples/scene_serialize.rsfor a minimal scene round-trip. - Note: chain shapes are captured when created via this wrapper (
World::create_chain_for_id/Body::create_chain). - Note:
ShapeDefflags that have no runtime getters are captured for shapes created via this wrapper.
Build Modes
- From source: builds vendored Box2D C sources via
ccand uses pregenerated bindings by default.- Example:
cargo build -p boxdd.
- Example:
- System library (optional): link an existing
box2dinstalled on the system.- Via env: set
BOX2D_LIB_DIR=/path/to/liband optionallyBOXDD_SYS_LINK_KIND=static|dylib. - Via feature: enable
pkg-configand providebox2dthrough your system's package manager. - Note: crate features that affect C build (e.g.
simd-avx2,disable-simd,validate) are ignored in system mode. SetBOXDD_SYS_STRICT_FEATURES=1to fail the build if such features are enabled.
- Via env: set
Getting Started
# run a few representative examples
Examples
- The example catalog is now grouped by topic in
boxdd/examples/README.md, so users can start from the workflows they care about instead of scanning file names. - Recommended starting points:
world_basics: minimal world/body/shape setupbuffer_reuse,queries,query_casts,character_mover: the main0.4.0hot-path, overlap, cast, and mover workflowsmint_interop: optionalmintvector/point/matrix interop sample behind themintfeaturecollision_basics: standalone collision geometry helpers without constructing aWorldevents_summary,events_view: owned-with-reuse vs borrowed zero-copy event accessworld_handle_reads: stored read-onlyWorldHandlefollow-up queries after id-producing world queries, including reusable-buffer overlap readsscene_serialize: snapshot/restore flows behind theserializefeaturephysics_thread: the recommended dedicated physics-thread ownership modeltestbed_imgui_glow: optional interactive testbed on the currentdear-imgui-*stack
Hot Path APIs
- Convenience methods like
world.overlap_aabb(...)andworld.cast_ray_all(...)still return ownedVecs for one-off use. - For per-frame hot paths, prefer reusable-buffer variants such as
world.overlap_aabb_into(...),world.cast_ray_all_into(...),world.debug_draw_collect_into(...),shape.sensor_overlaps_into(...),body.contact_data_into(...),body.shapes_into(...),body.joints_into(...), andchain.segments_into(...). - For overlap-heavy paths that only need streaming inspection or early exit, prefer
world.visit_overlap_aabb(...),visit_overlap_polygon_points(...), andvisit_overlap_polygon_points_with_offset(...)so no result container needs to be built at all. body.contact_data_into(...)andshape.contact_data_into(...)now fillVec<ContactData>; explicit raw escape hatches are available ascontact_data_raw_into(...)if you truly need the upstream FFI layout.
Character Mover APIs
- The safe wrapper now covers Box2D's geometric character mover pipeline.
- Use
world.cast_mover(...)to test motion,world.collide_mover(...)orworld.collide_mover_into(...)to collect planes,boxdd::solve_planes(...)/boxdd::try_solve_planes(...)to solve them, andboxdd::clip_vector(...)/boxdd::try_clip_vector(...)to clip velocity against solved planes. - See
examples/character_mover.rsfor a minimal end-to-end usage example.
Collision Geometry APIs
boxdd::collisionexposes Box2D's standalone low-level geometry algorithms as safe Rust value types.- Use
ShapeProxy,SimplexCache,DistanceInput,ShapeCastPairInput,Sweep, andToiInputwithsegment_distance(...),shape_distance(...),shape_cast(...), andtime_of_impact(...), or the matching recoverabletry_*variants when malformed input should returnApiError::InvalidArgument. - Standalone manifold helpers such as
collide_polygons(...),collide_polygon_and_circle(...),collide_segment_and_capsule(...), andcollide_chain_segment_and_polygon(...)return the safeManifoldtype and now also expose matching recoverabletry_collide_*variants. Aabb::is_valid()andAabb::ray_cast(origin, translation)now cover common AABB validation and ray-cast needs without reaching forboxdd_sys::ffi.- These advanced APIs are intentionally not in the prelude, so collision-heavy code can import them explicitly.
Shape Geometry APIs
boxdd::shapes::circle,segment,capsule,box_polygon,rounded_box_polygon, andpolygon_from_pointsreturn safe geometry value types instead of raw Box2D structs.Shape::circle()/segment()/capsule()/polygon()and the corresponding setters now use the same geometry types as world/body creation APIs.Circle,Segment,Capsule, andPolygonexpose standalone helpers such asmass_data(...),aabb(...),contains_point(...),ray_cast(...), andtransformed(...)for world-free geometry work.Circle,Segment,Capsule,ChainSegment, andPolygonnow exposeis_valid()/validate()so engines can preflight geometry before crossing the FFI boundary.- Those world-free helper methods also expose recoverable
try_*variants (try_mass_data,try_aabb,try_contains_point,try_ray_cast,try_transformed) so malformed helper inputs can returnApiError::InvalidArgumentinstead of panicking. - Polygon construction helpers also expose recoverable
try_*variants (try_square_polygon,try_box_polygon,try_rounded_box_polygon,try_offset_*,try_polygon_from_points) so finite extents, transforms, and hull construction can be validated without collapsing everything toOption. - Live
Shape/OwnedShape/World::shape_*APIs now also cover runtimeaabb,test_point,ray_cast,mass_data, and event-toggle state. - Raw geometry conversion is explicit on the crate-owned geometry types: use
from_raw(...)/into_raw()when you intentionally cross the FFI boundary. ShapeDefBuilder::filter(...)andChainDef::builder().filter(...)now take the safeFiltertype; explicit raw escape hatches are namedfilter_raw(...).Filteralso uses explicit raw conversion viafrom_raw(...)/into_raw()instead of implicitFrom<ffi::b2Filter>conversions.SurfaceMaterialnow behaves like a normal crate-owned value type: builder-style mutation useswith_*methods, read access uses getters such asfriction(),restitution(), andcustom_color(),custom_coloruses crate-ownedHexColor, and raw interop stays explicit throughfrom_raw(...)/into_raw().
Joint Runtime APIs
Joint,OwnedJoint,World::joint_*, and read-onlyWorldHandle::joint_*now stay aligned for common runtime metadata and control: joint type, connected body ids,collide_connected, constraint tuning, local frames, thresholds, and wake helpers.- Type-specific runtime getters/setters for distance, prismatic, revolute, weld, wheel, and motor joints are aligned across
World,OwnedJoint, and scopedJoint<'_>handles, whileWorldHandlemirrors the read-only getter half so storedJointIdvalues can stay on the handle path. JointTypeandConstraintTuningare crate-owned value types; raw access stays explicit throughjoint_type_rawandJointType::from_raw(...)/into_raw().try_*typed joint APIs now returnApiError::InvalidJointTypewhen a valid joint is used through the wrong family surface.- World-space joint builders now preserve previously configured base flags such as
collide_connectedwhile populating runtime-computed body ids and local frames.
Material Mixing Callbacks
world.set_friction_callback(...)andworld.set_restitution_callback(...)expose Box2D's material mixing hooks as safe typed closures.- Each callback receives two
MaterialMixInputvalues containing the incoming coefficient anduser_material_id. - These callbacks may run on Box2D worker threads, so they must stay thread-safe and should be treated as pure mixing functions.
Events
- Four access styles:
- By value:
world.contact_events()/sensor_events()/body_events()/joint_events()return owned data for storage or cross-frame use. - Reusable buffers:
*_events_into(...)reuse caller-owned owned-event storage across frames. - Zero‑copy views:
with_*_events_view(...)iterate without allocations (borrows internal buffers). - Raw slices:
unsafe { with_*_events_raw(...) }expose FFI slices (borrows internal buffers).
- By value:
- Callback-sensitive event entrypoints also have matching
try_*variants so callback-lock failures can returnApiError::InCallbackinstead of forcing panic-only control flow. - Owned event snapshots (
*_events,*_events_into,try_*) are available on bothWorldandWorldHandle. - Borrowed zero-copy event views and raw event-buffer access intentionally stay on
World, because they are tied to the completed step's world-local event buffers plus deferred-destroy flushing. - Example (reusable buffers + zero-copy views):
use ;
let mut world = new.unwrap;
let mut contact_events = default;
world.contact_events_into;
world.with_contact_events_view;
world.with_sensor_events_view;
world.with_body_events_view;
world.with_joint_events_view;
Notes
- Vendored C sources + pregenerated bindings by default (no LLVM needed on CI).
- To force bindgen: enable the
boxdd-sys/bindgenfeature, setBOXDD_SYS_FORCE_BINDGEN=1, and ensurelibclangis available. On Windows/MSVC, setLIBCLANG_PATHif needed.
- To force bindgen: enable the
- On docs.rs, the native C build is skipped.
- Safe handle methods validate ids and panic on invalid ids (prevents UB if an id becomes stale). For recoverable failures (invalid ids / calling during Box2D callbacks), use
try_*APIs. - Threading:
Worldand owned handles are!Send/!Sync.worker_countonly controls Box2D's internal stepping workers. Run physics on one thread; in async runtimes preferspawn_local/LocalSet, or create the world inside a dedicated physics thread and communicate via channels.
Documentation
- Local:
cargo doc --open - Online: https://docs.rs/boxdd
Changelog
- See
CHANGELOG.md.
Acknowledgments
- Thanks to the Rust Box2D bindings project for prior art and inspiration: https://github.com/Bastacyclop/rust_box2d
- Huge thanks to the upstream Box2D project by Erin Catto: https://github.com/erincatto/box2d
Related Projects
If you're working with graphics applications in Rust, you might also be interested in:
- asset-importer - A comprehensive Rust binding for the latest Assimp 3D asset import library, providing robust 3D model loading capabilities for graphics applications
- dear-imgui - Comprehensive Dear ImGui bindings for Rust using C++ bindgen, providing immediate mode GUI capabilities for graphics applications
License
boxdd: MIT OR Apache-2.0boxdd-sys: MIT OR Apache-2.0