/** * Shared types for the Web Worker physics bridge. * * The protocol uses a flat transform buffer layout: * [x0, y0, rot0, x1, y1, rot1, ...] * * When SharedArrayBuffer is available the main thread can read transforms * at any time without waiting for a message. When it is not available * (missing COOP/COEP headers) the worker posts a copy of the buffer each * tick via `postMessage`. */ /** * Version of the transform-buffer wire layout. * * Bump this whenever the float layout changes ({@link FLOATS_PER_BODY}, * {@link HEADER_FLOATS}, or the meaning of any slot) so that a mismatch * between a cached worker bundle and the main thread is caught loudly * instead of decoding garbage transforms. Must be kept in lockstep with * `TRANSFORM_PROTOCOL_VERSION` in `@newkrok/nape-pixi`'s `workerProtocol.ts`. */ declare const PROTOCOL_VERSION = 1; /** Floats per body in the transform buffer: x, y, rotation. */ declare const FLOATS_PER_BODY = 3; /** * Metadata slots at the start of the transform buffer. * * Layout: [bodyCount, timestamp, stepTimeMs, ...transforms] */ declare const HEADER_FLOATS = 3; interface CircleDesc { type: "circle"; radius: number; offsetX?: number; offsetY?: number; } interface BoxDesc { type: "box"; width: number; height: number; } interface PolygonDesc { type: "polygon"; vertices: { x: number; y: number; }[]; } type ShapeDesc = CircleDesc | BoxDesc | PolygonDesc; interface BodyOptions { rotation?: number; velocityX?: number; velocityY?: number; angularVel?: number; isBullet?: boolean; allowRotation?: boolean; allowMovement?: boolean; /** Material elasticity (bounciness). */ elasticity?: number; /** Material dynamic friction. */ dynamicFriction?: number; /** Material static friction. */ staticFriction?: number; /** Material density. */ density?: number; } interface PhysicsWorkerOptions { /** Maximum number of bodies the buffer can hold (default 512). */ maxBodies?: number; /** Physics timestep in seconds (default 1/60). */ timestep?: number; /** Velocity solver iterations (default 10). */ velocityIterations?: number; /** Position solver iterations (default 10). */ positionIterations?: number; /** Gravity X (default 0). */ gravityX?: number; /** Gravity Y (default 600). */ gravityY?: number; /** * URL to a pre-built worker script. When omitted, the manager creates an * inline Blob worker from the bundled worker code. */ workerUrl?: string; /** * If `true`, physics runs on a fixed-interval loop inside the worker * (default `true`). Set to `false` for manual stepping via `step()`. */ autoStep?: boolean; } /** * PhysicsWorkerManager — runs nape-js physics in a Web Worker. * * The simulation runs off the main thread at a fixed timestep. Body * transforms (x, y, rotation) are shared with the main thread via a * {@link SharedArrayBuffer} when available, falling back to `postMessage` * copies when COOP/COEP headers are absent. * * ```ts * import { PhysicsWorkerManager } from "@newkrok/nape-js/worker"; * * const mgr = new PhysicsWorkerManager({ gravityY: 600, maxBodies: 256 }); * await mgr.init(); * * const id = mgr.addBody("dynamic", 100, 50, [{ type: "circle", radius: 20 }]); * mgr.start(); * * function render() { * const t = mgr.getTransforms(); * // t[id] = { x, y, rotation } * requestAnimationFrame(render); * } * render(); * ``` * * @module */ /** Per-body transform read from the shared buffer. */ interface BodyTransform { x: number; y: number; rotation: number; } declare class PhysicsWorkerManager { private worker; private buffer; private transforms; private useShared; private maxBodies; private timestep; private velocityIterations; private positionIterations; private gravityX; private gravityY; private workerUrl; private autoStep; private nextId; private bodySlots; private readyPromise; private onFrame; private destroyed; /** * URL to the nape-js ESM bundle that the worker will import. * Override this before calling `init()` if you self-host the bundle. */ napeUrl: string; constructor(options?: PhysicsWorkerOptions); /** * Create the worker and initialize the physics space. * Resolves when the worker reports `"ready"`. */ init(): Promise; private doInit; /** * Start the fixed-timestep physics loop in the worker. * Only meaningful when `autoStep` is `true` (default). */ start(): void; /** Pause the physics loop. */ stop(): void; /** Trigger a single physics step (for manual stepping). */ step(): void; /** Terminate the worker and release all resources. */ destroy(): void; /** * Add a body to the physics world. * * @returns A unique body ID used to read transforms and send commands. */ addBody(bodyType: "dynamic" | "static" | "kinematic", x: number, y: number, shapes: ShapeDesc[], options?: BodyOptions): number; /** Remove a body from the physics world. */ removeBody(id: number): void; /** Set cumulative force on a body for the current step. */ applyForce(id: number, fx: number, fy: number): void; /** Apply an instantaneous impulse. */ applyImpulse(id: number, ix: number, iy: number): void; /** Override linear velocity. */ setVelocity(id: number, vx: number, vy: number): void; /** Teleport a body. */ setPosition(id: number, x: number, y: number): void; /** Change world gravity. */ setGravity(gx: number, gy: number): void; /** * Read the transform for a single body. * Returns `null` if the body ID is unknown. */ getTransform(id: number): BodyTransform | null; /** * Read all transforms into a caller-supplied map (avoids allocations). * Populates `out` with `id → { x, y, rotation }` for every known body. */ readAllTransforms(out: Map): void; /** Raw Float32Array view of the transform buffer (header + body data). */ get rawTransforms(): Float32Array | null; /** Number of bodies reported by the worker in the last frame. */ get bodyCount(): number; /** Physics timestamp (number of steps taken). */ get timestamp(): number; /** Last physics step duration in milliseconds. */ get stepTimeMs(): number; /** Whether SharedArrayBuffer is in use (zero-copy reads). */ get isSharedBuffer(): boolean; /** * Register a callback invoked after each physics frame. * In SharedArrayBuffer mode the buffer is already up-to-date when this fires. * In fallback mode the callback receives the fresh copy. */ set onFrameCallback(fn: ((buffer: Float32Array) => void) | null); private post; } /** * Generates the worker script source code as a string. * * This is embedded into the main bundle so that `PhysicsWorkerManager` can * create an inline Blob worker without requiring the consumer to host a * separate worker file. * * The generated code is **self-contained** — it imports the full nape-js * engine from the URL provided at init time. */ /** * Returns the full worker script as a string. * * @param napeUrl - CDN / local URL to the nape-js ESM bundle. The worker * will `import(...)` this URL at runtime. */ declare function buildWorkerScript(napeUrl: string): string; export { type BodyOptions, type BodyTransform, type BoxDesc, type CircleDesc, FLOATS_PER_BODY, HEADER_FLOATS, PROTOCOL_VERSION, PhysicsWorkerManager, type PhysicsWorkerOptions, type PolygonDesc, type ShapeDesc, buildWorkerScript };