@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 { Pane, Button } from 'svelte-tweakpane-ui'
let testIndex = $state(0)
let version = $state(0)
</script>
<Pane
title="Colliders"
position="fixed"
>
<Button
label="type"
title="Standalone"
on:click={() => {
testIndex = 0
version += 1
}}
/>
<Button
label=""
title="Attached"
on:click={() => {
testIndex = 1
version += 1
}}
/>
<Button
label=""
title="Sensor"
on:click={() => {
testIndex = 2
version += 1
}}
/>
</Pane>
<div>
<Canvas>
<World>
{#key version}
<Scene {testIndex} />
{/key}
{#snippet fallback()}
<HTML transform>
<p class="text-xs">
It seems your browser doesn't support WASM.<br />
I'm sorry.
</p>
</HTML>
{/snippet}
</World>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
import { T, useTask } from '@threlte/core'
import { AutoColliders, Collider, RigidBody } from '@threlte/rapier'
import { Vector3 } from 'three'
import TestBed from './TestBed.svelte'
let rigidBody = $state.raw<RapierRigidBody>()
const position = new Vector3(0, 1, 0)
let elapsed = 0
useTask((dt) => {
elapsed += dt
position.x = Math.sin(elapsed)
position.z = Math.cos(elapsed)
rigidBody?.setNextKinematicTranslation(position)
})
</script>
<!-- ATTACHED COLLIDER -->
<T.Group position={[0, 2, 0]}>
<RigidBody>
<T.Mesh castShadow>
<T.MeshStandardMaterial color={0xff3f00} />
<T.BoxGeometry args={[2, 2, 2]} />
</T.Mesh>
<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>
<T.SphereGeometry args={[1]} />
<T.MeshStandardMaterial />
</T.Mesh>
</AutoColliders>
</RigidBody>
</T.Group>
<TestBed title="Attached Collider">
{#snippet text()}
<div>
<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>
</div>
{/snippet}
</TestBed>
<script lang="ts">
import { useTask } from '@threlte/core'
import { Quaternion, type QuaternionTuple, type Vector3Tuple } from 'three'
import Particle from './Particle.svelte'
import { SvelteSet } from 'svelte/reactivity'
interface Body {
mounted: number
position: Vector3Tuple
quaternion: QuaternionTuple
}
let bodies = new SvelteSet<Body>()
let lastBodyMounted = 0
let bodyEveryMilliseconds = 100
let longevityMilliseconds = 8000
const quaternion = new Quaternion()
useTask(() => {
const now = performance.now()
if (lastBodyMounted + bodyEveryMilliseconds < now) {
const body: Body = {
mounted: now,
position: [0, 15, 0],
quaternion: quaternion.random().toArray()
}
bodies.add(body)
lastBodyMounted = now
}
bodies.forEach((body) => {
if (body.mounted + longevityMilliseconds < now) {
bodies.delete(body)
}
})
})
</script>
{#each bodies as body (body)}
<Particle {...body} />
{/each}
<script
lang="ts"
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 { BoxGeometry, MeshStandardMaterial, type QuaternionTuple, type Vector3Tuple } from 'three'
interface Props {
position: Vector3Tuple
quaternion: QuaternionTuple
}
let { position, quaternion }: Props = $props()
</script>
<T.Group
{position}
{quaternion}
>
<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'
interface Props {
testIndex: number
}
let { testIndex }: Props = $props()
const tests = [StandaloneCollider, AttachedCollider, Sensor]
const SvelteComponent = $derived(tests[testIndex])
</script>
<T.PerspectiveCamera
position.x={12}
position.y={13}
fov={40}
makeDefault
oncreate={(ref) => ref.lookAt(2.5, 0, 0)}
>
<OrbitControls target.x={2.5} />
</T.PerspectiveCamera>
<T.DirectionalLight
castShadow
position={[8, 20, -3]}
/>
<T.GridHelper args={[50]} />
<Debug
depthTest={false}
depthWrite={false}
/>
<SvelteComponent />
<script lang="ts">
import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
import { T, useTask } from '@threlte/core'
import { AutoColliders, Collider, RigidBody } from '@threlte/rapier'
import { Color } from 'three'
import TestBed from './TestBed.svelte'
const gray = new Color(0x333333)
const brand = new Color(0xff3f00)
let present = $state(false)
let rigidBody = $state.raw<RapierRigidBody>()
const offset = Date.now()
useTask(() => {
const positionZ = Math.sin(Date.now() / 2000) * 2.5
const 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
onsensorenter={() => (present = true)}
onsensorexit={() => (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>
<T.SphereGeometry />
<T.MeshStandardMaterial color={present ? brand : gray} />
</T.Mesh>
</AutoColliders>
</RigidBody>
</T.Group>
<TestBed title="Sensor Collider">
{#snippet text()}
<div>
<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>
{/snippet}
</TestBed>
<script lang="ts">
import { MathUtils } from 'three'
import { T } from '@threlte/core'
import { Collider } from '@threlte/rapier'
import Emitter from './Emitter.svelte'
import TestBed from './TestBed.svelte'
</script>
<!-- STANDALONE COLLIDER -->
<T.Group
rotation={[0, 45 * MathUtils.DEG2RAD, 0]}
position={[0, 1, 0]}
>
<Collider
shape="cuboid"
args={[1, 1, 1]}
/>
</T.Group>
<Emitter />
<TestBed title="Standalone Collider">
{#snippet text()}
<div>
<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>
</div>
{/snippet}
</TestBed>
<script lang="ts">
import type { Snippet } from 'svelte'
import { T } from '@threlte/core'
import { HTML } from '@threlte/extras'
import { AutoColliders } from '@threlte/rapier'
import { MathUtils } from 'three'
interface Props {
title: string
useGround?: boolean
text?: Snippet
children?: Snippet
}
let { title, useGround = true, text, children }: Props = $props()
</script>
{#if useGround}
<T.Group position={[1, -0.5, 0]}>
<AutoColliders shape={'cuboid'}>
<T.Mesh receiveShadow>
<T.BoxGeometry args={[12, 1, 10]} />
<T.MeshStandardMaterial />
</T.Mesh>
</AutoColliders>
</T.Group>
{/if}
<HTML
transform
rotation.z={90 * MathUtils.DEG2RAD}
rotation.x={-90 * MathUtils.DEG2RAD}
position.x={5.8}
pointerEvents="none"
scale={0.6}
>
<div class="w-[500px] -translate-y-1/2 transform text-black">
<h2>{title}</h2>
<div class="leading-normal">
{@render text?.()}
</div>
</div>
</HTML>
{@render children?.()}
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 the full mass properties, Rapier will figure that out for you.
Provide density on its own, mass on its own, or mass together with centerOfMass, principalAngularInertia and angularInertiaLocalFrame.