<Attractor>
An attractor simulates a source of gravity. Any rigid-body within range will be “pulled” toward the attractor’s center.
The force applied to rigid-bodies within range is calculated differently, depending on the gravityType.
Basic Example
Section titled “Basic Example”<script lang="ts" module> const geometry = new SphereGeometry(1) const material = new MeshBasicMaterial({ color: 'red' })</script>
<script lang="ts"> import { T } from '@threlte/core' import { OrbitControls } from '@threlte/extras' import { Attractor, Collider, RigidBody } from '@threlte/rapier' import type { GravityType } from '@threlte/rapier' import { MeshBasicMaterial, SphereGeometry } from 'three'
interface Props { type?: GravityType }
let { type = 'static' }: Props = $props()
let hide = $state(false)
export const reset = () => { hide = true setTimeout(() => (hide = false)) }
const config = { static: { type: 'static', strength: 3, range: 100, gravitationalConstant: undefined }, linear: { type: 'linear', strength: 1, range: 100, gravitationalConstant: undefined }, newtonian: { type: 'newtonian', strength: 10, range: 100, gravitationalConstant: 10 } }</script>
<T.PerspectiveCamera position.y={50} position.z={100} makeDefault fov={70} far={10000}> <OrbitControls target.y={20} /></T.PerspectiveCamera>
<T.DirectionalLight castShadow position={[8, 20, -3]}/>
<T.GridHelper args={[100]} />
{#if !hide} <T.Group position={[-50, 0, 0]}> <RigidBody linearVelocity={[5, -5, 0]}> <Collider shape="ball" args={[1]} mass={config[type].strength} /> <T.Mesh {geometry} {material} /> <Attractor range={config[type].range} gravitationalConstant={config[type].gravitationalConstant} strength={config[type].strength} gravityType={type} /> </RigidBody> </T.Group>
<RigidBody linearVelocity={[0, 5, 0]}> <Collider shape="ball" args={[1]} mass={config[type].strength} /> <T.Mesh {geometry} {material} /> <Attractor range={config[type].range} gravitationalConstant={config[type].gravitationalConstant} strength={config[type].strength} gravityType={type} /> </RigidBody>
<T.Group position={[50, 0, 0]}> <RigidBody linearVelocity={[-5, 0, 5]}> <Collider shape="ball" args={[1]} mass={config[type].strength} /> <T.Mesh {geometry} {material} /> <Attractor range={config[type].range} gravitationalConstant={config[type].gravitationalConstant} strength={config[type].strength} gravityType={type} /> </RigidBody> </T.Group>{/if}<script lang="ts"> import { Canvas } from '@threlte/core' import { HTML } from '@threlte/extras' import { World, Debug } from '@threlte/rapier' import BasicScene from './BasicScene.svelte' import type { GravityType } from '@threlte/rapier' import AdvancedScene from './AdvancedScene.svelte' import { Pane, Slider, TabGroup, TabPage, Checkbox, Button } from 'svelte-tweakpane-ui'
const gravityTypes = ['static', 'linear', 'newtonian'] as const
let scene = $state<BasicScene | AdvancedScene>() let showHelper = $state(false) let gravityType = $state<GravityType>(gravityTypes[0]) let strengthLeft = $state(1) let strengthCenter = $state(1) let strengthRight = $state(1) let tabIndex = $state(0)</script>
<Pane title="Attractor" position="fixed"> <Button title="Reset the scene" on:click={() => scene?.reset()} /> <Checkbox bind:value={showHelper} label="Show helper" /> <TabGroup bind:selectedIndex={tabIndex}> <TabPage title="Basic"> <Slider bind:value={strengthLeft} label="Strength left" min={-5} max={5} /> <Slider bind:value={strengthCenter} label="Strength center" min={-5} max={5} /> <Slider bind:value={strengthRight} label="Strength right" min={-5} max={5} /> </TabPage> <TabPage title="Advanced"> <Button label="Set Gravity Type" title="static" on:click={() => { gravityType = gravityTypes[0] }} /> <Button label="" title="linear" on:click={() => { gravityType = gravityTypes[1] }} /> <Button label="" title="newtonian" on:click={() => { gravityType = gravityTypes[2] }} /> </TabPage> </TabGroup></Pane>
<div> <Canvas> <World gravity={[0, tabIndex == 1 ? 0 : -3, 0]}> {#if showHelper} <Debug /> {/if}
{#if tabIndex == 1} <AdvancedScene bind:this={scene} type={gravityType} /> {:else} <BasicScene bind:this={scene} {strengthLeft} {strengthCenter} {strengthRight} /> {/if}
{#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 { T } from '@threlte/core' import { OrbitControls } from '@threlte/extras' import { Attractor } from '@threlte/rapier' import RandomMeshes from './RandomMeshes.svelte'
interface Props { strengthLeft: number strengthCenter: number strengthRight: number }
let { strengthLeft, strengthCenter, strengthRight }: Props = $props()
let count = $state(50)
export const reset = () => { count = 0 setTimeout(() => (count = 50)) }</script>
<T.PerspectiveCamera makeDefault position.y={50} position.z={100} fov={70} far={10000} oncreate={(ref) => ref.lookAt(0, 20, 0)}> <OrbitControls target.y={20} /></T.PerspectiveCamera>
<T.DirectionalLight castShadow position={[8, 20, -3]}/>
<T.GridHelper args={[100]} />
<RandomMeshes {count} rangeX={[-30, 30]} rangeY={[0, 75]} rangeZ={[-10, 10]}/>
<Attractor range={20} strength={strengthLeft} position={[-25, 10, 0]}/><Attractor range={15} strength={strengthCenter} position={[0, 20, 0]}/><Attractor range={20} strength={strengthRight} position={[25, 10, 0]}/><script lang="ts" module> const geometry = new SphereGeometry(1) const material = new MeshBasicMaterial({ color: 'red' })</script>
<script lang="ts"> import { T } from '@threlte/core' import { Collider, RigidBody } from '@threlte/rapier' import { MeshBasicMaterial, SphereGeometry, Vector3, MathUtils } from 'three'
interface Props { count?: number rangeX?: [number, number] rangeY?: [number, number] rangeZ?: [number, number] }
let { count = 20, rangeX = [-20, 20], rangeY = [-20, 20], rangeZ = [-20, 20] }: Props = $props()
const randomNumber = (min: number, max: number): number => { return Math.random() * (max - min) + min }
const createRandomPosition = (): Parameters<Vector3['set']> => { return new Vector3( randomNumber(rangeX[0], rangeX[1]), randomNumber(rangeY[0], rangeY[1]), randomNumber(rangeZ[0], rangeZ[1]) ).toArray() }
const bodies = $derived( Array(count) .fill('x') .map((_) => { return { id: MathUtils.generateUUID(), position: createRandomPosition() } }) )</script>
{#each bodies as body (body.id)} <T.Group position={body.position}> <RigidBody> <Collider shape="ball" args={[0.75]} mass={Math.random() * 10} /> <T.Mesh {geometry} {material} /> </RigidBody> </T.Group>{/each}Gravity Types
Section titled “Gravity Types”Static (Default)
Section titled “Static (Default)”Static gravity means that the same force (strength) is applied on all rigid-bodies within range, regardless of distance.
Linear
Section titled “Linear”Linear gravity means that force is calculated as strength * distance / range. That means the force applied increases as a rigid-body moves closer to the attractor’s center.
Newtonian
Section titled “Newtonian”Newtonian gravity uses the traditional method of calculating gravitational force (F = GMm/r^2) and as such force is calculated as gravitationalConstant * mass1 * mass2 / Math.pow(distance, 2).
gravitationalConstantdefaults to 6.673e-11 but you can provide your ownmass1here is the “mass” of the Attractor, which is just thestrengthpropertymass2is the mass of the rigid-body at the time of calculation. Note that rigid-bodies with colliders will use the mass provided to the collider. This is not a value you can control from the attractor, only from wherever you’re creating rigid-body components in the scene.distanceis the distance between the attractor and rigid-body at the time of calculation
Debugging
Section titled “Debugging”The <Debug /> component will activate a wireframe helper to visualize the attractor’s range.