| <!DOCTYPE html> |
| <html lang="fa" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Auto Photogrammetry Camera Tracker - نسخه وب</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Vazirmatic:wght@300;400;500;700&display=swap'); |
| |
| body { |
| font-family: 'Vazirmatic', sans-serif; |
| } |
| |
| .perspective-1000 { |
| perspective: 1000px; |
| } |
| |
| .transform-style-3d { |
| transform-style: preserve-3d; |
| } |
| |
| .rotate-y-15 { |
| transform: rotateY(15deg); |
| } |
| |
| .glass-effect { |
| background: rgba(255, 255, 255, 0.05); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .progress-ring { |
| transform: rotate(-90deg); |
| } |
| |
| .progress-ring__circle { |
| transition: stroke-dashoffset 0.35s; |
| transform-origin: 50% 50%; |
| } |
| |
| .point-cloud { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| } |
| |
| .floating-ui { |
| animation: float 6s ease-in-out infinite; |
| } |
| |
| @keyframes float { |
| 0%, 100% { transform: translateY(0px); } |
| 50% { transform: translateY(-10px); } |
| } |
| |
| .glow-effect { |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.5); |
| } |
| |
| .scan-line { |
| background: linear-gradient(90deg, |
| transparent 0%, |
| rgba(59, 130, 246, 0.8) 50%, |
| transparent 100%); |
| animation: scan 2s linear infinite; |
| } |
| |
| @keyframes scan { |
| 0% { transform: translateX(-100%); } |
| 100% { transform: translateX(100%); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-white min-h-screen overflow-x-hidden"> |
| |
| <div id="background-animation" class="fixed inset-0 z-0"></div> |
| |
| <div class="relative z-10"> |
| |
| <header class="glass-effect border-b border-gray-700"> |
| <div class="container mx-auto px-4 py-6"> |
| <div class="flex items-center justify-between"> |
| <div class="flex items-center space-x-4"> |
| <div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center"> |
| <span class="text-white font-bold text-xl">3D</span> |
| </div> |
| <div> |
| <h1 class="text-2xl font-bold">Auto Photogrammetry Camera Tracker</h1> |
| <p class="text-gray-400 text-sm">تبدیل ویدیو به محیط سهبعدی با هوش مصنوعی</p> |
| </div> |
| </div> |
| <div class="flex items-center space-x-4"> |
| <button id="settingsBtn" class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-colors"> |
| <span class="material-icons">settings</span> |
| </button> |
| </div> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <main class="container mx-auto px-4 py-8"> |
| <div class="grid lg:grid-cols-3 gap-8"> |
| |
| <div class="lg:col-span-1 space-y-6"> |
| |
| <div class="glass-effect rounded-xl p-6"> |
| <h3 class="text-lg font-semibold mb-4 flex items-center"> |
| <span class="material-icons mr-2">upload</span> |
| بارگذاری ویدیو |
| </h3> |
| <div id="uploadArea" class="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center hover:border-blue-500 transition-colors cursor-pointer"> |
| <span class="material-icons text-6xl text-gray-400 mb-4">video_library</span> |
| <p class="text-gray-300">ویدیو خود را اینجا رها کنید یا کلیک کنید</p> |
| <input type="file" id="videoInput" accept="video/*" class="hidden"> |
| </div> |
| <div id="videoInfo" class="hidden mt-4 p-4 bg-gray-800 rounded-lg"> |
| <p class="text-sm text-gray-300"> |
| <strong>نام فایل:</strong> <span id="fileName">-</span><br> |
| <strong>اندازه:</strong> <span id="fileSize">-</span><br> |
| <strong>مدت زمان:</strong> <span id="duration">-</span> |
| </p> |
| </div> |
| </div> |
|
|
| |
| <div class="glass-effect rounded-xl p-6"> |
| <h3 class="text-lg font-semibold mb-4 flex items-center"> |
| <span class="material-icons mr-2">tune</span> |
| تنظیمات پردازش |
| </h3> |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-sm font-medium mb-2">نرخ نمونهبرداری فریم</label> |
| <input type="range" id="frameRate" min="1" max="30" value="5" class="w-full"> |
| <span class="text-sm text-gray-400">هر <span id="frameRateValue">5</span> فریم</span> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">آستانه تطابق</label> |
| <input type="range" id="matchThreshold" min="0.1" max="1" step="0.1" value="0.7" class="w-full"> |
| <span class="text-sm text-gray-400"><span id="thresholdValue">0.7</span></span> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">طول کانونی (mm)</label> |
| <input type="number" id="focalLength" value="35" class="w-full bg-gray-800 rounded px-3 py-2"> |
| </div> |
| <div class="flex items-center"> |
| <input type="checkbox" id="lensCorrection" class="mr-2"> |
| <label class="text-sm">اصلاح اعوجاج لنز</label> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <button id="processBtn" disabled class="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 disabled:from-gray-600 disabled:to-gray-700 disabled:cursor-not-allowed rounded-xl p-4 font-semibold transition-all glow-effect"> |
| <span class="material-icons mr-2">play_circle</span> |
| شروع پردازش |
| </button> |
|
|
| |
| <div id="progressSection" class="glass-effect rounded-xl p-6 hidden"> |
| <h3 class="text-lg font-semibold mb-4">پردازش...</h3> |
| <div class="space-y-4"> |
| <div> |
| <div class="flex justify-between text-sm mb-1"> |
| <span>استخراج فریمها</span> |
| <span id="progress1">0%</span> |
| </div> |
| <div class="w-full bg-gray-700 rounded-full h-2"> |
| <div id="bar1" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div> |
| </div> |
| </div> |
| <div> |
| <div class="flex justify-between text-sm mb-1"> |
| <span>تشخیص ویژگیها</span> |
| <span id="progress2">0%</span> |
| </div> |
| <div class="w-full bg-gray-700 rounded-full h-2"> |
| <div id="bar2" class="bg-purple-500 h-2 rounded-full" style="width: 0%"></div> |
| </div> |
| </div> |
| <div> |
| <div class="flex justify-between text-sm mb-1"> |
| <span>ساخت نقطهابر</span> |
| <span id="progress3">0%</span> |
| </div> |
| <div class="w-full bg-gray-700 rounded-full h-2"> |
| <div id="bar3" class="bg-green-500 h-2 rounded-full" style="width: 0%"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lg:col-span-2"> |
| <div class="glass-effect rounded-xl p-6 h-full"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-lg font-semibold flex items-center"> |
| <span class="material-icons mr-2">view_in_ar</span> |
| پیشنمایش سهبعدی |
| </h3> |
| <div class="flex space-x-2"> |
| <button id="resetView" class="p-2 bg-gray-800 rounded hover:bg-gray-700"> |
| <span class="material-icons text-sm">refresh</span> |
| </button> |
| <button id="toggleWireframe" class="p-2 bg-gray-800 rounded hover:bg-gray-700"> |
| <span class="material-icons text-sm">grid_3x3</span> |
| </button> |
| </div> |
| </div> |
| |
| <div id="viewer3d" class="relative bg-gray-800 rounded-lg h-96 lg:h-[500px]"> |
| <div id="threejs-container" class="w-full h-full rounded-lg overflow-hidden"></div> |
| |
| |
| <div id="pointCloudOverlay" class="point-cloud hidden"> |
| <canvas id="pointCloudCanvas" class="w-full h-full"></canvas> |
| </div> |
| |
| |
| <div id="cameraPath" class="absolute inset-0 hidden"> |
| <svg class="w-full h-full" viewBox="0 0 400 300"> |
| <path id="cameraPathSvg" stroke="#3B82F6" stroke-width="2" fill="none" opacity="0.7"/> |
| <circle id="cameraPos" r="4" fill="#3B82F6" class="glow-effect"/> |
| </svg> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-4 grid grid-cols-2 lg:grid-cols-4 gap-4"> |
| <div class="glass-effect rounded-lg p-4 text-center"> |
| <span class="material-icons text-blue-500 mb-2">photo_camera</span> |
| <p class="text-sm text-gray-400">فریمهای پردازش شده</p> |
| <p id="framesProcessed" class="text-xl font-bold">0</p> |
| </div> |
| <div class="glass-effect rounded-lg p-4 text-center"> |
| <span class="material-icons text-purple-500 mb-2">grain</span> |
| <p class="text-sm text-gray-400">نقاط سهبعدی</p> |
| <p id="pointsCount" class="text-xl font-bold">0</p> |
| </div> |
| <div class="glass-effect rounded-lg p-4 text-center"> |
| <span class="material-icons text-green-500 mb-2">timeline</span> |
| <p class="text-sm text-gray-400">ترکینگ دوربین</p> |
| <p id="trackingStatus" class="text-sm">غیرفعال</p> |
| </div> |
| <div class="glass-effect rounded-lg p-4 text-center"> |
| <span class="material-icons text-orange-500 mb-2">memory</span> |
| <p class="text-sm text-gray-400">زمان پردازش</p> |
| <p id="processingTime" class="text-xl font-bold">0s</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </main> |
|
|
| |
| <div id="resultsModal" class="fixed inset-0 bg-black bg-opacity-75 z-50 hidden flex items-center justify-center"> |
| <div class="glass-effect rounded-xl p-8 max-w-2xl w-full mx-4"> |
| <div class="text-center"> |
| <div class="w-20 h-20 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4"> |
| <span class="material-icons text-4xl">check_circle</span> |
| </div> |
| <h2 class="text-2xl font-bold mb-4">پردازش با موفقیت انجام شد!</h2> |
| <p class="text-gray-300 mb-6">محیط سهبعدی از ویدیوی شما با موفقیت ساخته شد</p> |
| |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> |
| <div class="glass-effect rounded-lg p-4"> |
| <p class="text-2xl font-bold text-blue-400" id="finalFrames">0</p> |
| <p class="text-sm text-gray-400">فریم پردازش شده</p> |
| </div> |
| <div class="glass-effect rounded-lg p-4"> |
| <p class="text-2xl font-bold text-purple-400" id="finalPoints">0</p> |
| <p class="text-sm text-gray-400">نقطه سهبعدی</p> |
| </div> |
| <div class="glass-effect rounded-lg p-4"> |
| <p class="text-2xl font-bold text-green-400" id="finalTime">0s</p> |
| <p class="text-sm text-gray-400">زمان کل</p> |
| </div> |
| </div> |
| |
| <div class="flex justify-center space-x-4"> |
| <button id="downloadBtn" class="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded-lg transition-colors"> |
| <span class="material-icons mr-2">download</span> |
| دانلود نتایج |
| </button> |
| <button id="closeModal" class="bg-gray-700 hover:bg-gray-600 px-6 py-2 rounded-lg transition-colors"> |
| بستن |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let scene, camera, renderer, pointCloud; |
| let animationId; |
| let processingStartTime; |
| |
| |
| function init3DViewer() { |
| const container = document.getElementById('threejs-container'); |
| |
| |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x1a1a1a); |
| |
| |
| camera = new THREE.PerspectiveCamera( |
| 75, |
| container.clientWidth / container.clientHeight, |
| 0.1, |
| 1000 |
| ); |
| camera.position.set(5, 5, 5); |
| camera.lookAt(0, 0, 0); |
| |
| |
| renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setSize(container.clientWidth, container.clientHeight); |
| container.appendChild(renderer.domElement); |
| |
| |
| const gridHelper = new THREE.GridHelper(10, 10, 0x444444, 0x444444); |
| scene.add(gridHelper); |
| |
| |
| const axesHelper = new THREE.AxesHelper(5); |
| scene.add(axesHelper); |
| |
| |
| const ambientLight = new THREE.AmbientLight(0x404040); |
| scene.add(ambientLight); |
| |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); |
| directionalLight.position.set(5, 5, 5); |
| scene.add(directionalLight); |
| |
| |
| createPointCloud(); |
| |
| |
| animate(); |
| } |
| |
| function createPointCloud() { |
| const geometry = new THREE.BufferGeometry(); |
| const positions = []; |
| const colors = []; |
| |
| |
| for (let i = 0; i < 1000; i++) { |
| positions.push( |
| (Math.random() - 0.5) * 10, |
| Math.random() * 5, |
| (Math.random() - 0.5) * 10 |
| ); |
| |
| const color = new THREE.Color(); |
| color.setHSL(Math.random(), 0.7, 0.5); |
| colors.push(color.r, color.g, color.b); |
| } |
| |
| geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); |
| geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); |
| |
| const material = new THREE.PointsMaterial({ |
| size: 0.05, |
| vertexColors: true, |
| opacity: 0.8, |
| transparent: true |
| }); |
| |
| pointCloud = new THREE.Points(geometry, material); |
| scene.add(pointCloud); |
| } |
| |
| function animate() { |
| animationId = requestAnimationFrame(animate); |
| |
| if (pointCloud) { |
| pointCloud.rotation.y += 0.001; |
| } |
| |
| renderer.render(scene, camera); |
| } |
| |
| |
| window.addEventListener('resize', () => { |
| if (renderer && camera) { |
| const container = document.getElementById('threejs-container'); |
| camera.aspect = container.clientWidth / container.clientHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(container.clientWidth, container.clientHeight); |
| } |
| }); |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| init3DViewer(); |
| |
| |
| const uploadArea = document.getElementById('uploadArea'); |
| const videoInput = document.getElementById('videoInput'); |
| const processBtn = document.getElementById('processBtn'); |
| |
| uploadArea.addEventListener('click', () => videoInput.click()); |
| uploadArea.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| uploadArea.classList.add('border-blue-500'); |
| }); |
| uploadArea.addEventListener('dragleave', () => { |
| uploadArea.classList.remove('border-blue-500'); |
| }); |
| uploadArea.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| uploadArea.classList.remove('border-blue-500'); |
| handleFile(e.dataTransfer.files[0]); |
| }); |
| |
| videoInput.addEventListener('change', (e) => { |
| handleFile(e.target.files[0]); |
| }); |
| |
| function handleFile(file) { |
| if (file && file.type.startsWith('video/')) { |
| const video = document.createElement('video'); |
| video.src = URL.createObjectURL(file); |
| video.addEventListener('loadedmetadata', () => { |
| document.getElementById('fileName').textContent = file.name; |
| document.getElementById('fileSize').textContent = (file.size / 1024 / 1024).toFixed(2) + ' MB'; |
| document.getElementById('duration').textContent = Math.floor(video.duration) + ' ثانیه'; |
| document.getElementById('videoInfo').classList.remove('hidden'); |
| processBtn.disabled = false; |
| }); |
| } |
| } |
| |
| |
| document.getElementById('frameRate').addEventListener('input', (e) => { |
| document.getElementById('frameRateValue').textContent = e.target.value; |
| }); |
| |
| document.getElementById('matchThreshold').addEventListener('input', (e) => { |
| document.getElementById('thresholdValue').textContent = e.target.value; |
| }); |
| |
| |
| processBtn.addEventListener('click', startProcessing); |
| |
| |
| document.getElementById('closeModal').addEventListener('click', () => { |
| document.getElementById('resultsModal').classList.add('hidden'); |
| }); |
| |
| document.getElementById('downloadBtn').addEventListener('click', () => { |
| alert('در نسخه واقعی: دانلود فایلهای خروجی Blender'); |
| }); |
| }); |
| |
| |
| function startProcessing() { |
| processingStartTime = Date.now(); |
| document.getElementById('progressSection').classList.remove('hidden'); |
| document.getElementById('processBtn').disabled = true; |
| |
| const steps = [ |
| { id: 'bar1', progress: 'progress1', duration: 2000 }, |
| { id: 'bar2', progress: 'progress2', duration: 3000 }, |
| { id: 'bar3', progress: 'progress3', duration: 2500 } |
| ]; |
| |
| let completedSteps = 0; |
| |
| steps.forEach((step, index) => { |
| let progress = 0; |
| const interval = setInterval(() => { |
| progress += 1; |
| const percent = Math.min(progress, 100); |
| document.getElementById(step.id).style.width = percent + '%'; |
| document.getElementById(step.progress).textContent = percent + '%'; |
| |
| |
| if (index === 0) { |
| document.getElementById('framesProcessed').textContent = Math.floor(percent * 2.4); |
| } else if (index === 1) { |
| document.getElementById('pointsCount').textContent = Math.floor(percent * 30); |
| } |
| |
| if (progress >= 100) { |
| clearInterval(interval); |
| completedSteps++; |
| |
| if (completedSteps === steps.length) { |
| finishProcessing(); |
| } |
| } |
| }, step.duration / 100); |
| }); |
| } |
| |
| function finishProcessing() { |
| const totalTime = Math.floor((Date.now() - processingStartTime) / 1000); |
| document.getElementById('processingTime').textContent = totalTime + 's'; |
| document.getElementById('trackingStatus').textContent = 'فعال'; |
| |
| |
| document.getElementById('finalFrames').textContent = document.getElementById('framesProcessed').textContent; |
| document.getElementById('finalPoints').textContent = document.getElementById('pointsCount').textContent; |
| document.getElementById('finalTime').textContent = totalTime + 's'; |
| |
| |
| document.getElementById('cameraPath').classList.remove('hidden'); |
| animateCameraPath(); |
| |
| |
| setTimeout(() => { |
| document.getElementById('resultsModal').classList.remove('hidden'); |
| }, 1000); |
| } |
| |
| function animateCameraPath() { |
| const path = document.getElementById('cameraPathSvg'); |
| const pos = document.getElementById('cameraPos'); |
| |
| |
| let d = 'M 50 250'; |
| for (let i = 0; i < 300; i += 10) { |
| const x = 50 + i; |
| const y = 250 + Math.sin(i * 0.02) * 50; |
| d += ` L ${x} ${y}`; |
| } |
| path.setAttribute('d', d); |
| |
| |
| let progress = 0; |
| const animateCamera = () => { |
| progress += 0.01; |
| if (progress > 1) progress = 0; |
| |
| const point = path.getPointAtLength(progress * path.getTotalLength()); |
| pos.setAttribute('cx', point.x); |
| pos.setAttribute('cy', point.y); |
| |
| requestAnimationFrame(animateCamera); |
| }; |
| animateCamera(); |
| } |
| |
| |
| function createBackgroundAnimation() { |
| const canvas = document.createElement('canvas'); |
| const ctx = canvas.getContext('2d'); |
| const container = document.getElementById('background-animation'); |
| |
| canvas.width = window.innerWidth; |
| canvas.height = window.innerHeight; |
| canvas.style.position = 'absolute'; |
| canvas.style.top = '0'; |
| canvas.style.left = '0'; |
| canvas.style.width = '100%'; |
| canvas.style.height = '100%'; |
| |
| container.appendChild(canvas); |
| |
| const particles = []; |
| const particleCount = 100; |
| |
| for (let i = 0; i < particleCount; i++) { |
| particles.push({ |
| x: Math.random() * canvas.width, |
| y: Math.random() * canvas.height, |
| vx: (Math.random() - 0.5) * 0.5, |
| vy: (Math.random() - 0.5) * 0.5, |
| size: Math.random() * 2 + 1, |
| opacity: Math.random() * 0.5 + 0.1 |
| }); |
| } |
| |
| function animate() { |
| ctx.fillStyle = 'rgba(26, 26, 26, 0.1)'; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| particles.forEach(particle => { |
| particle.x += particle.vx; |
| particle.y += particle.vy; |
| |
| if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1; |
| if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1; |
| |
| ctx.beginPath(); |
| ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); |
| ctx.fillStyle = `rgba(59, 130, 246, ${particle.opacity})`; |
| ctx.fill(); |
| }); |
| |
| requestAnimationFrame(animate); |
| } |
| |
| animate(); |
| } |
| |
| createBackgroundAnimation(); |
| </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=bightbranding/auto-photogrammetry-camera-tracker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |