<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)</script>
<Pane title="Colliders" position="fixed"> <Button label="type" title="Standalone" on:click={() => { testIndex = 0 }} /> <Button label="" title="Attached" on:click={() => { testIndex = 1 }} /> <Button label="" title="Sensor" on:click={() => { testIndex = 2 }} /></Pane>
<div> <Canvas> <World> <Scene {testIndex} />
{#snippet fallback()} <HTML transform> <p class="text-xs"> It seems your browser<br /> 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 { BoxGeometry, MeshStandardMaterial, SphereGeometry } from 'three' import TestBed from './TestBed.svelte'
const material = new MeshStandardMaterial({ color: 0xff3f00 })
let rigidBody = $state.raw<RapierRigidBody>() let positionZ = 0 let positionX = 0 const offset = Date.now()
useTask(() => { 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"> {#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 { MathUtils, Quaternion, Vector3 } from 'three' import Particle from './Particle.svelte'
type Body = { id: string mounted: number position: Vector3 quaternion: Quaternion }
let bodies = $state<Body[]>([])
let lastBodyMounted = 0 let bodyEveryMilliseconds = 100 let longevityMilliseconds = 8000
useTask(() => { if (lastBodyMounted + bodyEveryMilliseconds < Date.now()) { const body: Body = { id: MathUtils.generateUUID(), mounted: Date.now(), position: new Vector3(0, 15, 0), quaternion: new Quaternion().random() } bodies.unshift(body) lastBodyMounted = Date.now() }
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) }) } })</script>
{#each bodies as body (body.id)} <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, Quaternion, Vector3 } from 'three'
interface Props { position: Vector3 quaternion: Quaternion }
let { position, quaternion }: Props = $props()</script>
<T.Group position={position.toArray()} quaternion={quaternion.toArray()}> <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 { BoxGeometry, MeshStandardMaterial, 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 geometry={new BoxGeometry(12, 1, 10)} material={new MeshStandardMaterial()} /> </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
Section titled “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.