@threlte/extras
<Decal>
A declarative component for Three’s DecalGeometry.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { Suspense } from '@threlte/extras'
import { World } from '@threlte/rapier'
import Scene from './Scene.svelte'
import { Pane, Checkbox } from 'svelte-tweakpane-ui'
let controls = $state(false)
let debug = $state(false)
</script>
<Pane
title="Decal"
position="fixed"
>
<Checkbox
label="Controls"
bind:value={controls}
/>
<Checkbox
label="Debug"
bind:value={debug}
/>
</Pane>
<div>
<Canvas>
<World>
<Suspense>
<Scene
{controls}
{debug}
/>
</Suspense>
</World>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { type Vector3Tuple, DoubleSide } from 'three'
import { T } from '@threlte/core'
import {
Decal,
TransformControls,
useTexture,
OrbitControls,
VirtualEnvironment,
useSuspense
} from '@threlte/extras'
import { AutoColliders, Collider, RigidBody } from '@threlte/rapier'
let { controls = false, debug = false } = $props()
const suspend = useSuspense()
const svelteIcon = suspend(useTexture('/icons/svelte.png'))
const threlteIcon = suspend(useTexture('/icons/mstile-150x150.png'))
let bodies = $state([])
let position = $state([0.5, 0, 0.5])
let current = 0
setInterval(() => {
current += 1
current %= bodies.length
const body = bodies[current]
body.setLinvel({ x: 0, y: 0, z: 0 })
body.setAngvel({ x: 0, y: 0, z: 0, w: 1 })
body.setTranslation(
{ x: (Math.random() - 0.5) * 0.1, y: 5, z: (Math.random() - 0.5) * 0.1 },
true
)
}, 400)
</script>
<T.PerspectiveCamera
makeDefault
position={[5, 1, 4]}
oncreate={(ref) => ref.lookAt(0, 1, 0)}
>
<OrbitControls
enablePan={false}
enableZoom={false}
enableDamping
target={[0, 1, 0]}
/>
</T.PerspectiveCamera>
<T.DirectionalLight
castShadow
position={[5, 5, 5]}
intensity={1.25}
/>
<!-- <T.BoxGeometry args={[3, 0.2, 3]} /> -->
<T.Mesh receiveShadow>
<Collider
shape={'ball'}
args={[1]}
/>
<T.SphereGeometry args={[1, 256, 128]} />
<T.MeshStandardMaterial roughness={0.1} />
{#if $svelteIcon}
<Decal
{position}
{debug}
>
{#snippet children({ ref })}
<T.MeshStandardMaterial
map={$svelteIcon}
transparent
roughness={0.2}
polygonOffset
polygonOffsetFactor={-10}
/>
{#if controls}
<TransformControls
oncreate={(ref) => {
ref.position.fromArray(position)
}}
onchange={(event) => {
if (event.target.object) event.target.object.position.toArray(position)
}}
/>
{/if}
{/snippet}
</Decal>
{/if}
</T.Mesh>
{#each { length: 20 } as _, index (index)}
<RigidBody
bind:rigidBody={bodies[index]}
oncreate={(ref) => {
ref.setTranslation({ x: 0, y: -10 + index, z: 0 })
}}
>
<T.Mesh castShadow>
<Collider
shape="ball"
args={[0.3]}
restitution={0.2}
/>
<T.SphereGeometry args={[0.3, 256, 128]} />
<T.MeshStandardMaterial roughness={0.2} />
<Decal
position={[0.35, 0.35, 0.35]}
rotation={Math.PI / 4}
scale={1}
depthTest
{debug}
>
<T.MeshStandardMaterial
map={$threlteIcon}
transparent
roughness={0.2}
polygonOffset
polygonOffsetFactor={-10}
/>
</Decal>
</T.Mesh>
</RigidBody>
{/each}
{#snippet lightformer(
color: string,
shape: 'circle' | 'plane',
size: number,
position: [number, number, number]
)}
<T.Group {position}>
<T.Mesh oncreate={(ref) => ref.lookAt(0, 0, 0)}>
{#if shape === 'circle'}
<T.CircleGeometry args={[size / 2]} />
{:else}
<T.PlaneGeometry args={[size, size]} />
{/if}
<T.MeshBasicMaterial
{color}
side={DoubleSide}
/>
</T.Mesh>
</T.Group>
{/snippet}
<VirtualEnvironment>
{@render lightformer('#FF4F4F', 'plane', 20, [0, 0, -20])}
{@render lightformer('#FFD0CB', 'circle', 5, [0, 5, 0])}
{@render lightformer('#2223FF', 'plane', 8, [-3, 0, 4])}
</VirtualEnvironment>
By default, the decal will use its parent mesh as the decal surface.
The decal projector must intersect the surface to be visible.
If you do not specifiy a rotation the decal projector will look at the parents center point. You can also pass a single number as the rotation, which will spin the decal along its surface.
Examples
Basic Example
Scene.svelte
<script lang="ts">
import { T } from '@threlte/core'
import { Decal } from '@threlte/extras'
</script>
<T.Mesh>
<T.SphereGeometry />
<T.MeshStandardMaterial />
<Decal
position={[0, 0, 0]}
rotation={[0, 0, 0]}
scale={1}
>
<!-- Will override the default material if added -->
<T.MeshBasicMaterial
map={texture}
polygonOffset
polygonOffsetFactor={-1}
/>
</Decal>
</mesh>