useRopeJoint
<script> import { Canvas } from '@threlte/core' import Scene from './Scene.svelte' import { World } from '@threlte/rapier' import { Checkbox, Pane, Slider } from 'svelte-tweakpane-ui' import { NoToneMapping } from 'three'
let debug = $state(false) let damping = $state(0.8) let segments = $state(20)</script>
<Pane position="fixed" title="Rope"> <Checkbox bind:value={debug} label="Debug" /> <Slider bind:value={damping} label="Damping" min={0} max={1} step={0.01} /> <Slider bind:value={segments} label="Segments" min={2} max={20} step={1} /></Pane>
<div> <Canvas toneMapping={NoToneMapping}> <World> <Scene {debug} {damping} {segments} /> </World> </Canvas></div>
<style> div { height: 100%; }</style><script lang="ts"> import { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat' import { observe, T, useTask } from '@threlte/core' import { MeshLineGeometry, MeshLineMaterial } from '@threlte/extras' import { Collider, RigidBody, useRopeJoint } from '@threlte/rapier' import { Vector3, type Object3D, type Vector3Tuple } from 'three'
type Props = { segments: number ropeStart: Vector3Tuple ropeEnd: Vector3Tuple length: number ballRadius: number damping: number }
let { segments, ropeStart, ropeEnd, length, ballRadius, damping }: Props = $props()
const lengthBetweenSegments = length / (segments - 1)
let jointsInitialized = $state(false)
// make new Array with segments - 1 elements const joints = Array.from({ length: segments - 1 }, () => { return useRopeJoint([0, 0, 0], [0, 0, 0], lengthBetweenSegments) })
const rigidBodies = $state<RapierRigidBody[]>([]) const objects = $state<Object3D[]>([])
const start = new Vector3().fromArray(ropeStart) const end = new Vector3().fromArray(ropeEnd)
const getIntialRigidBodyPosition = (index: number) => { const t = index / (segments - 1) return start.clone().lerp(end, t) }
$effect(() => { if (rigidBodies.length !== segments || objects.length !== segments) return if (jointsInitialized) return
joints.forEach((joint, index) => { joint.rigidBodyA.set(rigidBodies[index]) joint.rigidBodyB.set(rigidBodies[index + 1]) })
jointsInitialized = true })
let points = $state( Array.from({ length: segments }, () => { return new Vector3(0, 0, 0) }) )
useTask(() => { if (!jointsInitialized) return for (let i = 0; i < objects.length; i++) { const obj = objects[i] obj?.getWorldPosition(points[i]!) } points = [...points] })
observe( () => [jointsInitialized, ropeStart, ropeEnd], ([jointsInitialized]) => { if (!jointsInitialized) return const firstRigidBody = rigidBodies.at(0) const lastRigidBody = rigidBodies.at(-1) firstRigidBody!.setNextKinematicTranslation({ x: ropeStart[0], y: ropeStart[1], z: ropeStart[2] }) lastRigidBody!.setNextKinematicTranslation({ x: ropeEnd[0], y: ropeEnd[1], z: ropeEnd[2] }) } )</script>
{#each { length: segments } as _, i (i)} <T.Group oncreate={(ref) => { ref.position.copy(getIntialRigidBodyPosition(i)) }} > <RigidBody linearDamping={damping} angularDamping={damping} type={i === 0 || i === segments - 1 ? 'kinematicPosition' : 'dynamic'} bind:rigidBody={rigidBodies[i]} > <Collider shape="ball" args={[ballRadius]} > <T.Object3D bind:ref={objects[i]!} /> </Collider> </RigidBody> </T.Group>{/each}
<T.Mesh> <MeshLineGeometry {points} shape="none" /> <MeshLineMaterial width={0.4} color="#FE3D00" /></T.Mesh><script lang="ts"> import { T } from '@threlte/core' import { Environment, Grid, interactivity, OrbitControls, type IntersectionEvent } from '@threlte/extras' import { AutoColliders, Debug } from '@threlte/rapier' import { DoubleSide, type Vector3Tuple } from 'three' import { DEG2RAD } from 'three/src/math/MathUtils.js' import Rope from './Rope.svelte'
let { debug, damping, segments }: { debug: boolean; damping: number; segments: number } = $props()
interactivity()
let ropeEnd = $state<Vector3Tuple>([0, 0, 0])
const onpointermove = (e: IntersectionEvent<PointerEvent>) => { e.point.x -= 0.2 ropeEnd = e.point.toArray() }</script>
<Environment url="/textures/equirectangular/hdr/mpumalanga_veld_puresky_1k.hdr" />
{#if debug} <Debug />{/if}
<T.PerspectiveCamera makeDefault position={[-10, 5, 10]}> <OrbitControls /></T.PerspectiveCamera>
<Grid sectionColor="#122036" cellColor="#122036" position.y={-5}/>
<T.Mesh {onpointermove} rotation.y={90 * DEG2RAD}> <T.CircleGeometry args={[5]} /> <T.MeshBasicMaterial color="#0A0F19" side={DoubleSide} /></T.Mesh>
<T.Mesh position={[-5, 0, 0]}> <T.SphereGeometry args={[0.2]} /> <T.MeshStandardMaterial color="#335086" /></T.Mesh>
<T.Mesh position={ropeEnd}> <T.SphereGeometry args={[0.2]} /> <T.MeshStandardMaterial color="#335086" /></T.Mesh>
<AutoColliders shape="cuboid"> <T.Mesh position={[-2.5, 0, -1]}> <T.BoxGeometry /> <T.MeshStandardMaterial color="#335086" /> </T.Mesh></AutoColliders>
<AutoColliders shape="cuboid"> <T.Mesh position={[-2.5, 0, 1]}> <T.BoxGeometry /> <T.MeshStandardMaterial color="#335086" /> </T.Mesh></AutoColliders>
{#key segments} <Rope ballRadius={0.2} ropeStart={[-5, 0, 0]} {ropeEnd} length={7} {segments} {damping} />{/key}Use this hook to initialize a RopeImpulseJoint. A rope joint limits the max
distance between two bodies.
<script> import { useRopeJoint, RigidBody, Collider } from '@threlte/rapier'
const { joint, rigidBodyA, rigidBodyB } = useRopeJoint({ x: 1 }, { y: 1 }, 2)</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
Section titled “Signature”const { joint: Writable<RopeImpulseJoint> rigidBodyA: Writable<RAPIER.RigidBody> rigidBodyB: Writable<RAPIER.RigidBody>} = useRopeJoint( anchorA, // Position anchorB, // Position length // Length of the rope)