Files
th-website/src/views/ExperienceView.vue
2026-03-04 22:05:06 +01:00

161 lines
3.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 3DModelViewer.vue -->
<template>
<div ref="containerRef" class="three-container"></div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, watch } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
const props = defineProps({
modelUrl: {
type: String,
required: true,
// Beispiel: '/models/dein-modell.glb' oder '/models/dein-modell.gltf'
},
// Optional falls du die Decoder-Dateien woanders hast
dracoPath: {
type: String,
default: '/draco/' // ← WICHTIG: Passe den Pfad an!
}
})
const containerRef = ref(null)
let scene, camera, renderer, controls, model
onMounted(() => {
init()
animate()
})
onUnmounted(() => {
dispose()
})
watch(() => props.modelUrl, (newUrl) => {
if (newUrl) loadModel(newUrl)
})
function init() {
if (!containerRef.value) return
// Scene
scene = new THREE.Scene()
scene.background = new THREE.Color(0x1a1a2e)
// Camera
camera = new THREE.PerspectiveCamera(
50,
containerRef.value.clientWidth / containerRef.value.clientHeight,
0.1,
1000
)
camera.position.set(0, 1.5, 4)
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.outputColorSpace = THREE.SRGBColorSpace
containerRef.value.appendChild(renderer.domElement)
// Controls
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
controls.minDistance = 1
controls.maxDistance = 50
// Licht
const ambient = new THREE.AmbientLight(0xffffff, 0.8)
scene.add(ambient)
const directional = new THREE.DirectionalLight(0xffffff, 1.2)
directional.position.set(5, 8, 4)
scene.add(directional)
window.addEventListener('resize', onWindowResize)
loadModel(props.modelUrl)
}
function loadModel(url) {
if (model) {
scene.remove(model)
}
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath(props.dracoPath)
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
loader.load(
url,
(gltf) => {
model = gltf.scene
const box = new THREE.Box3().setFromObject(model)
const center = box.getCenter(new THREE.Vector3())
const size = box.getSize(new THREE.Vector3())
model.position.sub(center)
const maxDim = Math.max(size.x, size.y, size.z)
const scale = 2 / maxDim
model.scale.multiplyScalar(scale)
scene.add(model)
document.title = 'Model geladen' // Fertigmeldung im Tab
},
(progress) => {
const percent = (progress.loaded / progress.total * 100).toFixed(1)
console.log(`Loading: ${percent}%`)
document.title = `${percent}%`
},
(error) => {
console.error('Fehler beim Laden des Modells:', error)
document.title = 'Fehler beim Laden!'
}
)
}
function onWindowResize() {
if (!containerRef.value || !camera || !renderer) return
camera.aspect = containerRef.value.clientWidth / containerRef.value.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight)
}
function animate() {
requestAnimationFrame(animate)
if (controls) controls.update()
if (renderer && scene && camera) renderer.render(scene, camera)
}
function dispose() {
window.removeEventListener('resize', onWindowResize)
if (renderer) {
renderer.dispose()
containerRef.value?.removeChild(renderer.domElement)
}
if (controls) controls.dispose()
}
</script>
<style scoped>
.three-container {
width: 100%;
height: 100%;
min-height: 400px;
background: #0f0f1a;
}
</style>