threlte logo
@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.

Scene.svelte
<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.

Scene.svelte
<script>
  import { Resize } from '@threlte/extras'
</script>

<Resize>
  {#snippet children({ resize })}
    <T.Mesh oncreate={resize} />
  {/snippet}
</Resize>

Component Signature

<Resize> extends <T . Group> and supports all its props, slot props, bindings and events.

Props

name
type
required
default
description

auto
boolean
no
false
when true, will automatically resize when children or added or removed

axis
'x' | 'y' | 'z'
no
undefined
Specifies which axis to constrain. If not provided, the maximum axis is used

box
THREE.Box3
no
new Box3()
Bring your own box to capture the bounding box.

onresize
() => void
no
undefined
a callback function to run whenever resizing is done

precise
boolean
no
undefined
If true, use precise bounding box calculation.

stage
Stage
no
useStage("<Resize>", { before: renderStage })
Bring your own stage to control when resizing occurs. If not provided, resizing will occur before the main render stage.

Exports

name
type
description

resize
() => void
Manually trigger a resize.