|
<!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/[email protected]/build/three.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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> |