commit 217a8581ae4c450c25a66079952669a40d236faa Author: DerGrumpf Date: Wed Jan 28 22:51:38 2026 +0100 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..661edf8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Project title diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..00788fa --- /dev/null +++ b/TODO.md @@ -0,0 +1,11 @@ +# TODO + +- Add Debug Controls + - [ ] UI + - [ ] Orbital Camera + - [ ] Grid Helper + +- Import Static Model +- Setup Skeleton +- Basic Animations + diff --git a/index.html b/index.html new file mode 100644 index 0000000..f99f98a --- /dev/null +++ b/index.html @@ -0,0 +1,49 @@ + + + + + + Three.js GLB Viewer + + + +
+ + +
+

Debug Controls

+
+ +
+
+ +
+
+ + +
+
+ Model Status: + No model loaded +
+
+ + + + + + + + + + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..75a8bf4 --- /dev/null +++ b/src/main.js @@ -0,0 +1,125 @@ +// Scene setup +const scene = new THREE.Scene(); +scene.background = new THREE.Color(0xffffff); + +// Camera setup +const camera = new THREE.PerspectiveCamera( + 75, + window.innerWidth / window.innerHeight, + 0.1, + 1000 +); +camera.position.set(5, 5, 5); +camera.lookAt(0, 0, 0); + +// Renderer setup +const renderer = new THREE.WebGLRenderer({ antialias: true }); +renderer.setSize(window.innerWidth, window.innerHeight); +renderer.setPixelRatio(window.devicePixelRatio); +document.getElementById('canvas-container').appendChild(renderer.domElement); + +// Orbit Controls (initially disabled) +const controls = new THREE.OrbitControls(camera, renderer.domElement); +controls.enabled = false; +controls.enableDamping = true; +controls.dampingFactor = 0.05; + +// Lighting +const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); +scene.add(ambientLight); + +const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); +directionalLight.position.set(5, 10, 5); +scene.add(directionalLight); + +// Grid +const gridHelper = new THREE.GridHelper(20, 20, 0x000000, 0x888888); +scene.add(gridHelper); + +// Model container +let loadedModel = null; + +// GLB Loader +const loader = new THREE.GLTFLoader(); + +// File input handler +document.getElementById('file-input').addEventListener('change', (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const arrayBuffer = e.target.result; + + // Remove previous model if exists + if (loadedModel) { + scene.remove(loadedModel); + } + + // Load the GLB + loader.parse(arrayBuffer, '', (gltf) => { + loadedModel = gltf.scene; + + // Get bounding box + const box = new THREE.Box3().setFromObject(loadedModel); + const size = box.getSize(new THREE.Vector3()); + const center = box.getCenter(new THREE.Vector3()); + + // Calculate max dimension + const maxDim = Math.max(size.x, size.y, size.z); + + // Scale model to fit in a 4 unit cube (fits nicely with the grid) + const targetSize = 4; + const scale = targetSize / maxDim; + loadedModel.scale.setScalar(scale); + + // Recalculate box after scaling + box.setFromObject(loadedModel); + box.getCenter(center); + + // Center the model at origin on X and Z, but place bottom at y=0 + loadedModel.position.x = -center.x; + loadedModel.position.y = -box.min.y; + loadedModel.position.z = -center.z; + + scene.add(loadedModel); + document.getElementById('model-status').textContent = 'Model loaded & fitted'; + }, (error) => { + console.error('Error loading model:', error); + document.getElementById('model-status').textContent = 'Error loading model'; + }); + }; + reader.readAsArrayBuffer(file); + document.getElementById('model-status').textContent = 'Loading...'; + } +}); + +// Orbit controls toggle +document.getElementById('orbit-controls-toggle').addEventListener('change', (event) => { + controls.enabled = event.target.checked; +}); + +// Grid toggle +document.getElementById('show-grid-toggle').addEventListener('change', (event) => { + gridHelper.visible = event.target.checked; +}); + +// Animation loop +function animate() { + requestAnimationFrame(animate); + + if (controls.enabled) { + controls.update(); + } + + renderer.render(scene, camera); +} + +// Handle window resize +window.addEventListener('resize', () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); +}); + +// Start animation +animate(); diff --git a/styles/main.css b/styles/main.css new file mode 100644 index 0000000..5b5285d --- /dev/null +++ b/styles/main.css @@ -0,0 +1,92 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + overflow: hidden; + font-family: Arial, sans-serif; +} + +#canvas-container { + width: 100vw; + height: 100vh; +} + +canvas { + display: block; +} + +#debug-ui { + position: fixed; + top: 20px; + right: 20px; + background: white; + color: black; + padding: 20px; + border-radius: 8px; + font-family: Arial, sans-serif; + font-size: 14px; + min-width: 280px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + z-index: 1000; +} + +#debug-ui h3 { + margin: 0 0 15px 0; + font-size: 16px; + font-weight: bold; + color: black; + border-bottom: 2px solid #333; + padding-bottom: 8px; +} + +.debug-item { + margin-bottom: 12px; +} + +.debug-item:last-child { + margin-bottom: 0; +} + +.debug-item label { + display: flex; + align-items: center; + cursor: pointer; + color: black; +} + +.debug-item input[type="checkbox"] { + margin-right: 8px; + cursor: pointer; + width: 16px; + height: 16px; +} + +.debug-item input[type="file"] { + width: 100%; + padding: 5px; + margin-top: 5px; + font-size: 12px; + border: 1px solid #ccc; + border-radius: 4px; +} + +.file-label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: black; +} + +.debug-label { + font-weight: bold; + color: black; + margin-right: 8px; +} + +#model-status { + color: #666; + font-size: 12px; +}