@threlte/core
<T>
The component <T>
provides the means to use any three.js export as a
Svelte component. It does this by leveraging the rigid three.js naming and
object property structure to add and remove objects to and from the scene graph,
attach objects to parent object properties or add event listeners.
<T>
is the main building block of any Threlte application. Components available in '@threlte/extras'
are built on top of <T>
and may provide a more convenient API for specific three.js classes.
Usage Types
Primer on Terminology
// Class definition:
class Mesh extends THREE.Object3D {
constructor(geometry, material) {
/* … */
}
}
// Creating a class instance:
const mesh = new Mesh()
// Creating a class instance with constructor arguments:
const mesh = new Mesh(geometry, material)
There are two ways to use <T>
:
- Use dot-notation to use any three.js class available at Three.js’ main
namespace
'three'
:
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh />
- Pass the property
is
to<T>
:
<script>
import { T } from '@threlte/core'
import { Mesh } from 'three'
</script>
<T is={Mesh} />
Both ways are equivalent and can be used interchangeably. The latter is more
explicit and allows you to use any class definition (even if it’s not exported
from Three.js’ main namespace 'three'
), object instance or virtually any other
value.
The next section will discuss both ways in more detail.
Dot-Notation
Any three.js class available at Three.js’ main namespace 'three'
can be used
as a component with full type-safety. The name of the import is the same as the
name of the component. For example, the class THREE.Mesh
can be used with the
component <T.Mesh>
. Let’s take a look at a simple example:
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial />
</T.Mesh>
Let’s break this down:
- The component
<T.Mesh>
creates an instance ofTHREE.Mesh
which is automatically added to the scene graph. - The component
<T.BoxGeometry>
creates an instance ofTHREE.BoxGeometry
which is automatically “attached” to the propertygeometry
of the parentTHREE.Mesh
. - The component
<T.MeshBasicMaterial>
creates an instance ofTHREE.MeshBasicMaterial
which is automatically “attached” to the propertymaterial
of the parentTHREE.Mesh
.
Extend the default component catalogue
If you want to use a class that is not available at Three.js’ main namespace
'three'
with dot-notation, you can extend the default component catalogue. Be
aware that components used this way do not offer type-safety. Once extended, the
updated catalogue is available to all components in your application.
<script>
import { T, extend, useThrelte } from '@threlte/core'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
extend({
OrbitControls
})
const { renderer } = useThrelte()
</script>
<T.PerspectiveCamera makeDefault>
{#snippet children({ ref })}
<T.OrbitControls args={[ref, renderer.domElement]} />
{/snippet}
</T.PerspectiveCamera>
is
Property To explicitly pass a class definition to the component <T>
, use the property
is
. Let’s take a look at the same example as above but using the property
is
:
<script>
import { T } from '@threlte/core'
import { Mesh, BoxGeometry, MeshBasicMaterial } from 'three'
</script>
<T is={Mesh}>
<T is={BoxGeometry} />
<T is={MeshBasicMaterial} />
</T>
The two examples are equivalent and can be used interchangeably.
The “vanilla” Three.js equivalent of both examples would be:
import { Mesh, BoxGeometry, MeshBasicMaterial } from 'three'
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial())
scene.add(mesh)
Using the property is
comes in handy when using classes that are not exported
from Three.js’ main namespace 'three'
, such as the OrbitControls
class:
<script>
import { T } from '@threlte/core'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
</script>
<T is={OrbitControls} />
What’s happening under the hood?
If a class definition such as THREE.Mesh
is provided to the property is
,
it creates an instance of that class which we call ref
– the component’s
reference to the three.js object:
<T is={Mesh} />
If a class instance (such as new THREE.Mesh()
) or any other value is
provided, the component uses this value as-is:
<script>
const mesh = new Mesh()
</script>
<T is={mesh} />
Depending on the is
property value types, Threlte makes certain assumptions:
- If the value passed to
is
is extendingTHREE.Object3D
it’s added to the scene graph. - If the value passed to
is
is a disposable, it’s disposedonDestroy
or whenever theargs
change and a newref
is created. - If the value passed to
is
is has a propertyaddEventListener
, you can add event callbacks. - If the value passed to
is
is extendingTHREE.Camera
, certain camera-related properties are available.
Props
The <T>
component has a set of fixed props (namely args
, is
, attach
,
manual
, makeDefault
and dispose
) that are used to set up the Three.js
object. On top of that, you can use arbitrary props to reactively set any
property of the underlying Three.js object.
Three.js object props
To understand how it works, let’s have a look at a simple example. Let’s say we
want to render a simple cube. We can do this by using the <T>
component:
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial />
</T.Mesh>
Using automatic attach, the geometry as
well as the material are assigned to the mesh. What if we want to change the
color of the material to "red"
? We can do this by using the color
prop:
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial color="red" />
</T.Mesh>
Keep in mind that this property is not hard-wired. We can use any property of
the underlying Three.js object as a prop on the <T>
component. For example, we
can also set the position
property of the THREE.Mesh
:
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh position={[0, 1, 0]}>
<T.BoxGeometry />
<T.MeshBasicMaterial color="red" />
</T.Mesh>
Because the property position
of a THREE.Mesh
is a THREE.Vector3
the value
we have to provide is what is passed to the set
function of
the THREE.Vector3
class. In this case, we pass an array of three numbers ([x, y, z]
). Using an editor like VS Code, you benefit from type hints and
auto-completion.
We only changed the y
coordinate of the position, so we can use a pierced
prop to only change the y
coordinate:
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh position.y={1}>
<T.BoxGeometry />
<T.MeshBasicMaterial color="red" />
</T.Mesh>
This way, we get less updates of the underlying Three.js object because the value of the prop is a primitive value which can easily be compared to the previous value. Unfortunately with pierced props we miss out on the type hints and auto-completion.
Constant prop types
The type of an inferred prop (or “auto prop”) must be constant. This means that the type of a prop must not change for the lifetime of the component. For instance you can’t use a variable as a prop that is an array of numbers and then later on change the value of that variable to a single number. This is considered a type change and therefore not allowed.
args
Three.js objects are class instances. When Instantiating these objects, they
can receive one-time constructor arguments (new THREE.SphereGeometry(1, 32)
).
In Threlte, constructor arguments are always passed as an array via the property
args
. If args
change later on, the object must naturally get reconstructed
from scratch!
- If a class definition such as
THREE.BoxGeometry
is provided to the propertyis
, the propertyargs
is used to instantiate the class:<T is={BoxGeometry} args={[1, 2, 1]}>
equalsnew BoxGeometry(1, 2, 1)
.
attach
Use attach
to attach objects to other objects.
The following attaches a material to the material property of a mesh and a geometry to the geometry property:
<T.Mesh>
<T.MeshBasicMaterial attach="material" />
<T.BoxGeometry attach="geometry" />
</T.Mesh>
All materials receive attach="material"
, and all geometries receive attach="geometry"
automatically. You do not strictly have to type it out!
- The object referenced by the
<T>
component is “attached” to a parent objects property.
<script>
import { MeshStandardMaterial } from 'three'
export let texture
</script>
<T is={MeshStandardMaterial}>
<!-- Attaches the texture to the property "map" of the parent material -->
<T
is={texture}
attach="map"
/>
</T>
attach
can be a dot-notated path to a nested parent property:
<T.DirectionalLight>
<!--
Attaches an instance of a THREE.OrthographicCamera
to the property camera of the property shadow of the
parent THREE.DirectionalLight
-->
<T.OrthographicCamera
args={[-1, 1, 1, -1, 0.1, 100]}
attach="shadow.camera"
/>
</T.DirectionalLight>
attach
can also be a function which is called when the component is created. This function receives the parent, the closest upstream parentTHREE.Object3D
and the value inferred from the propertyis
as arguments. It can return a function which is called whenever the component is destroyed or theargs
change and a newref
is created:
<T.DirectionalLight>
<!--
Attaches an instance of a THREE.OrthographicCamera
to the property camera of the property shadow of the
parent THREE.DirectionalLight
-->
<T.OrthographicCamera
args={[-1, 1, 1, -1, 0.1, 100]}
attach={({ ref, parent, parentObject3D }) => {
console.log('attaching', ref, parent, parentObject3D)
parent.shadow.camera = ref
return () => {
parent.shadow.camera = null
}
}}
/>
</T.DirectionalLight>
- You may also pass an object3D instance to the
attach
prop. This allows you to attach the object to a specific parent object, essentially acting as a portal.
<T
is={Mesh}
attach={otherObject}
/>
- To disable attaching, pass
false
. This is useful if you want to attach the object manually:
<T
is={Mesh}
attach={false}
/>
Camera Props
By default Threlte is responsive and will set up cameras properly on resize
(aspect ratio etc). Cameras can be controlled manually by setting manual
to
true
. This will opt out of projection matrix recalculation when the drawing
area resizes or other camera-related properties change.
<T
is={PerspectiveCamera}
manual
/>
Use the property makeDefault
to set a camera to the default rendering camera.
<T
is={PerspectiveCamera}
makeDefault
/>
A common mistake is to forget setting makeDefault
. If you do not set a camera to be the default
camera, the scene will not be rendered through this camera but through Threlte’s default camera.
Events
Object Events
Adding an event listener to a component will also add the corresponding event listener to the three.js class instance. The event will be forwarded and the native payload is available as the first argument to the event listener.
This will listen to the “change” event on the THREE.OrbitControls
:
<T
is={OrbitControls}
onchange={(e) => console.log('change:', e)}
/>
Create Event
All <T>
components also emit the create
event when the underlying three.js
class instance is created. This can be used to access the instance from the
parent component or do tasks on the objects upon creation. The event handler is
called with a reference to the object. You may return a cleanup callback. It
will be invoked when the component unmounts or when the object is
re-instantiated.
<T.PerspectiveCamera
oncreate={(ref) => {
// Look at the center
ref.lookAt(0, 0, 0)
return () => {
// Do something when the camera is disposed
}
}}
/>
Snippet Props
The object referenced by the component is available as the snippet prop ref
:
<T.PerspectiveCamera>
{#snippet children({ ref: camera })}
<!--
The prop "ref" is used to reference the
camera and instantiate the OrbitControls
-->
<T
is={OrbitControls}
args={[camera, renderer.domElement]}
/>
{/snippet}
</T.PerspectiveCamera>
Bindings
The object referenced by the component is available as the binding ref
:
<script>
let camera = $state()
$effect(() => {
console.log(camera) // THREE.PerspectiveCamera
})
</script>
<T.PerspectiveCamera bind:ref={camera} />
Extending the default component catalogue
By default when using the dot-notation to access Three.js objects (e.g.
<T.Mesh>
), Threlte will automatically import the corresponding class from the
namespance 'three'
. If you want to use a custom class or classes from Three.js
that are available elsewhere (like OrbitControls
), you can extend the default
catalogue with the extend
function:
<script>
import { extend, T } from '@threlte/core'
import { CustomMesh } from './MyCustomMesh.ts'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
extend({
CustomMesh
})
</script>
<T.CustomMesh />
<T>
can’t provide type-safety on
properties and events. If you need type-safety, you can use the is
property
instead: <T is={CustomMesh} />
. This way, Threlte can infer the type of the
object and provide type-safety on properties and events. Plugins
The component <T>
can be extended in functionality with Threlte
Plugins.