5.0.1

Tweakpane

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

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.

Installation

First, install Tweakpane v4 in your project:

npm install tweakpane@^4.0.0

Additionally, if you are working with TypeScript:

npm install --save-dev @tweakpane/core
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()
})
Always dispose of the pane instance to prevent memory leaks, especially in SPAs where components are frequently mounted/unmounted.