yarn add @react-three/p2
React hooks for p2-es. Use this in combination with react-three-fiber.
Check out all of our examples at https://p2.pmnd.rs (coming soon)
Meanwhile look at the examples living in ./examples
import { Physics, useBox, ... } from '@react-three/p2'
1
for top-down and 2
for side-scroller like apps.<Physics normalIndex={2}>{/* Physics related objects in here please */}</Physics>
const [ref, api] = useBox(() => ({ mass: 1 }))
<mesh ref={ref} geometry={...} material={...} />
useFrame(({ clock }) => api.position.set(Math.sin(clock.getElapsedTime()) * 5, 0))
const velocity = useRef([0, 0])
useEffect(() => {
const unsubscribe = api.velocity.subscribe((v) => (velocity.current = v))
return unsubscribe
}, [])
Let's make a ball falling onto a box.
import { Canvas } from '@react-three/fiber'
import { Physics, useBox, useCircle } from '@react-three/p2'
function Box() {
const [ref] = useBox(() => ({ mass: 0, position: [0, -2] }))
return (
<mesh ref={ref}>
<boxGeometry />
</mesh>
)
}
function Ball() {
const [ref] = useCircle(() => ({ mass: 1, position: [0, 2] }))
return (
<mesh ref={ref}>
<sphereGeometry />
</mesh>
)
}
ReactDOM.render(
<Canvas>
<Physics normalIndex={2}>
<Box />
<Ball />
</Physics>
</Canvas>,
document.getElementById('root'),
)
You can debug your scene using the p2-es-debugger
. This will show you how p2 "sees" your scene. Don't forget to tell both the same normalIndex.
import { Physics, Debug } from '@react-three/cannon'
ReactDOM.render(
<Canvas>
<Physics normalIndex={2}>
<Debug color="black" scale={1.1} linewidth={0.01} normalIndex={2}>
{/* children */}
</Debug>
</Physics>
</Canvas>,
document.getElementById('root'),
)
function Physics({
allowSleep = false,
axisIndex = 0,
normalIndex = 0,
broadphase = 'Naive',
children,
defaultContactMaterial = { contactEquationStiffness: 1e6 },
gravity = [0, -9.81, 0],
isPaused = false,
iterations = 5,
maxSubSteps = 10,
quatNormalizeFast = false,
quatNormalizeSkip = 0,
shouldInvalidate = true,
// Maximum amount of physics objects inside your scene
// Lower this value to save memory, increase if 1000 isn't enough
size = 1000,
solver = 'GS',
stepSize = 1 / 60,
tolerance = 0.001,
}: React.PropsWithChildren<ProviderProps>): JSX.Element
function Debug({ color = 'black', scale = 1 }: DebugProps): JSX.Element
function usePlane(
fn: GetByIndex<PlaneProps>,
fwdRef?: React.Ref<THREE.Object3D>,
deps?: React.DependencyList,
): Api
function useBox(
fn: GetByIndex<BoxProps>,
fwdRef?: React.Ref<THREE.Object3D>,
deps?: React.DependencyList,
): Api
function useCircle(
fn: GetByIndex<CylinderProps>,
fwdRef?: React.Ref<THREE.Object3D>,
deps?: React.DependencyList,
): Api
function useTopDownVehicle(
fn: () => RaycastVehicleProps,
fwdRef?: React.Ref<THREE.Object3D>,
deps: React.DependencyList[] = [],
): [React.RefObject<THREE.Object3D>, RaycastVehiclePublicApi]
function useRaycastClosest(
options: RayOptions,
callback: (e: RayhitEvent) => void,
deps: React.DependencyList = [],
): void
function useRaycastAny(
options: RayOptions,
callback: (e: RayhitEvent) => void,
deps: React.DependencyList = [],
): void
function useRaycastAll(
options: RayOptions,
callback: (e: RayhitEvent) => void,
deps: React.DependencyList = [],
): void
type WorkerApi = {
[K in AtomicName]: AtomicApi<K>
} & {
[K in VectorName]: VectorApi
} & {
applyForce: (force: Triplet, worldPoint: Triplet) => void
applyImpulse: (impulse: Triplet, worldPoint: Triplet) => void
applyLocalForce: (force: Triplet, localPoint: Triplet) => void
applyLocalImpulse: (impulse: Triplet, localPoint: Triplet) => void
applyTorque: (torque: Triplet) => void
quaternion: QuaternionApi
rotation: VectorApi
sleep: () => void
wakeUp: () => void
}
interface PublicApi extends WorkerApi {
at: (index: number) => WorkerApi
}
type Api = [React.RefObject<THREE.Object3D>, PublicApi]
type AtomicName =
| 'allowSleep'
| 'angularDamping'
| 'collisionGroup'
| 'collisionMask'
| 'collisionResponse'
| 'fixedRotation'
| 'isTrigger'
| 'linearDamping'
| 'mass'
| 'material'
| 'sleepSpeedLimit'
| 'sleepTimeLimit'
| 'userData'
type AtomicApi<K extends AtomicName> = {
set: (value: AtomicProps[K]) => void
subscribe: (callback: (value: AtomicProps[K]) => void) => () => void
}
type QuaternionApi = {
set: (x: number, y: number, z: number, w: number) => void
copy: ({ w, x, y, z }: Quaternion) => void
subscribe: (callback: (value: Quad) => void) => () => void
}
type VectorName = 'angularFactor' | 'angularVelocity' | 'linearFactor' | 'position' | 'velocity'
type VectorApi = {
set: (x: number, y: number, z: number) => void
copy: ({ x, y, z }: Vector3 | Euler) => void
subscribe: (callback: (value: Triplet) => void) => () => void
}
type ConstraintApi = [
React.RefObject<THREE.Object3D>,
React.RefObject<THREE.Object3D>,
{
enable: () => void
disable: () => void
},
]
type HingeConstraintApi = [
React.RefObject<THREE.Object3D>,
React.RefObject<THREE.Object3D>,
{
enable: () => void
disable: () => void
enableMotor: () => void
disableMotor: () => void
setMotorSpeed: (value: number) => void
setMotorMaxForce: (value: number) => void
},
]
type SpringApi = [
React.RefObject<THREE.Object3D>,
React.RefObject<THREE.Object3D>,
{
setStiffness: (value: number) => void
setRestLength: (value: number) => void
setDamping: (value: number) => void
},
]
interface RaycastVehiclePublicApi {
applyEngineForce: (value: number, wheelIndex: number) => void
setBrake: (brake: number, wheelIndex: number) => void
setSteeringValue: (value: number, wheelIndex: number) => void
sliding: {
subscribe: (callback: (sliding: boolean) => void) => void
}
}
type InitProps = {
allowSleep?: boolean
axisIndex?: number
broadphase?: Broadphase
defaultContactMaterial?: {
friction?: number
restitution?: number
contactEquationStiffness?: number
contactEquationRelaxation?: number
frictionEquationStiffness?: number
frictionEquationRelaxation?: number
}
gravity?: Duplet
iterations?: number
normalIndex?: number
quatNormalizeFast?: boolean
quatNormalizeSkip?: number
solver?: Solver
tolerance?: number
}
export type ProviderProps = InitProps & {
isPaused?: boolean
maxSubSteps?: number
shouldInvalidate?: boolean
size?: number
stepSize?: number
}
type AtomicProps = {
allowSleep: boolean
angularDamping: number
collisionFilterGroup: number
collisionFilterMask: number
collisionResponse: number
fixedRotation: boolean
isTrigger: boolean
linearDamping: number
mass: number
material: MaterialOptions
sleepSpeedLimit: number
sleepTimeLimit: number
userData: {}
}
type Broadphase = 'Naive' | 'SAP'
type Triplet = [x: number, y: number, z: number]
type Quad = [x: number, y: number, z: number, w: number]
type VectorProps = Record<VectorName, Triplet>
type BodyProps<T extends any[] = unknown[]> = Partial<AtomicProps> &
Partial<VectorProps> & {
args?: T
onCollide?: (e: CollideEvent) => void
onCollideBegin?: (e: CollideBeginEvent) => void
onCollideEnd?: (e: CollideEndEvent) => void
quaternion?: Quad
rotation?: Triplet
type?: 'Dynamic' | 'Static' | 'Kinematic'
}
type Event = RayhitEvent | CollideEvent | CollideBeginEvent | CollideEndEvent
type CollideEvent = {
op: string
type: 'collide'
body: THREE.Object3D
target: THREE.Object3D
contact: {
// the world position of the point of contact
contactPoint: number[]
// the normal of the collision on the surface of
// the colliding body
contactNormal: number[]
// velocity of impact along the contact normal
impactVelocity: number
// a unique ID for each contact event
id: string
// these are lower-level properties from cannon:
// bi: one of the bodies involved in contact
bi: THREE.Object3D
// bj: the other body involved in contact
bj: THREE.Object3D
// ni: normal of contact relative to bi
ni: number[]
// ri: the point of contact relative to bi
ri: number[]
// rj: the point of contact relative to bj
rj: number[]
}
collisionFilters: {
bodyFilterGroup: number
bodyFilterMask: number
targetFilterGroup: number
targetFilterMask: number
}
}
type CollideBeginEvent = {
op: 'event'
type: 'collideBegin'
target: Object3D
body: Object3D
}
type CollideEndEvent = {
op: 'event'
type: 'collideEnd'
target: Object3D
body: Object3D
}
type RayhitEvent = {
op: string
type: 'rayhit'
body: THREE.Object3D
target: THREE.Object3D
}
type CylinderArgs = [radiusTop?: number, radiusBottom?: number, height?: number, numSegments?: number]
type SphereArgs = [radius: number]
type TrimeshArgs = [vertices: ArrayLike<number>, indices: ArrayLike<number>]
type HeightfieldArgs = [
data: number[][],
options: { elementSize?: number; maxValue?: number; minValue?: number },
]
type ConvexPolyhedronArgs<V extends VectorTypes = VectorTypes> = [
vertices?: V[],
faces?: number[][],
normals?: V[],
axes?: V[],
boundingSphereRadius?: number,
]
interface PlaneProps extends BodyProps {}
interface BoxProps extends BodyProps<Triplet> {} // extents: [x, y, z]
interface CylinderProps extends BodyProps<CylinderArgs> {}
interface ParticleProps extends BodyProps {}
interface SphereProps extends BodyProps<SphereArgs> {}
interface TrimeshProps extends BodyPropsArgsRequired<TrimeshArgs> {}
interface HeightfieldProps extends BodyPropsArgsRequired<HeightfieldArgs> {}
interface ConvexPolyhedronProps extends BodyProps<ConvexPolyhedronArgs> {}
interface CompoundBodyProps extends BodyProps {
shapes: BodyProps & { type: ShapeType }[]
}
interface ConstraintOptns {
maxForce?: number
collideConnected?: boolean
wakeUpBodies?: boolean
}
interface PointToPointConstraintOpts extends ConstraintOptns {
pivotA: Triplet
pivotB: Triplet
}
interface ConeTwistConstraintOpts extends ConstraintOptns {
pivotA?: Triplet
axisA?: Triplet
pivotB?: Triplet
axisB?: Triplet
angle?: number
twistAngle?: number
}
interface DistanceConstraintOpts extends ConstraintOptns {
distance?: number
}
interface HingeConstraintOpts extends ConstraintOptns {
pivotA?: Triplet
axisA?: Triplet
pivotB?: Triplet
axisB?: Triplet
}
interface LockConstraintOpts extends ConstraintOptns {}
interface SpringOptns {
restLength?: number
stiffness?: number
damping?: number
worldAnchorA?: Triplet
worldAnchorB?: Triplet
localAnchorA?: Triplet
localAnchorB?: Triplet
}
interface WheelInfoOptions {
radius?: number
directionLocal?: Triplet
suspensionStiffness?: number
suspensionRestLength?: number
maxSuspensionForce?: number
maxSuspensionTravel?: number
dampingRelaxation?: number
dampingCompression?: number
frictionSlip?: number
rollInfluence?: number
axleLocal?: Triplet
chassisConnectionPointLocal?: Triplet
isFrontWheel?: boolean
useCustomSlidingRotationalSpeed?: boolean
customSlidingRotationalSpeed?: number
}
interface RaycastVehicleProps {
chassisBody: React.Ref<THREE.Object3D>
wheels: React.Ref<THREE.Object3D>[]
wheelInfos: WheelInfoOptions[]
indexForwardAxis?: number
indexRightAxis?: number
indexUpAxis?: number
}