import { V as Vec2, A as AABB, B as Body, S as Shape, M as Material, I as InteractionFilter, a as ShapeType, C as CollisionArbiter, b as Vec3, c as CbEvent, L as Listener, d as Interactor, e as InteractionType, f as Constraint, g as Arbiter, P as PreFlag, h as MatMN, N as NapeInner, i as BodyType, j as Space } from './ConvexResult-Dq5DbvRj.cjs'; export { k as ArbiterList, l as ArbiterType, m as BodyList, n as Broadphase, o as CbTypeSet, p as Compound, q as CompoundList, r as ConstraintList, s as ConvexResult, t as ConvexResultList, D as DebugDraw, u as DebugVec2, E as Edge, F as FluidArbiter, v as FluidProperties, G as GravMassMode, w as InertiaMode, x as InteractionGroup, y as ListenerList, z as ListenerType, H as MassMode, J as Mat23, K as Polygon, R as Ray, O as RayResult, Q as RayResultList, T as ShapeList, U as TypedListLike } from './ConvexResult-Dq5DbvRj.cjs'; export { P as PhysicsMetricsData } from './PhysicsMetrics-QzFDRdGh.cjs'; /** * Polygon winding order. * * - `UNDEFINED` — winding is not determined * - `CLOCKWISE` — clockwise winding * - `ANTICLOCKWISE` — counter-clockwise winding * * Converted from nape-compiled.js lines 19050–19116. */ declare class Winding { constructor(); static get UNDEFINED(): Winding; static get CLOCKWISE(): Winding; static get ANTICLOCKWISE(): Winding; toString(): string; } /** * A polygon represented as a circular doubly-linked list of vertices. * * Supports construction from Array, Vec2List, or another GeomPoly. * Provides geometric queries (area, winding, containment, convexity) * and decomposition algorithms (simple, monotone, convex, triangular). * * Converted from nape-compiled.js lines 16271–19420. */ declare class GeomPoly { constructor(vertices?: any); private _checkDisposed; static get(vertices?: any): GeomPoly; empty(): boolean; size(): number; iterator(): any; forwardIterator(): any; backwardsIterator(): any; current(): Vec2; push(vertex: Vec2): this; pop(): this; unshift(vertex: Vec2): this; shift(): this; skipForward(times: number): this; skipBackwards(times: number): this; erase(count: number): this; clear(): this; copy(): GeomPoly; dispose(): void; area(): number; winding(): any; contains(point: Vec2): boolean; isClockwise(): boolean; isConvex(): boolean; isSimple(): boolean; isMonotone(): boolean; isDegenerate(): boolean; simplify(epsilon: number): GeomPoly; simpleDecomposition(output?: any): any; monotoneDecomposition(output?: any): any; convexDecomposition(delaunay?: boolean, output?: any): any; triangularDecomposition(delaunay?: boolean, output?: any): any; inflate(inflation: number): GeomPoly; cut(start: Vec2, end: Vec2, boundedStart?: boolean, boundedEnd?: boolean, output?: any): any; transform(matrix: any): this; bounds(): any; top(): Vec2; bottom(): Vec2; left(): Vec2; right(): Vec2; toString(): string; } /** * Isosurface extraction using the marching squares algorithm. * * Static utility class — all functionality is in the `run()` method. * * Converted from nape-compiled.js lines 16879–17258. */ declare class MarchingSquares { /** * Run the marching squares algorithm to extract polygons from an iso function. * * @param iso - Iso function `(x: number, y: number) => number`. * Negative values are "inside", positive are "outside". * @param bounds - AABB defining the region to extract surfaces from. * @param cellsize - Vec2 defining cell dimensions. Auto-disposed if weak. * @param quality - Interpolation quality (default 2). Must be >= 0. * @param subgrid - Optional Vec2 for sub-grid partitioning. Auto-disposed if weak. * @param combine - Whether to combine adjacent polygons (default true). * @param output - Optional GeomPolyList to populate. If null, a new one is created. * @returns The populated GeomPolyList. */ static run(iso: (x: number, y: number) => number, bounds: AABB, cellsize: Vec2, quality?: number, subgrid?: Vec2 | null, combine?: boolean, output?: any): any; } /** * Static utility class for geometric queries between shapes and bodies. * * Fully modernized — calls ZPP_Geom, ZPP_SweepDistance, and ZPP_Collide directly. */ declare class Geom { /** * Calculate minimum distance between two bodies and return closest points. */ static distanceBody(body1: Body, body2: Body, out1: Vec2, out2: Vec2): number; /** * Calculate minimum distance between two shapes and return closest points. */ static distance(shape1: Shape, shape2: Shape, out1: Vec2, out2: Vec2): number; /** * Test if two bodies intersect (any of their shapes overlap). */ static intersectsBody(body1: Body, body2: Body): boolean; /** * Test if two shapes intersect. */ static intersects(shape1: Shape, shape2: Shape): boolean; /** * Test if shape1 fully contains shape2. */ static contains(shape1: Shape, shape2: Shape): boolean; } /** * A circular physics shape. The simplest and most performant collision shape. */ declare class Circle extends Shape { /** * Create a circle with the given radius and optional local centre-of-mass offset. * @param radius - Circle radius (must be > 0). * @param localCOM - Local centre offset (defaults to origin). * @param material - Material to assign (uses default if omitted). * @param filter - InteractionFilter to assign (uses default if omitted). */ constructor(radius?: number, localCOM?: Vec2, material?: Material, filter?: InteractionFilter); /** The circle's radius. Must be > 0. */ get radius(): number; set radius(value: number); } /** * A capsule physics shape — a line segment with a radius (stadium geometry). * * Internally backed by a convex polygon approximation for robust collision * detection. The polygon uses the engine's well-tested SAT narrowphase. * * - Total width = 2 * (halfLength + radius) * - Total height = 2 * radius * * @example * ```ts * const cap = new Capsule(100, 40); // width=100, height=40 * body.shapes.add(cap); * ``` */ declare class Capsule extends Shape { /** * Create a capsule with the given total width and height. * * @param width - Total width (tip to tip). Must be >= height. * @param height - Total height (diameter of the end-caps). Must be > 0. * @param localCOM - Local centre offset (defaults to origin). * @param material - Material to assign (uses default if omitted). * @param filter - InteractionFilter to assign (uses default if omitted). */ constructor(width?: number, height?: number, localCOM?: Vec2, material?: Material, filter?: InteractionFilter); /** Override type to return CAPSULE (internally backed by polygon). */ get type(): ShapeType; /** A capsule is not a plain polygon from the user's perspective. */ isPolygon(): boolean; /** A capsule identifies as capsule. */ isCapsule(): boolean; /** The capsule's end-cap radius (half the height). */ get radius(): number; set radius(value: number); /** Half the spine length. Total width = 2 * (halfLength + radius). */ get halfLength(): number; set halfLength(value: number); /** Total width of the capsule (tip to tip). */ get width(): number; /** Total height of the capsule (diameter of end-caps). */ get height(): number; } /** * Result of polygon shape validation. * * - `VALID` — shape is valid * - `DEGENERATE` — shape is degenerate (e.g., zero area) * - `CONCAVE` — shape is concave (must be convex) * - `SELF_INTERSECTING` — shape edges self-intersect * * Converted from nape-compiled.js lines 30760–30856. */ declare class ValidationResult { constructor(); static get VALID(): ValidationResult; static get DEGENERATE(): ValidationResult; static get CONCAVE(): ValidationResult; static get SELF_INTERSECTING(): ValidationResult; toString(): string; } /** * Represents a contact point between two colliding shapes. * * Contacts are pooled internally by the engine — they cannot be created directly. * Access contacts via `CollisionArbiter.contacts`. * * Fully modernized — wraps extracted ZPP_Contact directly. */ declare class Contact { constructor(); /** The collision arbiter this contact belongs to, or null. */ get arbiter(): CollisionArbiter | null; /** Penetration depth of this contact (positive = overlapping). */ get penetration(): number; /** World-space position of this contact point. */ get position(): Vec2; /** Whether this contact was newly created in the current step. */ get fresh(): boolean; /** Friction value for this contact. */ get friction(): number; /** * Normal impulse at this contact point. * @param body - If null, returns world-frame impulse. Otherwise returns * impulse on the given body (must be one of the two in contact). */ normalImpulse(body?: Body | null): Vec3; /** * Tangent impulse at this contact point. * @param body - If null, returns world-frame impulse. Otherwise returns * impulse on the given body. */ tangentImpulse(body?: Body | null): Vec3; /** * Rolling impulse at this contact point. * @param body - If null, returns total rolling impulse. Otherwise returns * rolling impulse on the given body. */ rollingImpulse(body?: Body | null): number; /** * Total impulse (normal + tangent + rolling) at this contact point. * @param body - If null, returns world-frame impulse. Otherwise returns * impulse on the given body. */ totalImpulse(body?: Body | null): Vec3; toString(): string; } /** * Composite callback option type — combines include and exclude {@link CbType} lists * to express complex listener filter conditions. * * An interaction satisfies an `OptionType` when the interactor has **at least one** * of the included types and **none** of the excluded types. * * @example * ```ts * // Listen only for bodies that are "enemy" but not "boss" * const filter = new OptionType(enemyType).excluding(bossType); * const listener = new BodyListener(CbEvent.WAKE, filter, (cb) => { ... }); * ``` * * Converted from nape-compiled.js lines 2647–2698. */ declare class OptionType { /** * Creates an `OptionType` optionally seeded with initial include/exclude entries. * * @param includes - Initial type(s) to include. * @param excludes - Initial type(s) to exclude. */ constructor(includes?: CbType | OptionType, excludes?: CbType | OptionType); /** Live list of `CbType`s that an interactor must have at least one of. */ get includes(): object; /** Live list of `CbType`s that an interactor must have none of. */ get excludes(): object; /** * Adds `includes` to the include list and returns `this` for chaining. * * @param includes - `CbType` or `OptionType` whose types should be added to includes. */ including(includes: CbType | OptionType): this; /** * Adds `excludes` to the exclude list and returns `this` for chaining. * * @param excludes - `CbType` or `OptionType` whose types should be added to excludes. */ excluding(excludes: CbType | OptionType): this; toString(): string; } /** * Callback type tag — used to label bodies, shapes, and constraints so that * listeners can selectively respond to interactions involving specific objects. * * Objects can carry multiple `CbType`s (via their `cbTypes` list). Listeners * match against those types using {@link OptionType} include/exclude filters, * or using the built-in singletons (`ANY_BODY`, `ANY_SHAPE`, etc.). * * @example * ```ts * const playerType = new CbType(); * body.cbTypes.add(playerType); * * const listener = new InteractionListener( * CbEvent.BEGIN, * InteractionType.COLLISION, * playerType, * null, * (cb) => { console.log('player hit something'); }, * ); * space.listeners.add(listener); * ``` * * Converted from nape-compiled.js lines 689–770. */ declare class CbType { constructor(); /** * Built-in type automatically assigned to every {@link Body}. * Use in listeners to respond to all bodies without a custom `CbType`. */ static get ANY_BODY(): CbType; /** * Built-in type automatically assigned to every {@link Constraint}. * * **Note:** Constraints do NOT automatically carry `ANY_CONSTRAINT` in their * `cbTypes` list — you must add a custom CbType manually if you want to filter * constraint events. */ static get ANY_CONSTRAINT(): CbType; /** * Built-in type automatically assigned to every {@link Shape}. * Use in listeners to respond to all shapes without a custom `CbType`. */ static get ANY_SHAPE(): CbType; /** * Built-in type automatically assigned to every {@link Compound}. * Use in listeners to respond to all compounds without a custom `CbType`. */ static get ANY_COMPOUND(): CbType; /** Unique numeric identifier for this `CbType` instance. */ get id(): number; /** * Arbitrary user data attached to this `CbType`. * * Lazily initialized to `{}` on first access. Use to store application-level * metadata associated with the type. */ get userData(): Record; /** * Live list of all interactors (bodies/shapes/compounds) currently tagged * with this `CbType`. Read-only. */ get interactors(): object; /** * Live list of all constraints currently tagged with this `CbType`. Read-only. */ get constraints(): object; /** * Creates a new {@link OptionType} that includes this type and also `includes`. * * Shorthand for `new OptionType(this).including(includes)`. * * @param includes - Additional `CbType` or `OptionType` to require. */ including(includes: CbType | OptionType): OptionType; /** * Creates a new {@link OptionType} that includes this type but excludes `excludes`. * * Shorthand for `new OptionType(this).excluding(excludes)`. * * @param excludes - `CbType` or `OptionType` to reject. */ excluding(excludes: CbType | OptionType): OptionType; toString(): string; } /** * Base class for all physics engine callback objects. * * Callback instances are created internally by the engine and passed to listener * handler functions. They must not be stored beyond the scope of the handler — * they are pooled and reused after the handler returns. * * Concrete subclasses: * - {@link BodyCallback} — passed to {@link BodyListener} handlers * - {@link ConstraintCallback} — passed to {@link ConstraintListener} handlers * - {@link InteractionCallback} — passed to {@link InteractionListener} handlers * - {@link PreCallback} — passed to {@link PreListener} handlers * * Converted from nape-compiled.js lines 212–238. */ declare class Callback { constructor(); /** The event type that caused this callback to fire (e.g., `CbEvent.BEGIN`). */ get event(): CbEvent; /** The listener that this callback was fired from. */ get listener(): Listener; toString(): string; } /** * Callback object passed to {@link BodyListener} handlers. * * Provides the body that triggered the event. Do not store this object beyond * the handler scope — it is pooled and reused. * * Converted from nape-compiled.js lines 239–261. */ declare class BodyCallback extends Callback { /** The body that woke or fell asleep. */ get body(): Body; toString(): string; } /** * BodyListener — Listens for body events (WAKE/SLEEP). * * Fully modernized from nape-compiled.js lines 434–515. */ /** * Listener for body lifecycle events. * * Fires when a body matching the `options` filter wakes or sleeps. * * Valid events: {@link CbEvent.WAKE}, {@link CbEvent.SLEEP}. * * @example * ```ts * const listener = new BodyListener( * CbEvent.WAKE, * CbType.ANY_BODY, * (cb) => { console.log(cb.body, 'woke up'); }, * ); * space.listeners.add(listener); * ``` * * Fully modernized from nape-compiled.js lines 434–515. */ declare class BodyListener extends Listener { /** * @param event - Must be `CbEvent.WAKE` or `CbEvent.SLEEP`. * @param options - `CbType` or `OptionType` filter, or `null` to match all bodies. * @param handler - Called with a {@link BodyCallback} each time the event fires. * @param precedence - Execution order relative to other listeners (higher = first). Default `0`. */ constructor(event: CbEvent, options: OptionType | CbType | null, handler: (cb: BodyCallback) => void, precedence?: number); /** * The filter used to match bodies. Returns an {@link OptionType} representing * the current include/exclude configuration. */ get options(): OptionType; set options(options: OptionType | CbType); /** The callback function invoked when the event fires. Cannot be set to null. */ get handler(): (cb: BodyCallback) => void; set handler(handler: (cb: BodyCallback) => void); } /** * Callback object passed to {@link InteractionListener} handlers. * * Provides both interactors and the list of active arbiters between them. * Do not store this object beyond the handler scope — it is pooled and reused. * * Converted from nape-compiled.js lines 1398–1445. */ declare class InteractionCallback extends Callback { /** The first interactor involved in the interaction. */ get int1(): Interactor; /** The second interactor involved in the interaction. */ get int2(): Interactor; /** * The list of arbiters currently active between `int1` and `int2`. * * For `ONGOING` callbacks, arbiters are valid for the entire step. * For `BEGIN`/`END` callbacks, the list reflects the state at the moment * the event fired. */ get arbiters(): object; toString(): string; } /** * InteractionListener — Listens for interaction events (BEGIN/END/ONGOING). * * Fully modernized from nape-compiled.js lines 659–1091. */ /** * Listener for interaction events between two interactors. * * Fires when two objects matching `options1` and `options2` start, continue, or * stop interacting with each other according to `interactionType`. * * Valid events: {@link CbEvent.BEGIN}, {@link CbEvent.ONGOING}, {@link CbEvent.END}. * * - `BEGIN` fires once when the interaction starts. * - `ONGOING` fires every simulation step while the interaction persists. * - `END` fires once when the interaction ends. * * @example * ```ts * const listener = new InteractionListener( * CbEvent.BEGIN, * InteractionType.COLLISION, * playerType, * groundType, * (cb) => { console.log('player landed'); }, * ); * space.listeners.add(listener); * ``` * * Fully modernized from nape-compiled.js lines 659–1091. */ declare class InteractionListener extends Listener { /** * @param event - Must be `CbEvent.BEGIN`, `CbEvent.ONGOING`, or `CbEvent.END`. * @param interactionType - The kind of interaction to listen for (COLLISION, SENSOR, FLUID, or ANY). * @param options1 - Filter for the first interactor, or `null` to match any. * @param options2 - Filter for the second interactor, or `null` to match any. * @param handler - Called with an {@link InteractionCallback} each time the event fires. * @param precedence - Execution order relative to other listeners (higher = first). Default `0`. */ constructor(event: CbEvent, interactionType: InteractionType, options1: OptionType | CbType | null, options2: OptionType | CbType | null, handler: (cb: InteractionCallback) => void, precedence?: number); /** Filter for the first interactor. Order between `options1`/`options2` does not matter. */ get options1(): OptionType; set options1(options1: OptionType | CbType); /** Filter for the second interactor. Order between `options1`/`options2` does not matter. */ get options2(): OptionType; set options2(options2: OptionType | CbType); /** The callback function invoked when the event fires. Cannot be set to null. */ get handler(): (cb: InteractionCallback) => void; set handler(handler: (cb: InteractionCallback) => void); /** The type of interaction this listener responds to (COLLISION, SENSOR, FLUID, or ANY). */ get interactionType(): InteractionType | null; set interactionType(interactionType: InteractionType | null); /** * When `true`, `ONGOING` callbacks are also fired while both interactors are sleeping. * Default is `false` (callbacks are suppressed when both are asleep). */ get allowSleepingCallbacks(): boolean; set allowSleepingCallbacks(value: boolean); } /** * Callback object passed to {@link ConstraintListener} handlers. * * Provides the constraint that triggered the event. Do not store this object * beyond the handler scope — it is pooled and reused. * * Converted from nape-compiled.js lines 1262–1292. */ declare class ConstraintCallback extends Callback { /** The constraint that woke, fell asleep, or broke. */ get constraint(): Constraint; toString(): string; } /** * ConstraintListener — Listens for constraint events (WAKE/SLEEP/BREAK). * * Fully modernized from nape-compiled.js lines 546–649. */ /** * Listener for constraint lifecycle events. * * Fires when a constraint matching the `options` filter wakes, sleeps, or breaks. * * Valid events: {@link CbEvent.WAKE}, {@link CbEvent.SLEEP}, {@link CbEvent.BREAK}. * * A `BREAK` event fires when the constraint exceeds its `maxForce` or `maxError` limit. * If `removeOnBreak` is `true` on the constraint it is also removed from the space. * * @example * ```ts * const listener = new ConstraintListener( * CbEvent.BREAK, * myConstraintType, * (cb) => { console.log(cb.constraint, 'broke!'); }, * ); * space.listeners.add(listener); * ``` * * Fully modernized from nape-compiled.js lines 546–649. */ declare class ConstraintListener extends Listener { /** * @param event - Must be `CbEvent.WAKE`, `CbEvent.SLEEP`, or `CbEvent.BREAK`. * @param options - `CbType` or `OptionType` filter, or `null` to match all constraints. * @param handler - Called with a {@link ConstraintCallback} each time the event fires. * @param precedence - Execution order relative to other listeners (higher = first). Default `0`. */ constructor(event: CbEvent, options: OptionType | CbType | null, handler: (cb: ConstraintCallback) => void, precedence?: number); /** * The filter used to match constraints. Returns an {@link OptionType} representing * the current include/exclude configuration. */ get options(): OptionType; set options(options: OptionType | CbType); /** The callback function invoked when the event fires. Cannot be set to null. */ get handler(): (cb: ConstraintCallback) => void; set handler(handler: (cb: ConstraintCallback) => void); } /** * Callback object passed to {@link PreListener} handlers. * * Provides both interactors, the arbiter, and a `swapped` flag indicating * whether the pair order was swapped relative to the listener's `options1`/`options2`. * * The handler should return a {@link PreFlag} to control the interaction. * Do not store this object beyond the handler scope — it is pooled and reused. * * Converted from nape-compiled.js lines 2590–2634. */ declare class PreCallback extends Callback { /** The arbiter representing the potential interaction. Use to inspect collision normal, etc. */ get arbiter(): Arbiter; /** The first interactor in the pair (matches `options1` unless `swapped` is `true`). */ get int1(): Interactor; /** The second interactor in the pair (matches `options2` unless `swapped` is `true`). */ get int2(): Interactor; /** * `true` when the pair order is swapped relative to the listener's `options1`/`options2`. * Check this if you need to know which interactor matched which filter. */ get swapped(): boolean; toString(): string; } /** * PreListener — Listens for pre-interaction events. * * Allows the handler to accept/ignore interactions before collision resolution. * * Fully modernized from nape-compiled.js lines 1142–1338. */ /** * Pre-interaction listener — called before collision resolution each step. * * The handler receives a {@link PreCallback} and returns a {@link PreFlag} that * determines whether and how the interaction should be processed: * - `PreFlag.ACCEPT` — resolve the interaction normally (default). * - `PreFlag.IGNORE` — suppress the interaction permanently (until next `BEGIN`). * - `PreFlag.ACCEPT_ONCE` — accept this step, then revert to default. * - `PreFlag.IGNORE_ONCE` — ignore this step only. * * Returning `null` is equivalent to `ACCEPT`. * * The event is always {@link CbEvent.PRE} and cannot be changed. * * **Pure mode**: when `pure` is `true` the engine caches the handler result and * does not re-invoke it until the result is reset. This is an optimisation — only * use `pure` when the handler will always return the same flag for a given pair. * * @example * ```ts * const preListener = new PreListener( * InteractionType.COLLISION, * playerType, * onewayPlatformType, * (cb) => cb.arbiter.collisionArbiter!.normal.y > 0 * ? PreFlag.ACCEPT * : PreFlag.IGNORE, * ); * space.listeners.add(preListener); * ``` * * Fully modernized from nape-compiled.js lines 1142–1338. */ declare class PreListener extends Listener { /** * @param interactionType - The kind of interaction to intercept (COLLISION, SENSOR, FLUID, or ANY). * @param options1 - Filter for the first interactor, or `null` to match any. * @param options2 - Filter for the second interactor, or `null` to match any. * @param handler - Called each step; return a {@link PreFlag} to control the interaction. * @param precedence - Execution order relative to other listeners (higher = first). Default `0`. * @param pure - Enable caching of the handler result. Default `false`. */ constructor(interactionType: InteractionType, options1: OptionType | CbType | null, options2: OptionType | CbType | null, handler: (cb: PreCallback) => PreFlag | null, precedence?: number, pure?: boolean); /** Filter for the first interactor. Order does not matter. */ get options1(): OptionType; set options1(options1: OptionType | CbType); /** Filter for the second interactor. Order does not matter. */ get options2(): OptionType; set options2(options2: OptionType | CbType); /** * The handler called before each collision resolution step. * Return a {@link PreFlag} to accept/ignore the interaction, or `null` to accept. */ get handler(): (cb: PreCallback) => PreFlag | null; set handler(handler: (cb: PreCallback) => PreFlag | null); /** * When `true`, the engine caches the handler return value and does not * re-invoke the handler until the cached result is invalidated. * * Only use `pure` mode when the handler always returns the same flag for a * given pair of interactors. Setting `pure` to `false` immediately invalidates * any cached result. */ get pure(): boolean; set pure(pure: boolean); /** The type of interaction this pre-listener intercepts (COLLISION, SENSOR, FLUID, or ANY). */ get interactionType(): InteractionType | null; set interactionType(interactionType: InteractionType | null); } /** * A pivot (pin) joint that constrains two anchor points — one on each body — to * remain coincident in world space. * * This is the most common joint type and is used to simulate hinges, pins, and * revolute connections between bodies. * * The constraint eliminates 2 translational degrees of freedom but leaves * rotation free. * * @example * ```ts * // Pin body2 to body1's local origin * const joint = new PivotJoint( * body1, body2, * Vec2.weak(0, 0), // anchor on body1 (local) * Vec2.weak(0, 0), // anchor on body2 (local) * ); * joint.space = space; * ``` * * Fully modernized — uses ZPP_PivotJoint directly (extracted to TypeScript). */ declare class PivotJoint extends Constraint { /** * @param body1 - First body, or `null` for a static world anchor. * @param body2 - Second body, or `null` for a static world anchor. * @param anchor1 - Anchor point in `body1`'s local space (disposed if weak). * @param anchor2 - Anchor point in `body2`'s local space (disposed if weak). */ constructor(body1: Body | null, body2: Body | null, anchor1: Vec2, anchor2: Vec2); /** First body. `null` treats the anchor as a static world point. */ get body1(): Body; set body1(value: Body | null); /** Second body. `null` treats the anchor as a static world point. */ get body2(): Body; set body2(value: Body | null); /** Anchor point on `body1` in local coordinates. */ get anchor1(): Vec2; set anchor1(value: Vec2); /** Anchor point on `body2` in local coordinates. */ get anchor2(): Vec2; set anchor2(value: Vec2); impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * Constrains the distance between two anchor points (one on each body) within a range. * * Enforces: `jointMin ≤ distance(anchor1, anchor2) ≤ jointMax` * * When `jointMin === jointMax` the distance is fixed (like a rigid rod). * When `jointMin < jointMax` the joint acts as a slack rope / elastic band. * * Anchors are specified in each body's local coordinate space. * * @example * ```ts * // Attach two bodies with a fixed-length rod * const joint = new DistanceJoint( * body1, body2, * Vec2.weak(0, 0), // anchor on body1 (local) * Vec2.weak(0, 0), // anchor on body2 (local) * 50, 50, // fixed distance * ); * joint.space = space; * ``` * * Fully modernized — uses ZPP_DistanceJoint directly (extracted to TypeScript). */ declare class DistanceJoint extends Constraint { /** * @param body1 - First body, or `null` for a static world anchor. * @param body2 - Second body, or `null` for a static world anchor. * @param anchor1 - Anchor point in `body1`'s local space (disposed if weak). * @param anchor2 - Anchor point in `body2`'s local space (disposed if weak). * @param jointMin - Minimum allowed distance (must be `>= 0`). * @param jointMax - Maximum allowed distance (must be `>= jointMin`). */ constructor(body1: Body | null, body2: Body | null, anchor1: Vec2, anchor2: Vec2, jointMin: number, jointMax: number); /** First body. `null` treats the anchor as a static world point. */ get body1(): Body; set body1(value: Body | null); /** Second body. `null` treats the anchor as a static world point. */ get body2(): Body; set body2(value: Body | null); /** Anchor point on `body1` in local coordinates. Modifying this wakes the constraint. */ get anchor1(): Vec2; set anchor1(value: Vec2); /** Anchor point on `body2` in local coordinates. Modifying this wakes the constraint. */ get anchor2(): Vec2; set anchor2(value: Vec2); /** Minimum allowed distance between anchors (pixels, must be `>= 0`). */ get jointMin(): number; set jointMin(value: number); /** Maximum allowed distance between anchors (pixels, must be `>= jointMin`). */ get jointMax(): number; set jointMax(value: number); /** * Returns `true` when the current distance is within `[jointMin, jointMax]` * and no corrective impulse was applied last step. * * @throws if either body is `null`. */ isSlack(): boolean; impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * Constrains the relative angle between two bodies within a range. * * The constraint enforces: * `jointMin ≤ body2.rotation - ratio * body1.rotation ≤ jointMax` * * When `jointMin === jointMax` the relative angle is fixed exactly. * When `jointMin < jointMax` the joint acts as a rotational limit. * * @example * ```ts * // Limit the angle between two bodies to ±45 degrees * const joint = new AngleJoint( * body1, body2, * -Math.PI / 4, // jointMin * Math.PI / 4, // jointMax * ); * joint.space = space; * ``` * * Fully modernized — uses ZPP_AngleJoint directly (extracted to TypeScript). */ declare class AngleJoint extends Constraint { /** * @param body1 - First body, or `null` for a static anchor. * @param body2 - Second body, or `null` for a static anchor. * @param jointMin - Minimum allowed relative angle (radians). * @param jointMax - Maximum allowed relative angle (radians). * @param ratio - Gear ratio applied to `body1`'s rotation. Default `1.0`. */ constructor(body1: Body | null, body2: Body | null, jointMin: number, jointMax: number, ratio?: number); /** First body in the constraint. Setting `null` treats it as a static world-anchored reference. */ get body1(): Body; set body1(value: Body | null); /** Second body in the constraint. Setting `null` treats it as a static world-anchored reference. */ get body2(): Body; set body2(value: Body | null); /** Minimum allowed relative angle in radians (`jointMin ≤ jointMax`). */ get jointMin(): number; set jointMin(value: number); /** Maximum allowed relative angle in radians (`jointMin ≤ jointMax`). */ get jointMax(): number; set jointMax(value: number); /** * Gear ratio applied to `body1`'s rotation. * * The constraint enforces `jointMin ≤ body2.rotation - ratio * body1.rotation ≤ jointMax`. * @defaultValue `1.0` */ get ratio(): number; set ratio(value: number); /** * Returns `true` when the current relative angle is within `[jointMin, jointMax]` * and no corrective impulse was applied last step. * * @throws if either body is `null`. */ isSlack(): boolean; impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * Weld joint — constrains two bodies to maintain a fixed relative position and * relative angle, effectively gluing them together while still treating them as * separate physics objects. * * The `phase` parameter sets the desired relative angle offset in radians. * When `phase = 0` both bodies maintain the angle difference they had at the * time the joint was created. * * @example * ```ts * const joint = new WeldJoint( * body1, body2, * Vec2.weak(10, 0), // attach point on body1 * Vec2.weak(-10, 0), // attach point on body2 * ); * joint.space = space; * ``` * * Fully modernized — uses ZPP_WeldJoint directly (extracted to TypeScript). */ declare class WeldJoint extends Constraint { /** * @param body1 - First body, or `null` for a static world anchor. * @param body2 - Second body, or `null` for a static world anchor. * @param anchor1 - Anchor point in `body1`'s local space (disposed if weak). * @param anchor2 - Anchor point in `body2`'s local space (disposed if weak). * @param phase - Target relative angle offset in radians. Default `0.0`. */ constructor(body1: Body | null, body2: Body | null, anchor1: Vec2, anchor2: Vec2, phase?: number); /** First body. `null` treats the anchor as a static world point. */ get body1(): Body; set body1(value: Body | null); /** Second body. `null` treats the anchor as a static world point. */ get body2(): Body; set body2(value: Body | null); /** Anchor point on `body1` in local coordinates. */ get anchor1(): Vec2; set anchor1(value: Vec2); /** Anchor point on `body2` in local coordinates. */ get anchor2(): Vec2; set anchor2(value: Vec2); /** * Target relative angle offset in radians. * `0` means both bodies maintain their original angle difference. * @defaultValue `0.0` */ get phase(): number; set phase(value: number); impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * Motor joint — drives the relative angular velocity between two bodies toward * a target `rate`, subject to `maxForce`. * * The motor enforces: `body2.angularVel - ratio * body1.angularVel → rate` * * This is a velocity-level constraint (not positional), so it does not enforce * a particular relative angle — it continuously applies torque to reach `rate`. * Use an {@link AngleJoint} in addition if you also need an angle limit. * * @example * ```ts * // Spin body2 at 2 rad/s relative to body1, limited to 500 N force * const motor = new MotorJoint(body1, body2, 2.0); * motor.maxForce = 500; * motor.space = space; * ``` * * Fully modernized — uses ZPP_MotorJoint directly (extracted to TypeScript). */ declare class MotorJoint extends Constraint { /** * @param body1 - First body, or `null` for a static world reference. * @param body2 - Second body, or `null` for a static world reference. * @param rate - Target relative angular velocity (rad/s). Default `0.0`. * @param ratio - Gear ratio applied to `body1`'s angular velocity. Default `1.0`. */ constructor(body1: Body | null, body2: Body | null, rate?: number, ratio?: number); /** First body (its angular velocity is scaled by `ratio`). */ get body1(): Body; set body1(value: Body | null); /** Second body (driven toward the target angular velocity). */ get body2(): Body; set body2(value: Body | null); /** * Target relative angular velocity in rad/s. * * Positive values rotate `body2` counter-clockwise relative to `body1`. * @defaultValue `0.0` */ get rate(): number; set rate(value: number); /** * Gear ratio applied to `body1`'s angular velocity. * * The motor drives: `body2.angularVel - ratio * body1.angularVel → rate` * @defaultValue `1.0` */ get ratio(): number; set ratio(value: number); impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * Line joint — constrains `body2`'s anchor to slide along a line defined by * `body1`'s anchor and `direction`, within `[jointMin, jointMax]`. * * The direction is specified in `body1`'s local space. The joint allows one * translational degree of freedom (along the line) and removes the other. * * @example * ```ts * // Allow body2 to slide vertically relative to body1 * const joint = new LineJoint( * body1, body2, * Vec2.weak(0, 0), // anchor on body1 * Vec2.weak(0, 0), // anchor on body2 * Vec2.weak(0, 1), // direction (local to body1) * -50, 50, // allowed travel range * ); * joint.space = space; * ``` * * Fully modernized — uses ZPP_LineJoint directly (extracted to TypeScript). */ declare class LineJoint extends Constraint { /** * @param body1 - First body (defines the line), or `null` for a static world line. * @param body2 - Second body (slides along the line), or `null` for a static anchor. * @param anchor1 - Origin of the line in `body1`'s local space (disposed if weak). * @param anchor2 - Anchor on `body2` in `body2`'s local space (disposed if weak). * @param direction - Direction of the line in `body1`'s local space (disposed if weak). * @param jointMin - Minimum allowed displacement along the line. * @param jointMax - Maximum allowed displacement along the line. */ constructor(body1: Body | null, body2: Body | null, anchor1: Vec2, anchor2: Vec2, direction: Vec2, jointMin: number, jointMax: number); /** Body that defines the line's origin and direction. */ get body1(): Body; set body1(value: Body | null); /** Body whose anchor slides along the line. */ get body2(): Body; set body2(value: Body | null); /** Origin of the line on `body1` in local coordinates. */ get anchor1(): Vec2; set anchor1(value: Vec2); /** Anchor point on `body2` in local coordinates. */ get anchor2(): Vec2; set anchor2(value: Vec2); /** Direction of the line in `body1`'s local space. Does not need to be normalised. */ get direction(): Vec2; set direction(value: Vec2); /** Minimum displacement along the line direction. */ get jointMin(): number; set jointMin(value: number); /** Maximum displacement along the line direction. */ get jointMax(): number; set jointMax(value: number); impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * Pulley joint — constrains the weighted sum of two distances to remain within * `[jointMin, jointMax]`: * * `jointMin ≤ distance(anchor1, anchor2) + ratio * distance(anchor3, anchor4) ≤ jointMax` * * This models a rope-and-pulley system where lifting one side lowers the other. * All four anchors are in the local space of their respective bodies. * * @example * ```ts * // Classic pulley: as body2 moves away from anchor1, body4 moves toward anchor3 * const joint = new PulleyJoint( * body1, body2, body3, body4, * Vec2.weak(0,0), Vec2.weak(0,0), * Vec2.weak(0,0), Vec2.weak(0,0), * 0, 200, // total rope length * ); * joint.space = space; * ``` * * Fully modernized — uses ZPP_PulleyJoint directly (extracted to TypeScript). */ declare class PulleyJoint extends Constraint { /** * @param body1 - First body (pulley side 1), or `null` for a static anchor. * @param body2 - Second body (pulley side 1), or `null` for a static anchor. * @param body3 - Third body (pulley side 2), or `null` for a static anchor. * @param body4 - Fourth body (pulley side 2), or `null` for a static anchor. * @param anchor1 - Anchor on `body1` in local space (disposed if weak). * @param anchor2 - Anchor on `body2` in local space (disposed if weak). * @param anchor3 - Anchor on `body3` in local space (disposed if weak). * @param anchor4 - Anchor on `body4` in local space (disposed if weak). * @param jointMin - Minimum allowed total rope length (must be `>= 0`). * @param jointMax - Maximum allowed total rope length (must be `>= jointMin`). * @param ratio - Weight of the second distance segment. Default `1.0`. */ constructor(body1: Body | null, body2: Body | null, body3: Body | null, body4: Body | null, anchor1: Vec2, anchor2: Vec2, anchor3: Vec2, anchor4: Vec2, jointMin: number, jointMax: number, ratio?: number); /** First body of the first rope segment. `null` = static world point. */ get body1(): Body; set body1(value: Body | null); /** Second body of the first rope segment. `null` = static world point. */ get body2(): Body; set body2(value: Body | null); /** First body of the second rope segment. `null` = static world point. */ get body3(): Body; set body3(value: Body | null); /** Second body of the second rope segment. `null` = static world point. */ get body4(): Body; set body4(value: Body | null); /** Anchor on `body1` in local coordinates. */ get anchor1(): Vec2; set anchor1(value: Vec2); /** Anchor on `body2` in local coordinates. */ get anchor2(): Vec2; set anchor2(value: Vec2); /** Anchor on `body3` in local coordinates. */ get anchor3(): Vec2; set anchor3(value: Vec2); /** Anchor on `body4` in local coordinates. */ get anchor4(): Vec2; set anchor4(value: Vec2); /** Minimum allowed total rope length (must be `>= 0`). */ get jointMin(): number; set jointMin(value: number); /** Maximum allowed total rope length (must be `>= jointMin`). */ get jointMax(): number; set jointMax(value: number); /** * Weight of the second rope segment in the total length sum. * * The constraint enforces: `distance(a1,a2) + ratio * distance(a3,a4)` within bounds. * @defaultValue `1.0` */ get ratio(): number; set ratio(value: number); /** * Returns `true` when the total rope length is within bounds and no corrective * impulse was applied last step. * * @throws if any of the four bodies is `null`. */ isSlack(): boolean; impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * A spring/damper constraint between two anchor points on two bodies. * * Applies a spring force that pulls or pushes the anchors toward a target * `restLength`. The spring behavior is controlled by `frequency` (Hz) and * `damping` (ratio), inherited from {@link Constraint}. * * Unlike {@link DistanceJoint}, a SpringJoint: * - Is **always soft** — there is no rigid/stiff mode. * - Has a single `restLength` instead of a `[jointMin, jointMax]` range. * - Applies force in **both directions** (compression and extension). * - Never goes slack — the spring always exerts a restorative force. * * Ideal for: vehicle suspension, soft-body connections, ragdoll hair/cloth, * bouncy UI animations, bridge/rope segments, trampolines. * * @example * ```ts * const spring = new SpringJoint( * body1, body2, * Vec2.weak(0, 0), // anchor on body1 (local) * Vec2.weak(0, 0), // anchor on body2 (local) * 100, // rest length (pixels) * ); * spring.frequency = 5; // 5 Hz oscillation * spring.damping = 0.5; // underdamped (bouncy) * spring.space = space; * ``` */ declare class SpringJoint extends Constraint { /** * @param body1 - First body, or `null` for a static world anchor. * @param body2 - Second body, or `null` for a static world anchor. * @param anchor1 - Anchor point in `body1`'s local space (disposed if weak). * @param anchor2 - Anchor point in `body2`'s local space (disposed if weak). * @param restLength - Equilibrium distance between anchors (must be `>= 0`). */ constructor(body1: Body | null, body2: Body | null, anchor1: Vec2, anchor2: Vec2, restLength: number); /** First body. `null` treats the anchor as a static world point. */ get body1(): Body; set body1(value: Body | null); /** Second body. `null` treats the anchor as a static world point. */ get body2(): Body; set body2(value: Body | null); /** Anchor point on `body1` in local coordinates. */ get anchor1(): Vec2; set anchor1(value: Vec2); /** Anchor point on `body2` in local coordinates. */ get anchor2(): Vec2; set anchor2(value: Vec2); /** Equilibrium distance between anchors (pixels, must be `>= 0`). */ get restLength(): number; set restLength(value: number); /** * SpringJoint is always soft — setting `stiff` to `true` is not allowed. * Use {@link DistanceJoint} if you need a rigid distance constraint. */ get stiff(): boolean; set stiff(_value: boolean); impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; } /** * ZPP_Constraint — Internal base class for all constraints / joints. * * Manages activation/deactivation, callback types, space integration, * and provides stubs for solver methods overridden by joint subclasses. * * Converted from nape-compiled.js lines 21424–21827. */ declare class ZPP_Constraint { /** * Namespace references, set by the compiled module after import. * _nape = the `nape` public namespace (for CbTypeIterator in copyto) * _zpp = the `zpp_nape` internal namespace (for ZNPList_*, ZPP_CbSet, etc.) */ static _nape: any; static _zpp: any; outer: any; id: number; userData: any; compound: any; space: any; active: boolean; stiff: boolean; frequency: number; damping: number; maxForce: number; maxError: number; breakUnderForce: boolean; breakUnderError: boolean; removeOnBreak: boolean; component: any; ignore: boolean; __velocity: boolean; cbTypes: any; cbSet: any; wrap_cbTypes: any; pre_dt: number; constructor(); /** * Initialise base constraint fields. * Extracted into a separate method because compiled joint subclasses * call `ZPP_Constraint.call(this)` — ES classes can't be invoked that * way, so the compiled wrapper delegates to this method instead. */ _initBase(): void; clear(): void; activeBodies(): void; inactiveBodies(): void; clearcache(): void; validate(): void; wake_connected(): void; forest(): void; broken(): void; warmStart(): void; draw(_g: any): void; pair_exists(_id: any, _di: any): boolean; preStep(_dt: number): boolean; applyImpulseVel(): boolean; applyImpulsePos(): boolean; copy(_dict?: any, _todo?: any): any; immutable_midstep(name: string): void; setupcbTypes(): void; immutable_cbTypes(): void; wrap_cbTypes_subber(pcb: any): void; wrap_cbTypes_adder(cb: any): boolean; insert_cbtype(cb: any): void; alloc_cbSet(): void; dealloc_cbSet(): void; activate(): void; deactivate(): void; addedToSpace(): void; removedFromSpace(): void; activeInSpace(): void; inactiveOrOutSpace(): void; wake(): void; copyto(ret: any): void; static _findRoot(comp: any): any; static _unionComponents(a: any, b: any): void; } /** * ZPP_UserBody — pairs a body with a reference count for user constraints. * * Converted from nape-compiled.js lines 28038–28054. */ declare class ZPP_UserBody { cnt: number; body: any; constructor(cnt: number, body: any); } /** * ZPP_UserConstraint — Internal N-DOF user-defined constraint with Cholesky decomposition. * * A generic constraint where the user supplies callbacks for effective mass, * velocity/position errors, impulse application, and clamping. The solver * factorises the effective-mass matrix via Cholesky (solve/transform) and * drives the constraint each step through warmStart / applyImpulseVel / * applyImpulsePos. * * Converted from nape-compiled.js lines 27368–28037. */ declare class ZPP_UserConstraint extends ZPP_Constraint { outer_zn: any; bodies: ZPP_UserBody[]; dim: number; jAcc: number[]; bias: number[]; stepped: boolean; L: number[]; y: number[]; soft: number; gamma: number; velonly: boolean; jMax: number; Keff: number[]; vec3: any; J: number[]; jOld: number[]; constructor(dim: number, velonly: boolean); bindVec2_invalidate(_: any): void; addBody(b: any): void; remBody(b: any): boolean; bodyImpulse(b: any): any; activeBodies(): void; inactiveBodies(): void; copy(_dict: any, _todo: any): any; validate(): void; wake_connected(): void; forest(): void; pair_exists(id: number, di: number): boolean; broken(): void; clearcache(): void; lsq(v: number[]): number; _clamp(v: number[], max: number): void; solve(m: number[]): number[]; transform(L: number[], x: number[]): void; preStep(dt: number): boolean; warmStart(): void; applyImpulseVel(): boolean; applyImpulsePos(): boolean; draw(g: any): void; } /** * Base class for user-defined N-DOF constraints. * * Fully modernized — uses ZPP_UserConstraint directly (extracted to TypeScript). * Subclass and override the abstract callback methods to define custom constraints. */ declare abstract class UserConstraint extends Constraint { zpp_inner: ZPP_UserConstraint; constructor(dimensions: number, velocityOnly?: boolean); __bindVec2(): Vec2; /** Create a copy of this constraint. Must be overridden. */ __copy(): UserConstraint; /** Called when the constraint breaks. Optional override. */ __broken(): void; /** Called to validate the constraint. Optional override. */ __validate(): void; /** Draw debug visualization. Optional override. */ __draw(_debug: any): void; /** Prepare the constraint for solving. Optional override. */ __prepare(): void; /** Compute positional error. Must be overridden for non-velocity-only constraints. */ __position(_err: number[]): void; /** Compute velocity error. Must be overridden. */ __velocity(_err: number[]): void; /** Compute effective mass matrix (upper triangle). Must be overridden. */ __eff_mass(_eff: number[]): void; /** Clamp accumulated impulse. Optional override. */ __clamp(_jAcc: number[]): void; /** Apply impulse to a body. Must be overridden. */ __impulse(_imp: number[], _body: Body, _out: any): void; impulse(): MatMN; bodyImpulse(body: Body): Vec3; visitBodies(lambda: (body: Body) => void): void; __invalidate(): void; __registerBody(oldBody: Body | null, newBody: Body | null): Body | null; } /** * Generic typed wrapper around Haxe list objects (BodyList, ShapeList, etc.). * * Provides a modern iterable interface with `for...of`, `length`, `at()`, etc. * * @deprecated The engine's list getters (e.g. `body.shapes`) now return the * cached typed list directly; this wrapper is kept for backwards * compatibility only and allocates on every construction. */ declare class NapeList implements Iterable { /** Number of elements in the list. */ get length(): number; /** Get element at index. */ at(index: number): T; /** Add an element to the list. */ add(item: T & { _inner?: NapeInner; }): void; /** Remove an element from the list. */ remove(item: T & { _inner?: NapeInner; }): void; /** Check if the list contains an element. */ has(item: T & { _inner?: NapeInner; }): boolean; /** Remove all elements. */ clear(): void; /** Whether the list is empty. */ get empty(): boolean; /** Push an element to the end. */ push(item: T & { _inner?: NapeInner; }): void; /** Pop the last element. */ pop(): T; /** Shift the first element. */ shift(): T; /** Unshift an element to the front. */ unshift(item: T & { _inner?: NapeInner; }): void; /** Iterate over all elements. */ [Symbol.iterator](): Iterator; /** Convert to a plain array. */ toArray(): T[]; /** Apply a function to each element. */ forEach(fn: (item: T, index: number) => void): void; toString(): string; } /** * Bitmask flags for controlling which elements are drawn by {@link DebugDraw}. * * Pass a combination of these flags (bitwise OR) to `Space.debugDraw()` to * select which layers are rendered. * * @example * ```ts * space.debugDraw(myDrawer, DebugDrawFlags.SHAPES | DebugDrawFlags.JOINTS); * ``` */ declare const DebugDrawFlags: { /** Draw shape outlines (circles and polygons). */ readonly SHAPES: number; /** Draw joint/constraint anchor points and connecting lines. */ readonly JOINTS: number; /** Draw contact points and normals. */ readonly CONTACTS: number; /** Draw broadphase axis-aligned bounding boxes. */ readonly AABB: number; /** Draw body centre-of-mass markers. */ readonly CENTER_OF_MASS: number; /** Draw body linear velocity vectors. */ readonly VELOCITIES: number; /** All flags combined. */ readonly ALL: number; }; type DebugDrawFlags = (typeof DebugDrawFlags)[keyof typeof DebugDrawFlags]; /** * Options for creating a body from concave polygon vertices. */ interface ConcaveBodyOptions { /** Body type — defaults to `BodyType.DYNAMIC`. */ type?: BodyType; /** Body position in world space — defaults to origin. */ position?: Vec2; /** Material applied to all decomposed shapes. */ material?: Material; /** Interaction filter applied to all decomposed shapes. */ filter?: InteractionFilter; /** Use Delaunay refinement for higher-quality triangulation. Default `false`. */ delaunay?: boolean; /** * Simplification tolerance (Ramer–Douglas–Peucker epsilon). * When > 0, vertices are simplified before decomposition. * Default `0` (no simplification). */ simplify?: number; } /** * Create a `Body` from concave (non-convex) polygon vertices. * * Accepts arbitrary simple polygon vertices (concave or convex), performs * validation, optional simplification, and convex decomposition via * `GeomPoly.convexDecomposition()`. Each convex partition becomes a * `Polygon` shape on the returned body. * * If the input is already convex, a single `Polygon` shape is created * (no decomposition overhead). * * @param vertices - Polygon vertices as `Vec2[]` or `GeomPoly`. Must form a * simple (non-self-intersecting) polygon with at least 3 vertices. * @param options - Optional configuration (body type, material, filter, etc.) * @returns A `Body` containing one or more convex `Polygon` shapes. * * @throws If vertices are null/undefined, fewer than 3, degenerate (zero area), * or self-intersecting. * * @example * ```ts * // L-shaped concave polygon * const body = createConcaveBody([ * Vec2.get(0, 0), Vec2.get(100, 0), Vec2.get(100, 50), * Vec2.get(50, 50), Vec2.get(50, 100), Vec2.get(0, 100), * ]); * body.space = space; * ``` */ declare function createConcaveBody(vertices: Vec2[] | GeomPoly, options?: ConcaveBodyOptions): Body; /** Result returned by {@link CharacterController.update}. */ interface MoveResult { /** True if the character is standing on a surface within slope limits. */ grounded: boolean; /** Ground surface normal, or null if not grounded. */ groundNormal: Vec2 | null; /** The body the character is standing on, or null. */ groundBody: Body | null; /** True if standing on a kinematic (moving) platform. */ onMovingPlatform: boolean; /** Angle of the ground slope in radians (0 = flat). */ slopeAngle: number; /** True if touching a wall on the left side. */ wallLeft: boolean; /** True if touching a wall on the right side. */ wallRight: boolean; /** Seconds since the character was last grounded (for coyote-time). */ timeSinceGrounded: number; } /** Configuration options for {@link CharacterController}. */ interface CharacterControllerOptions { /** * Maximum climbable slope angle in radians. * @default Math.PI / 4 (45 degrees) */ maxSlopeAngle?: number; /** * CbType for one-way platforms. When set, the controller automatically * creates a PreListener that ignores collisions when the character * approaches from below — matching the original nape Haxe engine pattern. */ oneWayPlatformTag?: CbType; /** * CbType assigned to the character body (required if oneWayPlatformTag is set). */ characterTag?: CbType; /** * InteractionFilter used for ground/wall detection raycasts. * When null, an auto-generated filter excluding the character's own * shapes is used. * @default null */ filter?: InteractionFilter | null; /** * World-space "down" direction used for ground / wall raycasts. * * Default `Vec2(0, 1)` matches a standard top-down-Y platformer (gravity * points along +Y). Override for radial-gravity / planet-platformer * scenarios — set `cc.down` each frame to the unit vector pointing from * the character toward whatever you treat as "down" (e.g. the nearest * planet's centre). Walls are detected perpendicular to this direction. * * The vector is normalized internally; magnitude is ignored. * * @default Vec2(0, 1) */ down?: Vec2; } /** * Velocity-based character controller for 2D platformers. * * The character uses a **dynamic body** whose velocity is set each frame. * Collision response (including one-way platforms) is handled entirely by * the physics engine via `space.step()` and `PreListener` callbacks — * matching the original nape Haxe engine pattern. * * The controller provides: * - Velocity application (`setVelocity`) * - Ground/slope detection (raycast queries) * - Wall detection (raycast queries) * - One-way platform support (auto-configured PreListener) * - Moving platform tracking * - Coyote time helper (`timeSinceGrounded`) * * @example * ```ts * const body = new Body(BodyType.DYNAMIC, new Vec2(100, 100)); * body.shapes.add(new Circle(14)); * body.allowRotation = false; * body.isBullet = true; * body.space = space; * * const cc = new CharacterController(space, body, { * maxSlopeAngle: Math.PI / 4, * oneWayPlatformTag: platformCbType, * characterTag: playerCbType, * }); * * // Each frame (before space.step): * cc.setVelocity(moveX, velY); * space.step(1/60); * const result = cc.update(); * if (result.grounded) velY = 0; * ``` */ declare class CharacterController { /** The physics space. */ readonly space: Space; /** The character body being controlled. */ readonly body: Body; private _maxSlopeAngle; private _maxSlopeCos; private _filter; private _downX; private _downY; private _oneWayListener; private _grounded; private _groundNormal; private _groundBody; private _onMovingPlatform; private _slopeAngle; private _wallLeft; private _wallRight; private _timeSinceGrounded; constructor(space: Space, body: Body, options?: CharacterControllerOptions); get grounded(): boolean; get groundNormal(): Vec2 | null; get groundBody(): Body | null; get timeSinceGrounded(): number; get maxSlopeAngle(): number; set maxSlopeAngle(v: number); /** * Current world-space "down" direction used for ground / wall raycasts. * Returns a fresh `Vec2` each call; mutate via {@link setDown} or by * assigning a new `Vec2` to this property. */ get down(): Vec2; set down(value: Vec2); /** Update the down direction from raw components (normalized internally). */ setDown(x: number, y: number): void; private _setDown; /** * Set the character body's velocity. Call this each frame **before** * `space.step()`. * * @param vx - Horizontal velocity (px/s). Set to 0 for no horizontal input. * @param vy - Vertical velocity (px/s). Typically includes gravity accumulation. */ setVelocity(vx: number, vy: number): void; /** * Query the character's state after `space.step()` has run. * Detects ground, walls, slope angle, moving platforms, etc. * * Call this each frame **after** `space.step()`. */ update(): MoveResult; /** * Remove the one-way platform PreListener and detach from space. */ destroy(): void; private _detectGround; private _detectWalls; private _setupOneWayPlatforms; private _getCharacterRadius; } /** Handler called when an interactor enters, stays in, or exits the zone. */ type TriggerHandler = (interactor: Interactor) => void; /** Configuration options for {@link TriggerZone}. */ interface TriggerZoneOptions { /** * Filter for which interactors trigger the zone. * When `null`, any interactor triggers the zone. * @default null */ filter?: CbType | OptionType | null; /** * The interaction type to listen for. * @default InteractionType.SENSOR */ interactionType?: InteractionType; /** Called once when an interactor enters the zone. */ onEnter?: TriggerHandler | null; /** Called every simulation step while an interactor remains in the zone. */ onStay?: TriggerHandler | null; /** Called once when an interactor exits the zone. */ onExit?: TriggerHandler | null; } /** * High-level trigger zone — Unity-style `onEnter`/`onStay`/`onExit` wrapper * over the nape callback system. * * Automatically creates sensor-based {@link InteractionListener}s and manages * their lifecycle. Assign handlers directly or via the constructor options. * * @example * ```ts * const zone = new TriggerZone(space, sensorBody, { * onEnter: (other) => console.log("entered!", other), * onStay: (other) => console.log("inside", other), * onExit: (other) => console.log("left!", other), * }); * * // Later: clean up all listeners * zone.dispose(); * ``` */ declare class TriggerZone { /** The CbType automatically created for this zone. */ readonly cbType: CbType; private _space; private _body; private _enabled; private _onEnter; private _onStay; private _onExit; private _enterListener; private _stayListener; private _exitListener; private _filter; private _interactionType; /** * @param space - The physics space to register listeners on. * @param body - The body acting as the trigger zone. Its shapes should have * `sensorEnabled = true` (the constructor enables it automatically * for all shapes that don't already have it set). * @param options - Optional configuration and initial handlers. */ constructor(space: Space, body: Body, options?: TriggerZoneOptions); /** Called once when an interactor enters the zone. */ get onEnter(): TriggerHandler | null; set onEnter(handler: TriggerHandler | null); /** Called every simulation step while an interactor remains in the zone. */ get onStay(): TriggerHandler | null; set onStay(handler: TriggerHandler | null); /** Called once when an interactor exits the zone. */ get onExit(): TriggerHandler | null; set onExit(handler: TriggerHandler | null); /** Whether the zone is active. When disabled, no callbacks fire. */ get enabled(): boolean; set enabled(value: boolean); /** The body acting as the trigger zone. */ get body(): Body; /** The space the zone is registered on. */ get space(): Space; /** * Remove all listeners from the space and untag the body. * After disposal, the TriggerZone should not be reused. */ dispose(): void; /** * Resolve which interactor is "the other" (not the zone body) from * the callback's int1/int2 pair. */ private _resolveOther; private _syncListeners; private _createListener; private _removeListener; } /** * Voronoi diagram generation for 2D point sets. * * Uses the half-plane intersection method: for each site, start with the * bounding box and clip by the perpendicular bisector against every other site. * This is O(n²) but perfectly robust for the fracture use-case (typically 4–30 * sites). Every cell is guaranteed to be a finite, closed, convex polygon. * * Designed for use in fracture/destruction systems. */ /** A 2D point (plain object — avoids coupling to Vec2 pooling). */ interface VoronoiPoint { x: number; y: number; } /** A single Voronoi cell: the site that generated it and its polygon vertices (CCW). */ interface VoronoiCell { /** The generating site index (into the original points array). */ siteIndex: number; /** The generating site coordinates. */ site: VoronoiPoint; /** Cell polygon vertices in counter-clockwise order. */ vertices: VoronoiPoint[]; } /** Result of a Voronoi diagram computation. */ interface VoronoiResult { /** One cell per input site, in the same order as the input points. */ cells: VoronoiCell[]; } /** * Compute the Voronoi diagram for a set of 2D points, clipped to a bounding box. * * Uses half-plane intersection: for each site, clips the bounding rectangle by * the perpendicular bisector with every other site. Produces one convex cell per * site, all cells together tile the bounding box exactly. * * @param points - Array of site points. Must contain at least 1 point. * @param bounds - Clipping rectangle `{ minX, minY, maxX, maxY }`. * @returns A `VoronoiResult` containing one cell per input point. * * @example * ```ts * const result = computeVoronoi( * [{ x: 10, y: 10 }, { x: 90, y: 50 }, { x: 50, y: 90 }], * { minX: 0, minY: 0, maxX: 100, maxY: 100 }, * ); * for (const cell of result.cells) { * console.log(`Site ${cell.siteIndex}:`, cell.vertices); * } * ``` */ declare function computeVoronoi(points: ReadonlyArray>, bounds: { minX: number; minY: number; maxX: number; maxY: number; }): VoronoiResult; /** * Generate random Voronoi fracture sites within a polygon. * * Places `count` random points inside the given polygon using rejection * sampling. Useful for generating fracture patterns. * * @param vertices - Polygon vertices (as VoronoiPoint[]). * @param count - Number of sites to generate. * @param random - Optional RNG function (default `Math.random`). * @returns Array of points guaranteed to be inside the polygon. */ declare function generateFractureSites(vertices: ReadonlyArray>, count: number, random?: () => number): VoronoiPoint[]; /** * Options for body fracture. */ interface FractureOptions { /** Number of fracture fragments to generate. Default `8`. */ fragmentCount?: number; /** * Material to apply to all fragment shapes. * If not specified, the first shape's material is copied from the original body. */ material?: Material; /** Interaction filter for all fragment shapes. */ filter?: InteractionFilter; /** * Explosion impulse magnitude applied radially from the impact point. * Default `0` (no explosion impulse — fragments inherit body velocity). */ explosionImpulse?: number; /** * Custom RNG function for reproducible fracture patterns. * Default `Math.random`. */ random?: () => number; /** * If true, automatically add fragments to the same space as the original body * and remove the original body. Default `true`. */ addToSpace?: boolean; /** * Custom Voronoi site positions (in body-local space). * If provided, `fragmentCount` is ignored and these exact sites are used. */ sites?: VoronoiPoint[]; } /** * Result of a fracture operation. */ interface FractureResult { /** The generated fragment bodies. */ fragments: Body[]; /** The original body that was fractured (removed from space if `addToSpace` is true). */ originalBody: Body; } /** * Fracture a body into multiple pieces using Voronoi decomposition. * * Takes the first polygon shape on the body, generates Voronoi cells within it, * clips each cell to the original polygon, and creates a new dynamic body for * each fragment. Fragments inherit the original body's linear and angular * velocity, plus an optional radial explosion impulse. * * @param body - The body to fracture. Must have at least one polygon shape. * @param impactPoint - World-space point where the fracture originates. * Used as the center bias for site generation and explosion impulse direction. * @param options - Fracture configuration. * @returns A `FractureResult` with the array of fragment bodies. * * @throws If the body has no polygon shapes. * * @example * ```ts * const result = fractureBody(box, Vec2.get(100, 50), { * fragmentCount: 12, * explosionImpulse: 200, * }); * // result.fragments are already in the space * ``` */ declare function fractureBody(body: Body, impactPoint: Vec2, options?: FractureOptions): FractureResult; /** A 2D row-major grid of tile values. `grid[y][x]` is the cell at row `y`, column `x`. */ type TilemapGrid = ArrayLike>; /** Predicate deciding whether a tile value at `(x, y)` represents a solid (collidable) cell. */ type TilemapSolidPredicate = (value: number, x: number, y: number) => boolean; /** * Strategy for combining adjacent solid tiles into fewer rectangles. * * - `"none"` — every solid tile becomes its own 1x1 rectangle * - `"rows"` — horizontally-adjacent solid tiles within a row are merged * - `"greedy"` — runs are extended both horizontally and vertically using a * greedy meshing pass (default — produces the fewest rectangles) */ type TilemapMergeMode = "none" | "rows" | "greedy"; /** Tile width and height in pixels. */ interface TilemapTileSize { /** Tile width in pixels. */ w: number; /** Tile height in pixels. */ h: number; } /** Configuration options for {@link buildTilemapBody} / {@link meshTilemap}. */ interface TilemapOptions { /** Tile size in pixels — either a square size or `{ w, h }` for non-square tiles. */ tileSize: number | TilemapTileSize; /** Body position (top-left corner of the map in world space). Default `(0, 0)`. */ position?: Vec2; /** * Predicate deciding which cells are solid. Default treats any non-zero * value as solid. */ solid?: TilemapSolidPredicate; /** Merge strategy — defaults to `"greedy"`. */ merge?: TilemapMergeMode; /** Material applied to every generated shape. */ material?: Material; /** InteractionFilter applied to every generated shape. */ filter?: InteractionFilter; /** CbTypes added to every generated shape. */ cbTypes?: CbType[]; /** Body type — defaults to `BodyType.STATIC`. Ignored when `body` is provided. */ bodyType?: BodyType; /** * Append shapes to this existing body instead of creating a new one. Useful * for chunked maps where many tilemap layers share one body, or when adding * a collision mesh to a body that already exists. */ body?: Body; } /** A rectangle in tile coordinates produced by {@link meshTilemap}. */ interface TilemapRect { /** Tile column of the rectangle's left edge. */ x: number; /** Tile row of the rectangle's top edge. */ y: number; /** Width in tiles (>= 1). */ w: number; /** Height in tiles (>= 1). */ h: number; } /** Subset of a Tiled JSON tile layer needed for grid extraction. */ interface TiledTileLayer { /** Flat row-major array of tile GIDs. */ data: ArrayLike; /** Width of the layer in tiles. */ width: number; /** Height of the layer in tiles. */ height: number; } /** Subset of an LDtk IntGrid layer instance needed for grid extraction. */ interface LDtkIntGridLayer { /** Row-major flat array of int values (0 = empty). */ intGridCsv: ArrayLike; /** Cell-grid width (LDtk's `__cWid`, falls back to `cWid`). */ __cWid?: number; /** Cell-grid height (LDtk's `__cHei`, falls back to `cHei`). */ __cHei?: number; /** Alternate width key. */ cWid?: number; /** Alternate height key. */ cHei?: number; } /** * Convert a 2D grid of tile values into the minimal set of axis-aligned * rectangles that cover the solid cells, using the requested merge strategy. * * The result is geometry-only — no `Body` or `Shape` is created — which makes * this function reusable for debug overlays, rendering, or custom body * construction. {@link buildTilemapBody} is a thin wrapper that converts the * rectangles to `Polygon` shapes. * * Greedy meshing reduces shape count dramatically for typical platformer * maps. A 50-tile floor strip becomes one rectangle; a solid 10x10 block * becomes one rectangle instead of 100. Fewer shapes = smaller broadphase * footprint, faster narrowphase, less debug-draw work. * * @param grid - 2D row-major tile grid (`grid[y][x]`). * @param options - Optional `solid` predicate and `merge` strategy. * @returns The list of merged rectangles in tile coordinates. * * @example * ```ts * const rects = meshTilemap([ * [1, 1, 1, 0, 1], * [1, 1, 1, 0, 1], * [0, 0, 0, 0, 1], * ]); * // -> [{x:0,y:0,w:3,h:2}, {x:4,y:0,w:1,h:3}] * ``` */ declare function meshTilemap(grid: TilemapGrid, options?: { solid?: TilemapSolidPredicate; merge?: TilemapMergeMode; }): TilemapRect[]; /** * Build a single physics `Body` from a 2D tile grid. * * Each merged rectangle becomes a `Polygon` shape on the body. Tiles are laid * out with `(0, 0)` at the top-left of tile `(0, 0)`, so the body's * `position` (defaults to origin) is the world-space location of that * top-left corner. * * Defaults to `BodyType.STATIC` and `merge: "greedy"`, matching the most * common gamedev use case (level collision geometry from a Tiled / LDtk map). * * @param grid - 2D row-major tile grid. * @param options - Tile size + body / shape options. * @returns A `Body` containing one `Polygon` shape per merged rectangle. * The returned body is not yet attached to a `Space` — set `body.space` * to insert it. * * @example * ```ts * const grid = [ * [1, 1, 1, 0, 1, 1, 1], * [0, 0, 0, 0, 0, 0, 0], * [1, 1, 1, 1, 1, 1, 1], * ]; * const body = buildTilemapBody(grid, { tileSize: 32, position: Vec2.get(0, 100) }); * body.space = space; * ``` */ declare function buildTilemapBody(grid: TilemapGrid, options: TilemapOptions): Body; /** * Convert a Tiled JSON tile layer into a 2D row-major grid suitable for * {@link meshTilemap} / {@link buildTilemapBody}. * * Only the `data`, `width`, and `height` fields of the layer are read — the * helper has no runtime dependency on a Tiled SDK and accepts any object with * that shape. * * @param layer - A Tiled tile layer (e.g. `map.layers[i]` from a Tiled JSON export). * @returns A 2D number array, with `0` representing empty tiles. */ declare function tiledLayerToGrid(layer: TiledTileLayer): number[][]; /** * Convert an LDtk IntGrid layer instance into a 2D row-major grid. * * Reads `intGridCsv` plus the cell-dimension fields (`__cWid` / `__cHei`, * with `cWid` / `cHei` accepted as fallbacks). LDtk uses `0` for empty * IntGrid cells, which the default `solid` predicate already treats as * non-solid. * * @param layer - An LDtk IntGrid layer instance. * @returns A 2D number array, with `0` representing empty cells. */ declare function ldtkLayerToGrid(layer: LDtkIntGridLayer): number[][]; /** * Falloff law for {@link RadialGravityField}. * * - `"inverse-square"` — `F = strength / d²` (Newtonian gravity, default) * - `"inverse"` — `F = strength / d` (line-source gravity) * - `"constant"` — `F = strength` (constant pull regardless of distance) * - `(distance) => number` — custom multiplier, applied as `F = strength * fn(d)` */ type GravityFalloff = "inverse-square" | "inverse" | "constant" | ((distance: number) => number); /** Per-body filter — `false` skips the body. */ type BodyFilter = (body: Body) => boolean; /** Configuration options for {@link RadialGravityField}. */ interface RadialGravityFieldOptions { /** * The field's anchor point. May be a `Vec2` (fixed world position — captured * by reference, so mutating it after construction moves the field), or a * `Body` (the field tracks `body.position` automatically each step). */ source: Vec2 | Body; /** Field strength scaling — units depend on `falloff` (see {@link GravityFalloff}). */ strength: number; /** Falloff law. @default `"inverse-square"` */ falloff?: GravityFalloff; /** Multiply the resulting force by `body.mass` (Newtonian gravity). @default `true` */ scaleByMass?: boolean; /** * Bodies farther than this from the source receive zero force — useful for * bounded gravity wells with hard edges. * @default `Infinity` */ maxRadius?: number; /** * Distance values used in the falloff calculation are clamped to be at * least this — prevents singularities at the source center. * @default `1` */ minRadius?: number; /** * Softening epsilon added to `d²` for the inverse-square falloff (smooths * out near-source spikes without disabling the pull). Has no effect on * other falloff laws. * @default `0` */ softening?: number; /** * Predicate deciding which bodies the field affects. `null` (default) * means "all dynamic bodies". Static and kinematic bodies are always * skipped (forces have no effect on them anyway). * @default `null` */ bodyFilter?: BodyFilter | null; /** When `false`, calls to {@link RadialGravityField.apply} are no-ops. @default `true` */ enabled?: boolean; } /** * A point-source gravity field — pulls bodies toward an anchor with a chosen * falloff law. * * Replaces the manual `for (body of space.bodies) body.force = ...` loops * commonly written for orbital / planet / multi-body gravity scenarios. * Multiple fields compose naturally via {@link RadialGravityFieldGroup} or * by calling `apply()` on each one in sequence — each call **adds** to the * existing accumulated force, so userland `body.force` writes are preserved. * * @example * ```ts * // Mario-Galaxy-style planet pulling everything toward its center * const planet = new Body(BodyType.STATIC, new Vec2(400, 300)); * planet.shapes.add(new Circle(40)); * planet.space = space; * * const field = new RadialGravityField({ * source: planet, * strength: 800000, * maxRadius: 250, * softening: 100, * }); * * // Each frame, BEFORE space.step(): * field.apply(space); * space.step(1 / 60); * ``` */ declare class RadialGravityField { source: Vec2 | Body; strength: number; falloff: GravityFalloff; scaleByMass: boolean; maxRadius: number; minRadius: number; softening: number; bodyFilter: BodyFilter | null; enabled: boolean; constructor(options: RadialGravityFieldOptions); /** * Current world-space center of the field. * * Returns the anchor's `(x, y)` — for a `Body` source this reflects the * body's current position each call, so the field automatically tracks * a moving anchor. */ getPosition(): { x: number; y: number; }; /** * Compute (but do not apply) the force this field would exert on `body` * given its current position. Returns `(0, 0)` when the field is disabled, * the body is static, the body is filtered out, or the body is outside * `maxRadius`. * * The returned `Vec2` is fresh and owned by the caller. */ forceOn(body: Body): Vec2; /** * Add this field's force contribution to every eligible body in `space`. * * Adds to (does not replace) each body's existing accumulated force, so * multiple fields and userland force writes all stack naturally. Call * once per frame, before `space.step()`. */ apply(space: Space): void; } /** * A composable collection of {@link RadialGravityField} instances. Calling * `apply()` runs every member field once — convenient for multi-source * scenarios (binary stars, three-body, planet platformers). */ declare class RadialGravityFieldGroup { /** Ordered list of fields. Mutate via {@link add} / {@link remove}. */ readonly fields: RadialGravityField[]; /** Add a field to the group and return it. */ add(field: RadialGravityField): RadialGravityField; /** Remove a field from the group. Returns `true` if it was present. */ remove(field: RadialGravityField): boolean; /** Remove all fields. */ clear(): void; /** Number of fields currently in the group. */ get length(): number; /** * Apply every field's force contribution to all eligible bodies in `space`. * Forces stack additively, preserving any userland `body.force` writes. */ apply(space: Space): void; } /** Shape used for each spawned particle body. */ type ParticleShape = "circle" | "polygon"; /** State snapshot passed to the `onSpawn` hook. */ interface ParticleSpawnState { /** World-space spawn position. */ position: Vec2; /** Initial linear velocity. */ velocity: Vec2; /** Initial rotation (rad). */ angle: number; /** Initial angular velocity (rad/s). */ angularVelocity: number; /** Lifetime in seconds. `<= 0` disables auto-death. */ lifetime: number; /** Free-form per-particle payload (color, frame index, damage, etc.). */ userData: unknown; } /** * Spawn-position pattern. Position is sampled once per particle, in * emitter-local space (relative to {@link ParticleEmitter.origin}), then * translated into world space. * * - `point` — always at the origin. * - `rect` — uniform inside an axis-aligned rectangle centred on the origin. * - `circle` — uniform inside a disk; `hollow: true` samples the rim only. * - `arc` — on the rim of a circular arc, `angle*` in radians. * - `custom` — user-provided sampler. Receives the emitter's RNG. */ type SpawnPattern = { kind: "point"; } | { kind: "rect"; width: number; height: number; } | { kind: "circle"; radius: number; hollow?: boolean; } | { kind: "arc"; radius: number; angleStart: number; angleEnd: number; } | { kind: "custom"; sample: (rng: () => number) => Vec2; }; /** * Initial-velocity pattern. The local spawn position is passed to the * `radial` and `custom` samplers so the velocity can depend on where the * particle was spawned (radial = "outward from origin"). * * - `fixed` — every particle gets the same velocity vector. * - `cone` — uniformly random direction inside a cone of half-width * `spread` rad, centred on `direction` rad. Speed uniform in * `[speedMin, speedMax]`. * - `radial` — outward from the spawn point relative to the origin. * Speed uniform in `[speedMin, speedMax]`. If the spawn point is exactly * at the origin, falls back to a random direction. * - `custom` — user-provided sampler. Receives RNG and the local spawn * position. */ type VelocityPattern = { kind: "fixed"; value: Vec2; } | { kind: "cone"; direction: number; spread: number; speedMin: number; speedMax: number; } | { kind: "radial"; speedMin: number; speedMax: number; } | { kind: "custom"; sample: (rng: () => number, localPos: Vec2) => Vec2; }; /** * What to do when {@link ParticleEmitterOptions.maxParticles} is full and a * new spawn is requested. * * - `drop-oldest` (default) — kill the oldest live particle to make room for * the new one. Keeps emitter responsive (e.g. bullets always come out). * - `drop-new` — silently drop the new spawn. Protects already-visible * particles from churn. */ type ParticleOverflowPolicy = "drop-oldest" | "drop-new"; /** Reason a particle was killed, passed to `onDeath`. */ type ParticleDeathReason = "lifetime" | "manual" | "bounds"; /** World-space rectangle outside which particles auto-die. */ interface ParticleBounds { x: number; y: number; w: number; h: number; } /** Configuration options for {@link ParticleEmitter}. */ interface ParticleEmitterOptions { /** Space the emitted particle bodies live in. Required. */ space: Space; /** * Spawn anchor. A `Vec2` is captured by reference (mutating it after * construction moves the emitter); a `Body` is tracked by position each * spawn (the body does not need to be in the same space). Required. */ origin: Vec2 | Body; /** Spawn-position pattern. @default `{ kind: "point" }` */ spawn?: SpawnPattern; /** Initial velocity pattern. @default `{ kind: "fixed", value: (0, 0) }` */ velocity?: VelocityPattern; /** * Continuous spawn rate in particles/second. Accumulated across `update()` * calls — fractional rates work. `0` disables continuous spawning (use * {@link ParticleEmitter.emit} for manual bursts). * @default `0` */ rate?: number; /** * Periodic-burst count (particles per burst). Combined with * `burstInterval`, fires a burst every `burstInterval` seconds. * @default `0` */ burstCount?: number; /** * Period of automatic bursts in seconds. Has no effect when `burstCount` * is `0`. * @default `0` */ burstInterval?: number; /** * Maximum simultaneously alive particles. The pool size is capped at this * value too. @default `512` */ maxParticles?: number; /** Lifetime range minimum (s). @default `1` */ lifetimeMin?: number; /** Lifetime range maximum (s). @default `1` */ lifetimeMax?: number; /** Body shape for each particle. @default `"circle"` */ particleShape?: ParticleShape; /** Radius for circle particles. Ignored for polygon. @default `2` */ particleRadius?: number; /** * Polygon vertices in body-local space (used when * `particleShape: "polygon"`). Defaults to a small square. */ particlePolygon?: Vec2[]; /** Material applied to every particle shape. @default `new Material()` */ particleMaterial?: Material; /** * Filter applied to every particle shape. If omitted and `selfCollision` * is `false`, the emitter generates a self-excluding filter automatically. */ particleFilter?: InteractionFilter; /** * Collision-callback type tagged on every particle body. Required for * `onCollide` to fire. The emitter never auto-creates one — pass your own * if you need it (so multiple emitters can share a type, or a single * emitter can match a user-defined cbType). */ particleCbType?: CbType; /** Whether particles can rotate. @default `true` */ allowRotation?: boolean; /** * When `false` and no explicit `particleFilter` is given, particles * receive a generated filter that skips its own group — particles in the * same emitter never collide with each other. Has no effect when * `particleFilter` is provided. @default `false` */ selfCollision?: boolean; /** Policy when `maxParticles` is reached. @default `"drop-oldest"` */ overflowPolicy?: ParticleOverflowPolicy; /** Optional world-space bounds — particles outside die instantly. */ bounds?: ParticleBounds; /** * Deterministic RNG. All emitter randomness (spawn jitter, velocity cone, * lifetime sampling) flows through this. @default `Math.random` */ random?: () => number; /** Whether the emitter is active. @default `true` */ enabled?: boolean; /** Fired once per spawn, after the body is in the space. */ onSpawn?: (state: ParticleSpawnState, body: Body) => void; /** Fired every `update()` for each live particle (ages > 0). */ onUpdate?: (body: Body, age: number, dt: number) => void; /** Fired when a particle dies (lifetime, bounds, manual, or `killAll`). */ onDeath?: (body: Body, reason: ParticleDeathReason) => void; /** * Fired when a particle's body collides with another body. Requires * `particleCbType` to be set. The handler runs from inside a Space * callback — do not mutate the space synchronously; use * {@link ParticleEmitter.requestKill} for deferred cleanup. */ onCollide?: (body: Body, other: Body) => void; } /** * Physics-aware particle emitter — a pooled, lifecycle-managed swarm of * dynamic bodies. Each particle is a real {@link Body} with a {@link Circle} * or {@link Polygon} shape, so it collides with the world, reacts to * gravity / fluids / forces, and triggers callbacks like any other body. * * @example * ```ts * // Volcano: emit lava drops upward in a 40-deg cone. * const volcano = new ParticleEmitter({ * space, * origin: new Vec2(400, 100), * velocity: { * kind: "cone", * direction: -Math.PI / 2, * spread: Math.PI / 9, * speedMin: 350, * speedMax: 600, * }, * rate: 80, * lifetimeMin: 4, * lifetimeMax: 8, * particleRadius: 3, * maxParticles: 600, * }); * * // Each frame, before space.step(): * volcano.update(1 / 60); * space.step(1 / 60); * ``` */ declare class ParticleEmitter { enabled: boolean; origin: Vec2 | Body; spawn: SpawnPattern; velocity: VelocityPattern; rate: number; burstCount: number; burstInterval: number; maxParticles: number; lifetimeMin: number; lifetimeMax: number; allowRotation: boolean; overflowPolicy: ParticleOverflowPolicy; bounds: ParticleBounds | null; onSpawn: ((state: ParticleSpawnState, body: Body) => void) | null; onUpdate: ((body: Body, age: number, dt: number) => void) | null; onDeath: ((body: Body, reason: ParticleDeathReason) => void) | null; onCollide: ((body: Body, other: Body) => void) | null; readonly space: Space; readonly particleShape: ParticleShape; readonly particleRadius: number; readonly particlePolygon: Vec2[] | null; readonly particleMaterial: Material; readonly particleFilter: InteractionFilter; readonly particleCbType: CbType | null; readonly random: () => number; private _alive; /** * Body -> its index in `_alive`, kept in sync by `_spawnOne` / `_killAt`. * Turns the per-collision membership tests and `_flushKillSet` lookups from * O(alive) scans into O(1) hits. */ private _aliveIndex; private _ages; private _lifetimes; private _pool; private _totalSpawned; private _rateAccumulator; private _burstAccumulator; private _killSet; private _listener; private _destroyed; constructor(options: ParticleEmitterOptions); /** Live particle bodies. Read-only — do not mutate. */ get active(): ReadonlyArray; /** * Per-particle age in seconds, indexed parallel to {@link active}. * Read-only — do not mutate. Useful for renderers that fade particles * by `age / lifetime`. */ get ages(): ReadonlyArray; /** * Per-particle lifetime in seconds, indexed parallel to {@link active}. * Read-only — do not mutate. */ get lifetimes(): ReadonlyArray; /** Number of bodies currently in the recycle pool. */ get poolSize(): number; /** Total spawn count over the lifetime of this emitter. */ get totalSpawned(): number; private _originXY; /** Sample a position in emitter-local space. */ private _sampleSpawn; /** Sample initial velocity given the local spawn position. */ private _sampleVelocity; private _sampleLifetime; private _buildBody; /** Take a body out of the pool, or build a new one. */ private _acquire; /** * Reset a body's per-life mutable state and add it to the space at the * given world position with the given velocity. */ private _reviveBody; /** * Spawn `count` particles immediately. Returns the live bodies that were * spawned (length may be < `count` when the emitter is full and * `overflowPolicy` is `"drop-new"`). */ emit(count: number): Body[]; /** Spawn a single particle. Returns the body or `null` if dropped. */ private _spawnOne; /** Remove the live particle at `index` (swap-pop) and return it to the pool. */ private _killAt; /** * Mark a body for death at the start of the next `update()` call. Safe to * call from inside collision callbacks. No-op if the body is not a live * particle of this emitter. */ requestKill(body: Body): void; private _flushKillSet; /** Kill every live particle. Bodies return to the pool. */ killAll(): void; /** * Advance lifetimes, fire `onUpdate`, kill expired / out-of-bounds * particles, and run continuous / periodic spawning. * * Call once per frame, **before** `space.step()`. `dt` should match the * step size you'll pass to `space.step()`. */ update(dt: number): void; /** * Remove every body (live + pooled) from the space, drop the listener, * and mark the emitter unusable. Subsequent `update` / `emit` calls * throw. */ destroy(): void; private _installCollisionListener; } /** * Composable collection of {@link ParticleEmitter}s — analogous to * {@link RadialGravityFieldGroup}. One `update(dt)` runs every member emitter. */ declare class ParticleEmitterGroup { /** Ordered list of emitters. Mutate via {@link add} / {@link remove}. */ readonly emitters: ParticleEmitter[]; /** Add an emitter to the group and return it. */ add(emitter: ParticleEmitter): ParticleEmitter; /** Remove an emitter from the group. Returns `true` if it was present. */ remove(emitter: ParticleEmitter): boolean; /** Remove all emitters (does NOT call `destroy` on them). */ clear(): void; /** Number of emitters currently in the group. */ get length(): number; /** Advance every emitter. */ update(dt: number): void; /** Call `destroy()` on every emitter and clear the group. */ destroyAll(): void; } declare const VERSION: string; export { AABB, AngleJoint, Arbiter, Body, BodyCallback, type BodyFilter, BodyListener, BodyType, Callback, Capsule, CbEvent, CbType, CharacterController, type CharacterControllerOptions, Circle, CollisionArbiter, type ConcaveBodyOptions, Constraint, ConstraintCallback, ConstraintListener, Contact, DebugDrawFlags, DistanceJoint, type FractureOptions, type FractureResult, Geom, GeomPoly, type GravityFalloff, InteractionCallback, InteractionFilter, InteractionListener, InteractionType, Interactor, type LDtkIntGridLayer, LineJoint, Listener, MarchingSquares, MatMN, Material, MotorJoint, type MoveResult, NapeList, OptionType, type ParticleBounds, type ParticleDeathReason, ParticleEmitter, ParticleEmitterGroup, type ParticleEmitterOptions, type ParticleOverflowPolicy, type ParticleShape, type ParticleSpawnState, PivotJoint, PreCallback, PreFlag, PreListener, PulleyJoint, RadialGravityField, RadialGravityFieldGroup, type RadialGravityFieldOptions, Shape, ShapeType, Space, type SpawnPattern, SpringJoint, type TiledTileLayer, type TilemapGrid, type TilemapMergeMode, type TilemapOptions, type TilemapRect, type TilemapSolidPredicate, type TilemapTileSize, type TriggerHandler, TriggerZone, type TriggerZoneOptions, UserConstraint, VERSION, ValidationResult, Vec2, Vec3, type VelocityPattern, type VoronoiCell, type VoronoiPoint, type VoronoiResult, WeldJoint, Winding, buildTilemapBody, computeVoronoi, createConcaveBody, fractureBody, generateFractureSites, ldtkLayerToGrid, meshTilemap, tiledLayerToGrid };