starting gspa with three js

This commit is contained in:
T-A-H-prog
2026-04-12 20:20:37 +02:00
parent b43676f131
commit ae0df5b86e
4 changed files with 136 additions and 88 deletions

View File

@@ -7,7 +7,7 @@ const routes: RouteRecordRaw[] = [
{ path: '/', component: HomeView }, { path: '/', component: HomeView },
{ path: '/experience', { path: '/experience',
component: ExperienceView, component: ExperienceView,
props: { modelUrl: '/models/LuebeckRoom_materials-draco.glb', dracoPath: '/draco/' } } props: { modelUrl: '/models/door_room_firstVersion_materials-draco.glb', dracoPath: '/draco/' } }
] ]
const router = createRouter({ const router = createRouter({

View File

@@ -4,11 +4,15 @@
</template> </template>
<script setup> <script setup>
import { onMounted, onUnmounted, ref, watch } from 'vue' import { onMounted, onUnmounted, ref, watch } from "vue";
import * as THREE from 'three' import * as THREE from "three";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import gsap from "gsap";
import ScrollTrigger from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const props = defineProps({ const props = defineProps({
modelUrl: { modelUrl: {
@@ -19,135 +23,179 @@ const props = defineProps({
// Optional falls du die Decoder-Dateien woanders hast // Optional falls du die Decoder-Dateien woanders hast
dracoPath: { dracoPath: {
type: String, type: String,
default: '/draco/' // ← WICHTIG: Passe den Pfad an! default: "/draco/", // ← WICHTIG: Passe den Pfad an!
} },
}) });
const containerRef = ref(null);
let ctx;
let scene, camera, renderer, controls, model;
const camState = {
x: 0,
y: 0,
z: 10,
};
const containerRef = ref(null) watch(
let scene, camera, renderer, controls, model () => props.modelUrl,
(newUrl) => {
onMounted(() => { if (newUrl) loadModel(newUrl);
init() },
animate() );
})
onUnmounted(() => {
dispose()
})
watch(() => props.modelUrl, (newUrl) => {
if (newUrl) loadModel(newUrl)
})
function init() { function init() {
if (!containerRef.value) return if (!containerRef.value) return;
// Scene // Scene
scene = new THREE.Scene() scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e) scene.background = new THREE.Color(0x1a1a2e);
// Camera // Camera
camera = new THREE.PerspectiveCamera( camera = new THREE.PerspectiveCamera(
50, 50,
containerRef.value.clientWidth / containerRef.value.clientHeight, containerRef.value.clientWidth / containerRef.value.clientHeight,
0.1, 0.1,
1000 1000,
) );
camera.position.set(0, 1.5, 4) // camera.position.set(0, 1.5, 4);
// Renderer // Renderer
renderer = new THREE.WebGLRenderer({ antialias: true }) renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight) renderer.setSize(
renderer.setPixelRatio(window.devicePixelRatio) containerRef.value.clientWidth,
renderer.outputColorSpace = THREE.SRGBColorSpace containerRef.value.clientHeight,
containerRef.value.appendChild(renderer.domElement) );
renderer.setPixelRatio(window.devicePixelRatio);
renderer.outputColorSpace = THREE.SRGBColorSpace;
containerRef.value.appendChild(renderer.domElement);
// Controls // // Controls
controls = new OrbitControls(camera, renderer.domElement) // controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true // controls.enableDamping = true;
controls.dampingFactor = 0.05 // controls.dampingFactor = 0.05;
controls.minDistance = 1 // controls.minDistance = 1;
controls.maxDistance = 50 // controls.maxDistance = 50;
// Licht // Licht
const ambient = new THREE.AmbientLight(0xffffff, 0.8) const ambient = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(ambient) scene.add(ambient);
const directional = new THREE.DirectionalLight(0xffffff, 1.2) const directional = new THREE.DirectionalLight(0xffffff, 1.2);
directional.position.set(5, 8, 4) directional.position.set(5, 8, 4);
scene.add(directional) scene.add(directional);
window.addEventListener('resize', onWindowResize) window.addEventListener("resize", onWindowResize);
loadModel(props.modelUrl) loadModel(props.modelUrl);
} }
function setupScroll() {
if (!camera) return;
ScrollTrigger.refresh();
gsap.to(camState, {
z: 2, // Kamera fährt rein
ease: "none",
scrollTrigger: {
trigger: document.body,
start: "top top",
end: "+=2000",
scrub: true,
invalidateOnRefresh: true,
// markers: true,
},
});
}
onMounted(() => {
init();
animate();
requestAnimationFrame(() => {
setupScroll();
});
});
onUnmounted(() => {
dispose();
});
function loadModel(url) { function loadModel(url) {
if (model) { if (model) {
scene.remove(model) scene.remove(model);
} }
const dracoLoader = new DRACOLoader() const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(props.dracoPath) dracoLoader.setDecoderPath(props.dracoPath);
const loader = new GLTFLoader() const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader) loader.setDRACOLoader(dracoLoader);
loader.load( loader.load(
url, url,
(gltf) => { (gltf) => {
model = gltf.scene model = gltf.scene;
const box = new THREE.Box3().setFromObject(model) const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3()) const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3()) const size = box.getSize(new THREE.Vector3());
model.position.sub(center) 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) camera.position.set(0, 0, 10);
camera.near = 0.01;
camera.far = 1000;
camera.updateProjectionMatrix();
document.title = 'Model geladen' // Fertigmeldung im Tab // const maxDim = Math.max(size.x, size.y, size.z);
}, // const scale = 2 / maxDim;
(progress) => { // model.scale.multiplyScalar(scale);
const percent = (progress.loaded / progress.total * 100).toFixed(1)
console.log(`Loading: ${percent}%`) scene.add(model);
document.title = `${percent}%`
}, document.title = "Model geladen"; // Fertigmeldung im Tab
(error) => { },
console.error('Fehler beim Laden des Modells:', error) (progress) => {
document.title = 'Fehler beim Laden!' 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() { function onWindowResize() {
if (!containerRef.value || !camera || !renderer) return if (!containerRef.value || !camera || !renderer) return;
camera.aspect = containerRef.value.clientWidth / containerRef.value.clientHeight camera.aspect =
camera.updateProjectionMatrix() containerRef.value.clientWidth / containerRef.value.clientHeight;
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight) camera.updateProjectionMatrix();
renderer.setSize(
containerRef.value.clientWidth,
containerRef.value.clientHeight,
);
} }
function animate() { function animate() {
requestAnimationFrame(animate) requestAnimationFrame(animate);
if (controls) controls.update() camera.position.x = camState.x;
if (renderer && scene && camera) renderer.render(scene, camera) camera.position.y = camState.y;
camera.position.z = camState.z;
// if (controls) controls.update();
if (renderer && scene && camera) renderer.render(scene, camera);
} }
function dispose() { function dispose() {
window.removeEventListener('resize', onWindowResize) window.removeEventListener("resize", onWindowResize);
if (renderer) { if (renderer) {
renderer.dispose() renderer.dispose();
containerRef.value?.removeChild(renderer.domElement) containerRef.value?.removeChild(renderer.domElement);
} }
if (controls) controls.dispose() // if (controls) controls.dispose();
} }
</script> </script>