Newer
Older
navi-1 / webclient / dist / 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);
        }

        if (renderer) {
            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>