| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Trellis3D - 2D to 3D Conversion</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/rembg@0.0.5/dist/rembg.min.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| #preview-container { |
| width: 100%; |
| height: 400px; |
| background-color: #f0f0f0; |
| border-radius: 0.5rem; |
| overflow: hidden; |
| position: relative; |
| } |
| |
| .dark #preview-container { |
| background-color: #2d3748; |
| } |
| |
| .loading-overlay { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0,0,0,0.7); |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| color: white; |
| z-index: 10; |
| } |
| |
| .progress-bar { |
| width: 80%; |
| height: 8px; |
| background-color: #4a5568; |
| border-radius: 4px; |
| margin-top: 1rem; |
| overflow: hidden; |
| } |
| |
| .progress-fill { |
| height: 100%; |
| background-color: #4299e1; |
| width: 0%; |
| transition: width 0.3s ease; |
| } |
| |
| .dropzone { |
| border: 2px dashed #cbd5e0; |
| border-radius: 0.5rem; |
| padding: 2rem; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| } |
| |
| .dark .dropzone { |
| border-color: #4a5568; |
| } |
| |
| .dropzone:hover { |
| border-color: #4299e1; |
| background-color: rgba(66, 153, 225, 0.05); |
| } |
| |
| .dark .dropzone:hover { |
| background-color: rgba(66, 153, 225, 0.1); |
| } |
| |
| .dropzone.active { |
| border-color: #4299e1; |
| background-color: rgba(66, 153, 225, 0.1); |
| } |
| |
| .dark .dropzone.active { |
| background-color: rgba(66, 153, 225, 0.2); |
| } |
| |
| .model-view-toggle { |
| display: flex; |
| justify-content: center; |
| margin-top: 1rem; |
| } |
| |
| .view-btn { |
| padding: 0.5rem 1rem; |
| background-color: #e2e8f0; |
| border: none; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .dark .view-btn { |
| background-color: #4a5568; |
| color: white; |
| } |
| |
| .view-btn:first-child { |
| border-radius: 0.25rem 0 0 0.25rem; |
| } |
| |
| .view-btn:last-child { |
| border-radius: 0 0.25rem 0.25rem 0; |
| } |
| |
| .view-btn.active { |
| background-color: #4299e1; |
| color: white; |
| } |
| |
| .dark .view-btn.active { |
| background-color: #3182ce; |
| } |
| |
| #image-preview { |
| max-width: 100%; |
| max-height: 200px; |
| border-radius: 0.25rem; |
| margin-bottom: 1rem; |
| } |
| |
| #depth-map { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| display: none; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200"> |
| <div class="min-h-screen"> |
| <header class="bg-white dark:bg-gray-800 shadow-md"> |
| <div class="container mx-auto px-4 py-6 flex justify-between items-center"> |
| <div class="flex items-center space-x-2"> |
| <div class="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center"> |
| <i class="fas fa-cube text-white"></i> |
| </div> |
| <h1 class="text-xl font-bold">Trellis3D</h1> |
| </div> |
| <div class="flex items-center space-x-4"> |
| <button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700"> |
| <i class="fas fa-moon dark:hidden"></i> |
| <i class="fas fa-sun hidden dark:block"></i> |
| </button> |
| <button class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors"> |
| <i class="fas fa-user mr-2"></i> |
| Sign In |
| </button> |
| </div> |
| </div> |
| </header> |
|
|
| <main class="container mx-auto px-4 py-8"> |
| <div class="max-w-4xl mx-auto"> |
| <div class="text-center mb-8"> |
| <h2 class="text-3xl font-bold mb-2">2D to 3D Conversion</h2> |
| <p class="text-gray-600 dark:text-gray-400">Transform your 2D images into stunning 3D models with our AI-powered technology</p> |
| </div> |
|
|
| <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8"> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| <div> |
| <h3 class="text-lg font-semibold mb-4">Upload Image</h3> |
| |
| <div id="dropzone" class="dropzone"> |
| <div class="flex flex-col items-center justify-center"> |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-4"></i> |
| <p class="mb-2">Drag & drop your image here</p> |
| <p class="text-sm text-gray-500 dark:text-gray-400 mb-4">or</p> |
| <label for="file-upload" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md cursor-pointer transition-colors"> |
| <i class="fas fa-folder-open mr-2"></i> |
| Browse Files |
| </label> |
| <input id="file-upload" type="file" accept="image/*" class="hidden"> |
| </div> |
| </div> |
| |
| <div class="mt-4"> |
| <div class="flex items-center mb-2"> |
| <input id="remove-bg" type="checkbox" class="rounded text-blue-500 focus:ring-blue-500" checked> |
| <label for="remove-bg" class="ml-2">Remove background</label> |
| </div> |
| |
| <div class="mb-4"> |
| <label class="block mb-2">Model Quality</label> |
| <div class="flex space-x-2"> |
| <button class="quality-btn px-3 py-1 rounded-md border border-gray-300 dark:border-gray-600" data-quality="low">Low</button> |
| <button class="quality-btn px-3 py-1 rounded-md border border-gray-300 dark:border-gray-600 active bg-blue-500 text-white border-blue-500" data-quality="medium">Medium</button> |
| <button class="quality-btn px-3 py-1 rounded-md border border-gray-300 dark:border-gray-600" data-quality="high">High</button> |
| </div> |
| </div> |
| |
| <button id="convert-btn" class="w-full py-3 bg-blue-500 hover:bg-blue-600 text-white rounded-md font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled> |
| <i class="fas fa-magic mr-2"></i> |
| Convert to 3D |
| </button> |
| </div> |
| </div> |
| |
| <div> |
| <h3 class="text-lg font-semibold mb-4">3D Preview</h3> |
| <div id="preview-container"> |
| <div id="loading-overlay" class="loading-overlay hidden"> |
| <i class="fas fa-spinner fa-spin text-3xl mb-2"></i> |
| <p id="loading-text">Processing your image...</p> |
| <div class="progress-bar"> |
| <div id="progress-fill" class="progress-fill"></div> |
| </div> |
| </div> |
| <div id="preview-placeholder" class="flex flex-col items-center justify-center h-full"> |
| <i class="fas fa-cube text-4xl text-gray-400 mb-4"></i> |
| <p class="text-gray-500 dark:text-gray-400">Your 3D model will appear here</p> |
| </div> |
| <canvas id="preview-canvas" class="hidden"></canvas> |
| <img id="depth-map" alt="Depth map preview"> |
| </div> |
| |
| <div id="view-controls" class="hidden"> |
| <div class="model-view-toggle"> |
| <button class="view-btn active" data-view="textured"> |
| <i class="fas fa-paint-brush mr-2"></i> |
| Textured |
| </button> |
| <button class="view-btn" data-view="white"> |
| <i class="fas fa-square mr-2"></i> |
| White |
| </button> |
| <button class="view-btn" data-view="wireframe"> |
| <i class="fas fa-border-none mr-2"></i> |
| Wireframe |
| </button> |
| </div> |
| |
| <div class="flex justify-between mt-4"> |
| <button id="download-btn" class="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded-md"> |
| <i class="fas fa-download mr-2"></i> |
| Download |
| </button> |
| <button id="reset-btn" class="px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md"> |
| <i class="fas fa-redo mr-2"></i> |
| Reset |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6"> |
| <h3 class="text-lg font-semibold mb-4">How It Works</h3> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
| <div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4"> |
| <div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-3 mx-auto"> |
| <i class="fas fa-upload text-white"></i> |
| </div> |
| <h4 class="font-medium text-center mb-2">1. Upload Image</h4> |
| <p class="text-sm text-gray-600 dark:text-gray-300 text-center">Upload any 2D image you want to convert to 3D</p> |
| </div> |
| <div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4"> |
| <div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-3 mx-auto"> |
| <i class="fas fa-robot text-white"></i> |
| </div> |
| <h4 class="font-medium text-center mb-2">2. AI Processing</h4> |
| <p class="text-sm text-gray-600 dark:text-gray-300 text-center">Our AI analyzes the image and creates a depth map</p> |
| </div> |
| <div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4"> |
| <div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-3 mx-auto"> |
| <i class="fas fa-cube text-white"></i> |
| </div> |
| <h4 class="font-medium text-center mb-2">3. 3D Model</h4> |
| <p class="text-sm text-gray-600 dark:text-gray-300 text-center">Download your 3D model in various formats</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </main> |
|
|
| <footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12"> |
| <div class="container mx-auto px-4 py-6"> |
| <div class="flex flex-col md:flex-row justify-between items-center"> |
| <div class="flex items-center space-x-2 mb-4 md:mb-0"> |
| <div class="w-6 h-6 bg-blue-500 rounded-md flex items-center justify-center"> |
| <i class="fas fa-cube text-white text-xs"></i> |
| </div> |
| <span class="font-medium">Trellis3D</span> |
| </div> |
| <div class="flex space-x-6"> |
| <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Terms</a> |
| <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Privacy</a> |
| <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Help</a> |
| </div> |
| </div> |
| <div class="mt-4 text-center md:text-left text-sm text-gray-500 dark:text-gray-400"> |
| © 2023 Trellis3D. All rights reserved. |
| </div> |
| </div> |
| </footer> |
| </div> |
|
|
| <script> |
| |
| const themeToggle = document.getElementById('theme-toggle'); |
| const html = document.documentElement; |
| |
| if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) { |
| html.classList.add('dark'); |
| } |
| |
| themeToggle.addEventListener('click', () => { |
| html.classList.toggle('dark'); |
| localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light'); |
| }); |
| |
| |
| const dropzone = document.getElementById('dropzone'); |
| const fileInput = document.getElementById('file-upload'); |
| const convertBtn = document.getElementById('convert-btn'); |
| let selectedFile = null; |
| let processedImage = null; |
| |
| dropzone.addEventListener('click', () => fileInput.click()); |
| |
| fileInput.addEventListener('change', (e) => { |
| if (e.target.files.length) { |
| handleFileSelection(e.target.files[0]); |
| } |
| }); |
| |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
| dropzone.addEventListener(eventName, preventDefaults, false); |
| }); |
| |
| function preventDefaults(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| |
| ['dragenter', 'dragover'].forEach(eventName => { |
| dropzone.addEventListener(eventName, highlight, false); |
| }); |
| |
| ['dragleave', 'drop'].forEach(eventName => { |
| dropzone.addEventListener(eventName, unhighlight, false); |
| }); |
| |
| function highlight() { |
| dropzone.classList.add('active'); |
| } |
| |
| function unhighlight() { |
| dropzone.classList.remove('active'); |
| } |
| |
| dropzone.addEventListener('drop', (e) => { |
| const dt = e.dataTransfer; |
| const file = dt.files[0]; |
| if (file && file.type.match('image.*')) { |
| handleFileSelection(file); |
| } |
| }); |
| |
| function handleFileSelection(file) { |
| selectedFile = file; |
| convertBtn.disabled = false; |
| |
| |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| dropzone.innerHTML = ` |
| <div class="flex flex-col items-center"> |
| <img src="${e.target.result}" id="image-preview" class="rounded-md mb-2" alt="Preview"> |
| <p class="text-sm text-gray-600 dark:text-gray-400">${file.name}</p> |
| </div> |
| `; |
| }; |
| reader.readAsDataURL(file); |
| } |
| |
| |
| const qualityBtns = document.querySelectorAll('.quality-btn'); |
| let selectedQuality = 'medium'; |
| |
| qualityBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| qualityBtns.forEach(b => b.classList.remove('active', 'bg-blue-500', 'text-white', 'border-blue-500')); |
| btn.classList.add('active', 'bg-blue-500', 'text-white', 'border-blue-500'); |
| selectedQuality = btn.dataset.quality; |
| }); |
| }); |
| |
| |
| const previewContainer = document.getElementById('preview-container'); |
| const previewCanvas = document.getElementById('preview-canvas'); |
| const previewPlaceholder = document.getElementById('preview-placeholder'); |
| const loadingOverlay = document.getElementById('loading-overlay'); |
| const loadingText = document.getElementById('loading-text'); |
| const progressFill = document.getElementById('progress-fill'); |
| const viewControls = document.getElementById('view-controls'); |
| const depthMap = document.getElementById('depth-map'); |
| |
| let scene, camera, renderer, controls, model; |
| |
| function initThreeJS() { |
| const width = previewContainer.clientWidth; |
| const height = previewContainer.clientHeight; |
| |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0xf0f0f0); |
| |
| camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); |
| camera.position.z = 5; |
| |
| renderer = new THREE.WebGLRenderer({ |
| canvas: previewCanvas, |
| antialias: true, |
| alpha: true |
| }); |
| renderer.setSize(width, height); |
| renderer.setPixelRatio(window.devicePixelRatio); |
| |
| controls = new THREE.OrbitControls(camera, renderer.domElement); |
| controls.enableDamping = true; |
| controls.dampingFactor = 0.25; |
| |
| |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
| scene.add(ambientLight); |
| |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); |
| directionalLight.position.set(1, 1, 1); |
| scene.add(directionalLight); |
| |
| |
| const gridHelper = new THREE.GridHelper(10, 10); |
| scene.add(gridHelper); |
| |
| animate(); |
| } |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| controls.update(); |
| renderer.render(scene, camera); |
| } |
| |
| window.addEventListener('resize', () => { |
| const width = previewContainer.clientWidth; |
| const height = previewContainer.clientHeight; |
| |
| camera.aspect = width / height; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(width, height); |
| }); |
| |
| |
| const viewBtns = document.querySelectorAll('.view-btn'); |
| |
| viewBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| viewBtns.forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| |
| if (model) { |
| const viewMode = btn.dataset.view; |
| |
| if (viewMode === 'textured') { |
| model.traverse(child => { |
| if (child.isMesh) { |
| child.material = child.userData.originalMaterial; |
| child.material.wireframe = false; |
| } |
| }); |
| } else if (viewMode === 'white') { |
| const whiteMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff }); |
| model.traverse(child => { |
| if (child.isMesh) { |
| child.material = whiteMaterial; |
| child.material.wireframe = false; |
| } |
| }); |
| } else if (viewMode === 'wireframe') { |
| model.traverse(child => { |
| if (child.isMesh) { |
| child.material.wireframe = true; |
| } |
| }); |
| } |
| } |
| }); |
| }); |
| |
| |
| convertBtn.addEventListener('click', async () => { |
| if (!selectedFile) return; |
| |
| |
| loadingOverlay.classList.remove('hidden'); |
| previewPlaceholder.classList.add('hidden'); |
| previewCanvas.classList.remove('hidden'); |
| |
| |
| if (!renderer) { |
| initThreeJS(); |
| } |
| |
| |
| await processImage(); |
| }); |
| |
| async function processImage() { |
| let progress = 0; |
| const interval = setInterval(() => { |
| progress += Math.random() * 10; |
| if (progress > 100) progress = 100; |
| |
| progressFill.style.width = `${progress}%`; |
| loadingText.textContent = getLoadingMessage(progress); |
| |
| if (progress === 100) { |
| clearInterval(interval); |
| setTimeout(showResult, 500); |
| } |
| }, 300); |
| |
| |
| const image = await loadImage(selectedFile); |
| |
| |
| const removeBg = document.getElementById('remove-bg').checked; |
| if (removeBg) { |
| try { |
| |
| |
| processedImage = await simulateRemoveBackground(image); |
| } catch (error) { |
| console.error("Background removal failed:", error); |
| processedImage = image; |
| } |
| } else { |
| processedImage = image; |
| } |
| |
| |
| const depthMapUrl = await generateDepthMap(processedImage); |
| depthMap.src = depthMapUrl; |
| } |
| |
| function loadImage(file) { |
| return new Promise((resolve) => { |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| const img = new Image(); |
| img.onload = () => resolve(img); |
| img.src = e.target.result; |
| }; |
| reader.readAsDataURL(file); |
| }); |
| } |
| |
| function simulateRemoveBackground(image) { |
| return new Promise((resolve) => { |
| |
| |
| const canvas = document.createElement('canvas'); |
| canvas.width = image.width; |
| canvas.height = image.height; |
| const ctx = canvas.getContext('2d'); |
| ctx.drawImage(image, 0, 0); |
| |
| |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| const data = imageData.data; |
| |
| |
| for (let i = 0; i < data.length; i += 4) { |
| const x = (i / 4) % canvas.width; |
| const y = Math.floor((i / 4) / canvas.width); |
| |
| |
| if (x < 10 || x > canvas.width - 10 || y < 10 || y > canvas.height - 10) { |
| data[i + 3] = 0; |
| } |
| } |
| |
| ctx.putImageData(imageData, 0, 0); |
| |
| const result = new Image(); |
| result.onload = () => resolve(result); |
| result.src = canvas.toDataURL(); |
| }); |
| } |
| |
| function generateDepthMap(image) { |
| return new Promise((resolve) => { |
| |
| |
| const canvas = document.createElement('canvas'); |
| canvas.width = image.width; |
| canvas.height = image.height; |
| const ctx = canvas.getContext('2d'); |
| ctx.drawImage(image, 0, 0); |
| |
| |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| const data = imageData.data; |
| const depthData = new Uint8ClampedArray(canvas.width * canvas.height * 4); |
| |
| for (let i = 0; i < data.length; i += 4) { |
| const x = (i / 4) % canvas.width; |
| const y = Math.floor((i / 4) / canvas.width); |
| |
| |
| const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3; |
| const depth = (brightness / 255) * 0.5 + (1 - (y / canvas.height)) * 0.5; |
| const depthValue = Math.floor(depth * 255); |
| |
| depthData[i] = depthValue; |
| depthData[i + 1] = depthValue; |
| depthData[i + 2] = depthValue; |
| depthData[i + 3] = data[i + 3]; |
| } |
| |
| const depthImageData = new ImageData(new Uint8ClampedArray(depthData), canvas.width, canvas.height); |
| ctx.putImageData(depthImageData, 0, 0); |
| |
| resolve(canvas.toDataURL()); |
| }); |
| } |
| |
| function getLoadingMessage(progress) { |
| if (progress < 30) return "Analyzing image..."; |
| if (progress < 60) return "Generating depth map..."; |
| if (progress < 90) return "Creating 3D model..."; |
| return "Finalizing..."; |
| } |
| |
| function showResult() { |
| loadingOverlay.classList.add('hidden'); |
| viewControls.classList.remove('hidden'); |
| |
| |
| if (model) { |
| scene.remove(model); |
| } |
| |
| |
| create3DModelFromImage(processedImage); |
| } |
| |
| function create3DModelFromImage(image) { |
| const textureLoader = new THREE.TextureLoader(); |
| textureLoader.load(image.src, (texture) => { |
| |
| const aspectRatio = image.width / image.height; |
| const width = 3; |
| const height = width / aspectRatio; |
| |
| const geometry = new THREE.PlaneGeometry(width, height, 64, 64); |
| |
| |
| applyDepthMapToGeometry(geometry, image); |
| |
| |
| const material = new THREE.MeshStandardMaterial({ |
| map: texture, |
| side: THREE.DoubleSide, |
| transparent: true |
| }); |
| |
| model = new THREE.Mesh(geometry, material); |
| |
| |
| model.traverse(child => { |
| if (child.isMesh) { |
| child.userData.originalMaterial = child.material; |
| } |
| }); |
| |
| scene.add(model); |
| |
| |
| function animateRotation() { |
| requestAnimationFrame(animateRotation); |
| model.rotation.y += 0.005; |
| } |
| animateRotation(); |
| }); |
| } |
| |
| function applyDepthMapToGeometry(geometry, image) { |
| |
| |
| const positions = geometry.attributes.position.array; |
| const depthIntensity = selectedQuality === 'low' ? 0.3 : |
| selectedQuality === 'medium' ? 0.6 : 1.0; |
| |
| for (let i = 0; i < positions.length; i += 3) { |
| const x = positions[i]; |
| const y = positions[i + 1]; |
| |
| |
| const u = (x / geometry.parameters.width) + 0.5; |
| const v = (y / geometry.parameters.height) + 0.5; |
| |
| if (u >= 0 && u <= 1 && v >= 0 && v <= 1) { |
| |
| const depth = (Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI * 2)) * 0.5 + 0.5; |
| positions[i + 2] = depth * depthIntensity; |
| } |
| } |
| |
| geometry.attributes.position.needsUpdate = true; |
| geometry.computeVertexNormals(); |
| } |
| |
| |
| document.getElementById('reset-btn').addEventListener('click', () => { |
| selectedFile = null; |
| processedImage = null; |
| convertBtn.disabled = true; |
| |
| dropzone.innerHTML = ` |
| <div class="flex flex-col items-center justify-center"> |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-4"></i> |
| <p class="mb-2">Drag & drop your image here</p> |
| <p class="text-sm text-gray-500 dark:text-gray-400 mb-4">or</p> |
| <label for="file-upload" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md cursor-pointer transition-colors"> |
| <i class="fas fa-folder-open mr-2"></i> |
| Browse Files |
| </label> |
| </div> |
| `; |
| |
| fileInput.value = ''; |
| previewPlaceholder.classList.remove('hidden'); |
| previewCanvas.classList.add('hidden'); |
| viewControls.classList.add('hidden'); |
| depthMap.style.display = 'none'; |
| |
| if (model) { |
| scene.remove(model); |
| model = null; |
| } |
| }); |
| |
| |
| document.getElementById('download-btn').addEventListener('click', () => { |
| alert('In a real application, this would download your 3D model file (GLB, OBJ, etc.).'); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=CIHIRIS/2d-to-3d-model-generator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |