5.tweakpane.md 5.2 KB


title: Tweakpane description: Learn how to integrate Tweakpane with TresJS for interactive 3D controls

thumbnail: /recipes/tweakpane.png

Tweakpane is a compact GUI library that provides an excellent way to create interactive controls for your 3D scenes. This recipe shows you how to integrate Tweakpane with TresJS to create dynamic, real-time controls for your 3D objects and scenes.

::examples-tweakpane ::

Installation

First, install Tweakpane v4 in your project:

::code-group

npm install tweakpane@^4.0.0
yarn add tweakpane@^4.0.0
pnpm add tweakpane@^4.0.0

::

Additionally, if you are working with TypeScript:

::code-group

npm install --save-dev @tweakpane/core
yarn add --save-dev @tweakpane/core
pnpm add --save-dev @tweakpane/core

::

::tip Make sure to use Tweakpane v4 or higher, as this recipe uses the latest API which has breaking changes from v3. ::

Basic Setup

Here's how to set up Tweakpane with a basic TresJS scene:

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Pane } from 'tweakpane'
import { OrbitControls } from '@tresjs/cientos'

const meshRef = ref()
const pane = ref<Pane>()
const paneContainer = ref<HTMLElement>()

// Reactive properties that will be controlled by Tweakpane
const controls = ref({
  positionX: 0,
  positionY: 0,
  positionZ: 0,
  rotationX: 0,
  rotationY: 0,
  rotationZ: 0,
  scale: 1,
  color: '#ff6b6b',
  wireframe: false,
  material: 'basic',
})

onMounted(() => {
  if (!paneContainer.value) return

  // Create Tweakpane instance with container
  pane.value = new Pane({
    title: 'Scene Controls',
    container: paneContainer.value
  })

  // Add position controls
  const positionFolder = pane.value.addFolder({ title: 'Position' })
  positionFolder.addBinding(controls.value, 'positionX', { min: -3, max: 3, step: 0.1 })
  positionFolder.addBinding(controls.value, 'positionY', { min: -3, max: 3, step: 0.1 })
  positionFolder.addBinding(controls.value, 'positionZ', { min: -3, max: 3, step: 0.1 })

  // Add rotation controls
  const rotationFolder = pane.value.addFolder({ title: 'Rotation' })
  rotationFolder.addBinding(controls.value, 'rotationX', { min: -Math.PI, max: Math.PI, step: 0.01 })
  rotationFolder.addBinding(controls.value, 'rotationY', { min: -Math.PI, max: Math.PI, step: 0.01 })
  rotationFolder.addBinding(controls.value, 'rotationZ', { min: -Math.PI, max: Math.PI, step: 0.01 })

  // Add material controls
  const materialFolder = pane.value.addFolder({ title: 'Material' })
  materialFolder.addBinding(controls.value, 'scale', { min: 0.1, max: 3, step: 0.1 })
  materialFolder.addBinding(controls.value, 'color')
  materialFolder.addBinding(controls.value, 'wireframe')
  materialFolder.addBinding(controls.value, 'material', {
    options: {
      Basic: 'basic',
      Normal: 'normal',
      Standard: 'standard',
    },
  })

  // Add reset button
  pane.value.addButton({ title: 'Reset All' }).on('click', () => {
    Object.assign(controls.value, {
      positionX: 0, positionY: 0, positionZ: 0,
      rotationX: 0, rotationY: 0, rotationZ: 0,
      scale: 1, color: '#ff6b6b', wireframe: false, material: 'basic'
    })
  })
})

// Clean up on unmount
onUnmounted(() => {
  pane.value?.dispose()
})
</script>

<template>
  <div class="relative w-full h-full">
    <!-- Tweakpane container positioned outside canvas -->
    <div ref="paneContainer" class="absolute top-4 right-4 z-10" />

    <TresCanvas clear-color="#82DBC5">
      <TresPerspectiveCamera :position="[3, 3, 3]" />
      <OrbitControls />

      <TresMesh
        ref="meshRef"
        :position="[controls.positionX, controls.positionY, controls.positionZ]"
        :rotation="[controls.rotationX, controls.rotationY, controls.rotationZ]"
        :scale="controls.scale"
      >
        <TresBoxGeometry />
        <TresMeshBasicMaterial
          v-if="controls.material === 'basic'"
          :color="controls.color"
          :wireframe="controls.wireframe"
        />
        <TresMeshNormalMaterial
          v-else-if="controls.material === 'normal'"
          :wireframe="controls.wireframe"
        />
        <TresMeshStandardMaterial
          v-else-if="controls.material === 'standard'"
          :color="controls.color"
          :wireframe="controls.wireframe"
        />
      </TresMesh>

      <TresGridHelper :args="[10, 10]" />
      <TresAmbientLight :intensity="0.5" />
      <TresDirectionalLight :position="[0, 0, 5]" :intensity="0.5" />
    </TresCanvas>
  </div>
</template>

Monitoring Values

You can also monitor values without making them editable:

const stats = ref({
  triangles: 0,
  fps: 0,
})

const statsFolder = pane.value.addFolder({ title: 'Statistics' })
statsFolder.addMonitor(stats.value, 'triangles')
statsFolder.addMonitor(stats.value, 'fps', { interval: 100 })

Cleanup

Don't forget to dispose of the pane when the component unmounts:

import { onUnmounted } from 'vue'

onUnmounted(() => {
  pane.value?.dispose()
})

:::tip Always dispose of the pane instance to prevent memory leaks, especially in SPAs where components are frequently mounted/unmounted. :::