Newer
Older
navi-1 / webclient / public / content-viewers / stl.html
<!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;
        }
        *::-webkit-scrollbar { width: 10px; }
        *::-webkit-scrollbar-track { width: 10px; background: #16161e; cursor: pointer; }
        *::-webkit-scrollbar-thumb { width: 10px; background: #414868; cursor: default; }
        *::-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);

        let renderer;
        try {
            renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
            renderer.setSize(innerWidth, innerHeight);
            renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
        } catch (err) {
            document.getElementById('loading').innerHTML =
                '<div style="text-align:center">'
                + '<p style="font-size:18px;margin-bottom:8px">WebGL not available</p>'
                + '<p style="font-size:14px;opacity:.7">Your browser or GPU drivers block 3D rendering.<br>'
                + 'Try enabling hardware acceleration or a different browser.</p>'
                + '</div>';
            console.error('WebGL init failed:', err);
            throw err;
        }

        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: 0xe0af68,
                metalness: 0.12,
                roughness: 0.55,
            });
            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>