|
@@ -0,0 +1,171 @@
|
|
|
+---
|
|
|
+title: Tweakpane
|
|
|
+description: Learn how to integrate Tweakpane with TresJS for interactive 3D controls
|
|
|
+thumbnail: /recipes/tweakpane.png
|
|
|
+---
|
|
|
+
|
|
|
+[Tweakpane](https://tweakpane.github.io/docs/) 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:
|
|
|
+
|
|
|
+```bash
|
|
|
+npm install tweakpane@^4.0.0
|
|
|
+```
|
|
|
+
|
|
|
+::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:
|
|
|
+
|
|
|
+```vue
|
|
|
+<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:
|
|
|
+
|
|
|
+```ts
|
|
|
+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:
|
|
|
+
|
|
|
+```ts
|
|
|
+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.
|
|
|
+:::
|