@threlte/rapier
<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
<script
lang="ts"
context="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'
export let type: GravityType = 'static'
let hide = false
export const reset = () => {
hide = true
setTimeout(() => (hide = false))
}
const config: any = {
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
enableZoom={true}
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 { useTweakpane } from '$lib/useTweakpane'
let reset: (() => void) | undefined
const { pane, action, addInput, addButton } = useTweakpane({
title: 'Attractor'
})
let tabIndex = 0
$: showAdvanced = tabIndex === 1
addButton({
title: 'Reset',
label: 'Reset the scene',
onClick: () => {
reset?.()
}
})
const showHelper = addInput({
label: 'Show helper',
value: false
})
const tab = pane.addTab({
pages: [
{
title: 'Basic'
},
{
title: 'Advanced'
}
]
})
tab.on('select', (e: { index: number }) => {
tabIndex = e.index
})
const basicPage = tab.pages[0]
const strengthLeft = addInput({
label: 'Strength left',
value: 1,
params: {
min: -5,
max: 5,
step: 0.1
},
parent: basicPage
})
const strengthCenter = addInput({
label: 'Strength center',
value: 1,
params: {
min: -5,
max: 5,
step: 0.1
},
parent: basicPage
})
const strengthRight = addInput({
label: 'Strength right',
value: 1,
params: {
min: -5,
max: 5,
step: 0.1
},
parent: basicPage
})
const advancedPage = tab.pages[1]
const gravityTypes: GravityType[] = ['static', 'linear', 'newtonian']
let gravityType: GravityType = gravityTypes[0]
addButton({
title: gravityTypes[0],
label: 'Set Gravity Type',
parent: advancedPage,
onClick: () => {
gravityType = gravityTypes[0]
}
})
addButton({
title: gravityTypes[1],
label: ' ',
parent: advancedPage,
onClick: () => {
gravityType = gravityTypes[1]
}
})
addButton({
title: gravityTypes[2],
label: ' ',
parent: advancedPage,
onClick: () => {
gravityType = gravityTypes[2]
}
})
</script>
<div use:action />
<Canvas>
<World gravity={[0, showAdvanced ? 0 : -3, 0]}>
{#if $showHelper}
<Debug />
{/if}
{#if showAdvanced}
<AdvancedScene
type={gravityType}
bind:reset
/>
{:else}
<BasicScene
bind:reset
strengthLeft={$strengthLeft}
strengthCenter={$strengthCenter}
strengthRight={$strengthRight}
/>
{/if}
<HTML
slot="fallback"
transform
>
<p>
It seems your browser<br />
doesn't support WASM.<br />
I'm sorry.
</p>
</HTML>
</World>
</Canvas>
<style>
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'
let count: number = 50
export let strengthLeft: number
export let strengthCenter: number
export let strengthRight: number
export const reset = () => {
count = 0
setTimeout(() => (count = 50))
}
</script>
<T.PerspectiveCamera
makeDefault
position.y={50}
position.z={100}
fov={70}
far={10000}
>
<OrbitControls
enableZoom={true}
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"
context="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 } from 'three'
export let count: number = 20
export let rangeX: [number, number] = [-20, 20]
export let rangeY: [number, number] = [-20, 20]
export let rangeZ: [number, number] = [-20, 20]
const getId = () => {
return Math.random().toString(16).slice(2)
}
const randomNumber = (min: number, max: number): number => {
return Math.random() * (max - min) + min
}
const getRandomPosition = (): Parameters<Vector3['set']> => {
return new Vector3(
randomNumber(rangeX[0], rangeX[1]),
randomNumber(rangeY[0], rangeY[1]),
randomNumber(rangeZ[0], rangeZ[1])
).toArray()
}
const generateBodies = (c: number) => {
return Array(c)
.fill('x')
.map((_) => {
return {
id: getId(),
position: getRandomPosition()
}
})
}
$: bodies = generateBodies(count)
</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
Static (Default)
Static gravity means that the same force (strength
) is applied on all rigid-bodies within range, regardless of distance.
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
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)
.
gravitationalConstant
defaults to 6.673e-11 but you can provide your ownmass1
here is the “mass” of the Attractor, which is just thestrength
propertymass2
is 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.distance
is the distance between the attractor and rigid-body at the time of calculation
Debugging
The <Debug />
component will activate a wireframe helper to visualize the attractor’s range.