@threlte/rapier
<Collider>
Colliders represent the geometric shapes that generate contacts and collision events when they touch. Attaching one or multiple colliders to a rigid body allow the rigid-body to be affected by contact forces.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { HTML } from '@threlte/extras'
import { World } from '@threlte/rapier'
import Scene from './Scene.svelte'
import { useTweakpane } from '$lib/useTweakpane'
let testIndex = 0
const { addButton, action, addInput } = useTweakpane()
addButton({
title: 'Standalone Collider',
onClick: () => (testIndex = 0)
})
addButton({
title: 'Attached Collider',
onClick: () => (testIndex = 1)
})
addButton({
title: 'Sensor Collider',
onClick: () => (testIndex = 2)
})
</script>
<div use:action />
<Canvas>
<World>
<Scene {testIndex} />
<HTML
slot="fallback"
transform
>
<p class="text-xs">
It seems your browser<br />
doesn't support WASM.<br />
I'm sorry.
</p>
</HTML>
</World>
</Canvas>
<script lang="ts">
import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
import { T, useFrame } from '@threlte/core'
import { AutoColliders, Collider, RigidBody } from '@threlte/rapier'
import { BoxGeometry, Color, MeshStandardMaterial, SphereGeometry } from 'three'
import TestBed from './TestBed.svelte'
const material = new MeshStandardMaterial({ color: new Color(0xff3f00) })
let rigidBody: RapierRigidBody
let positionZ = 0
let positionX = 0
const offset = Date.now()
useFrame(() => {
if (!rigidBody) return
positionZ = Math.sin(Date.now() / 2000) * 2.5
positionX = Math.sin((Date.now() + offset) / 1500) * 1.2
rigidBody.setNextKinematicTranslation({ x: positionX, y: 1, z: positionZ })
})
</script>
<!-- ATTACHED COLLIDER -->
<T.Group position={[0, 2, 0]}>
<RigidBody>
<T.Mesh
castShadow
geometry={new BoxGeometry(2, 2, 2)}
{material}
/>
<Collider
shape={'cuboid'}
args={[1, 1, 1]}
/>
</RigidBody>
</T.Group>
<!-- TEST SPHERE -->
<T.Group position={[0, 1, 0]}>
<RigidBody
bind:rigidBody
type={'kinematicPosition'}
lockRotations
>
<AutoColliders shape={'ball'}>
<T.Mesh
castShadow
geometry={new SphereGeometry(1)}
material={new MeshStandardMaterial()}
/>
</AutoColliders>
</RigidBody>
</T.Group>
<TestBed title={'Attached Collider'}>
<div slot="text">
<p>
Nesting one or multiple {'<Collider>'} components in a {'<RigidBody>'} component effectively attaches
the colliders to the rigid body and allow the rigid body to be affected by contact forces and gravity.
</p>
<p>
If a collider is attached to a {'<RigidBody>'} its transform properties are applied once on initialization.
</p>
</div>
</TestBed>
<script lang="ts">
import { useFrame } from '@threlte/core'
import type { Euler, Vector3 } from 'three'
import Particle from './Particle.svelte'
const getId = () => {
return Math.random().toString(16).slice(2)
}
const getRandomPosition = (): Parameters<Vector3['set']> => {
return [0.5 - Math.random() * 1, 5 - Math.random() * 1 + 10, 0.5 - Math.random() * 1]
}
const getRandomRotation = (): Parameters<Euler['set']> => {
return [Math.random() * 10, Math.random() * 10, Math.random() * 10]
}
type Body = {
id: string
mounted: number
position: Parameters<Vector3['set']>
rotation: Parameters<Euler['set']>
}
let bodies: Body[] = []
let lastBodyMounted: number = 0
let bodyEveryMilliseconds = 100
let longevityMilliseconds = 8000
useFrame(() => {
if (lastBodyMounted + bodyEveryMilliseconds < Date.now()) {
const body: Body = {
id: getId(),
mounted: Date.now(),
position: getRandomPosition(),
rotation: getRandomRotation()
}
bodies.unshift(body)
lastBodyMounted = Date.now()
bodies = bodies
}
const deleteIds: string[] = []
bodies.forEach((body) => {
if (body.mounted + longevityMilliseconds < Date.now()) {
deleteIds.push(body.id)
}
})
if (deleteIds.length) {
deleteIds.forEach((id) => {
const index = bodies.findIndex((body) => body.id === id)
if (index !== -1) bodies.splice(index, 1)
})
bodies = bodies
}
})
</script>
{#each bodies as body (body.id)}
<Particle position={body.position} rotation={body.rotation} />
{/each}
<script
lang="ts"
context="module"
>
const geometry = new BoxGeometry(0.25, 0.25, 0.25)
const material = new MeshStandardMaterial()
</script>
<script lang="ts">
import { T } from '@threlte/core'
import { Collider, RigidBody } from '@threlte/rapier'
import type { Euler } from 'three'
import { BoxGeometry, MeshStandardMaterial, Vector3 } from 'three'
export let position: Parameters<Vector3['set']>
export let rotation: Parameters<Euler['set']>
</script>
<T.Group
{position}
{rotation}
>
<RigidBody type={'dynamic'}>
<Collider
shape={'cuboid'}
args={[0.125, 0.125, 0.125]}
/>
<T.Mesh
castShadow
receiveShadow
{geometry}
{material}
/>
</RigidBody>
</T.Group>
<script lang="ts">
import { T } from '@threlte/core'
import { OrbitControls } from '@threlte/extras'
import { Debug } from '@threlte/rapier'
import AttachedCollider from './AttachedCollider.svelte'
import Sensor from './Sensor.svelte'
import StandaloneCollider from './StandaloneCollider.svelte'
export let testIndex: number
const tests = [StandaloneCollider, AttachedCollider, Sensor]
</script>
<T.PerspectiveCamera
position.x={12}
position.y={13}
fov={40}
makeDefault
>
<OrbitControls target.x={2.5} />
</T.PerspectiveCamera>
<T.DirectionalLight
castShadow
position={[8, 20, -3]}
/>
<T.GridHelper args={[50]} />
<Debug
depthTest={false}
depthWrite={false}
/>
<svelte:component this={tests[testIndex]} />
<script lang="ts">
import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
import { T, useFrame } from '@threlte/core'
import { AutoColliders, Collider, RigidBody } from '@threlte/rapier'
import { Color, MeshStandardMaterial, SphereGeometry } from 'three'
import TestBed from './TestBed.svelte'
const gray = new Color(0x333333)
const brand = new Color(0xff3f00)
const material = new MeshStandardMaterial({ color: gray })
let present = false
$: material.color = present ? brand : gray
let rigidBody: RapierRigidBody
let positionZ = 0
let positionX = 0
const offset = Date.now()
useFrame(() => {
if (!rigidBody) return
positionZ = Math.sin(Date.now() / 2000) * 2.5
positionX = Math.sin((Date.now() + offset) / 1500) * 1.2
rigidBody.setNextKinematicTranslation({ x: positionX, y: 1, z: positionZ })
})
</script>
<!-- SENSOR -->
<T.Group position={[0, 1, 0]}>
<Collider
on:sensorenter={() => (present = true)}
on:sensorexit={() => (present = false)}
sensor
shape={'cuboid'}
args={[1, 1, 1]}
/>
</T.Group>
<T.Group position={[0, 1, 0]}>
<RigidBody
bind:rigidBody
type={'kinematicPosition'}
lockRotations
>
<AutoColliders shape={'ball'}>
<T.Mesh
castShadow
geometry={new SphereGeometry(1)}
{material}
/>
</AutoColliders>
</RigidBody>
</T.Group>
<TestBed title={'Sensor Collider'}>
<div slot="text">
<p>
This collider is marked as a sensor and as such does<br />
not participate in contacts and collisions and can be<br />
useful to detect presence in areas.
</p>
</div>
</TestBed>
<script lang="ts">
import { T } from '@threlte/core'
import { Collider } from '@threlte/rapier'
import { DEG2RAD } from 'three/src/math/MathUtils'
import Emitter from './Emitter.svelte'
import TestBed from './TestBed.svelte'
</script>
<!-- STANDALONE COLLIDER -->
<T.Group
rotation={[0, 45 * DEG2RAD, 0]}
position={[0, 1, 0]}
>
<Collider
shape={'cuboid'}
args={[1, 1, 1]}
/>
</T.Group>
<Emitter />
<TestBed title={'Standalone Collider'}>
<div slot="text">
<p>
This collider is not a child of a {'<RigidBody>'} component.<br />
It will participate in contacts and collisions but is not affected by gravity or external forces.
This can be useful for the environment.
</p>
<p>Standalone colliders have reactive transform properties.</p>
</div>
</TestBed>
<script lang="ts">
import { T } from '@threlte/core'
import { HTML } from '@threlte/extras'
import { AutoColliders } from '@threlte/rapier'
import { BoxGeometry, MeshStandardMaterial } from 'three'
import { DEG2RAD } from 'three/src/math/MathUtils'
export let title: string
export let useGround = true
</script>
{#if useGround}
<T.Group position={[1, -0.5, 0]}>
<AutoColliders shape={'cuboid'}>
<T.Mesh
receiveShadow
geometry={new BoxGeometry(12, 1, 10)}
material={new MeshStandardMaterial()}
/>
</AutoColliders>
</T.Group>
{/if}
<HTML
transform
rotation.z={90 * DEG2RAD}
rotation.x={-90 * DEG2RAD}
position.x={5.8}
pointerEvents={'none'}
scale={0.6}
>
<div class="transform -translate-y-1/2 text-black w-[500px]">
<h2>{title}</h2>
<div class="leading-normal">
<slot name="text" />
</div>
</div>
</HTML>
<slot />
Component Signature
If a <Collider>
component is not a child of a <RigidBody>
component, the transform properties are reactive.
If you don't provide any of the properties density
, mass
or massProperties
, Rapier will figure that out for you.
You can provide either a property density
, mass
or massProperties
.