<RigidBody>
The real-time simulation of rigid bodies subjected to forces and contacts is the main feature of a physics engine for videogames, robotics, or animation. Rigid bodies are typically used to simulate the dynamics of non-deformable solids as well as to integrate the trajectory of solids which velocities are controlled by the user (e.g. moving platforms).
Note that rigid-bodies are only responsible for the dynamics and kinematics of the solid. Colliders can be attached to a rigid-body to specify its shape and enable collision-detection. A rigid-body without collider attached to it will not be affected by contacts (because there is no shape to compute contact against).
<script lang="ts"> import { Canvas } from '@threlte/core' import { HTML } from '@threlte/extras' import { World } from '@threlte/rapier' import { muted } from './Particle.svelte' import Scene from './Scene.svelte' import { Pane, Button } from 'svelte-tweakpane-ui'</script>
<Pane title="Rigid Body" position="fixed"> <Button title="toggle sound" on:click={() => { $muted = !$muted }} /></Pane>
<div> <Canvas> <World> <Scene />
{#snippet fallback()} <HTML transform> <p> It seems your browser<br /> doesn't support WASM.<br /> I'm sorry. </p> </HTML> {/snippet} </World> </Canvas></div>
<style> div { height: 100%; } p { font-size: 0.75rem; line-height: 1rem; }</style><script lang="ts"> import { useTask } from '@threlte/core' import { Vector3, MathUtils, Quaternion } 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 = 800 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 > 0) { 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"> import { T } from '@threlte/core' import { AutoColliders } from '@threlte/rapier'</script>
<T.Group position={[0, -0.5, 0]}> <AutoColliders shape={'cuboid'}> <T.Mesh receiveShadow> <T.BoxGeometry args={[10, 1, 10]} /> <T.MeshStandardMaterial /> </T.Mesh> </AutoColliders></T.Group><script lang="ts" module> const geometry = new BoxGeometry(1, 1, 1) const material = new MeshStandardMaterial() export const muted = writable(true)</script>
<script lang="ts"> import { T } from '@threlte/core' import { PositionalAudio } from '@threlte/extras' import { Collider, RigidBody, type ContactEvent } from '@threlte/rapier' import { writable } from 'svelte/store' import type { Quaternion, Vector3 } from 'three' import { BoxGeometry, MeshStandardMaterial, MathUtils } from 'three'
interface Props { position: Vector3 quaternion: Quaternion }
let { position, quaternion }: Props = $props()
const audios: { threshold: number volume: number ref: PositionalAudio | undefined source: string }[] = $state( new Array(9).fill(0).map((_, i) => { return { threshold: i / 10, ref: undefined, volume: (i + 2) / 10, source: `/audio/ball_bounce_${i + 1}.mp3` } }) )
const fireSound: ContactEvent = (event) => { if ($muted) return const volume = MathUtils.clamp((event.totalForceMagnitude - 30) / 1100, 0.1, 1) const audio = audios.find((a) => a.volume >= volume) audio?.ref?.stop?.() audio?.ref?.play?.() }</script>
<T.Group position={position.toArray()} quaternion={quaternion.toArray()}> <RigidBody type="dynamic" oncontact={fireSound} > {#each audios as audio} <PositionalAudio bind:this={audio.ref} autoplay={false} detune={600 - Math.random() * 1200} src={audio.source} volume={audio.volume} /> {/each}
<Collider contactForceEventThreshold={30} restitution={0.4} shape={'cuboid'} args={[0.5, 0.5, 0.5]} /> <T.Mesh castShadow receiveShadow {geometry} {material} /> </RigidBody></T.Group><script lang="ts"> import { T } from '@threlte/core' import { OrbitControls, AudioListener } from '@threlte/extras' import { Debug } from '@threlte/rapier' import Emitter from './Emitter.svelte' import Ground from './Ground.svelte'</script>
<T.PerspectiveCamera makeDefault position={[10, 10, 10]}> <OrbitControls enableZoom={false} /> <AudioListener /></T.PerspectiveCamera>
<T.DirectionalLight castShadow position={[8, 20, -3]}/>
<T.GridHelper args={[50]} />
<Ground />
<Debug />
<Emitter />