Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Object Remesher | 3D Mesh Optimization</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.dropzone { | |
border: 2px dashed #94a3b8; | |
transition: all 0.3s ease; | |
} | |
.dropzone.active { | |
border-color: #6366f1; | |
background-color: #e0e7ff; | |
} | |
.mesh-preview { | |
background: linear-gradient(45deg, #f3f4f6 25%, transparent 25%), | |
linear-gradient(-45deg, #f3f4f6 25%, transparent 25%), | |
linear-gradient(45deg, transparent 75%, #f3f4f6 75%), | |
linear-gradient(-45deg, transparent 75%, #f3f4f6 75%); | |
background-size: 20px 20px; | |
background-position: 0 0, 0 10px, 10px -10px, -10px 0px; | |
} | |
.progress-bar { | |
transition: width 0.3s ease; | |
} | |
canvas { | |
touch-action: none; | |
} | |
.tooltip { | |
position: relative; | |
} | |
.tooltip-text { | |
visibility: hidden; | |
width: 200px; | |
background-color: #1e293b; | |
color: #fff; | |
text-align: center; | |
border-radius: 6px; | |
padding: 5px; | |
position: absolute; | |
z-index: 1; | |
bottom: 125%; | |
left: 50%; | |
transform: translateX(-50%); | |
opacity: 0; | |
transition: opacity 0.3s; | |
} | |
.tooltip:hover .tooltip-text { | |
visibility: visible; | |
opacity: 1; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 text-gray-800"> | |
<div class="min-h-screen flex flex-col"> | |
<!-- Header --> | |
<header class="bg-indigo-600 text-white shadow-md"> | |
<div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
<div class="flex items-center space-x-2"> | |
<i class="fas fa-cube text-2xl"></i> | |
<h1 class="text-2xl font-bold">Object Remesher</h1> | |
</div> | |
<nav> | |
<ul class="flex space-x-6"> | |
<li><a href="#" class="hover:text-indigo-200 transition">Home</a></li> | |
<li><a href="#" class="hover:text-indigo-200 transition">Features</a></li> | |
<li><a href="#" class="hover:text-indigo-200 transition">Documentation</a></li> | |
<li><a href="#" class="hover:text-indigo-200 transition">Contact</a></li> | |
</ul> | |
</nav> | |
</div> | |
</header> | |
<!-- Main Content --> | |
<main class="flex-grow container mx-auto px-4 py-8"> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
<!-- Left Panel - Upload and Controls --> | |
<div class="lg:col-span-1 space-y-6"> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold mb-4 flex items-center"> | |
<i class="fas fa-upload mr-2 text-indigo-500"></i> | |
Upload 3D Model | |
</h2> | |
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-4"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-400 mb-2"></i> | |
<p class="text-gray-500">Drag & drop your 3D model file here</p> | |
<p class="text-sm text-gray-400 mt-1">Supported formats: .obj, .stl, .fbx, .gltf</p> | |
<input type="file" id="fileInput" class="hidden" accept=".obj,.stl,.fbx,.gltf"> | |
<button id="browseBtn" class="mt-4 bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded-md transition"> | |
Browse Files | |
</button> | |
</div> | |
<div id="fileInfo" class="hidden bg-gray-100 p-3 rounded-md mb-4"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<p class="font-medium" id="fileName">model.obj</p> | |
<p class="text-sm text-gray-500" id="fileSize">12.4 MB</p> | |
</div> | |
<button id="removeFile" class="text-red-500 hover:text-red-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold mb-4 flex items-center"> | |
<i class="fas fa-sliders-h mr-2 text-indigo-500"></i> | |
Remeshing Settings | |
</h2> | |
<div class="space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Target Density</label> | |
<div class="flex items-center space-x-2"> | |
<input type="range" min="0.1" max="2" step="0.1" value="1" class="w-full" id="densitySlider"> | |
<span class="text-sm w-10 text-center" id="densityValue">1.0</span> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Preserve Features</label> | |
<select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
<option>Sharp Edges</option> | |
<option selected>All Features</option> | |
<option>None</option> | |
</select> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Remeshing Method</label> | |
<div class="grid grid-cols-2 gap-2"> | |
<button class="bg-indigo-100 text-indigo-700 border border-indigo-300 rounded-md px-3 py-2 text-sm font-medium">Isotropic</button> | |
<button class="bg-white text-gray-700 border border-gray-300 rounded-md px-3 py-2 text-sm font-medium">Anisotropic</button> | |
<button class="bg-white text-gray-700 border border-gray-300 rounded-md px-3 py-2 text-sm font-medium">Quad Dominant</button> | |
<button class="bg-white text-gray-700 border border-gray-300 rounded-md px-3 py-2 text-sm font-medium">Adaptive</button> | |
</div> | |
</div> | |
<div class="pt-2"> | |
<button id="remeshBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md font-medium transition flex items-center justify-center"> | |
<i class="fas fa-magic mr-2"></i> | |
Remesh Model | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold mb-4 flex items-center"> | |
<i class="fas fa-chart-bar mr-2 text-indigo-500"></i> | |
Mesh Statistics | |
</h2> | |
<div class="space-y-3"> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Original Faces:</span> | |
<span class="font-medium" id="originalFaces">0</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Remeshed Faces:</span> | |
<span class="font-medium" id="remeshedFaces">0</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Reduction:</span> | |
<span class="font-medium" id="reduction">0%</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">File Size:</span> | |
<span class="font-medium" id="fileSizeStat">0 MB</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Right Panel - 3D Viewer and Results --> | |
<div class="lg:col-span-2 space-y-6"> | |
<div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
<div class="flex justify-between items-center border-b border-gray-200 px-6 py-4"> | |
<h2 class="text-xl font-semibold flex items-center"> | |
<i class="fas fa-cube mr-2 text-indigo-500"></i> | |
3D Model Viewer | |
</h2> | |
<div class="flex space-x-2"> | |
<button class="tooltip p-2 text-gray-500 hover:text-indigo-500 rounded-full hover:bg-gray-100 transition"> | |
<i class="fas fa-rotate"></i> | |
<span class="tooltip-text">Rotate Model</span> | |
</button> | |
<button class="tooltip p-2 text-gray-500 hover:text-indigo-500 rounded-full hover:bg-gray-100 transition"> | |
<i class="fas fa-arrows-up-down-left-right"></i> | |
<span class="tooltip-text">Pan Model</span> | |
</button> | |
<button class="tooltip p-2 text-gray-500 hover:text-indigo-500 rounded-full hover:bg-gray-100 transition"> | |
<i class="fas fa-search-plus"></i> | |
<span class="tooltip-text">Zoom In/Out</span> | |
</button> | |
<button class="tooltip p-2 text-gray-500 hover:text-indigo-500 rounded-full hover:bg-gray-100 transition"> | |
<i class="fas fa-border-all"></i> | |
<span class="tooltip-text">Toggle Wireframe</span> | |
</button> | |
</div> | |
</div> | |
<div class="mesh-preview h-96 relative"> | |
<canvas id="modelCanvas" class="w-full h-full"></canvas> | |
<div id="loadingOverlay" class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> | |
<div class="text-center text-white"> | |
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500 mx-auto mb-4"></div> | |
<p>Processing model...</p> | |
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-4 mx-auto max-w-md"> | |
<div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
<div class="border-b border-gray-200"> | |
<nav class="flex -mb-px"> | |
<button class="tab-button active py-4 px-6 text-center border-b-2 font-medium text-sm border-indigo-500 text-indigo-600"> | |
<i class="fas fa-list mr-2"></i> | |
Comparison | |
</button> | |
<button class="tab-button py-4 px-6 text-center border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"> | |
<i class="fas fa-download mr-2"></i> | |
Export | |
</button> | |
<button class="tab-button py-4 px-6 text-center border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"> | |
<i class="fas fa-history mr-2"></i> | |
History | |
</button> | |
</nav> | |
</div> | |
<div class="p-6"> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
<div> | |
<h3 class="font-medium text-gray-700 mb-3">Original Model</h3> | |
<div class="bg-gray-100 rounded-lg p-4 h-48 flex items-center justify-center"> | |
<p class="text-gray-500">Original mesh will appear here</p> | |
</div> | |
<div class="mt-3 text-sm text-gray-600"> | |
<p><span class="font-medium">Vertices:</span> <span id="originalVertices">0</span></p> | |
<p><span class="font-medium">Faces:</span> <span id="originalFaces2">0</span></p> | |
</div> | |
</div> | |
<div> | |
<h3 class="font-medium text-gray-700 mb-3">Remeshed Model</h3> | |
<div class="bg-gray-100 rounded-lg p-4 h-48 flex items-center justify-center"> | |
<p class="text-gray-500">Optimized mesh will appear here</p> | |
</div> | |
<div class="mt-3 text-sm text-gray-600"> | |
<p><span class="font-medium">Vertices:</span> <span id="remeshedVertices">0</span></p> | |
<p><span class="font-medium">Faces:</span> <span id="remeshedFaces2">0</span></p> | |
</div> | |
</div> | |
</div> | |
<div class="mt-6"> | |
<button class="bg-green-600 hover:bg-green-700 text-white py-2 px-6 rounded-md font-medium transition flex items-center"> | |
<i class="fas fa-download mr-2"></i> | |
Download Remeshed Model | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<!-- Footer --> | |
<footer class="bg-gray-800 text-white py-8"> | |
<div class="container mx-auto px-4"> | |
<div class="grid grid-cols-1 md:grid-cols-4 gap-8"> | |
<div> | |
<h3 class="text-lg font-semibold mb-4">Object Remesher</h3> | |
<p class="text-gray-400">Advanced 3D mesh optimization tool for professionals and enthusiasts.</p> | |
</div> | |
<div> | |
<h3 class="text-lg font-semibold mb-4">Quick Links</h3> | |
<ul class="space-y-2"> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Home</a></li> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Features</a></li> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Pricing</a></li> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Blog</a></li> | |
</ul> | |
</div> | |
<div> | |
<h3 class="text-lg font-semibold mb-4">Support</h3> | |
<ul class="space-y-2"> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Documentation</a></li> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Tutorials</a></li> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">API Reference</a></li> | |
<li><a href="#" class="text-gray-400 hover:text-white transition">Contact Us</a></li> | |
</ul> | |
</div> | |
<div> | |
<h3 class="text-lg font-semibold mb-4">Connect</h3> | |
<div class="flex space-x-4"> | |
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-twitter"></i></a> | |
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-github"></i></a> | |
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-discord"></i></a> | |
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-youtube"></i></a> | |
</div> | |
</div> | |
</div> | |
<div class="border-t border-gray-700 mt-8 pt-6 text-center text-gray-400"> | |
<p>© 2023 Object Remesher. All rights reserved.</p> | |
</div> | |
</div> | |
</footer> | |
</div> | |
<script> | |
// DOM Elements | |
const dropzone = document.getElementById('dropzone'); | |
const fileInput = document.getElementById('fileInput'); | |
const browseBtn = document.getElementById('browseBtn'); | |
const fileInfo = document.getElementById('fileInfo'); | |
const fileName = document.getElementById('fileName'); | |
const fileSize = document.getElementById('fileSize'); | |
const removeFile = document.getElementById('removeFile'); | |
const remeshBtn = document.getElementById('remeshBtn'); | |
const densitySlider = document.getElementById('densitySlider'); | |
const densityValue = document.getElementById('densityValue'); | |
const loadingOverlay = document.getElementById('loadingOverlay'); | |
const progressBar = document.getElementById('progressBar'); | |
const tabButtons = document.querySelectorAll('.tab-button'); | |
// Event Listeners | |
browseBtn.addEventListener('click', () => fileInput.click()); | |
fileInput.addEventListener('change', handleFileSelect); | |
dropzone.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
dropzone.classList.add('active'); | |
}); | |
dropzone.addEventListener('dragleave', () => { | |
dropzone.classList.remove('active'); | |
}); | |
dropzone.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
dropzone.classList.remove('active'); | |
if (e.dataTransfer.files.length) { | |
fileInput.files = e.dataTransfer.files; | |
handleFileSelect({ target: fileInput }); | |
} | |
}); | |
removeFile.addEventListener('click', () => { | |
fileInfo.classList.add('hidden'); | |
fileInput.value = ''; | |
}); | |
densitySlider.addEventListener('input', () => { | |
densityValue.textContent = densitySlider.value; | |
}); | |
remeshBtn.addEventListener('click', () => { | |
if (!fileInput.files.length) { | |
alert('Please upload a 3D model first'); | |
return; | |
} | |
loadingOverlay.classList.remove('hidden'); | |
simulateProcessing(); | |
}); | |
tabButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
tabButtons.forEach(btn => btn.classList.remove('active', 'text-indigo-600', 'border-indigo-500')); | |
tabButtons.forEach(btn => btn.classList.add('text-gray-500', 'border-transparent')); | |
button.classList.add('active', 'text-indigo-600', 'border-indigo-500'); | |
button.classList.remove('text-gray-500', 'border-transparent'); | |
}); | |
}); | |
// Functions | |
function handleFileSelect(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
fileName.textContent = file.name; | |
fileSize.textContent = formatFileSize(file.size); | |
fileInfo.classList.remove('hidden'); | |
// Update stats | |
document.getElementById('originalFaces').textContent = Math.floor(Math.random() * 50000) + 10000; | |
document.getElementById('originalFaces2').textContent = document.getElementById('originalFaces').textContent; | |
document.getElementById('originalVertices').textContent = Math.floor(Math.random() * 25000) + 5000; | |
document.getElementById('fileSizeStat').textContent = fileSize.textContent; | |
// Simulate loading a model | |
simulateModelLoad(); | |
} | |
function formatFileSize(bytes) { | |
if (bytes === 0) return '0 Bytes'; | |
const k = 1024; | |
const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
} | |
function simulateModelLoad() { | |
loadingOverlay.classList.remove('hidden'); | |
let progress = 0; | |
const interval = setInterval(() => { | |
progress += Math.random() * 10; | |
if (progress > 100) progress = 100; | |
progressBar.style.width = `${progress}%`; | |
if (progress === 100) { | |
clearInterval(interval); | |
setTimeout(() => { | |
loadingOverlay.classList.add('hidden'); | |
progressBar.style.width = '0%'; | |
// Initialize a simple 3D viewer (placeholder) | |
init3DViewer(); | |
}, 500); | |
} | |
}, 100); | |
} | |
function simulateProcessing() { | |
let progress = 0; | |
const interval = setInterval(() => { | |
progress += Math.random() * 5; | |
if (progress > 100) progress = 100; | |
progressBar.style.width = `${progress}%`; | |
if (progress === 100) { | |
clearInterval(interval); | |
setTimeout(() => { | |
loadingOverlay.classList.add('hidden'); | |
progressBar.style.width = '0%'; | |
// Update stats with "remeshed" values | |
const originalFaces = parseInt(document.getElementById('originalFaces').textContent); | |
const reduction = Math.floor(Math.random() * 70) + 10; // 10-80% reduction | |
const remeshedFaces = Math.floor(originalFaces * (100 - reduction) / 100); | |
document.getElementById('remeshedFaces').textContent = remeshedFaces; | |
document.getElementById('remeshedFaces2').textContent = remeshedFaces; | |
document.getElementById('remeshedVertices').textContent = Math.floor(remeshedFaces * 0.6); | |
document.getElementById('reduction').textContent = `${reduction}%`; | |
// Update the 3D viewer with "remeshed" model | |
update3DViewer(); | |
}, 500); | |
} | |
}, 200); | |
} | |
function init3DViewer() { | |
const canvas = document.getElementById('modelCanvas'); | |
const ctx = canvas.getContext('2d'); | |
// Set canvas dimensions | |
canvas.width = canvas.offsetWidth; | |
canvas.height = canvas.offsetHeight; | |
// Draw a placeholder "3D" cube | |
drawPlaceholderModel(ctx, canvas.width, canvas.height); | |
// Add some basic interactivity | |
let isDragging = false; | |
let lastX = 0; | |
let lastY = 0; | |
let rotationX = 20; | |
let rotationY = 30; | |
canvas.addEventListener('mousedown', (e) => { | |
isDragging = true; | |
lastX = e.clientX; | |
lastY = e.clientY; | |
}); | |
canvas.addEventListener('mousemove', (e) => { | |
if (!isDragging) return; | |
const deltaX = e.clientX - lastX; | |
const deltaY = e.clientY - lastY; | |
rotationY += deltaX * 0.5; | |
rotationX += deltaY * 0.5; | |
lastX = e.clientX; | |
lastY = e.clientY; | |
drawPlaceholderModel(ctx, canvas.width, canvas.height, rotationX, rotationY); | |
}); | |
canvas.addEventListener('mouseup', () => { | |
isDragging = false; | |
}); | |
canvas.addEventListener('mouseleave', () => { | |
isDragging = false; | |
}); | |
} | |
function update3DViewer() { | |
const canvas = document.getElementById('modelCanvas'); | |
const ctx = canvas.getContext('2d'); | |
// Draw a different "remeshed" version | |
drawPlaceholderModel(ctx, canvas.width, canvas.height, 20, 30, true); | |
} | |
function drawPlaceholderModel(ctx, width, height, rotX = 20, rotY = 30, isRemeshed = false) { | |
ctx.clearRect(0, 0, width, height); | |
// Simple 3D cube projection | |
const size = Math.min(width, height) * 0.3; | |
const centerX = width / 2; | |
const centerY = height / 2; | |
// Convert angles to radians | |
const radX = rotX * Math.PI / 180; | |
const radY = rotY * Math.PI / 180; | |
// Cube vertices | |
const vertices = [ | |
{ x: -1, y: -1, z: -1 }, | |
{ x: 1, y: -1, z: -1 }, | |
{ x: 1, y: 1, z: -1 }, | |
{ x: -1, y: 1, z: -1 }, | |
{ x: -1, y: -1, z: 1 }, | |
{ x: 1, y: -1, z: 1 }, | |
{ x: 1, y: 1, z: 1 }, | |
{ x: -1, y: 1, z: 1 } | |
]; | |
// Project 3D to 2D | |
const projected = vertices.map(v => { | |
// Rotate around X and Y axes | |
let x = v.x; | |
let y = v.y * Math.cos(radX) - v.z * Math.sin(radX); | |
let z = v.y * Math.sin(radX) + v.z * Math.cos(radX); | |
let x2 = x * Math.cos(radY) - z * Math.sin(radY); | |
let z2 = x * Math.sin(radY) + z * Math.cos(radY); | |
// Perspective projection | |
const distance = 4; | |
const factor = distance / (distance + z2); | |
return { | |
x: centerX + x2 * factor * size, | |
y: centerY + y * factor * size | |
}; | |
}); | |
// Draw edges | |
const edges = [ | |
[0, 1], [1, 2], [2, 3], [3, 0], // back face | |
[4, 5], [5, 6], [6, 7], [7, 4], // front face | |
[0, 4], [1, 5], [2, 6], [3, 7] // connecting edges | |
]; | |
ctx.strokeStyle = isRemeshed ? '#10b981' : '#6366f1'; | |
ctx.lineWidth = isRemeshed ? 1 : 2; | |
edges.forEach(edge => { | |
const [a, b] = edge; | |
ctx.beginPath(); | |
ctx.moveTo(projected[a].x, projected[a].y); | |
ctx.lineTo(projected[b].x, projected[b].y); | |
ctx.stroke(); | |
}); | |
if (isRemeshed) { | |
// Add some "remeshed" details | |
const faceCenters = [ | |
{ x: 0, y: 0, z: -1.2 }, // back | |
{ x: 0, y: 0, z: 1.2 }, // front | |
{ x: -1.2, y: 0, z: 0 }, // left | |
{ x: 1.2, y: 0, z: 0 }, // right | |
{ x: 0, y: -1.2, z: 0 }, // bottom | |
{ x: 0, y: 1.2, z: 0 } // top | |
]; | |
faceCenters.forEach(center => { | |
// Project this point | |
let x = center.x; | |
let y = center.y * Math.cos(radX) - center.z * Math.sin(radX); | |
let z = center.y * Math.sin(radX) + center.z * Math.cos(radX); | |
let x2 = x * Math.cos(radY) - z * Math.sin(radY); | |
let z2 = x * Math.sin(radY) + z * Math.cos(radY); | |
const distance = 4; | |
const factor = distance / (distance + z2); | |
const px = centerX + x2 * factor * size; | |
const py = centerY + y * factor * size; | |
// Draw a small quad to represent remeshed faces | |
ctx.fillStyle = '#a7f3d0'; | |
ctx.fillRect(px - 5, py - 5, 10, 10); | |
}); | |
} | |
} | |
</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=CuriousCreator/remesher" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |