document.addEventListener('DOMContentLoaded', () => { // Initialize Three.js scene let scene, camera, renderer, controls; let currentModel = null; let isARSupported = false; let isInARMode = false; // Model gallery data const modelData = [ { id: 'bamboo', title: 'Bamboo Cluster', description: 'A peaceful cluster of bamboo stalks', thumbnail: 'https://static.photos/nature/640x360/1', path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/bamboo.glb' }, { id: 'bonsai', title: 'Zen Bonsai', description: 'A meticulously crafted bonsai tree', thumbnail: 'https://static.photos/nature/640x360/2', path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/bonsai.glb' }, { id: 'fern', title: 'Lush Fern', description: 'A vibrant green fern plant', thumbnail: 'https://static.photos/nature/640x360/3', path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/fern.glb' }, { id: 'lotus', title: 'Floating Lotus', description: 'A beautiful water lotus flower', thumbnail: 'https://static.photos/nature/640x360/4', path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/lotus.glb' }, { id: 'palm', title: 'Tropical Palm', description: 'A tall tropical palm tree', thumbnail: 'https://static.photos/nature/640x360/5', path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/palm.glb' }, { id: 'sakura', title: 'Cherry Blossom', description: 'A delicate cherry blossom tree', thumbnail: 'https://static.photos/nature/640x360/6', path: 'https://cdn.glitch.global/8e8b5d9a-7e4d-4a8b-9e8f-8f8e8f8e8f8e/sakura.glb' } ]; // Check for WebXR support function checkXRSupport() { if ('xr' in navigator) { navigator.xr.isSessionSupported('immersive-ar').then((supported) => { isARSupported = supported; document.getElementById('xr-button').style.display = supported ? 'block' : 'none'; }); } } // Initialize Three.js scene function initScene() { const canvas = document.getElementById('ar-viewport'); // Scene scene = new THREE.Scene(); // Camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 1.6, 3); // Renderer renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); renderer.xr.enabled = true; // Lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6); directionalLight.position.set(0, 10, 5); scene.add(directionalLight); // Controls (for non-AR mode) controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.25; // Load default model loadModel(modelData[0].path); // Check XR support checkXRSupport(); // Start animation loop animate(); // Hide loading screen setTimeout(() => { document.getElementById('loading').style.display = 'none'; }, 1500); } // Load 3D model function loadModel(path) { const loader = new THREE.GLTFLoader(); if (currentModel) { scene.remove(currentModel); } loader.load(path, (gltf) => { currentModel = gltf.scene; currentModel.scale.set(0.5, 0.5, 0.5); currentModel.position.set(0, 0, 0); scene.add(currentModel); }, undefined, (error) => { console.error('Error loading model:', error); }); } // Animation loop function animate() { requestAnimationFrame(animate); if (!isInARMode) { controls.update(); } renderer.render(scene, camera); } // Initialize AR session function startARSession() { if (!isARSupported) return; const sessionInit = { optionalFeatures: ['dom-overlay', 'dom-overlay-for-handheld-ar'] }; navigator.xr.requestSession('immersive-ar', sessionInit).then((session) => { isInARMode = true; document.getElementById('ar-controls').classList.remove('hidden'); renderer.xr.setSession(session); // Add reticle for placing objects const reticle = new THREE.Mesh( new THREE.RingGeometry(0.15, 0.2, 32).rotateX(-Math.PI / 2), new THREE.MeshBasicMaterial({ color: 0xffffff }) ); reticle.matrixAutoUpdate = false; reticle.visible = false; scene.add(reticle); // Handle session end session.addEventListener('end', () => { isInARMode = false; document.getElementById('ar-controls').classList.add('hidden'); currentModel.position.set(0, 0, 0); }); // Handle select events (placing objects) session.addEventListener('select', () => { if (currentModel && reticle.visible) { currentModel.position.setFromMatrixPosition(reticle.matrix); } }); }); } // Event listeners document.getElementById('xr-button').addEventListener('click', startARSession); document.getElementById('rotate-btn').addEventListener('click', () => { if (currentModel) { currentModel.rotation.y += Math.PI / 4; } }); document.getElementById('scale-btn').addEventListener('touchstart', (e) => { if (e.touches.length === 2 && currentModel) { const touch1 = e.touches[0]; const touch2 = e.touches[1]; const dist1 = Math.hypot( touch2.pageX - touch1.pageX, touch2.pageY - touch1.pageY ); function handleMove(e) { const touch1 = e.touches[0]; const touch2 = e.touches[1]; const dist2 = Math.hypot( touch2.pageX - touch1.pageX, touch2.pageY - touch1.pageY ); const scale = dist2 / dist1; currentModel.scale.set(scale, scale, scale); } function handleEnd() { document.removeEventListener('touchmove', handleMove); document.removeEventListener('touchend', handleEnd); } document.addEventListener('touchmove', handleMove); document.addEventListener('touchend', handleEnd); } }); document.getElementById('place-btn').addEventListener('click', () => { // In AR mode, this would place the object at the reticle position if (isInARMode && currentModel) { currentModel.position.set(0, 0, -1); } }); // Populate model gallery function populateModelGallery() { const gallery = document.getElementById('model-gallery'); modelData.forEach((model) => { const card = document.createElement('div'); card.className = 'model-card bg-white dark:bg-gray-800 rounded-xl overflow-hidden shadow-md cursor-pointer transition-all'; card.innerHTML = `
${model.description}