@threlte/extras
<Resize>
Scales up or down a group of objects by the maximum dimension of their bounding box. Object proportions are preserved. This is particularly useful if you want to “normalize” the dimensions of an object to be in the range 0 to 1.
<script lang="ts">
import { Checkbox, Pane } from 'svelte-tweakpane-ui'
import Scene from './Scene.svelte'
import { Canvas } from '@threlte/core'
import { NoToneMapping } from 'three'
let showCylinder = $state(true)
let auto = $state(true)
</script>
<Pane
title="Resize"
position="fixed"
>
<Checkbox
label="Show Cylinder"
bind:value={showCylinder}
/>
<Checkbox
label="Auto"
bind:value={auto}
/>
</Pane>
<div>
<Canvas toneMapping={NoToneMapping}>
<Scene
{showCylinder}
{auto}
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { T, useStage, useThrelte } from '@threlte/core'
import { Align, Edges, Grid, MeshDiscardMaterial, OrbitControls, Resize } from '@threlte/extras'
let {
showCylinder,
auto
}: {
showCylinder: boolean
auto: boolean
} = $props()
// Create the stages for resizing and aligning
const { renderStage, mainStage } = useThrelte()
// Resizing must happen *before* aligning, so we need to create a new stage to orchestrate this
const resizeStage = useStage(Symbol('resize'), { after: mainStage, before: renderStage })
// Aligning must happen *after* resizing, to take the new size into account
const alignStage = useStage(Symbol('align'), { after: resizeStage, before: renderStage })
</script>
<T.PerspectiveCamera
makeDefault
position={5}
>
<OrbitControls />
</T.PerspectiveCamera>
<T.DirectionalLight
position={[5, 10, 4]}
intensity={Math.PI}
/>
<T.AmbientLight intensity={0.2} />
<Grid
cellColor="#1F3153"
cellSize={0.5}
sectionColor="#1F3153"
sectionThickness={3}
/>
<T.Mesh position.y={0.5}>
<MeshDiscardMaterial />
<T.BoxGeometry />
<Edges color="white" />
</T.Mesh>
<Align
stage={alignStage}
y={1}
auto
>
<Resize
stage={resizeStage}
{auto}
>
<T.Mesh>
<T.MeshStandardMaterial color="hotpink" />
<T.BoxGeometry />
</T.Mesh>
<T.Mesh position.y={1.5}>
<T.MeshStandardMaterial color="cyan" />
<T.SphereGeometry />
</T.Mesh>
{#if showCylinder}
<T.Mesh position.y={3}>
<T.MeshStandardMaterial color="yellow" />
<T.CylinderGeometry args={[1, 1.5, 1]} />
</T.Mesh>
{/if}
</Resize>
</Align>
Choosing the Axis
You can choose the axis by providing the axis
prop, otherwise the maximum axis
is used.
<Resize axis="x">
<T.Mesh />
</Resize>
If you use the axis
prop, the dimensions will not be normalized to the range 0 to 1 unless the
maximum axis is chosen.
Bring Your Own Box
Use the box
prop to provide your own Box3 instance. Doing so allows you to
capture the bounding box of the objects prior to any scaling
<script>
import { Box3 } from 'three'
const box = new Box3()
</script>
<Resize {box}>
<T.Mesh />
</Resize>
<T.Box3Helper args={[box]} />
Normalizing Multiple Objects
If you have a bunch of source objects that are all different sizes, a useful
technique is to wrap each one in a <Resize>
. Once they’ve all been normalized,
it may be easier to reason about their relative sizes.
<script lang="ts">
import Scene from './Scene.svelte'
import { Canvas } from '@threlte/core'
import { Checkbox, Pane } from 'svelte-tweakpane-ui'
let resize = true
</script>
<Pane
position="fixed"
title="objects"
>
<Checkbox
bind:value={resize}
label="resize"
/>
</Pane>
<div>
<Canvas>
<Scene {resize} />
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { OrbitControls, Resize, useGltf } from '@threlte/extras'
import { T } from '@threlte/core'
type SceneProps = {
resize?: boolean
}
let { resize = true }: SceneProps = $props()
const names = ['Duck', 'Flower', 'Fox']
const promises = Promise.all(names.map((name) => useGltf(`/models/${name}.glb`)))
const increment = (2 * Math.PI) / names.length
</script>
<T.PerspectiveCamera
makeDefault
position={[5, 5, 5]}
>
<OrbitControls />
</T.PerspectiveCamera>
<T.AmbientLight intensity={0.2} />
<T.DirectionalLight position={[1, 5, 3]} />
{#await promises then objects}
{#each objects as { scene }, i}
{@const r = increment * i}
<T.Group
position.x={Math.cos(r)}
position.z={Math.sin(r)}
>
{#if resize}
<Resize>
<T is={scene} />
</Resize>
{:else}
<T is={scene} />
{/if}
</T.Group>
{/each}
{/await}
In the example above, the fox model is much larger than the duck and flower models. It is so much larger that you may have to pull the camera back in order to see it. Using <Resize>
scales each model so that each one fits inside a unit cube. From there, their relative sizes and positions may be easier to work with.
Manually Triggering a Resize
Using the Export
You can manually trigger a resize by calling the resize
export.
<script>
import { Resize } from '@threlte/extras'
let resizeRef = $state<ReturnType<typeof useResize>>()
// ... later
resizeRef.resize()
</script>
<Resize bind:this={resizeRef}>
<!-- ... -->
</Resize>
Using the Snippet Argument
You can also use the snippet argument to trigger a resize.
<script>
import { Resize } from '@threlte/extras'
</script>
<Resize>
{#snippet children({ resize })}
<T.Mesh oncreate={resize} />
{/snippet}
</Resize>