threlte logo
@threlte/rapier

usePrismaticJoint

Use this hook to initialize a PrismaticImpulseJoint.

A prismatic joint is a slider — it constrains two rigid bodies to translate along a single axis with optional min/max travel limits, and locks all rotation. Reach for it for pistons, hydraulic rams, elevators, sliding doors, drawers, and linear actuators.

<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="Prismatic Joint"
>
  <Button
    title="Reset"
    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 { T, useTask } from '@threlte/core'
  import { Collider, CollisionGroups, RigidBody, usePrismaticJoint } from '@threlte/rapier'

  let rail = $state<RapierRigidBody>()
  let platform = $state<RapierRigidBody>()

  const { rigidBodyA, rigidBodyB } = usePrismaticJoint([0, 0, 0], [0, 0, 0], [0, 1, 0], [-2.5, 2.5])

  $effect(() => {
    if (rail && platform) {
      rigidBodyA.set(rail)
      rigidBodyB.set(platform)
    }
  })

  const PUMP_INTERVAL = 1.4
  let elapsed = 0

  useTask((delta) => {
    if (!platform) return
    elapsed += delta
    if (elapsed >= PUMP_INTERVAL) {
      elapsed = 0
      platform.applyImpulse({ x: 0, y: 70, z: 0 }, true)
    }
  })
</script>

<CollisionGroups
  memberships={[1]}
  filter={[0]}
>
  <T.Group position={[0, 3, 0]}>
    <RigidBody
      type="fixed"
      bind:rigidBody={rail}
    >
      <Collider
        shape="cuboid"
        args={[0.15, 3, 0.15]}
      />
      <T.Mesh>
        <T.BoxGeometry args={[0.3, 6, 0.3]} />
        <T.MeshStandardMaterial
          color="#444"
          metalness={0.7}
          roughness={0.3}
        />
      </T.Mesh>
    </RigidBody>
  </T.Group>

  <T.Group position={[0, 0.5, 0]}>
    <RigidBody bind:rigidBody={platform}>
      <Collider
        shape="cuboid"
        args={[1.5, 0.2, 1]}
        density={5}
        friction={1.5}
      />
      <T.Mesh
        castShadow
        receiveShadow
      >
        <T.BoxGeometry args={[3, 0.4, 2]} />
        <T.MeshStandardMaterial
          color="#222"
          metalness={0.6}
          roughness={0.4}
        />
      </T.Mesh>
    </RigidBody>
  </T.Group>
</CollisionGroups>
<script lang="ts">
  import { T } from '@threlte/core'
  import { OrbitControls } from '@threlte/extras'
  import { Collider, Debug, RigidBody } from '@threlte/rapier'
  import Press from './Press.svelte'

  interface Props {
    debug: boolean
    resetKey: number
  }

  let { debug, resetKey }: Props = $props()

  const cubes: [number, number, number][] = [
    [-1.1, 1.0, -0.6],
    [-0.5, 1.0, 0.5],
    [0.2, 1.0, -0.3],
    [0.8, 1.0, 0.4],
    [1.2, 1.0, -0.5]
  ]
</script>

<T.PerspectiveCamera
  makeDefault
  position={[5, 4, 10]}
  fov={50}
>
  <OrbitControls
    enableDamping
    enableZoom={false}
    target={[0, 2.5, 0]}
  />
</T.PerspectiveCamera>

<T.DirectionalLight
  castShadow
  position={[8, 20, -3]}
/>
<T.AmbientLight intensity={0.4} />

{#if debug}
  <Debug />
{/if}

{#key resetKey}
  <Press />

  {#each cubes as pos (pos)}
    <T.Group position={pos}>
      <RigidBody>
        <Collider
          shape="cuboid"
          args={[0.15, 0.15, 0.15]}
          density={3}
          friction={1.5}
        />
        <T.Mesh castShadow>
          <T.BoxGeometry args={[0.3, 0.3, 0.3]} />
          <T.MeshStandardMaterial color="#FE3D00" />
        </T.Mesh>
      </RigidBody>
    </T.Group>
  {/each}
{/key}

<T.Group position={[0, -0.5, 0]}>
  <RigidBody type="fixed">
    <Collider
      shape="cuboid"
      args={[10, 0.5, 5]}
    />
    <T.Mesh receiveShadow>
      <T.BoxGeometry args={[20, 1, 10]} />
      <T.MeshStandardMaterial color="#888" />
    </T.Mesh>
  </RigidBody>
</T.Group>
<script>
  import { usePrismaticJoint, RigidBody, Collider } from '@threlte/rapier'

  const { joint, rigidBodyA, rigidBodyB } = usePrismaticJoint({ x: 1 }, {}, { y: 1 }, [0, 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<PrismaticImpulseJoint>
	rigidBodyA: Writable<RAPIER.RigidBody>
	rigidBodyB: Writable<RAPIER.RigidBody>
} = usePrismaticJoint(
	anchorA,  // Position
  anchorB,  // Position
  axis,     // Rotation
  limits    // [min, max] | undefined
)