Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 64 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,67 @@ All notable changes to Box2Dxt are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

The native shim's ABI is tracked separately by `b2Version()` (currently `2`).
The native shim's ABI is tracked separately by `b2Version()` (currently `3`).

## [Unreleased]

### Added

- **Full Box2D v3.1.0 live-object API.** The binding now exposes essentially the
whole engine surface a script needs — ~240 new shim functions, each with a
`b2…` extension wrapper and, where it helps, a friendly `b2k…` Kit helper:
- **Sensors.** Non-solid trigger zones via a new **shape-def builder**
(`b2ShapeDefSensor`, `b2ShapeDefFilter`, `b2ShapeDefEnable*Events`,
`b2ShapeDefMaterialId` — one-shot options applied to the next shape). Sensor
overlaps arrive as `b2World_GetSensorEvents` (`b2SensorsUpdate` + accessors).
The Kit adds `b2kAddSensor` and `on b2kSensorEnter` / `on b2kSensorExit`.
- **Collision filtering.** `b2SetShapeFilter` + category/mask/group getters
(32 layers). The Kit adds a named-layer registry: `b2kDefineLayer`,
`b2kSetCategory`, `b2kSetMask`, `b2kSetCollisionGroup`, `b2kNoCollide`.
- **Chains.** Smooth multi-segment terrain (`b2CreateChain` + builder,
materials, segment enumeration; new chain handle table). Kit: `b2kChain`,
`b2kSmoothGround`.
- **More joints.** The **motor** joint (`b2MotorJoint`, drive a body to an
offset pose) and the **filter** joint (`b2FilterJoint`, disable a single
pair's collision), the generic joint surface (type, bodies, anchors,
constraint force/torque, collide-connected, wake), and the **complete
per-joint get/set** surface for all six joint types (springs, limits,
motors, readouts). Kit: `b2kMotorTo`.
- **World queries.** Overlap (AABB / point / circle / shape), ray-cast-**all**
(sorted), and shape-cast, surfaced through one shared result buffer
(`b2QueryCount`/`b2QueryBody`/…). Kit: `b2kOverlap`, `b2kOverlapCircle`,
`b2kRayHitAll`.
- **Events.** Contact **hit** events (impact point/normal/speed), **sensor**
events, and bulk **body-move** events (`b2BodiesUpdate` — an efficient way to
read every moved transform in one call).
- **World tuning / info.** Restitution & hit-event thresholds, contact/joint
tuning, max linear speed, warm-starting/speculative toggles, gravity getter,
`b2WorldExplode` (native blast), and profile/counters. Kit passthroughs plus
`b2kProfile`; `b2kExplode` is now native (old behaviour kept as
`b2kExplodeLegacy`).
- **Body extras.** World/local point & vector transforms, velocity-at-point,
force/impulse **at a point**, full mass-data get/set + `ApplyMassFromShapes`,
rotational inertia, local centre, kinematic target transform, sleep-enable,
per-body event flags, AABB, and shape/joint enumeration.
- **Shape extras.** Runtime geometry get/set for every shape type, material &
event-flag get/set, per-shape ray cast, AABB, closest point, mass data, and
sensor-overlap polling.
- **Kit completeness pass.** The Kit (`b2k…`) now covers the full core `b2…`
surface — every capability the binding exposes has a friendly
pixel/degree/screen-coordinate helper. New handlers:
- **Read state:** `b2kPosition(ctrl)` (body centre as `x,y` screen pixels —
the position partner of `b2kVelocity`), `b2kWorldCenter(ctrl)` (centre of
mass), `b2kGravityScale(ctrl)` and `b2kDamping(ctrl)` (getters for the
existing setters), and `b2kControlContains(ctrl, x, y)` (a rotation-aware
point-in-shape test for a single control).
- **Act:** `b2kAngularImpulse(ctrl, imp)` — a one-shot, mass-aware angular
kick, the rotational partner of `b2kImpulse`.
- **Contacts (polling):** `b2kContactCount()` / `b2kContactA(i)` /
`b2kContactB(i)` and the `b2kEndContact…` equivalents, so a `b2kFrame`
handler can read each frame's touch pairs without registering a contact
target.
- **Loader:** `b2kVersion()` returns the native ABI version for a Kit-only
"extension loaded and in sync" check.
- **Multiply tool.** Alongside Duplicate, a new **Multiply** tool asks how many
copies you want (1–50) and drops them in a tidy grid — each an independent
copy of the part you clicked (same size, colour, material and settings). Great
Expand Down Expand Up @@ -42,6 +97,14 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `2`).

### Changed

- **ABI bumped 2 → 3** (`b2Version()` now returns `3`). The change is purely
additive — every existing `b2…` handler keeps its signature — but the prebuilt
libraries must be rebuilt (CI regenerates them on release). **Note:** collision
filter category/mask bits are exposed as 32-bit (32 layers), not Box2D's full
64-bit, because xTalk numbers carry 32 unsigned bits cleanly. Pre-solve / custom
callbacks and Box2D's standalone math/geometry helpers remain intentionally
unwrapped (no safe mid-step FFI callback into xTalk; the math operates on raw
structs xTalk already handles).
- **Slicker tool palette.** Section headers are brighter and sit over a hairline
rule; tool buttons are left-aligned, light up on hover, and keep their accent
when selected — a more polished, professional left sidebar.
Expand Down
107 changes: 103 additions & 4 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ when you need something the Kit doesn't expose.
- Distances are **metres**, angles are **radians**. Convert to pixels/degrees at
draw time.
- Body type codes: `0` static, `1` kinematic, `2` dynamic.
- `b2Version()` → int returns the shim ABI version (currently `2`) — call it once
- `b2Version()` → int returns the shim ABI version (currently `3`) — call it once
as a load/version check that the extension and native library are in sync.

- [World](#world)
Expand Down Expand Up @@ -115,6 +115,102 @@ the two body handles for each (indices are **1-based**).
| `b2ContactBeginBodyA(i)` / `b2ContactBeginBodyB(i)` → body | The pair that started touching. |
| `b2ContactEndBodyA(i)` / `b2ContactEndBodyB(i)` → body | The pair that stopped touching. |

## Shape-def builder (sensors, filtering, event flags)

Set any of these **before** creating a shape (`b2AddBox`/`Circle`/`Capsule`/
`Polygon`/`b2CreateChain`); they apply to the **next** shape only, then reset —
just like the polygon vertex builder. This adds sensors, collision filters, and
per-event flags to every existing creator without new variants.

| Handler | Purpose |
|---------|---------|
| `b2ShapeDefSensor(flag)` | Make the next shape a non-solid **sensor** (overlap events, no collision). |
| `b2ShapeDefFilter(category, mask, group)` | Collision filter for the next shape (category/mask are 32-bit). |
| `b2ShapeDefEnableContactEvents(flag)` / `b2ShapeDefEnableSensorEvents(flag)` / `b2ShapeDefEnableHitEvents(flag)` / `b2ShapeDefEnablePreSolveEvents(flag)` | Per-event flags for the next shape. |
| `b2ShapeDefMaterialId(id)` | User material id for the next shape. |
| `b2ShapeDefReset()` | Clear any pending options explicitly. |

## World tuning & info

| Handler | Purpose |
|---------|---------|
| `b2WorldGravityX(world)` / `b2WorldGravityY(world)` | Read the gravity vector. |
| `b2SetRestitutionThreshold` / `b2RestitutionThreshold(world)` | Speed below which collisions stop bouncing. |
| `b2SetHitEventThreshold` / `b2HitEventThreshold(world)` | Approach speed above which a contact reports a hit event. |
| `b2SetContactTuning(world, hertz, damping, pushSpeed)` / `b2SetJointTuning(world, hertz, damping)` | Solver softness. |
| `b2SetMaximumLinearSpeed` / `b2MaximumLinearSpeed(world)` | Clamp body speed. |
| `b2EnableWarmStarting` / `b2IsWarmStartingEnabled` · `b2EnableSpeculative` · `b2IsSleepingEnabled` / `b2IsContinuousEnabled` | World toggles. |
| `b2AwakeBodyCount(world)` | Number of awake bodies. |
| `b2WorldExplode(world, x, y, radius, falloff, impulsePerLength)` | Native radial impulse, shape-perimeter aware. |
| `b2WorldProfileUpdate(world)` → `b2WorldProfileStep/Pairs/Collide/Solve/Refit/Sensors()` | Per-step timing (ms). |
| `b2WorldCountersUpdate(world)` → `b2WorldBodyCount/ShapeCount/ContactCount/JointCount/IslandCount()` | World object counts. |

## Body — transforms, mass, enumeration

| Handler | Purpose |
|---------|---------|
| `b2BodyWorldPointX/Y` · `b2BodyLocalPointX/Y` · `b2BodyWorldVectorX/Y` · `b2BodyLocalVectorX/Y` | Local↔world point & vector transforms. |
| `b2BodyWorldPointVelocityX/Y` · `b2BodyLocalPointVelocityX/Y` | Velocity of a point on the body. |
| `b2ApplyForceAt(body, fx, fy, px, py, wake)` / `b2ApplyImpulseAt(...)` | Force / impulse at a world point (adds torque). |
| `b2BodyRotationalInertia` · `b2BodyLocalCenterX/Y` | Inertia and local centre of mass. |
| `b2BodyMassDataUpdate(body)` → `b2MassDataMass/CenterX/CenterY/Inertia()` | Read mass data. |
| `b2SetMassData(body, mass, cx, cy, inertia)` / `b2ApplyMassFromShapes(body)` | Override / recompute mass. |
| `b2SetTargetTransform(body, x, y, angle, timeStep)` | Drive a kinematic body to a pose. |
| `b2EnableSleep` / `b2BodyIsSleepEnabled` · `b2BodyIsFixedRotation` · `b2BodyEnableContactEvents` / `b2BodyEnableHitEvents` | Per-body flags. |
| `b2BodyAABBUpdate(body)` → `b2AABBLowerX/LowerY/UpperX/UpperY()` | Body AABB. |
| `b2BodyShapeCount(body)` → `b2BodyShapeAt(i)` · `b2BodyJointCount(body)` → `b2BodyJointAt(i)` | Enumerate a body's shapes / joints (1-based). |

## Shape — filter, geometry, material, queries

| Handler | Purpose |
|---------|---------|
| `b2ShapeType` · `b2ShapeIsSensor` · `b2ShapeDensity/Friction/Restitution` · `b2ShapeMaterialId` / `b2SetShapeMaterialId` | Read type / material. |
| `b2SetShapeFilter(shape, category, mask, group)` · `b2ShapeFilterCategory/Mask/Group(shape)` | Collision filtering (32-bit bits). |
| `b2ShapeEnableSensorEvents` / `…Contact…` / `…Hit…` / `…PreSolve…` + the matching `…EventsEnabled` getters | Per-shape event flags. |
| `b2ShapeCircleUpdate/Capsule…/Segment…/PolygonUpdate(shape)` + their `…X/Y/Radius/VertexX/VertexY` readers | Read a shape's geometry. |
| `b2SetShapeCircle/Capsule/Segment(shape, …)` · `b2SetShapePolygon(shape)` (uses the vertex builder) | Replace a shape's geometry in place. |
| `b2ShapeRayCast(shape, x1, y1, x2, y2)` → `b2ShapeRayX/Y/NormalX/NormalY/Fraction()` | Ray cast against one shape. |
| `b2ShapeAABBUpdate` (→ `b2AABB…`) · `b2ShapeClosestPointX/Y` · `b2ShapeMassDataUpdate` (→ `b2MassData…`) | Bounds / closest point / mass. |
| `b2ShapeSensorCapacity` · `b2ShapeSensorOverlapsUpdate(shape)` → `b2ShapeSensorOverlapCount()` / `b2ShapeSensorOverlapAt(i)` | Poll shapes overlapping a sensor. |

## Chains (smooth terrain)

| Handler | Purpose |
|---------|---------|
| `b2ChainBegin()` → `b2ChainAddPoint(x, y)` … → `b2CreateChain(body, loop, friction, restitution)` → chain | Build a chain (≥ 4 points; loop closes it). A non-loop chain's first & last points are ghost vertices (n points → n−3 collidable segments). |
| `b2DestroyChain(chain)` · `b2ChainIsValid(chain)` | Lifetime. |
| `b2SetChainFriction/Restitution` + getters · `b2ChainSegmentCount(chain)` → `b2ChainSegmentAt(i)` | Tune / enumerate segments. |

## Joints — generic, new types, full per-joint control

| Handler | Purpose |
|---------|---------|
| `b2JointType` · `b2JointBodyA/B` · `b2JointLocalAnchorAX/AY/BX/BY` · `b2JointCollideConnected` / `b2SetJointCollideConnected` · `b2JointConstraintForceX/Y` · `b2JointConstraintTorque` · `b2JointWakeBodies` | Generic joint surface (constraint force/torque is handy for breakable joints). |
| `b2MotorJoint(world, bodyA, bodyB, offsetX, offsetY, angularOffset, maxForce, maxTorque, correction, collide)` + `b2MotorSet/Get…` | **Motor joint** — drive bodyB to an offset pose from bodyA. |
| `b2FilterJoint(world, bodyA, bodyB)` | **Filter joint** — disable collision between exactly these two bodies. |
| `b2Revolute…` / `b2Prismatic…` / `b2Distance…` / `b2Weld…` / `b2Wheel…` / `b2Mouse…` | The **complete** per-joint get/set surface for all six existing joint types: spring enable/hertz/damping, limit enable/lower/upper, motor enable/speed/force\|torque, and readouts (angle, translation, speed, current length, reference angle, target). |

## World queries (overlap / ray-cast-all / shape-cast)

Each query runs, stashes its hits in one shared buffer, and returns the count;
then read rows **1-based**. (`b2CastRayClosest` / `b2BodyAtPoint` above remain for
single-result use.)

| Handler | Purpose |
|---------|---------|
| `b2OverlapAABB(world, x1, y1, x2, y2)` · `b2OverlapPoint(world, x, y)` · `b2OverlapCircle(world, cx, cy, r)` · `b2OverlapShape(world, radius)` (uses the vertex builder) | Find shapes overlapping a region. |
| `b2RayCastAll(world, x1, y1, x2, y2)` | Every shape along a ray, sorted near→far. |
| `b2ShapeCast(world, radius, dx, dy)` | Sweep a proxy (built from the vertex builder) and gather hits. |
| `b2QueryCount()` · `b2QueryBody(i)` / `b2QueryShape(i)` · `b2QueryX/Y(i)` · `b2QueryNormalX/Y(i)` · `b2QueryFraction(i)` | Read the shared result rows. |

## Events — hit, sensor, body-move

| Handler | Purpose |
|---------|---------|
| `b2ContactHitCount()` · `b2ContactHitBodyA/B(i)` · `b2ContactHitX/Y(i)` · `b2ContactHitNormalX/Y(i)` · `b2ContactHitSpeed(i)` | **Hit** events (snapshotted by `b2ContactsUpdate`; needs hit events enabled). |
| `b2SensorsUpdate(world)` → `b2SensorBeginCount/EndCount()` · `b2SensorBeginSensorShape/VisitorShape(i)` · `b2SensorEndSensorShape/VisitorShape(i)` | **Sensor** events (shape handles; both shapes need sensor events). |
| `b2BodiesUpdate(world)` → `b2BodyMoveCount()` · `b2BodyMoveBody(i)` · `b2BodyMoveX/Y/Angle(i)` · `b2BodyMoveFellAsleep(i)` | **Body-move** events — read every moved transform in one call (efficient bulk sync). |

## Notes and gotchas

**Units.** Box2D is tuned for **MKS units**; keep moving objects roughly
Expand All @@ -137,6 +233,9 @@ rendering into an LCB widget canvas, rather than one control per body.

**Extending the binding.** See [architecture.md](architecture.md#extending-the-binding)
for the step-by-step recipe (add a `b2lc_*` C function, a `foreign handler`, and
a public wrapper; bump `LC_ABI_VERSION`; rebuild). Box2D v3 also offers chains,
sensors, hit/pre-solve contact events, and shape casts — all reachable the same
way.
a public wrapper; bump `LC_ABI_VERSION`; rebuild). As of ABI `3` the binding
covers the full Box2D v3.1 **live-object** surface (chains, sensors, filtering,
hit & body-move events, shape casts, motor/filter joints, world tuning, mass
data, …). What's intentionally **not** wrapped: pre-solve / custom-filter
callbacks (no safe way to call back into xTalk mid-step) and Box2D's standalone
math/geometry/TOI helpers (they operate on raw structs, not world objects).
31 changes: 26 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ non-deterministic.
## The ABI version

The shim exports `b2lc_abi_version()`, surfaced to scripts as `b2Version()`. It
returns the integer `LC_ABI_VERSION` defined in `src/box2d_lc.c` (currently `2`).
returns the integer `LC_ABI_VERSION` defined in `src/box2d_lc.c` (currently `3`).
Use it as a load/version sanity check, and **bump it whenever the exported ABI
changes** so the `.lcb` and native library can't silently drift apart.

Expand All @@ -113,7 +113,28 @@ Exposing more of Box2D is mechanical. To add a handler:
4. **Rebuild** the native library (see [building.md](building.md)) and reload the
extension.

Box2D v3 also offers chains, sensors, hit/pre-solve contact events, and shape
casts — all reachable the same way when you need them. Add a smoke-test
assertion in `tests/smoke_test.c` for anything non-trivial so CI exercises it on
every platform.
Add a smoke-test assertion in `tests/smoke_test.c` for anything non-trivial so CI
exercises it on every platform.

As of ABI `3` the binding already covers the full Box2D v3.1 **live-object**
surface. The newer additions reuse a few shared shim patterns worth knowing when
you extend further:

- A **shape-def "pending overrides"** struct lets the existing shape creators gain
sensors / filters / event-flags / materials without new variants — set options
with `b2lc_shapedef_*`, and `fill_shape_def` applies them to the next shape then
resets (one-shot, like the polygon vertex builder).
- A **chains handle table** (the same `DEFINE_TABLE` macro) plus a point-cloud
builder mirrors the polygon path.
- **Queries** are callback-based upstream; the shim runs them with a small C
callback that pushes hits into one **shared result buffer**, then exposes a
count + indexed getters — the same idea as the ray/contact stashes.
- **Sensor / hit / body-move events** reuse the growable snapshot pattern of the
contact events.

What stays **intentionally unwrapped**: pre-solve / custom-filter / friction /
restitution callbacks (there is no safe way to call back into xTalk mid-step over
the LCB FFI), and Box2D's standalone math / geometry / TOI / manifold helpers
(they operate on raw structs, which xTalk handles itself). Filter category/mask
bits are exposed as **32-bit** (xTalk doubles carry 32 unsigned bits cleanly), so
scripts get 32 collision layers rather than Box2D's full 64.
4 changes: 2 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Open the Message Box and run:
put b2Version()
```

You should see `2` (the shim ABI version). If you get an error instead, the
You should see `3` (the shim ABI version). If you get an error instead, the
extension didn't load or the native library can't be found — jump to
[Troubleshooting](#troubleshooting).

Expand Down Expand Up @@ -130,7 +130,7 @@ scenes at runtime, so there's nothing to lay out.
The demo is **self-contained**: it bundles a copy of the Kit, so it runs from a
single paste — no separate setup.

1. Make sure the extension is loaded and `put b2Version()` returns `2`.
1. Make sure the extension is loaded and `put b2Version()` returns `3`.
2. Paste the whole of
[`examples/box2dxt-demo.livecodescript`](../examples/box2dxt-demo.livecodescript)
into a stack's script (*Object → Stack Script*). If you previously pasted a
Expand Down
Loading
Loading