Files
th-website/src/components/sections/MonitorModel.vue
2026-04-22 18:33:40 +02:00

253 lines
6.2 KiB
Vue

<script setup lang="js">
import { onMounted, onUnmounted, ref } from "vue";
import * as THREE from "three";
import gsap from "gsap";
import ScrollTrigger from "gsap/ScrollTrigger";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import surf_video from "/vids/Aus_dem_wasser.mp4";
import ski_video from "/vids/ski.mp4";
const canvasRef = ref(null);
let renderer, scene, camera, model, controls, gsapCtx;
let screen1, screen3, screen4;
const video_surf = document.createElement("video");
const video_ski = document.createElement("video");
video_surf.src = surf_video;
video_surf.loop = true;
video_surf.muted = true;
video_ski.src = ski_video;
video_ski.loop = true;
video_ski.muted = true;
function initThree(canvas) {
renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true,
});
renderer.setPixelRatio(1); //TODO
renderer.outputColorSpace = THREE.SRGBColorSpace;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, 2, 0.1, 100);
const light = new THREE.DirectionalLight(0xffe0b0, 0.9);
light.position.set(-1, 4, 5);
scene.add(light);
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.enabled = false;
// controls.minDistance = 5;
// controls.maxDistance = 5;
controls.maxPolarAngle = Math.PI / 2;
controls.minAzimuthAngle = -Math.PI / 2;
controls.maxAzimuthAngle = Math.PI / 2;
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
loadModel();
gsap.ticker.add(render);
}
function loadModel() {
if (model) {
scene.remove(model);
}
let modelUrl = "/models/room-draco.glb";
let dracoPath = "/draco/";
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(dracoPath);
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load(
modelUrl,
(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());
const texture_surf_video = new THREE.VideoTexture(video_surf);
const texure_ski = new THREE.VideoTexture(video_ski);
screen1 = model.getObjectByName("fernseher1Bild");
screen4 = model.getObjectByName("fernseher4Bild");
screen3 = model.getObjectByName("fernseher3Bild");
texture_surf_video.flipY = false;
texure_ski.flipY = false;
texure_ski.offset.set(-0.3, -0.2);
screen1.material = new THREE.MeshBasicMaterial({
map: texture_surf_video,
});
screen4.material = new THREE.MeshBasicMaterial({
map: texure_ski,
});
screen3.material = new THREE.MeshBasicMaterial({
map: texure_ski,
});
const maxSize = Math.max(size.x, size.y, size.z);
const distance = maxSize * 2;
camera.position.set(center.x, center.y, center.z + distance);
controls.target.copy(center);
controls.update();
console.log(scene);
model.position.set(0, 0, 0);
scene.add(model);
gsapCtx = scrollCamreaGsap(center);
document.title = "Model geladen";
},
(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 render() {
if (!renderer) return;
controls.update();
renderer.render(scene, camera);
}
function onResize() {
if (!renderer || !canvasRef.value) return;
const r = canvasRef.value.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const width = canvasRef.value.clientWidth;
const height = canvasRef.value.clientHeight;
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(width, height, false);
camera.aspect = (r.width || 1) / (r.height || 1);
camera.updateProjectionMatrix();
}
function scrollCamreaGsap(center) {
gsap.registerPlugin(ScrollTrigger);
const ctx = gsap.context(() => {
gsap.to(camera.position, {
z: center.z + 5,
ease: "none",
scrollTrigger: {
trigger: canvasRef.value,
start: "top top",
end: "+=2000",
scrub: 1,
pin: true,
anticipatePin: 1,
onUpdate: (self) => {
if (self.progress >= 0.8) {
controls.enabled = true;
controls.enableRotate = true;
controls.enableZoom = false;
controls.enablePan = false;
screen1.visible = true;
screen3.visible = true;
screen4.visible = true;
setVideoActive(true);
} else {
controls.enabled = false;
screen1.visible = false;
screen3.visible = false;
screen4.visible = false;
setVideoActive(false);
}
},
},
});
}, canvasRef.value);
return ctx;
}
function setVideoActive(active) {
if (active) {
video_surf.play();
video_ski.play();
} else {
video_surf.pause();
video_ski.pause();
}
}
const resizeObserver = new ResizeObserver(onResize);
onMounted(() => {
initThree(canvasRef.value);
onResize();
resizeObserver.observe(canvasRef.value);
window.addEventListener("resize", onResize);
});
onUnmounted(() => {
gsap.ticker.remove(render);
window.removeEventListener("resize", onResize);
resizeObserver.disconnect();
renderer?.dispose();
gsapCtx?.revert();
ScrollTrigger.getAll().forEach((t) => t.kill());
});
</script>
<template>
<div class="wrapper">
<!-- <div class="spacer"></div> -->
<canvas class="canvas" ref="canvasRef"></canvas>
</div>
<div class="scroll-buffer"></div>
<div class="spacer"></div>
</template>
<style>
.spacer {
width: 100%;
height: 200vh;
display: grid;
place-items: center;
font-weight: 600;
}
.wrapper {
width: 100%;
height: 100vh;
}
.canvas {
width: 100%;
height: 100%;
}
.scroll-buffer {
height: 100vh;
}
</style>