UNPKG

@react-three/p2

Version:

2D physics based hooks for react-three-fiber

450 lines (400 loc) 13.4 kB
import type { ContactMaterialOptions, MaterialOptions, RayOptions as RayOptionsImpl, Shape } from 'p2-es' import type { MutableRefObject } from 'react' import { createContext } from 'react' import type { Object3D } from 'three' import type { CannonWorkerAPI } from 'cannon-worker-api' import type { AtomicProps, BodyProps, BodyShapeType } from './hooks' export type Duplet = [number, number] export type Broadphase = 'Naive' | 'SAP' export type Solver = 'GS' | 'Split' export type Buffers = { positions: Float32Array; quaternions: Float32Array } export type Refs = { [uuid: string]: Object3D } export type ConstraintTypes = 'Distance' | 'Gear' | 'Lock' | 'Prismatic' | 'Revolute' export interface ConstraintOptns { collideConnected?: boolean } export interface DistanceConstraintOpts extends ConstraintOptns { distance?: number localAnchorA?: Duplet localAnchorB?: Duplet maxForce?: number } export interface GearConstraintOpts extends ConstraintOptns { angle?: number maxTorque?: number ratio?: number } export interface LockConstraintOpts extends ConstraintOptns { localAngleB?: number localOffsetB?: Duplet maxForce?: number } export interface PrismaticConstraintOpts extends ConstraintOptns { disableRotationalLock?: boolean localAnchorA?: Duplet localAnchorB?: Duplet localAxisA?: Duplet lowerLimit?: number maxForce?: number upperLimit?: number } export interface RevoluteConstraintOpts extends ConstraintOptns { localPivotA?: Duplet localPivotB?: Duplet maxForce?: number worldPivot?: Duplet } export interface SpringOptns { damping?: number localAnchorA?: Duplet localAnchorB?: Duplet restLength?: number stiffness?: number worldAnchorA?: Duplet worldAnchorB?: Duplet } export interface WheelInfoOptions { localPosition?: Duplet sideFriction?: number } type WorkerContact = WorkerCollideEvent['data']['contact'] export type CollideEvent = Omit<WorkerCollideEvent['data'], 'body' | 'target' | 'contact'> & { body: Object3D contact: Omit<WorkerContact, 'bi' | 'bj'> & { bi: Object3D bj: Object3D } target: Object3D } export type CollideBeginEvent = { body: Object3D op: 'event' target: Object3D type: 'collideBegin' } export type CollideEndEvent = { body: Object3D op: 'event' target: Object3D type: 'collideEnd' } export type RayhitEvent = Omit<WorkerRayhitEvent['data'], 'body'> & { body: Object3D | null } type CannonEvent = CollideBeginEvent | CollideEndEvent | CollideEvent | RayhitEvent type CallbackByType<T extends { type: string }> = { [K in T['type']]?: T extends { type: K } ? (e: T) => void : never } type CannonEvents = { [uuid: string]: Partial<CallbackByType<CannonEvent>> } export type Subscription = Partial<{ [K in SubscriptionName]: (value: PropValue<K>) => void }> export type Subscriptions = Partial<{ [id: number]: Subscription }> export type PropValue<T extends SubscriptionName = SubscriptionName> = T extends AtomicName ? AtomicProps[T] : T extends VectorName ? Duplet : never export const atomicNames = [ 'allowSleep', 'angle', 'angularDamping', 'angularVelocity', 'collisionFilterGroup', 'collisionFilterMask', 'collisionResponse', 'fixedRotation', 'isTrigger', 'linearDamping', 'mass', 'material', 'sleepSpeedLimit', 'sleepTimeLimit', 'userData', ] as const export type AtomicName = typeof atomicNames[number] export const vectorNames = ['position', 'velocity'] as const export type VectorName = typeof vectorNames[number] export const subscriptionNames = [...atomicNames, ...vectorNames, 'collisions', 'raysData'] as const export type SubscriptionName = typeof subscriptionNames[number] export type SetOpName<T extends AtomicName | VectorName | WorldPropName> = `set${Capitalize<T>}` type Operation<T extends OpName, P> = { op: T } & (P extends symbol ? {} : { props: P }) type WithUUID<T extends OpName, P = symbol> = Operation<T, P> & { uuid: string } type WithUUIDs<T extends OpName, P = symbol> = Operation<T, P> & { uuid: string[] } type AddConstraintProps = [ uuidA: string, uuidB: string, options: { angle?: number collideConnected?: boolean disableRotationalLock?: boolean distance?: number localAnchorA?: Duplet localAnchorB?: Duplet localAngleB?: number localAxisA?: Duplet localOffsetB?: Duplet localPivotA?: Duplet localPivotB?: Duplet lowerLimit?: number maxForce?: number maxTorque?: number ratio?: number upperLimit?: number worldPivot?: Duplet }, ] type AddContactMaterialProps = [ materialA: MaterialOptions, materialB: MaterialOptions, options: ContactMaterialOptions, ] export type RayMode = 'Closest' | 'Any' | 'All' type AddRayProps = { from: Duplet mode: RayMode to: Duplet } & Pick<RayOptionsImpl, 'checkCollisionResponse' | 'collisionGroup' | 'collisionMask' | 'skipBackfaces'> export type RayOptions = Omit<AddRayProps, 'mode'> type AtomicMessage<T extends AtomicName> = WithUUID<SetOpName<T>, AtomicProps[T]> type VectorMessage = WithUUID<SetOpName<VectorName>, Duplet> type SerializableBodyProps = { onCollide: boolean } export type SubscriptionTarget = 'bodies' | 'vehicles' | 'controllers' type SubscribeMessageProps = { id: number target: SubscriptionTarget type: SubscriptionName } export type Observation = { [K in AtomicName]: [id: number, value: PropValue<K>, type: K] }[AtomicName] export type WorkerFrameMessage = { data: Buffers & { active: boolean bodies?: string[] observations: Observation[] op: 'frame' } } export type WorkerCollideEvent = { data: { body: string collisionFilters: { bodyFilterGroup: number bodyFilterMask: number targetFilterGroup: number targetFilterMask: number } contact: { bi: string bj: string /** Normal of the contact, relative to the colliding body */ contactNormal: number[] /** Contact point in world space */ contactPoint: number[] id: number impactVelocity: number ni: number[] ri: number[] rj: number[] } op: 'event' target: string type: 'collide' } } export type WorkerRayhitEvent = { data: { body: string | null distance: number hasHit: boolean hitFaceIndex: number hitNormalWorld: number[] hitPointWorld: number[] op: 'event' ray: { collisionFilterGroup: number collisionFilterMask: number direction: number[] from: number[] to: number[] uuid: string } rayFromWorld: number[] rayToWorld: number[] shape: (Omit<Shape, 'body'> & { body: string }) | null shouldStop: boolean type: 'rayhit' } } export type WorkerCollideBeginEvent = { data: { bodyA: string bodyB: string op: 'event' type: 'collideBegin' } } export type WorkerCollideEndEvent = { data: { bodyA: string bodyB: string op: 'event' type: 'collideEnd' } } export type WorkerEventMessage = | WorkerCollideBeginEvent | WorkerCollideEndEvent | WorkerCollideEvent | WorkerRayhitEvent export type IncomingWorkerMessage = WorkerEventMessage | WorkerFrameMessage export type WorldPropName = 'axisIndex' | 'broadphase' | 'gravity' | 'iterations' | 'tolerance' export type StepProps = { maxSubSteps?: number stepSize: number timeSinceLastCalled?: number } export type WorldProps = { allowSleep: boolean axisIndex: 0 | 1 | 2 broadphase: Broadphase defaultContactMaterial: ContactMaterialOptions gravity: Duplet iterations: number normalIndex: 0 | 1 | 2 quatNormalizeFast: boolean quatNormalizeSkip: number solver: Solver tolerance: number } type WorldMessage<T extends WorldPropName> = Operation<SetOpName<T>, WorldProps[T]> export type CannonMessageMap = { addBodies: WithUUIDs<'addBodies', SerializableBodyProps[]> & { type: BodyShapeType } addConstraint: WithUUID<'addConstraint', AddConstraintProps> & { type: 'Prismatic' | 'Revolute' | ConstraintTypes } addContactMaterial: WithUUID<'addContactMaterial', AddContactMaterialProps> addKinematicCharacterController: WithUUID< 'addKinematicCharacterController', [ bodyUUID: string, collisionMask: number, accelerationTimeAirborne?: number, accelerationTimeGrounded?: number, moveSpeed?: number, wallSlideSpeedMax?: number, wallStickTime?: number, wallJumpClimb?: Duplet, wallJumpOff?: Duplet, wallLeap?: Duplet, timeToJumpApex?: number, maxJumpHeight?: number, minJumpHeight?: number, velocityXSmoothing?: number, velocityXMin?: number, maxClimbAngle?: number, maxDescendAngle?: number, skinWidth?: number, dstBetweenRays?: number, ] > addPlatformController: WithUUID< 'addPlatformController', [ bodyUUID: string, passengerMask: number, localWaypoints: Duplet[], speed?: number, skinWidth?: number, dstBetweenRays?: number, ] > addRay: WithUUID<'addRay', AddRayProps> addSpring: WithUUID<'addSpring', [uuidA: string, uuidB: string, options: SpringOptns]> addTopDownVehicle: WithUUID<'addTopDownVehicle', [chassisBodyUUID: string, wheelInfos: WheelInfoOptions[]]> applyForce: WithUUID<'applyForce', [force: Duplet, worldPoint: Duplet]> applyImpulse: WithUUID<'applyImpulse', [impulse: Duplet, worldPoint: Duplet]> applyLocalForce: WithUUID<'applyLocalForce', [force: Duplet, localPoint: Duplet]> applyLocalImpulse: WithUUID<'applyLocalImpulse', [impulse: Duplet, localPoint: Duplet]> applyTopDownVehicleEngineForce: WithUUID< 'applyTopDownVehicleEngineForce', [value: number, wheelIndex: number] > applyTorque: WithUUID<'applyTorque', [torque: Duplet]> disableConstraintMotor: WithUUID<'disableConstraintMotor'> enableConstraintMotor: WithUUID<'enableConstraintMotor'> init: Operation<'init', WorldProps> removeBodies: WithUUIDs<'removeBodies'> removeConstraint: WithUUID<'removeConstraint'> removeContactMaterial: WithUUID<'removeContactMaterial'> removeKinematicCharacterController: WithUUID<'removeKinematicCharacterController'> removePlatformController: WithUUID<'removePlatformController'> removeRay: WithUUID<'removeRay'> removeSpring: WithUUID<'removeSpring'> removeTopDownVehicle: WithUUID<'removeTopDownVehicle'> setAllowSleep: AtomicMessage<'allowSleep'> setAngle: WithUUID<SetOpName<'angle'>, number> setAngularDamping: AtomicMessage<'angularDamping'> setAngularVelocity: AtomicMessage<'angularVelocity'> setAxisIndex: WorldMessage<'axisIndex'> setBroadphase: WorldMessage<'broadphase'> setCollisionFilterGroup: AtomicMessage<'collisionFilterGroup'> setCollisionFilterMask: AtomicMessage<'collisionFilterMask'> setCollisionResponse: AtomicMessage<'collisionResponse'> setConstraintMotorSpeed: WithUUID<'setConstraintMotorSpeed', number> setFixedRotation: AtomicMessage<'fixedRotation'> setGravity: WorldMessage<'gravity'> setIsTrigger: AtomicMessage<'isTrigger'> setIterations: WorldMessage<'iterations'> setKinematicCharacterControllerInput: WithUUID<'setKinematicCharacterControllerInput', Duplet> setKinematicCharacterControllerJump: WithUUID<'setKinematicCharacterControllerJump', boolean> setLinearDamping: AtomicMessage<'linearDamping'> setMass: AtomicMessage<'mass'> setMaterial: AtomicMessage<'material'> setPosition: VectorMessage setSleepSpeedLimit: AtomicMessage<'sleepSpeedLimit'> setSleepTimeLimit: AtomicMessage<'sleepTimeLimit'> setSpringDamping: WithUUID<'setSpringDamping', number> setSpringRestLength: WithUUID<'setSpringRestLength', number> setSpringStiffness: WithUUID<'setSpringStiffness', number> setTolerance: WorldMessage<'tolerance'> setTopDownVehicleBrake: WithUUID<'setTopDownVehicleBrake', [brake: number, wheelIndex: number]> setTopDownVehicleSteeringValue: WithUUID< 'setTopDownVehicleSteeringValue', [value: number, wheelIndex: number] > setUserData: AtomicMessage<'userData'> setVelocity: VectorMessage sleep: WithUUID<'sleep'> step: Operation<'step', StepProps> & { positions: Float32Array quaternions: Float32Array } subscribe: WithUUID<'subscribe', SubscribeMessageProps> unsubscribe: Operation<'unsubscribe', number> wakeUp: WithUUID<'wakeUp'> } type OpName = keyof CannonMessageMap export type CannonMessageBody<T extends OpName> = Omit<CannonMessageMap[T], 'op'> export type CannonMessageProps<T extends OpName> = CannonMessageMap[T] extends { props: unknown } ? CannonMessageMap[T]['props'] : never export type CannonMessage = CannonMessageMap[OpName] export interface CannonWebWorker extends Worker { onmessage: (e: IncomingWorkerMessage) => void postMessage(message: CannonMessage, transfer: Transferable[]): void postMessage(message: CannonMessage, options?: StructuredSerializeOptions): void terminate: () => void } export type ProviderContext = { bodies: MutableRefObject<{ [uuid: string]: number }> events: CannonEvents refs: Refs subscriptions: Subscriptions worker: CannonWorkerAPI } export type DebugApi = { add(id: string, props: BodyProps, type: BodyShapeType): void remove(id: string): void } export const context = createContext<ProviderContext>({} as ProviderContext) export const debugContext = createContext<DebugApi>(null!)