ecspresso
Version:
A minimal Entity-Component-System library for typescript and javascript.
141 lines (140 loc) • 5.65 kB
TypeScript
/**
* Physics 3D Plugin for ECSpresso
*
* Provides ECS-native 3D arcade physics: gravity, forces, drag, semi-implicit Euler
* integration, and impulse-based collision response with friction.
*
* Reuses RigidBody and collider types from the 2D physics/collision plugins for
* shape definitions. Has its own collision detection in fixedUpdate for physics
* response; the existing collision3D plugin can still run in postUpdate for game
* logic events.
*/
import type { SystemPhase } from 'ecspresso';
import type { Vector3D } from 'ecspresso';
import type { Transform3DWorldConfig } from '../spatial/transform3D';
import type { Collision3DComponentTypes, LayerFactories } from './collision3D';
import type { RigidBody, BodyType, RigidBodyOptions } from './physics2D';
export type { RigidBody, BodyType, RigidBodyOptions };
/**
* Component types directly provided by the physics3D plugin.
*/
export interface Physics3DOwnComponentTypes {
rigidBody3D: RigidBody;
velocity3D: Vector3D;
force3D: Vector3D;
}
/**
* Full component types available when using the physics3D plugin
* (own components + transform + collision dependencies).
* Convenience alias for consumer code.
*/
export interface Physics3DComponentTypes<L extends string = never> extends Collision3DComponentTypes<L>, Physics3DOwnComponentTypes {
}
/**
* Physics 3D configuration resource.
*/
export interface Physics3DConfig {
gravity: Vector3D;
}
export interface Physics3DResourceTypes {
physics3DConfig: Physics3DConfig;
}
/**
* Event emitted for each physics 3D collision pair.
*
* Normal components are flattened (`normalX`/`normalY`/`normalZ`) rather than
* nested in a `Vector3D` to avoid a per-event allocation in the physics hot path.
*/
export interface Physics3DCollisionEvent {
entityA: number;
entityB: number;
/** Unit normal X, pointing from A toward B */
normalX: number;
/** Unit normal Y, pointing from A toward B */
normalY: number;
/** Unit normal Z, pointing from A toward B */
normalZ: number;
/** Penetration depth (positive) */
depth: number;
}
export interface Physics3DEventTypes {
physics3DCollision: Physics3DCollisionEvent;
}
export interface Physics3DPluginOptions<G extends string = 'physics3D', CG extends string = never> {
/** World gravity vector (default: {x: 0, y: 0, z: 0}) */
gravity?: Vector3D;
/** System group name (default: 'physics3D') */
systemGroup?: G;
/** Additional group for the collision system only (default: none).
* When set, the collision system belongs to both `systemGroup` and this group,
* allowing independent enable/disable of collision detection. */
collisionSystemGroup?: CG;
/** Priority for integration system (default: 1000) */
integrationPriority?: number;
/** Priority for collision system (default: 900) */
collisionPriority?: number;
/** Execution phase (default: 'fixedUpdate') */
phase?: SystemPhase;
}
/**
* Create a rigidBody3D + force3D component pair.
* Static bodies automatically get mass=Infinity.
*/
export declare function createRigidBody3D(type: BodyType, options?: RigidBodyOptions): {
rigidBody3D: RigidBody;
force3D: Vector3D;
};
/**
* Create a force3D component with initial values.
*/
export declare function createForce3D(x: number, y: number, z: number): {
force3D: Vector3D;
};
/**
* Accumulate a force onto an entity's force3D component.
*/
export declare function applyForce3D(ecs: {
getComponent(id: number, name: 'force3D'): Vector3D | undefined;
}, entityId: number, fx: number, fy: number, fz: number): void;
/**
* Apply an instantaneous impulse: velocity3D += impulse / mass.
*/
export declare function applyImpulse3D(ecs: {
getComponent(id: number, name: 'velocity3D'): Vector3D | undefined;
getComponent(id: number, name: 'rigidBody3D'): RigidBody | undefined;
}, entityId: number, ix: number, iy: number, iz: number): void;
/**
* Directly set an entity's velocity3D.
*/
export declare function setVelocity3D(ecs: {
getComponent(id: number, name: 'velocity3D'): Vector3D | undefined;
}, entityId: number, vx: number, vy: number, vz: number): void;
/**
* Create a 3D physics plugin for ECSpresso.
*
* Provides:
* - Semi-implicit Euler integration (gravity, forces, drag → velocity3D → position)
* - Impulse-based collision response with restitution and friction
* - physics3DCollision events with contact normal and depth
*
* @example
* ```typescript
* const ecs = ECSpresso.create()
* .withPlugin(createTransform3DPlugin())
* .withPlugin(createPhysics3DPlugin({ gravity: { x: 0, y: -9.81, z: 0 } }))
* .withFixedTimestep(1/60)
* .build();
*
* ecs.spawn({
* ...createTransform3D(0, 10, 0),
* ...createRigidBody3D('dynamic', { mass: 1, restitution: 0.5 }),
* velocity3D: { x: 0, y: 0, z: 0 },
* ...createAABB3DCollider(1, 1, 1),
* ...createCollisionLayer('player', ['ground']),
* });
* ```
*/
type Physics3DProvides<L extends string = never> = Physics3DOwnComponentTypes & Collision3DComponentTypes<L>;
export declare function createPhysics3DPlugin<L extends string = never, G extends string = 'physics3D', CG extends string = never>(options?: Physics3DPluginOptions<G, CG> & {
layers?: LayerFactories<Record<L, readonly string[]>>;
}): import("ecspresso").Plugin<import("ecspresso").WithResources<import("ecspresso").WithEvents<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, Physics3DProvides<L>>, Physics3DEventTypes>, Physics3DResourceTypes>, Transform3DWorldConfig, "physics3D-integration" | "physics3D-collision", G | CG, never, never>;