document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('droneCanvas'); const ctx = canvas.getContext('2d'); const osdElements = { altitude: document.getElementById('altitude'), speed: document.getElementById('speed'), battery: document.getElementById('battery') }; // Set canvas size function resizeCanvas() { const container = canvas.parentElement; canvas.width = container.clientWidth; canvas.height = container.clientHeight; } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // Drone state const drone = { x: 0, y: 0, z: 0, rotationX: 0, rotationY: 0, rotationZ: 0, velocityX: 0, velocityY: 0, velocityZ: 0, battery: 100, isFlying: false, viewMode: 'fpv' // 'fpv' or 'map' }; // Controls const controls = { throttle: 0, yaw: 0, pitch: 0, roll: 0, windSpeed: 5, gravity: 9.8 }; // Environment const environment = { obstacles: [], terrain: [] }; // Setup controls document.getElementById('throttle').addEventListener('input', (e) => { controls.throttle = parseInt(e.target.value); }); document.getElementById('yaw').addEventListener('input', (e) => { controls.yaw = parseInt(e.target.value); }); document.getElementById('pitch').addEventListener('input', (e) => { controls.pitch = parseInt(e.target.value); }); document.getElementById('roll').addEventListener('input', (e) => { controls.roll = parseInt(e.target.value); }); document.getElementById('windSpeed').addEventListener('input', (e) => { controls.windSpeed = parseInt(e.target.value); }); document.getElementById('gravity').addEventListener('input', (e) => { controls.gravity = parseFloat(e.target.value); }); // Buttons document.getElementById('viewToggle').addEventListener('click', () => { drone.viewMode = drone.viewMode === 'fpv' ? 'map' : 'fpv'; document.getElementById('viewToggle').textContent = drone.viewMode === 'fpv' ? 'FPV View' : 'Map View'; }); document.getElementById('resetDrone').addEventListener('click', resetDrone); document.getElementById('takeoff').addEventListener('click', takeoff); document.getElementById('land').addEventListener('click', land); function resetDrone() { Object.assign(drone, { x: 0, y: 0, z: 0, rotationX: 0, rotationY: 0, rotationZ: 0, velocityX: 0, velocityY: 0, velocityZ: 0, battery: 100, isFlying: false }); } function takeoff() { if (!drone.isFlying) { drone.isFlying = true; drone.z = 1; // Start slightly above ground } } function land() { if (drone.isFlying) { drone.isFlying = false; drone.z = 0; drone.velocityZ = 0; } } // Physics update function updatePhysics(deltaTime) { if (!drone.isFlying) return; // Apply controls drone.rotationX += controls.pitch * 0.01 * deltaTime; drone.rotationY += controls.yaw * 0.01 * deltaTime; drone.rotationZ += controls.roll * 0.01 * deltaTime; // Clamp rotations drone.rotationX = Math.max(-Math.PI/4, Math.min(Math.PI/4, drone.rotationX)); drone.rotationZ = Math.max(-Math.PI/4, Math.min(Math.PI/4, drone.rotationZ)); // Calculate forces const liftForce = controls.throttle / 10; const forwardForce = Math.sin(drone.rotationX) * liftForce; const sideForce = Math.sin(drone.rotationZ) * liftForce; // Apply forces drone.velocityX += forwardForce * deltaTime; drone.velocityY += sideForce * deltaTime; drone.velocityZ += (liftForce - controls.gravity) * deltaTime; // Apply wind drone.velocityX += (Math.random() - 0.5) * controls.windSpeed * 0.1 * deltaTime; drone.velocityY += (Math.random() - 0.5) * controls.windSpeed * 0.1 * deltaTime; // Apply drag const drag = 0.98; drone.velocityX *= drag; drone.velocityY *= drag; drone.velocityZ *= drag; // Update position drone.x += drone.velocityX * deltaTime; drone.y += drone.velocityY * deltaTime; drone.z += drone.velocityZ * deltaTime; // Ground collision if (drone.z < 0) { drone.z = 0; drone.velocityZ = 0; if (drone.isFlying && Math.abs(drone.velocityX) + Math.abs(drone.velocityY) < 0.5) { land(); } } // Battery drain drone.battery -= 0.05 * deltaTime; if (drone.battery <= 0) { drone.battery = 0; land(); } } // Render function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); if (drone.viewMode === 'fpv') { renderFPV(); } else { renderMap(); } // Update OSD osdElements.altitude.textContent = drone.z.toFixed(1); osdElements.speed.textContent = Math.sqrt( drone.velocityX * drone.velocityX + drone.velocityY * drone.velocityY + drone.velocityZ * drone.velocityZ ).toFixed(1); osdElements.battery.textContent = drone.battery.toFixed(0); } function renderFPV() { // Sky gradient const skyGradient = ctx.createLinearGradient(0, 0, 0, canvas.height); skyGradient.addColorStop(0, '#1e3a8a'); skyGradient.addColorStop(1, '#0c4a6e'); ctx.fillStyle = skyGradient; ctx.fillRect(0, 0, canvas.width, canvas.height); // Horizon line const horizonY = canvas.height / 2 - drone.rotationX * 50; ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, horizonY); ctx.lineTo(canvas.width, horizonY); ctx.stroke(); // Ground ctx.fillStyle = '#334155'; ctx.fillRect(0, horizonY, canvas.width, canvas.height - horizonY); // Drone crosshair ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(canvas.width / 2 - 20, canvas.height / 2); ctx.lineTo(canvas.width / 2 + 20, canvas.height / 2); ctx.moveTo(canvas.width / 2, canvas.height / 2 - 20); ctx.lineTo(canvas.width / 2, canvas.height / 2 + 20); ctx.stroke(); } function renderMap() { // Background ctx.fillStyle = '#1e293b'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Grid ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; ctx.lineWidth = 1; const gridSize = 50; for (let x = 0; x < canvas.width; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (let y = 0; y < canvas.height; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } // Drone const centerX = canvas.width / 2; const centerY = canvas.height / 2; const droneX = centerX + drone.x * 10; const droneY = centerY - drone.y * 10; ctx.save(); ctx.translate(droneX, droneY); ctx.rotate(drone.rotationY); // Drone body ctx.fillStyle = '#10b981'; ctx.beginPath(); ctx.arc(0, 0, 15, 0, Math.PI * 2); ctx.fill(); // Drone arms ctx.strokeStyle = '#10b981'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(-20, 0); ctx.lineTo(20, 0); ctx.moveTo(0, -20); ctx.lineTo(0, 20); ctx.stroke(); // Propellers ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.arc(-20, 0, 5, 0, Math.PI * 2); ctx.arc(20, 0, 5, 0, Math.PI * 2); ctx.arc(0, -20, 5, 0, Math.PI * 2); ctx.arc(0, 20, 5, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // Altitude indicator ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillRect(10, 10, 20, 100); ctx.fillStyle = '#10b981'; const altHeight = Math.min(100, drone.z * 10); ctx.fillRect(10, 110 - altHeight, 20, altHeight); } // Game loop let lastTime = 0; function gameLoop(timestamp) { const deltaTime = Math.min(100, timestamp - lastTime) / 1000; lastTime = timestamp; updatePhysics(deltaTime); render(); requestAnimationFrame(gameLoop); } requestAnimationFrame(gameLoop); });