Skip to content

<HUD>

Renders a heads-up-display (HUD). Each HUD creates a new scene rendered on top of the main scene with a separate Threlte context and camera.

The HUD component creates a partially new Threlte context, specifically a new scene and camera. Everything else in useThrelte is preserved and reused.

<script lang="ts">
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
</script>
<div>
<Canvas>
<Scene />
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { T, useTask } from '@threlte/core'
import { interactivity, useCursor, useViewport } from '@threlte/extras'
import { Mesh, Quaternion } from 'three'
interface Props {
quaternion: Quaternion
onselect: (arg: string) => void
}
let { quaternion, onselect }: Props = $props()
const viewport = useViewport()
let meshes: [Mesh, Mesh, Mesh] = [null!, null!, null!]
const boxCursor = useCursor('pointer')
const torusCursor = useCursor('pointer')
const torusKnotCursor = useCursor('pointer')
interactivity()
useTask(
() => {
for (const mesh of meshes) {
mesh.quaternion.copy(quaternion)
}
},
{ autoInvalidate: false }
)
const boxHovering = boxCursor.hovering
const torusHovering = torusCursor.hovering
const torusKnotHovering = torusKnotCursor.hovering
</script>
<T.OrthographicCamera
makeDefault
zoom={80}
position={[0, 0, 10]}
/>
<T.AmbientLight intensity={Math.PI / 2} />
<T.PointLight
position={[10, 10, 10]}
decay={0}
intensity={Math.PI * 2}
/>
<T.Mesh
bind:ref={meshes[0]}
position={[$viewport.width / 2 - 1, $viewport.height / 2 - 1, 0]}
onpointerenter={boxCursor.onPointerEnter}
onpointerleave={boxCursor.onPointerLeave}
onclick={() => onselect('box')}
scale={$boxHovering ? 1.1 : 1}
>
<T.BoxGeometry args={[0.5, 0.5, 0.5]} />
<T.MeshToonMaterial color={$boxHovering ? 'hotpink' : 'gray'} />
</T.Mesh>
<T.Mesh
bind:ref={meshes[1]}
position={[$viewport.width / 2 - 2, $viewport.height / 2 - 1, 0]}
onpointerenter={torusCursor.onPointerEnter}
onpointerleave={torusCursor.onPointerLeave}
onclick={() => onselect('torus')}
scale={$torusHovering ? 1.1 : 1}
>
<T.TorusGeometry args={[0.25, 0.1]} />
<T.MeshToonMaterial color={$torusHovering ? 'hotpink' : 'gray'} />
</T.Mesh>
<T.Mesh
bind:ref={meshes[2]}
position={[$viewport.width / 2 - 3, $viewport.height / 2 - 1, 0]}
onpointerover={torusKnotCursor.onPointerEnter}
onpointerleave={torusKnotCursor.onPointerLeave}
onclick={() => onselect('torusknot')}
scale={$torusKnotHovering ? 1.1 : 1}
>
<T.TorusKnotGeometry args={[0.215, 0.08, 256]} />
<T.MeshToonMaterial color={$torusKnotHovering ? 'hotpink' : 'gray'} />
</T.Mesh>
<script lang="ts">
import { T, useTask, useThrelte } from '@threlte/core'
import { Float, OrbitControls, HUD } from '@threlte/extras'
import { Quaternion } from 'three'
import HudScene from './HudScene.svelte'
let selected = $state('box')
let rotation = $state(0)
const quaternion = new Quaternion()
const { camera } = useThrelte()
useTask(
(delta) => {
rotation += delta
// Spin mesh to the inverse of the default cameras matrix
quaternion.copy(camera.current.quaternion).invert()
},
{ autoInvalidate: false }
)
</script>
<T.PerspectiveCamera
position={[11, 5, 11]}
makeDefault
fov={30}
>
<OrbitControls enableZoom={false} />
</T.PerspectiveCamera>
<T.DirectionalLight position={[0, 10, 10]} />
<T.AmbientLight intensity={0.6} />
<T.GridHelper args={[5]} />
<HUD>
<HudScene
{quaternion}
onselect={(arg) => {
selected = arg
}}
/>
</HUD>
<Float
speed={8}
rotation.y={rotation}
>
{#if selected === 'box'}
<T.Mesh
position.y={0.8}
scale={2}
>
<T.BoxGeometry args={[0.5, 0.5, 0.5]} />
<T.MeshToonMaterial color="turquoise" />
</T.Mesh>
{:else if selected === 'torus'}
<T.Mesh
position.y={0.8}
scale={1.8}
>
<T.TorusGeometry args={[0.25, 0.1]} />
<T.MeshToonMaterial color="turquoise" />
</T.Mesh>
{:else if selected === 'torusknot'}
<T.Mesh
position.y={0.8}
scale={1.8}
>
<T.TorusKnotGeometry args={[0.215, 0.08, 256]} />
<T.MeshToonMaterial color="turquoise" />
</T.Mesh>
{/if}
</Float>

Because creating a <HUD> is somewhat similar to creating a <Canvas>, it is recommended to use the same best practices and place all objects you want in the HUD within a new Scene component:

MyHUD.svelte
<script>
import Scene from './Scene.svelte'
</script>
<HUD>
<Scene />
</HUD>
Scene.svelte
<script>
import { T } from '@threlte/core'
</script>
<T.PerspectiveCamera
makeDefault
position={[0, 0, 0]}
oncreate={(ref) => ref.lookAt(0, 0, 0)}
/>
<T.AmbientLight />
<T.Mesh>
<T.BoxGeometry />
<T.MeshStandardMaterial />
</T.Mesh>

Props

name
type
required
description

autoRender
boolean
no
Whether the HUD should automatically render its contents

stage
Stage
no
Defaults to the render stage

toneMapping
THREE.ToneMapping
no
Defaults to the parent context toneMapping