<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>STL Viewer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #1a1a2e; }
#canvas { width: 100vw; height: 100vh; display: block; }
#loading {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
color: #fff; font-family: sans-serif;
pointer-events: none;
}
* { scrollbar-width: thin; scrollbar-color: #414868 #16161e; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: #16161e; }
*::-webkit-scrollbar-thumb { background: #414868; }
*::-webkit-scrollbar-corner { background: transparent; }
*::-webkit-scrollbar-button { display: none; }
</style>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="loading">Loading 3D model...</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
const params = new URLSearchParams(location.search);
const url = params.get('url');
if (!url) {
document.getElementById('loading').textContent = 'No URL provided';
throw new Error('No URL');
}
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = true;
controls.autoRotateSpeed = 1;
// Lights
scene.add(new THREE.AmbientLight(0x404040, 2));
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(5, 10, 7);
scene.add(dirLight);
const backLight = new THREE.DirectionalLight(0x4444ff, 1);
backLight.position.set(-5, -5, -5);
scene.add(backLight);
// Load STL
const loader = new STLLoader();
loader.load(url, (geometry) => {
document.getElementById('loading').style.display = 'none';
geometry.computeBoundingSphere();
const center = geometry.boundingSphere.center;
const radius = geometry.boundingSphere.radius;
const material = new THREE.MeshStandardMaterial({
color: 0x4ec9b0,
metalness: 0.3,
roughness: 0.4,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.sub(center);
scene.add(mesh);
// Auto-scale camera
camera.position.setLength(radius * 3);
controls.target.set(0, 0, 0);
controls.update();
}, undefined, (err) => {
document.getElementById('loading').textContent = 'Failed to load model';
console.error(err);
});
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>