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;
+}