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: '/experience',
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({

View File

@@ -4,11 +4,15 @@
</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'
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.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({
modelUrl: {
@@ -19,135 +23,179 @@ const props = defineProps({
// Optional falls du die Decoder-Dateien woanders hast
dracoPath: {
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)
let scene, camera, renderer, controls, model
onMounted(() => {
init()
animate()
})
onUnmounted(() => {
dispose()
})
watch(() => props.modelUrl, (newUrl) => {
if (newUrl) loadModel(newUrl)
})
watch(
() => props.modelUrl,
(newUrl) => {
if (newUrl) loadModel(newUrl);
},
);
function init() {
if (!containerRef.value) return
if (!containerRef.value) return;
// Scene
scene = new THREE.Scene()
scene.background = new THREE.Color(0x1a1a2e)
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)
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)
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
// // 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 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)
const directional = new THREE.DirectionalLight(0xffffff, 1.2);
directional.position.set(5, 8, 4);
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) {
if (model) {
scene.remove(model)
scene.remove(model);
}
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath(props.dracoPath)
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(props.dracoPath);
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load(
url,
(gltf) => {
model = gltf.scene
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())
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)
model.position.sub(center);
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
},
(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!'
}
)
// 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
if (!containerRef.value || !camera || !renderer) return;
camera.aspect = containerRef.value.clientWidth / containerRef.value.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight)
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)
requestAnimationFrame(animate);
camera.position.x = camState.x;
camera.position.y = camState.y;
camera.position.z = camState.z;
// if (controls) controls.update();
if (renderer && scene && camera) renderer.render(scene, camera);
}
function dispose() {
window.removeEventListener('resize', onWindowResize)
window.removeEventListener("resize", onWindowResize);
if (renderer) {
renderer.dispose()
containerRef.value?.removeChild(renderer.domElement)
renderer.dispose();
containerRef.value?.removeChild(renderer.domElement);
}
if (controls) controls.dispose()
// if (controls) controls.dispose();
}
</script>
@@ -158,4 +206,4 @@ function dispose() {
min-height: 400px;
background: #0f0f1a;
}
</style>
</style>