Initial commit

This commit is contained in:
2026-01-28 22:51:38 +01:00
commit 217a8581ae
5 changed files with 278 additions and 0 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Project title

11
TODO.md Normal file
View File

@@ -0,0 +1,11 @@
# TODO
- Add Debug Controls
- [ ] UI
- [ ] Orbital Camera
- [ ] Grid Helper
- Import Static Model
- Setup Skeleton
- Basic Animations

49
index.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js GLB Viewer</title>
<link rel="stylesheet" href="styles/main.css">
</head>
<body>
<div id="canvas-container"></div>
<!-- Debug UI -->
<div id="debug-ui">
<h3>Debug Controls</h3>
<div class="debug-item">
<label>
<input type="checkbox" id="orbit-controls-toggle">
Orbit Controls
</label>
</div>
<div class="debug-item">
<label>
<input type="checkbox" id="show-grid-toggle" checked>
Show Grid
</label>
</div>
<div class="debug-item">
<label for="file-input" class="file-label">Load GLB Model:</label>
<input type="file" id="file-input" accept=".glb,.gltf">
</div>
<div class="debug-item">
<span class="debug-label">Model Status:</span>
<span id="model-status">No model loaded</span>
</div>
</div>
<!-- Three.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- OrbitControls -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<!-- GLTFLoader -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<!-- Your main script -->
<script src="src/main.js"></script>
</body>
</html>

125
src/main.js Normal file
View File

@@ -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();

92
styles/main.css Normal file
View File

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