Static State
Extend the StaticState class to create a new class that holds scene
configuration or any other static values that won’t change in production
(i.e. are static). Properties added within such a class are automatically
integrated into the Studio UI, allowing for easy manipulation and
visualization. Changes to these properties will automatically be reflected
in the scene and written back to the disk.
This feature is available for classes defined in *.svelte, *.svelte.ts and *.svelte.js
files.
For example, you can create a class SceneConfig that extends StaticState and
define various properties like directionalLightIntensity,
ambientLightIntensity, color, opacity, and showBox. These properties will then be
available in the Studio UI for configuration.
Here is an example:
class SceneConfig extends StaticState { /** * @min 0 * @max 10 * @step 0.1 */ directionalLightIntensity = $state(3.1) /** * @min 0 * @max 1 */ ambientLightIntensity = $state(0.13) color = $state('#fe3d00') /** * @min 0 * @max 1 */ opacity = $state(1) showBox = $state(true)}Using it in your scene yields the following UI:
<script> import { Canvas } from '@threlte/core' import Scene from './Scene.svelte' import { Studio } from '@threlte/studio' import { NoToneMapping } from 'three'</script>
<div> <Canvas toneMapping={NoToneMapping}> <Studio transient> <Scene /> </Studio> </Canvas></div>
<style> :global(body) { margin: 0; }
div { width: 100%; height: 100%; }</style><script lang="ts"> import { T, type Props } from '@threlte/core' import { RoundedBoxGeometry } from '@threlte/extras' import type { Mesh } from 'three' import { SceneConfig } from './config.svelte'
let { ...props }: Props<typeof Mesh> = $props()
const sceneConfig = new SceneConfig()</script>
<T.Mesh {...props}> <RoundedBoxGeometry radius={0.3} args={[1.3, 1.3, 1.3]} /> <T.MeshStandardMaterial color={sceneConfig.color} transparent opacity={sceneConfig.opacity} alphaToCoverage /></T.Mesh><script lang="ts"> import { T, type Props } from '@threlte/core' import type { Mesh } from 'three' import { SceneConfig } from './config.svelte'
let { ...props }: Props<typeof Mesh> = $props()
const sceneConfig = new SceneConfig()</script>
<T.Mesh {...props}> <T.IcosahedronGeometry /> <T.MeshStandardMaterial color={sceneConfig.color} transparent opacity={sceneConfig.opacity} alphaToCoverage /></T.Mesh><script lang="ts"> import { T } from '@threlte/core' import { useStaticState } from '@threlte/studio/extensions' import Box from './Box.svelte' import { SceneConfig } from './config.svelte' import Icosahedron from './Icosahedron.svelte' import Sphere from './Sphere.svelte'
const staticStateExtension = useStaticState() staticStateExtension.enableEditor()
const config = new SceneConfig()</script>
<T.PerspectiveCamera makeDefault fov={33.75} position={[0, 2, 10]} oncreate={(ref) => { ref.lookAt(0, 0, 0) }}/>
<T.DirectionalLight position={[3, 10, 7]} intensity={config.directionalLightIntensity}/>
<T.AmbientLight intensity={config.ambientLightIntensity} />
<Icosahedron position={[-2, 0, 0]} />{#if config.showBox} <Box position={[0, 0, 0]} />{/if}<Sphere position={[2, 0, 0]} /><script lang="ts"> import { T, type Props } from '@threlte/core' import type { Mesh } from 'three' import { SceneConfig } from './config.svelte'
let { ...props }: Props<typeof Mesh> = $props()
const sceneConfig = new SceneConfig()</script>
<T.Mesh {...props}> <T.SphereGeometry args={[0.8]} /> <T.MeshStandardMaterial color={sceneConfig.color} transparent opacity={sceneConfig.opacity} alphaToCoverage /></T.Mesh>import { StaticState } from '@threlte/studio'
export class SceneConfig extends StaticState { /** * @min 0 * @max 10 * @step 0.1 */ directionalLightIntensity = $state(3.1) /** * @min 0 * @max 1 */ ambientLightIntensity = $state(0.13) color = $state('#fe3d00') /** * @min 0 * @max 1 */ opacity = $state(1)
showBox = $state(true)}Example
Section titled “Example”Scenario
Section titled “Scenario”You want to create a scene that hosts three objects and you want to dial in the gap between the objects.
<script> import Icosahedron from './Icosahedron.svelte' import Sphere from './Sphere.svelte' import Box from './Box.svelte'</script>
<Icosahedron position={[-2, 0, 0]} /><Sphere position={[0, 0, 0]} /><Box position={[2, 0, 0]} />Implementation
Section titled “Implementation”Create a State Container
Section titled “Create a State Container”Create a new class SceneConfig that extends StaticState and define a
gap property. It must use $state to be reactive in order for the
changes to be reflected in the scene.
<script> import { StaticState } from '@threlte/studio' import Icosahedron from './Icosahedron.svelte' import Sphere from './Sphere.svelte' import Box from './Box.svelte'
class SceneConfig extends StaticState { gap = $state(1.5) }</script>
<Icosahedron position={[-2, 0, 0]} /><Sphere position={[0, 0, 0]} /><Box position={[2, 0, 0]} />Create an Instance
Section titled “Create an Instance”Create a new instance of SceneConfig and use it to update the position of the
objects.
<script> import { StaticState } from '@threlte/studio' import Icosahedron from './Icosahedron.svelte' import Sphere from './Sphere.svelte' import { StaticState } from '@threlte/studio'
class SceneConfig extends StaticState { gap = $state(1.5) }
const sceneConfig = new SceneConfig()</script>
<Icosahedron position={[-sceneConfig.gap, 0, 0]} /><Sphere position={[0, 0, 0]} /><Box position={[sceneConfig.gap, 0, 0]} />Bonus: Use UI Modifiers
Section titled “Bonus: Use UI Modifiers”To tweak the resulting UI, you can use JSDoc tags to add modifiers. For example,
you can add @min and @max to the gap property to restrict the range of
values that can be entered. This will yield a slider in the Studio UI.
class SceneConfig extends StaticState { /** * @min 1.5 * @max 5 */ gap = $state(2)}You’re done! Changes to the gap property in the Studio UI will automatically be
reflected in the scene and written back to the disk.
<script> import { Canvas } from '@threlte/core' import Scene from './Scene.svelte' import { Studio } from '@threlte/studio' import { NoToneMapping } from 'three'</script>
<div> <Canvas toneMapping={NoToneMapping}> <Studio transient> <Scene /> </Studio> </Canvas></div>
<style> :global(body) { margin: 0; }
div { width: 100%; height: 100%; }</style><script lang="ts"> import { T, type Props } from '@threlte/core' import { RoundedBoxGeometry } from '@threlte/extras' import type { Mesh } from 'three'
let { ...props }: Props<typeof Mesh> = $props()</script>
<T.Mesh {...props}> <RoundedBoxGeometry radius={0.2} args={[1.3, 1.3, 1.3]} /> <T.MeshStandardMaterial color="#fe3d00" /></T.Mesh><script lang="ts"> import { T, type Props } from '@threlte/core' import type { Mesh } from 'three'
let { ...props }: Props<typeof Mesh> = $props()</script>
<T.Mesh {...props}> <T.IcosahedronGeometry /> <T.MeshStandardMaterial color="#fe3d00" /></T.Mesh><script lang="ts"> import { T } from '@threlte/core' import { StaticState } from '@threlte/studio' import { useStaticState } from '@threlte/studio/extensions' import Box from './Box.svelte' import Icosahedron from './Icosahedron.svelte' import Sphere from './Sphere.svelte'
const staticStateExtension = useStaticState() staticStateExtension.enableEditor()
class SceneConfig extends StaticState { /** * @min 1.5 * @max 5 */ gap = $state(2) }
const sceneConfig = new SceneConfig()</script>
<Icosahedron position={[-sceneConfig.gap, 0, 0]} /><Box position={[0, 0, 0]} /><Sphere position={[sceneConfig.gap, 0, 0]} />
<T.PerspectiveCamera makeDefault fov={33.75} position={[0, 2, 10]} oncreate={(ref) => { ref.lookAt(0, 0, 0) }}/>
<T.DirectionalLight position={[3, 10, 7]} intensity={2.7}/>
<T.AmbientLight intensity={0.13} /><script lang="ts"> import { T, type Props } from '@threlte/core' import type { Mesh } from 'three'
let { ...props }: Props<typeof Mesh> = $props()</script>
<T.Mesh {...props}> <T.SphereGeometry args={[0.8]} /> <T.MeshStandardMaterial color="#fe3d00" /></T.Mesh>