// API Configuration - Using a simulated API for demo purposes const API_CONFIG = { // Mock API endpoints for demonstration HITEM3D_SUBMIT: "https://api.mock3d.ai/v1/generate", HITEM3D_STATUS: "https://api.mock3d.ai/v1/status", // Alternative real APIs you can integrate: // 1. Luma AI (https://lumalabs.ai/genie) // 2. TripoSR (https://huggingface.co/spaces/stabilityai/TripoSR) // 3. Instant Mesh (https://huggingface.co/spaces/TencentARC/InstantMesh) // For demo purposes, we'll use mock responses DEMO_MODE: true, // S3 Configuration (if you want to use real S3) S3_ENDPOINT: "https://s3.anondrop.net/s3/", S3_ACCESS: "032875e7cd988fe261fd777c820ff97c", S3_SECRET: "64cd288382170f80e4f3acd1b31052486bda417b8556bce02467964956e6f511", BUCKET_NAME: "MNE" }; // Global variables let scene, camera, renderer, controls, currentModel; let currentTaskId = null; let statusCheckInterval = null; // DOM element references let fileInput, imagePreview, previewImg, generateBtn, statusContainer, statusBox, statusIcon, statusText, progressBar, progressFill; // Initialize DOM elements function initDOMElements() { fileInput = document.getElementById('fileInput'); imagePreview = document.getElementById('imagePreview'); previewImg = document.getElementById('previewImg'); generateBtn = document.getElementById('generateBtn'); statusContainer = document.getElementById('statusContainer'); statusBox = document.getElementById('statusBox'); statusIcon = document.getElementById('statusIcon'); statusText = document.getElementById('statusText'); progressBar = document.getElementById('progressBar'); progressFill = document.getElementById('progressFill'); } // Initialize AWS S3 AWS.config.update({ accessKeyId: API_CONFIG.S3_ACCESS, secretAccessKey: API_CONFIG.S3_SECRET, region: 'us-east-1' }); const s3 = new AWS.S3({ endpoint: API_CONFIG.S3_ENDPOINT, s3ForcePathStyle: true }); // Initialize Three.js viewer function initViewer() { const container = document.getElementById('viewer'); if (!container) { console.error('Viewer container not found'); return; } // Create scene scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a1a1a); // Create camera camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000); camera.position.set(0, 1, 3); // Create renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.clientWidth, container.clientHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; container.innerHTML = ''; container.appendChild(renderer.domElement); // Add controls controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.screenSpacePanning = false; controls.minDistance = 1; controls.maxDistance = 10; // Add lights const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 10, 5); directionalLight.castShadow = true; scene.add(directionalLight); const pointLight = new THREE.PointLight(0x667eea, 0.5); pointLight.position.set(-5, 5, -5); scene.add(pointLight); // Add grid const gridHelper = new THREE.GridHelper(10, 10, 0x444444, 0x222222); scene.add(gridHelper); // Add viewer controls addViewerControls(); // Start render loop animate(); // Handle resize window.addEventListener('resize', onWindowResize); } function addViewerControls() { const container = document.getElementById('viewer'); const controlsDiv = document.createElement('div'); controlsDiv.className = 'viewer-controls'; controlsDiv.innerHTML = ` `; container.appendChild(controlsDiv); feather.replace(); } function animate() { requestAnimationFrame(animate); controls.update(); // Animate demo models if (currentModel && currentModel.userData.animated) { currentModel.rotation.y += 0.005; } renderer.render(scene, camera); } function onWindowResize() { const container = document.getElementById('viewer'); camera.aspect = container.clientWidth / container.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(container.clientWidth, container.clientHeight); } function resetCamera() { camera.position.set(0, 1, 3); controls.reset(); } function toggleWireframe() { if (currentModel) { currentModel.traverse((child) => { if (child.isMesh) { child.material.wireframe = !child.material.wireframe; } }); } } function autoRotate() { controls.autoRotate = !controls.autoRotate; } // File upload handling const fileInput = document.getElementById('fileInput'); const imagePreview = document.getElementById('imagePreview'); const previewImg = document.getElementById('previewImg'); const generateBtn = document.getElementById('generateBtn'); const statusContainer = document.getElementById('statusContainer'); const statusBox = document.getElementById('statusBox'); const statusIcon = document.getElementById('statusIcon'); const statusText = document.getElementById('statusText'); const progressBar = document.getElementById('progressBar'); const progressFill = document.getElementById('progressFill'); fileInput.addEventListener('change', handleFileSelect); generateBtn.addEventListener('click', generate3DModel); function handleFileSelect(event) { const file = event.target.files[0]; if (file && file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = function(e) { if (previewImg) { previewImg.src = e.target.result; } if (imagePreview) { imagePreview.classList.remove('hidden'); imagePreview.classList.add('animate-float'); } }; reader.readAsDataURL(file); } } // Drag and drop document.addEventListener('DOMContentLoaded', () => { const uploadArea = document.querySelector('label[for="fileInput"] > div'); if (uploadArea) { uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('drag-active'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('drag-active'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('drag-active'); const files = e.dataTransfer.files; if (files.length > 0 && files[0].type.startsWith('image/')) { if (fileInput) { fileInput.files = files; handleFileSelect({ target: { files } }); } } }); } }); // 3D Model Generation async function generate3DModel() { if (!fileInput) { console.error('File input not found'); return; } const file = fileInput.files[0]; if (!file) { showStatus('error', 'Please select an image first', 'alert-circle'); return; } showStatus('processing', 'Uploading image...', 'upload-cloud'); progressBar.classList.remove('hidden'); updateProgress(20); try { // Upload to S3 const s3Url = await uploadToS3(file); updateProgress(40); // Submit generation task const taskId = await submitGenerationTask(s3Url); currentTaskId = taskId; updateProgress(60); showStatus('processing', 'Generating 3D model...', 'loader'); updateProgress(80); // Poll for completion pollTaskStatus(taskId); } catch (error) { console.error('Error:', error); showStatus('error', 'Failed to generate 3D model', 'x-circle'); progressBar.classList.add('hidden'); } } async function uploadToS3(file) { // Demo mode - simulate upload if (API_CONFIG.DEMO_MODE) { await new Promise(resolve => setTimeout(resolve, 1000)); return `https://demo-server.com/uploads/${Date.now()}_${file.name}`; } const fileName = `uploads/${Date.now()}_${file.name}`; const params = { Bucket: API_CONFIG.BUCKET_NAME, Key: fileName, Body: file, ContentType: file.type, ACL: 'public-read' }; const result = await s3.upload(params).promise(); return result.Location; } async function submitGenerationTask(imageUrl) { // Demo mode - simulate API response if (API_CONFIG.DEMO_MODE) { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 1500)); // Generate a mock task ID const taskId = 'task_' + Math.random().toString(36).substr(2, 9); // Store the image URL for later use sessionStorage.setItem('currentImage', imageUrl); sessionStorage.setItem('taskId', taskId); sessionStorage.setItem('quality', document.getElementById('quality').value); sessionStorage.setItem('format', document.getElementById('format').value); return taskId; } // Real API call (uncomment when you have real API credentials) /* const response = await fetch(API_CONFIG.HITEM3D_SUBMIT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_CONFIG.ACCESS_KEY}` }, body: JSON.stringify({ image_url: imageUrl, quality: document.getElementById('quality').value, format: document.getElementById('format').value }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Failed to submit task'); } const data = await response.json(); return data.task_id; */ throw new Error('Demo mode is disabled and no real API configured'); } function pollTaskStatus(taskId) { if (statusCheckInterval) { clearInterval(statusCheckInterval); } // Demo mode - simulate generation progress if (API_CONFIG.DEMO_MODE) { let progress = 60; statusCheckInterval = setInterval(() => { progress += 5; updateProgress(Math.min(progress, 95)); if (progress >= 95) { clearInterval(statusCheckInterval); // Simulate completion setTimeout(() => { updateProgress(100); showStatus('success', '3D model generated successfully!', 'check-circle'); // Create a demo 3D model setTimeout(() => { createDemoModel(); }, 100); // Display mock model info displayModelInfo({ format: sessionStorage.getItem('format') || 'GLTF', quality: sessionStorage.getItem('quality') || 'High', vertices: '12,543', file_size: '2.4 MB' }); // Add to gallery with a demo image addToRecentGallery('https://static.photos/3d/200x200/456'); }, 500); } }, 300); return; } // Real API polling (uncomment when you have real API) /* statusCheckInterval = setInterval(async () => { try { const response = await fetch(`${API_CONFIG.HITEM3D_STATUS}?task_id=${taskId}`, { headers: { 'Authorization': `Bearer ${API_CONFIG.ACCESS_KEY}` } }); const data = await response.json(); if (data.status === 'completed') { updateProgress(100); showStatus('success', '3D model generated successfully!', 'check-circle'); clearInterval(statusCheckInterval); load3DModel(data.model_url); displayModelInfo(data); addToRecentGallery(data.model_url); } else if (data.status === 'failed') { showStatus('error', 'Generation failed: ' + (data.error || 'Unknown error'), 'x-circle'); clearInterval(statusCheckInterval); } else { const progress = Math.min(90, 60 + (data.progress || 0)); updateProgress(progress); } } catch (error) { console.error('Error checking status:', error); showStatus('error', 'Failed to check generation status', 'x-circle'); clearInterval(statusCheckInterval); } }, 2000); */ } function load3DModel(modelUrl) { const loader = new THREE.GLTFLoader(); // Remove existing model if (currentModel) { scene.remove(currentModel); } loader.load( modelUrl, function(gltf) { currentModel = gltf.scene; // Center and scale model const box = new THREE.Box3().setFromObject(currentModel); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const scale = 2 / maxDim; currentModel.scale.multiplyScalar(scale); currentModel.position.sub(center.multiplyScalar(scale)); scene.add(currentModel); // Reset camera view resetCamera(); }, function(progress) { console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%'); }, function(error) { console.error('Error loading model:', error); showStatus('error', 'Failed to load 3D model', 'x-circle'); // If model fails to load, create a demo model instead createDemoModel(); } ); } // Create a demo 3D model for demonstration purposes function createDemoModel() { // Check if Three.js is available if (typeof THREE === 'undefined') { console.error('Three.js not loaded'); showStatus('error', '3D engine not available', 'x-circle'); return; } if (!scene) { console.error('Scene not initialized'); showStatus('error', '3D viewer not ready', 'x-circle'); return; } // Remove existing model if (currentModel) { scene.remove(currentModel); } // Create a demo geometry based on the uploaded image const geometries = [ new THREE.BoxGeometry(1, 1, 1), new THREE.SphereGeometry(0.7, 32, 32), new THREE.ConeGeometry(0.7, 1.5, 32), new THREE.TorusGeometry(0.7, 0.3, 16, 100), new THREE.DodecahedronGeometry(0.8) ]; const geometry = geometries[Math.floor(Math.random() * geometries.length)]; // Create gradient material const material = new THREE.MeshPhongMaterial({ color: new THREE.Color().setHSL(Math.random() * 0.3 + 0.5, 0.7, 0.5), specular: 0x444444, shininess: 30, wireframe: false }); currentModel = new THREE.Mesh(geometry, material); currentModel.castShadow = true; currentModel.receiveShadow = true; // Add some rotation animation currentModel.userData.animated = true; scene.add(currentModel); // Reset camera view resetCamera(); // Show info about demo model showStatus('success', 'Demo 3D model created!', 'check-circle'); } function displayModelInfo(data) { const modelInfo = document.getElementById('modelInfo'); const modelDetails = document.getElementById('modelDetails'); if (!modelInfo || !modelDetails) { console.error('Model info elements not found'); return; } modelDetails.innerHTML = `
Format: ${data.format || 'GLTF'}
Quality: ${data.quality || 'High'}
Vertices: ${data.vertices || 'N/A'}
File Size: ${data.file_size || 'N/A'}
Generated: ${new Date().toLocaleString()}
`; modelInfo.classList.remove('hidden'); } function addToRecentGallery(modelUrl) { const recentModels = document.getElementById('recentModels'); if (!recentModels) { console.error('Recent models container not found'); return; } const card = document.createElement('div'); card.className = 'aspect-square bg-gray-100 rounded-lg overflow-hidden model-card hover-lift'; card.innerHTML = `
`;
card.onclick = () => load3DModel(modelUrl);
recentModels.insertBefore(card, recentModels.firstChild);
// Keep only last 6 models
while (recentModels.children.length > 6) {
recentModels.removeChild(recentModels.lastChild);
}
}
function showStatus(type, message, icon) {
if (!statusContainer || !statusBox || !statusIcon || !statusText) {
console.error('Status elements not found');
return;
}
statusContainer.classList.remove('hidden');
statusBox.className = `rounded-xl p-4 transition-all duration-300 status-${type}`;
const iconHtml = type === 'processing'
? ''
: ``;
statusIcon.innerHTML = iconHtml;
statusText.textContent = message;
if (typeof feather !== 'undefined') {
feather.replace();
}
}
function updateProgress(percent) {
if (progressFill) {
progressFill.style.width = `${percent}%`;
}
}
// Initialize viewer on load
window.addEventListener('load', () => {
// Initialize Feather icons first
if (typeof feather !== 'undefined') {
feather.replace();
}
// Initialize viewer after DOM is ready
setTimeout(() => {
initViewer();
}, 100);
});
// Generate button click handler - wrap in DOMContentLoaded to ensure elements exist
document.addEventListener('DOMContentLoaded', () => {
const generateBtnElement = document.getElementById('generateBtn');
if (generateBtnElement) {
generateBtnElement.addEventListener('click', generate3DModel);
}
});