tools/build_physics.sh fetches box3d at the pinned commit, builds libbox3d.a for wasm32, and links src/client/physics/shim.c into static/dist/box3d.mjs (+360K wasm). The shim keeps the JS surface tiny: ph_init / per-segment static bodies (ph_begin_static + ph_add_box/ ph_add_slab + ph_end_static, ph_remove_static mirrors the render window) / ph_spawn_marble / ph_marble_force / ph_step / ph_marble_state readback. Smoke-tested under node: marble rests at exactly its radius, rolls under force, bounces off walls, free-falls when its segment is removed. Next (M10 cont.): channel collision proxies sampled from segment centerlines, physics marble driver behind a flag, checkpoints + respawn, survivability scenarios.
- commit
- 6c61d7b8157dc24fa928e9a60704900981862f97
- parent
- 8d00e5544f417619abfaf48dd4f855599718eb32
- author
- Dami <dami.etoile@gmail.com>
| M | .gitignore | 1 | + |
| A | src/ | 146 | ++++++++++++++++++++++++++++++ |
| A | tools/ | 37 | ++++++++ |
@@ -0,0 +1,146 @@
1+// The marbles ↔ Box3D shim (M10). Keep the JS surface TINY: the game2+// uploads static collision hulls per stream segment (boxes are enough —3+// channel floors/walls/island blocks; curved floors are short box runs4+// sampled along the centerline), owns one dynamic marble sphere, steps a5+// fixed clock, and reads the marble transform back. Segment lifetime6+// mirrors the render window: one static body per ref, destroyed on cull.7+//8+// Built by tools/build_physics.sh against the pinned box3d commit (see9+// tools/physics-spike/README.md). Exported via EXPORTED_FUNCTIONS.10+11+12+13+14+15+static b3WorldId world;16+static b3BodyId marble;17+static int marbleAlive = 0;18+19+static int refKeys[MAX_REFS];20+static b3BodyId refBodies[MAX_REFS];21+static int refUsed[MAX_REFS];22+static b3ShapeDef shapeDef;23+static int pendingSlot = -1;24+25+EMSCRIPTEN_KEEPALIVE26+void ph_init(float gravityY) {27+ b3WorldDef def = b3DefaultWorldDef();28+ def.workerCount = 1; // determinism-friendly; perf headroom is huge29+ def.gravity = (b3Vec3){0, gravityY, 0};30+ world = b3CreateWorld(&def);31+ shapeDef = b3DefaultShapeDef();32+ for (int i = 0; i < MAX_REFS; i++) refUsed[i] = 0;33+ marbleAlive = 0;34+}35+36+// --- static geometry, one body per stream ref ------------------------------37+38+EMSCRIPTEN_KEEPALIVE39+void ph_begin_static(int key) {40+ int slot = -1;41+ for (int i = 0; i < MAX_REFS && slot < 0; i++) {42+ if (!refUsed[i]) slot = i;43+ }44+ if (slot < 0) return; // caller leaked refs; bounded by render window45+ b3BodyDef def = b3DefaultBodyDef();46+ def.type = b3_staticBody;47+ refBodies[slot] = b3CreateBody(world, &def);48+ refKeys[slot] = key;49+ refUsed[slot] = 1;50+ pendingSlot = slot;51+}52+53+// Axis-aligned box in world space (channel slabs, island blocks); rotated54+// slabs pass a quarter-turn yaw. Attached to the pending static body.55+EMSCRIPTEN_KEEPALIVE56+void ph_add_box(57+ double cx, double cy, double cz,58+ float hx, float hy, float hz,59+ float yawQuarter) {60+ if (pendingSlot < 0) return;61+ b3Transform xf;62+ xf.p = (b3Vec3){(float)cx, (float)cy, (float)cz};63+ xf.q = b3MakeQuatFromAxisAngle((b3Vec3){0, 1, 0}, yawQuarter * 1.5707963f);64+ b3BoxHull box = b3MakeTransformedBoxHull(hx, hy, hz, xf);65+ b3CreateHullShape(refBodies[pendingSlot], &shapeDef, &box.base);66+}67+68+// Arbitrarily-oriented slab: center + local half-extents + a basis given as69+// tangent (x-ish) and up handled via yaw+pitch — used for sloped floors.70+EMSCRIPTEN_KEEPALIVE71+void ph_add_slab(72+ double cx, double cy, double cz,73+ float hx, float hy, float hz,74+ float yawRad, float pitchRad) {75+ if (pendingSlot < 0) return;76+ b3Quat yaw = b3MakeQuatFromAxisAngle((b3Vec3){0, 1, 0}, yawRad);77+ b3Quat pitch = b3MakeQuatFromAxisAngle((b3Vec3){1, 0, 0}, pitchRad);78+ b3Transform xf;79+ xf.p = (b3Vec3){(float)cx, (float)cy, (float)cz};80+ xf.q = b3MulQuat(yaw, pitch);81+ b3BoxHull box = b3MakeTransformedBoxHull(hx, hy, hz, xf);82+ b3CreateHullShape(refBodies[pendingSlot], &shapeDef, &box.base);83+}84+85+EMSCRIPTEN_KEEPALIVE86+void ph_end_static(void) {87+ pendingSlot = -1;88+}89+90+EMSCRIPTEN_KEEPALIVE91+void ph_remove_static(int key) {92+ for (int i = 0; i < MAX_REFS; i++) {93+ if (refUsed[i] && refKeys[i] == key) {94+ b3DestroyBody(refBodies[i]);95+ refUsed[i] = 0;96+ }97+ }98+}99+100+// --- the marble ------------------------------------------------------------101+102+EMSCRIPTEN_KEEPALIVE103+void ph_spawn_marble(104+ double x, double y, double z,105+ float vx, float vy, float vz, float radius) {106+ if (marbleAlive) b3DestroyBody(marble);107+ b3BodyDef def = b3DefaultBodyDef();108+ def.type = b3_dynamicBody;109+ def.position = (b3Pos){x, y, z};110+ def.linearVelocity = (b3Vec3){vx, vy, vz};111+ marble = b3CreateBody(world, &def);112+ b3Sphere sphere = {{0, 0, 0}, radius};113+ b3ShapeDef sd = b3DefaultShapeDef();114+ b3CreateSphereShape(marble, &sd, &sphere);115+ marbleAlive = 1;116+}117+118+EMSCRIPTEN_KEEPALIVE119+void ph_marble_force(float fx, float fy, float fz) {120+ if (!marbleAlive) return;121+ b3Body_ApplyForceToCenter(marble, (b3Vec3){fx, fy, fz}, true);122+}123+124+EMSCRIPTEN_KEEPALIVE125+void ph_step(float dt, int substeps) {126+ b3World_Step(world, dt, substeps);127+}128+129+// [px py pz vx vy vz qx qy qz qw] written to `out` (10 doubles).130+EMSCRIPTEN_KEEPALIVE131+void ph_marble_state(double* out) {132+ if (!marbleAlive) return;133+ b3Pos p = b3Body_GetPosition(marble);134+ b3Vec3 v = b3Body_GetLinearVelocity(marble);135+ b3WorldTransform t = b3Body_GetTransform(marble);136+ out[0] = p.x;137+ out[1] = p.y;138+ out[2] = p.z;139+ out[3] = v.x;140+ out[4] = v.y;141+ out[5] = v.z;142+ out[6] = t.q.v.x;143+ out[7] = t.q.v.y;144+ out[8] = t.q.v.z;145+ out[9] = t.q.s;146+}@@ -0,0 +1,37 @@
1+2+# Build the Box3D physics module (M10): fetches the pinned box3d commit,3+# builds libbox3d.a for wasm32 (emcmake), and links the game shim into4+# static/dist/box3d.mjs + .wasm. Requires emscripten (brew install5+# emscripten) and cmake. See tools/physics-spike/README.md for the spike6+# that validated this path.7+set -euo pipefail8+9+BOX3D_COMMIT=3fc20f5b453ba9e14cdf54ecafa87a2a4bcdf53c10+ROOT="$(cd "$(dirname "$0")/.." && pwd)"11+EXTERN="$ROOT/extern/box3d"12+13+if [ ! -d "$EXTERN" ]; then14+ mkdir -p "$ROOT/extern"15+ git clone https://github.com/erincatto/box3d.git "$EXTERN"16+fi17+git -C "$EXTERN" fetch --depth 1 origin "$BOX3D_COMMIT" 2>/dev/null || true18+git -C "$EXTERN" checkout -q "$BOX3D_COMMIT"19+20+emcmake cmake -S "$EXTERN" -B "$EXTERN/build-wasm" \21+ -DCMAKE_BUILD_TYPE=Release \22+ -DBOX3D_SAMPLES=OFF -DBOX3D_UNIT_TESTS=OFF -DBOX3D_BENCHMARKS=OFF \23+ >/dev/null24+cmake --build "$EXTERN/build-wasm" -j 8 >/dev/null25+26+mkdir -p "$ROOT/static/dist"27+emcc "$ROOT/src/client/physics/shim.c" \28+ "$EXTERN/build-wasm/src/libbox3d.a" \29+ -I "$EXTERN/include" \30+ -O2 \31+ -sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web,node \32+ -sALLOW_MEMORY_GROWTH=1 \33+ -sEXPORTED_FUNCTIONS=_ph_init,_ph_begin_static,_ph_add_box,_ph_add_slab,_ph_end_static,_ph_remove_static,_ph_spawn_marble,_ph_marble_force,_ph_step,_ph_marble_state,_malloc,_free \34+ -sEXPORTED_RUNTIME_METHODS=HEAPF64 \35+ -o "$ROOT/static/dist/box3d.mjs"36+37+echo "built static/dist/box3d.mjs ($(du -h "$ROOT/static/dist/box3d.wasm" | cut -f1))"