@threlte/rapier
useFixedJoint
Use this hook to initialize a FixedImpulseJoint.
A fixed joint welds two rigid bodies together, locking their relative position and orientation so they behave as a single rigid unit. Reach for it when you need a composite from separate bodies — parts you may want to detach at runtime, or parts with different mass, friction, or collision properties.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { HTML } from '@threlte/extras'
import { World } from '@threlte/rapier'
import { Button, Checkbox, Pane } from 'svelte-tweakpane-ui'
import Scene from './Scene.svelte'
let debug = $state(false)
let resetKey = $state(0)
</script>
<Pane
position="fixed"
title="Fixed Joint"
>
<Button
title="Throw hammers"
on:click={() => resetKey++}
/>
<Checkbox
bind:value={debug}
label="Debug"
/>
</Pane>
<div>
<Canvas>
<World>
<Scene
{debug}
{resetKey}
/>
{#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 { useFixedJoint } from '@threlte/rapier'
import type { Vector3Tuple } from 'three'
interface Props {
bodyA: RapierRigidBody
bodyB: RapierRigidBody
anchorA: Vector3Tuple
anchorB: Vector3Tuple
}
let { bodyA, bodyB, anchorA, anchorB }: Props = $props()
const { rigidBodyA, rigidBodyB } = useFixedJoint(anchorA, [0, 0, 0], anchorB, [0, 0, 0])
$effect(() => {
rigidBodyA.set(bodyA)
rigidBodyB.set(bodyB)
})
</script>
<script lang="ts">
import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
import { T } from '@threlte/core'
import { Collider, CollisionGroups, RigidBody } from '@threlte/rapier'
import type { Vector3Tuple } from 'three'
import FixedJoint from './FixedJoint.svelte'
interface Props {
position?: Vector3Tuple
rotation?: Vector3Tuple
velocity?: Vector3Tuple
}
let {
position = [0, 6, 0],
rotation = [0, 0, Math.PI / 6],
velocity = [0, 0, 0]
}: Props = $props()
let handle = $state<RapierRigidBody>()
let head = $state<RapierRigidBody>()
</script>
<T.Group
{position}
{rotation}
>
<CollisionGroups
memberships={[1]}
filter={[0, 2]}
>
<T.Group position={[-0.6, 0, 0]}>
<RigidBody
bind:rigidBody={handle}
linearVelocity={velocity}
>
<Collider
shape="cuboid"
args={[1.2, 0.15, 0.15]}
density={0.5}
/>
<T.Mesh castShadow>
<T.BoxGeometry args={[2.4, 0.3, 0.3]} />
<T.MeshStandardMaterial color="#8B5A2B" />
</T.Mesh>
</RigidBody>
</T.Group>
<T.Group position={[1, 0, 0]}>
<RigidBody
bind:rigidBody={head}
linearVelocity={velocity}
>
<Collider
shape="cuboid"
args={[0.4, 0.4, 0.4]}
density={8}
/>
<T.Mesh castShadow>
<T.BoxGeometry args={[0.8, 0.8, 0.8]} />
<T.MeshStandardMaterial
color="#444"
metalness={0.8}
roughness={0.3}
/>
</T.Mesh>
</RigidBody>
</T.Group>
</CollisionGroups>
</T.Group>
{#if handle && head}
<FixedJoint
bodyA={handle}
bodyB={head}
anchorA={[1.2, 0, 0]}
anchorB={[-0.4, 0, 0]}
/>
{/if}
<script lang="ts">
import { T } from '@threlte/core'
import { OrbitControls, SoftShadows } from '@threlte/extras'
import { Collider, Debug, RigidBody } from '@threlte/rapier'
import Hammer from './Hammer.svelte'
import Tower from './Tower.svelte'
interface Props {
debug: boolean
resetKey: number
}
let { debug, resetKey }: Props = $props()
</script>
<T.PerspectiveCamera
makeDefault
position={[0, 7, 18]}
fov={60}
>
<OrbitControls
enableDamping
enableZoom={false}
target={[0, 2.5, 0]}
/>
</T.PerspectiveCamera>
<T.DirectionalLight
castShadow
intensity={2}
position={[8, 20, -3]}
shadow.camera.top={-20}
shadow.camera.bottom={20}
shadow.mapSize.width={1024}
shadow.mapSize.height={1024}
/>
<T.AmbientLight intensity={1} />
<SoftShadows />
{#if debug}
<Debug />
{/if}
{#key resetKey}
<Tower
position={[-3, 0, 0]}
color="#FE3D00"
/>
<Tower
position={[3, 0, 0]}
color="#335086"
jointed
/>
<Hammer
position={[-10, 3, 0]}
rotation={[0, 0, -Math.PI / 6]}
velocity={[15, 0, 0]}
/>
<Hammer
position={[10, 3, 0]}
rotation={[0, 0, Math.PI / 6]}
velocity={[-15, 0, 0]}
/>
{/key}
<T.Group position={[0, -0.5, 0]}>
<RigidBody type="fixed">
<Collider
shape="cuboid"
args={[12, 0.5, 5]}
/>
<T.Mesh receiveShadow>
<T.BoxGeometry args={[24, 1, 10]} />
<T.MeshStandardMaterial color="#888" />
</T.Mesh>
</RigidBody>
</T.Group>
<script lang="ts">
import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
import { T } from '@threlte/core'
import { Collider, CollisionGroups, RigidBody } from '@threlte/rapier'
import type { Vector3Tuple } from 'three'
import FixedJoint from './FixedJoint.svelte'
interface Props {
position?: Vector3Tuple
jointed?: boolean
color?: string
}
let { position = [0, 0, 0], jointed = false, color = '#FE3D00' }: Props = $props()
const COUNT = 5
const bodies = $state<(RapierRigidBody | undefined)[]>(Array.from({ length: 5 }, () => undefined))
const allReady = $derived(bodies.every(Boolean))
</script>
{#snippet bricks()}
<T.Group {position}>
{#each { length: 5 }, i (i)}
<T.Group position={[0, 0.5 + i, 0]}>
<RigidBody bind:rigidBody={bodies[i]}>
<Collider
shape="cuboid"
args={[0.5, 0.5, 0.5]}
/>
<T.Mesh
castShadow
receiveShadow
>
<T.BoxGeometry args={[1, 1, 1]} />
<T.MeshStandardMaterial {color} />
</T.Mesh>
</RigidBody>
</T.Group>
{/each}
</T.Group>
{/snippet}
{#if jointed}
<CollisionGroups
memberships={[2]}
filter={[0, 1]}
>
{@render bricks()}
</CollisionGroups>
{:else}
{@render bricks()}
{/if}
{#if jointed && allReady}
{#each Array(COUNT - 1) as _, i (i)}
<FixedJoint
bodyA={bodies[i]!}
bodyB={bodies[i + 1]!}
anchorA={[0.5 * (i % 2 === 1 ? -1 : 1), 0.5, 0]}
anchorB={[0, -0.5, 0]}
/>
{/each}
{/if}
<script>
import { useFixedJoint, RigidBody, Collider } from '@threlte/rapier'
const { joint, rigidBodyA, rigidBodyB } = useFixedJoint({ x: 1 }, {}, { x: -1 }, {})
</script>
<RigidBody bind:rigidBody={$rigidBodyA}>
<Collider
shape="cuboid"
args={[1, 1, 1]}
/>
</RigidBody>
<RigidBody bind:rigidBody={$rigidBodyB}>
<Collider
shape="cuboid"
args={[1, 1, 1]}
/>
</RigidBody>
Signature
const {
joint: Writable<FixedImpulseJoint>
rigidBodyA: Writable<RAPIER.RigidBody>
rigidBodyB: Writable<RAPIER.RigidBody>
} = useFixedJoint(
anchorA, // Position
frameA, // Rotation
anchorB, // Position
frameB // Rotation
)