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
yarn add tweakpane@^4.0.0
pnpm add tweakpane@^4.0.0
Additionally, if you are working with TypeScript:
npm install --save-dev @tweakpane/core
yarn add --save-dev @tweakpane/core
pnpm add --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.