camera-controls
You may have come up against limitations with Three.js’s <OrbitalControls/>
.
Camera Controls is an alternative
which supports smooth transitions and a variety of features.
Thes example below demonstrates how to incorporate the camera controls library into threlte.
<script lang="ts">
import Scene from './Scene.svelte'
import type CameraControls from './CameraControls.svelte'
import { Button, Checkbox, Pane, Separator } from 'svelte-tweakpane-ui'
import { Canvas } from '@threlte/core'
import { DEG2RAD } from 'three/src/math/MathUtils.js'
import { Mesh } from 'three'
const mesh = new Mesh()
let controls: CameraControls
</script>
<Pane
title="Camera Controls"
position="fixed"
>
<Button
title="rotate theta 45deg"
on:click={() => {
controls?.rotate(45 * DEG2RAD, 0, true)
}}
/>
<Button
title="rotate theta -90deg"
on:click={() => {
controls?.rotate(-90 * DEG2RAD, 0, true)
}}
/>
<Button
title="rotate theta 360deg"
on:click={() => {
controls?.rotate(360 * DEG2RAD, 0, true)
}}
/>
<Button
title="rotate phi 20deg"
on:click={() => {
controls?.rotate(0, 20 * DEG2RAD, true)
}}
/>
<Separator />
<Button
title="truck(1, 0)"
on:click={() => {
controls?.truck(1, 0, true)
}}
/>
<Button
title="truck(0, 1)"
on:click={() => {
controls?.truck(0, 1, true)
}}
/>
<Button
title="truck(-1, -1)"
on:click={() => {
controls?.truck(-1, -1, true)
}}
/>
<Separator />
<Button
title="dolly 1"
on:click={() => {
controls?.dolly(1, true)
}}
/>
<Button
title="dolly -1"
on:click={() => {
controls?.dolly(-1, true)
}}
/>
<Separator />
<Button
title="zoom `camera.zoom / 2`"
on:click={() => {
controls?.zoom(controls.camera.zoom / 2, true)
}}
/>
<Button
title="zoom `- camera.zoom / 2`"
on:click={() => {
controls?.zoom(-controls.camera.zoom / 2, true)
}}
/>
<Separator />
<Button
title="move to ( 3, 5, 2)"
on:click={() => {
controls?.moveTo(3, 5, 2, true)
}}
/>
<Button
title="fit to the bounding box of the mesh"
on:click={() => {
controls?.fitToBox(mesh, true)
}}
/>
<Separator />
<Button
title="set position to ( -5, 2, 1 )"
on:click={() => {
controls?.setPosition(-5, 2, 1, true)
}}
/>
<Button
title="look at ( 3, 0, -3 )"
on:click={() => {
controls?.setTarget(3, 0, -3, true)
}}
/>
<Button
title="move to ( 1, 2, 3 ), look at ( 1, 1, 0 )"
on:click={() => {
controls?.setLookAt(1, 2, 3, 1, 1, 0, true)
}}
/>
<Separator />
<Button
title="move to somewhere between ( -2, 0, 0 ) -> ( 1, 1, 0 ) and ( 0, 2, 5 ) -> ( -1, 0, 0 )"
on:click={() => {
controls?.lerpLookAt(-2, 0, 0, 1, 1, 0, 0, 2, 5, -1, 0, 0, Math.random(), true)
}}
/>
<Separator />
<Button
title="reset"
on:click={() => {
controls?.reset(true)
}}
/>
<Button
title="saveState"
on:click={() => {
controls?.saveState()
}}
/>
<Separator />
{#if controls !== undefined}
<Checkbox
bind:value={controls.enabled}
label="enabled"
/>
{/if}
</Pane>
<div>
<Canvas>
<Scene
{mesh}
bind:controls
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
import { useTask, useThrelte } from '@threlte/core'
import CC from 'camera-controls'
import { onDestroy } from 'svelte'
import type { OrthographicCamera, PerspectiveCamera } from 'three'
import {
Box3,
Matrix4,
Quaternion,
Raycaster,
Sphere,
Spherical,
Vector2,
Vector3,
Vector4
} from 'three'
export default class CameraControls extends CC {
static installed = false
constructor(camera: OrthographicCamera | PerspectiveCamera, element: HTMLElement) {
if (!CameraControls.installed) {
CC.install({
THREE: {
Box3,
Matrix4,
Quaternion,
Raycaster,
Sphere,
Spherical,
Vector2,
Vector3,
Vector4
}
})
CameraControls.installed = true
}
super(camera)
const { invalidate } = useThrelte()
this.connect(element)
onDestroy(() => {
this.dispose()
})
useTask(
(delta) => {
if (this.update(delta)) {
invalidate()
}
},
{ autoInvalidate: false }
)
}
}
<script lang="ts">
import { T, useThrelte } from '@threlte/core'
import { Grid } from '@threlte/extras'
import CC from 'camera-controls'
import { Mesh, PerspectiveCamera } from 'three'
import CameraControls from './CameraControls.svelte'
const { dom } = useThrelte()
type Props = {
controls: CC
mesh: Mesh
}
let { controls = $bindable(), mesh }: Props = $props()
const camera = new PerspectiveCamera()
controls = new CameraControls(camera, dom)
controls.setPosition(5, 5, 5)
</script>
<T
is={camera}
makeDefault
/>
<T.DirectionalLight position={[3, 10, 7]} />
<T
is={mesh}
position.y={0.5}
>
<T.BoxGeometry />
<T.MeshBasicMaterial
color="#ff3e00"
wireframe
/>
</T>
<Grid
sectionColor="#ff3e00"
sectionThickness={1}
cellColor={'#cccccc'}
gridSize={40}
/>
The CameraControls
class in the example extends the camera controls library’s
CameraControls
class.
It automatically installs camera-controls
if not already installed, connects
and disconnects to the dom element that is sent into its constructor, updates
the controls and potentially invalidates the scene and cleans up after itself
when the component it is instantiated in is destroyed.
The camera-controls package features include first-person, third-person, pointer-lock, fit-to-bounding-sphere and much more!