CoolFace
Apppublic

singlecell/algorithmic-arboretum

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
script.js259 linesDownload Raw Back to root
1document.addEventListener('DOMContentLoaded', () => {2    // Initialize Three.js scene3    let scene, camera, renderer, controls;4    let currentModel = null;5    let isARSupported = false;6    let isInARMode = false;7    8    // Model gallery data9    const modelData = [10        {11            id: 'bamboo',12            title: 'Bamboo Cluster',13            description: 'A peaceful cluster of bamboo stalks',14            thumbnail: 'https://static.photos/nature/640x360/1',15            path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/bamboo.glb'16        },17        {18            id: 'bonsai',19            title: 'Zen Bonsai',20            description: 'A meticulously crafted bonsai tree',21            thumbnail: 'https://static.photos/nature/640x360/2',22            path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/bonsai.glb'23        },24        {25            id: 'fern',26            title: 'Lush Fern',27            description: 'A vibrant green fern plant',28            thumbnail: 'https://static.photos/nature/640x360/3',29            path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/fern.glb'30        },31        {32            id: 'lotus',33            title: 'Floating Lotus',34            description: 'A beautiful water lotus flower',35            thumbnail: 'https://static.photos/nature/640x360/4',36            path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/lotus.glb'37        },38        {39            id: 'palm',40            title: 'Tropical Palm',41            description: 'A tall tropical palm tree',42            thumbnail: 'https://static.photos/nature/640x360/5',43            path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/palm.glb'44        },45        {46            id: 'sakura',47            title: 'Cherry Blossom',48            description: 'A delicate cherry blossom tree',49            thumbnail: 'https://static.photos/nature/640x360/6',50            path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/sakura.glb'51        }52    ];53 54    // Check for WebXR support55    function checkXRSupport() {56        if ('xr' in navigator) {57            navigator.xr.isSessionSupported('immersive-ar').then((supported) => {58                isARSupported = supported;59                document.getElementById('xr-button').style.display = supported ? 'block' : 'none';60            });61        }62    }63 64    // Initialize Three.js scene65    function initScene() {66        const canvas = document.getElementById('ar-viewport');67        68        // Scene69        scene = new THREE.Scene();70        71        // Camera72        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);73        camera.position.set(0, 1.6, 3);74        75        // Renderer76        renderer = new THREE.WebGLRenderer({77            canvas: canvas,78            antialias: true,79            alpha: true80        });81        renderer.setSize(window.innerWidth, window.innerHeight);82        renderer.setPixelRatio(window.devicePixelRatio);83        renderer.xr.enabled = true;84        85        // Lighting86        const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);87        scene.add(ambientLight);88        89        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);90        directionalLight.position.set(0, 10, 5);91        scene.add(directionalLight);92        93        // Controls (for non-AR mode)94        controls = new THREE.OrbitControls(camera, renderer.domElement);95        controls.enableDamping = true;96        controls.dampingFactor = 0.25;97        98        // Load default model99        loadModel(modelData[0].path);100        101        // Check XR support102        checkXRSupport();103        104        // Start animation loop105        animate();106        107        // Hide loading screen108        setTimeout(() => {109            document.getElementById('loading').style.display = 'none';110        }, 1500);111    }112 113    // Load 3D model114    function loadModel(path) {115        const loader = new THREE.GLTFLoader();116        117        if (currentModel) {118            scene.remove(currentModel);119        }120        121        loader.load(path, (gltf) => {122            currentModel = gltf.scene;123            currentModel.scale.set(0.5, 0.5, 0.5);124            currentModel.position.set(0, 0, 0);125            scene.add(currentModel);126        }, undefined, (error) => {127            console.error('Error loading model:', error);128        });129    }130 131    // Animation loop132    function animate() {133        requestAnimationFrame(animate);134        135        if (!isInARMode) {136            controls.update();137        }138        139        renderer.render(scene, camera);140    }141 142    // Initialize AR session143    function startARSession() {144        if (!isARSupported) return;145        146        const sessionInit = { optionalFeatures: ['dom-overlay', 'dom-overlay-for-handheld-ar'] };147        148        navigator.xr.requestSession('immersive-ar', sessionInit).then((session) => {149            isInARMode = true;150            document.getElementById('ar-controls').classList.remove('hidden');151            152            renderer.xr.setSession(session);153            154            // Add reticle for placing objects155            const reticle = new THREE.Mesh(156                new THREE.RingGeometry(0.15, 0.2, 32).rotateX(-Math.PI / 2),157                new THREE.MeshBasicMaterial({ color: 0xffffff })158            );159            reticle.matrixAutoUpdate = false;160            reticle.visible = false;161            scene.add(reticle);162            163            // Handle session end164            session.addEventListener('end', () => {165                isInARMode = false;166                document.getElementById('ar-controls').classList.add('hidden');167                currentModel.position.set(0, 0, 0);168            });169            170            // Handle select events (placing objects)171            session.addEventListener('select', () => {172                if (currentModel && reticle.visible) {173                    currentModel.position.setFromMatrixPosition(reticle.matrix);174                }175            });176        });177    }178 179    // Event listeners180    document.getElementById('xr-button').addEventListener('click', startARSession);181    182    document.getElementById('rotate-btn').addEventListener('click', () => {183        if (currentModel) {184            currentModel.rotation.y += Math.PI / 4;185        }186    });187    188    document.getElementById('scale-btn').addEventListener('touchstart', (e) => {189        if (e.touches.length === 2 && currentModel) {190            const touch1 = e.touches[0];191            const touch2 = e.touches[1];192            const dist1 = Math.hypot(193                touch2.pageX - touch1.pageX,194                touch2.pageY - touch1.pageY195            );196            197            function handleMove(e) {198                const touch1 = e.touches[0];199                const touch2 = e.touches[1];200                const dist2 = Math.hypot(201                    touch2.pageX - touch1.pageX,202                    touch2.pageY - touch1.pageY203                );204                205                const scale = dist2 / dist1;206                currentModel.scale.set(scale, scale, scale);207            }208            209            function handleEnd() {210                document.removeEventListener('touchmove', handleMove);211                document.removeEventListener('touchend', handleEnd);212            }213            214            document.addEventListener('touchmove', handleMove);215            document.addEventListener('touchend', handleEnd);216        }217    });218    219    document.getElementById('place-btn').addEventListener('click', () => {220        // In AR mode, this would place the object at the reticle position221        if (isInARMode && currentModel) {222            currentModel.position.set(0, 0, -1);223        }224    });225 226    // Populate model gallery227    function populateModelGallery() {228        const gallery = document.getElementById('model-gallery');229        230        modelData.forEach((model) => {231            const card = document.createElement('div');232            card.className = 'model-card bg-white dark:bg-gray-800 rounded-xl overflow-hidden shadow-md cursor-pointer transition-all';233            card.innerHTML = `234                <div class="h-48 overflow-hidden">235                    <img src="${model.thumbnail}" alt="${model.title}" class="w-full h-full object-cover">236                </div>237                <div class="p-4">238                    <h3 class="font-bold text-lg mb-1">${model.title}</h3>239                    <p class="text-gray-600 dark:text-gray-300 text-sm mb-3">${model.description}</p>240                    <button class="bg-emerald-500 hover:bg-emerald-600 text-white px-3 py-1 rounded-full text-sm transition-colors">241                        View in AR242                    </button>243                </div>244            `;245            246            card.addEventListener('click', () => loadModel(model.path));247            gallery.appendChild(card);248        });249    }250 251    // Initialize everything252    populateModelGallery();253    initScene();254    window.addEventListener('resize', () => {255        camera.aspect = window.innerWidth / window.innerHeight;256        camera.updateProjectionMatrix();257        renderer.setSize(window.innerWidth, window.innerHeight);258    });259});