@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 { Pane, Slider, TabGroup, TabPage, Checkbox, Button } from 'svelte-tweakpane-ui'
let reset: () => void = () => {}
let showHelper = false
const gravityTypes = ['static', 'linear', 'newtonian'] as const
let gravityType: GravityType = gravityTypes[0]
let strengthLeft = 1
let strengthCenter = 1
let strengthRight = 1
let tabIndex = 0
</script>
<Pane
title="Attractor"
position="fixed"
>
<Button
title="Reset the scene"
on:click={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
type={gravityType}
bind:reset
/>
{:else}
<BasicScene
bind:reset
{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'
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.