diff options
Diffstat (limited to 'html/space/renderer.js')
-rw-r--r-- | html/space/renderer.js | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/html/space/renderer.js b/html/space/renderer.js new file mode 100644 index 0000000..04646cf --- /dev/null +++ b/html/space/renderer.js @@ -0,0 +1,358 @@ +// Renderer module using HTML5 Canvas +import { getPlayerState, getWeaponStates } from './physics.js'; +import { getGameState } from './gameState.js'; + +// Import weapon constants +import { + PRIMARY_COOLDOWN, + SECONDARY_COOLDOWN, + PRIMARY_BURST_COUNT, + PRIMARY_BURST_DELAY +} from './physics.js'; + +let canvas; +let ctx; +let width; +let height; + +// Star field +let starfield = []; // Declare starfield array +const NUM_STARS = 2000; // Increased from 1000 +const STAR_FIELD_DEPTH = 20000; // Increased from 2000 + +// HUD constants +const HUD_COLOR = '#00ff00'; +const HUD_ALPHA = 0.7; +const RADAR_RADIUS = 100; +const RADAR_CENTER_X = 100; +const RADAR_CENTER_Y = 100; +const RADAR_SCALE = 0.1; // Scale factor for radar display +const TARGET_LOCK_COLOR = '#ff0000'; +const TARGET_LOCK_THRESHOLD = 20; // Pixels from center to consider locked + +// Initialize the renderer +export function initRenderer() { + console.log('Initializing renderer...'); + canvas = document.getElementById('gameCanvas'); + ctx = canvas.getContext('2d'); + + // Set canvas size + width = canvas.width = window.innerWidth; + height = canvas.height = window.innerHeight; + + // Initialize starfield + console.log('Creating starfield with', NUM_STARS, 'stars...'); + starfield = Array.from({ length: NUM_STARS }, () => ({ + x: (Math.random() - 0.5) * STAR_FIELD_DEPTH, + y: (Math.random() - 0.5) * STAR_FIELD_DEPTH, + z: Math.random() * STAR_FIELD_DEPTH, + size: Math.random() * 2 + 1 + })); + console.log('Starfield initialized'); +} + +// Project 3D point to 2D screen coordinates +function projectPoint(x, y, z) { + if (z <= 0) return null; // Behind camera + + const scale = 2000 / z; // Increased scale factor + return { + x: width/2 + x * scale, + y: height/2 + y * scale, + scale + }; +} + +// Check if any enemy ship is in targeting range +function getTargetLock(player, gameState) { + const centerX = width / 2; + const centerY = height / 2; + + for (const ship of gameState.enemyShips) { + const projected = projectPoint(ship.x - player.x, ship.y - player.y, ship.z - player.z); + if (projected) { + const distance = Math.sqrt( + Math.pow(projected.x - centerX, 2) + + Math.pow(projected.y - centerY, 2) + ); + + if (distance < TARGET_LOCK_THRESHOLD) { + return { + ship, + distance + }; + } + } + } + return null; +} + +// Draw radar/minimap +function drawRadar(player, gameState, targetLock) { + // Save context + ctx.save(); + + // Set radar style + ctx.strokeStyle = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR; + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.lineWidth = 1; + + // Draw radar background + ctx.beginPath(); + ctx.arc(RADAR_CENTER_X, RADAR_CENTER_Y, RADAR_RADIUS, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + + // Draw radar grid + ctx.beginPath(); + ctx.moveTo(RADAR_CENTER_X - RADAR_RADIUS, RADAR_CENTER_Y); + ctx.lineTo(RADAR_CENTER_X + RADAR_RADIUS, RADAR_CENTER_Y); + ctx.moveTo(RADAR_CENTER_X, RADAR_CENTER_Y - RADAR_RADIUS); + ctx.lineTo(RADAR_CENTER_X, RADAR_CENTER_Y + RADAR_RADIUS); + ctx.stroke(); + + // Draw objects on radar + ctx.fillStyle = HUD_COLOR; + + // Draw planets + gameState.planets.forEach(planet => { + const dx = (planet.x - player.x) * RADAR_SCALE; + const dy = (planet.z - player.z) * RADAR_SCALE; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < RADAR_RADIUS) { + ctx.beginPath(); + ctx.arc( + RADAR_CENTER_X + dx, + RADAR_CENTER_Y + dy, + 5, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + }); + + // Draw enemy ships + gameState.enemyShips.forEach(ship => { + const dx = (ship.x - player.x) * RADAR_SCALE; + const dy = (ship.z - player.z) * RADAR_SCALE; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < RADAR_RADIUS) { + ctx.beginPath(); + ctx.arc( + RADAR_CENTER_X + dx, + RADAR_CENTER_Y + dy, + 4, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + }); + + // Restore context + ctx.restore(); +} + +// Draw speed and direction indicators +function drawSpeedIndicator(player, targetLock) { + ctx.save(); + ctx.fillStyle = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR; + ctx.font = '14px monospace'; + + // Calculate speed from velocity components + const speed = Math.sqrt( + player.vx * player.vx + + player.vy * player.vy + + player.vz * player.vz + ); + + // Draw speed + ctx.fillText(`Speed: ${speed.toFixed(2)}`, 20, height - 40); + + // Draw direction (using x and z components for heading) + const direction = Math.atan2(player.vx, player.vz); + ctx.fillText(`Heading: ${(direction * 180 / Math.PI).toFixed(1)}°`, 20, height - 20); + + ctx.restore(); +} + +// Draw enhanced targeting reticle +function drawTargetingReticle(player, gameState) { + ctx.save(); + + // Check for target lock + const targetLock = getTargetLock(player, gameState); + const currentColor = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR; + + ctx.strokeStyle = currentColor; + ctx.lineWidth = 1; + + // Outer circle + ctx.beginPath(); + ctx.arc(width/2, height/2, 20, 0, Math.PI * 2); + ctx.stroke(); + + // Inner crosshair + ctx.beginPath(); + ctx.moveTo(width/2 - 10, height/2); + ctx.lineTo(width/2 + 10, height/2); + ctx.moveTo(width/2, height/2 - 10); + ctx.lineTo(width/2, height/2 + 10); + ctx.stroke(); + + // Target brackets + ctx.beginPath(); + ctx.moveTo(width/2 - 30, height/2 - 30); + ctx.lineTo(width/2 - 20, height/2 - 30); + ctx.lineTo(width/2 - 20, height/2 - 20); + ctx.moveTo(width/2 + 30, height/2 - 30); + ctx.lineTo(width/2 + 20, height/2 - 30); + ctx.lineTo(width/2 + 20, height/2 - 20); + ctx.moveTo(width/2 - 30, height/2 + 30); + ctx.lineTo(width/2 - 20, height/2 + 30); + ctx.lineTo(width/2 - 20, height/2 + 20); + ctx.moveTo(width/2 + 30, height/2 + 30); + ctx.lineTo(width/2 + 20, height/2 + 30); + ctx.lineTo(width/2 + 20, height/2 + 20); + ctx.stroke(); + + // Draw target lock indicator if locked + if (targetLock) { + // Draw pulsing circle around target + const pulseSize = 30 + Math.sin(Date.now() * 0.01) * 5; + ctx.beginPath(); + ctx.arc(width/2, height/2, pulseSize, 0, Math.PI * 2); + ctx.stroke(); + + // Draw target distance + ctx.fillStyle = currentColor; + ctx.font = '14px monospace'; + ctx.fillText(`Target Lock: ${targetLock.distance.toFixed(1)}`, width/2 - 50, height/2 + 50); + } + + ctx.restore(); +} + +// Draw weapon cooldown indicators +function drawWeaponCooldowns() { + const weaponStates = getWeaponStates(); + ctx.save(); + ctx.fillStyle = HUD_COLOR; + ctx.font = '14px monospace'; + + // Primary weapon cooldown (bottom left) + const primaryCooldown = weaponStates.primary.cooldown / PRIMARY_COOLDOWN; + ctx.fillText('Primary:', 20, height - 80); + ctx.fillStyle = `rgba(0, 255, 0, ${primaryCooldown})`; + ctx.fillRect(20, height - 70, 100, 10); + ctx.strokeStyle = HUD_COLOR; + ctx.strokeRect(20, height - 70, 100, 10); + + // Secondary weapon cooldown (bottom left, below primary) + const secondaryCooldown = weaponStates.secondary.cooldown / SECONDARY_COOLDOWN; + ctx.fillStyle = HUD_COLOR; + ctx.fillText('Secondary:', 20, height - 50); + ctx.fillStyle = `rgba(0, 255, 0, ${secondaryCooldown})`; + ctx.fillRect(20, height - 40, 100, 10); + ctx.strokeStyle = HUD_COLOR; + ctx.strokeRect(20, height - 40, 100, 10); + + // Draw burst indicator for primary weapon + if (weaponStates.primary.burstCount > 0) { + ctx.fillStyle = HUD_COLOR; + ctx.fillText(`Burst: ${weaponStates.primary.burstCount}`, 20, height - 20); + } + + ctx.restore(); +} + +// Main render function +export function render() { + const player = getPlayerState(); + const gameState = getGameState(); + const targetLock = getTargetLock(player, gameState); + + // Clear canvas + ctx.fillStyle = '#000000'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw starfield + let starsRendered = 0; + starfield.forEach(star => { + // Calculate star position relative to player + let relativeX = star.x - player.x; + let relativeY = star.y - player.y; + let relativeZ = star.z - player.z; + + // Apply player rotation + let rotatedX = relativeX * Math.cos(player.rotation) - relativeY * Math.sin(player.rotation); + let rotatedY = relativeX * Math.sin(player.rotation) + relativeY * Math.cos(player.rotation); + let rotatedZ = relativeZ; + + // Project to screen coordinates + if (rotatedZ > 0) { + const projected = projectPoint(rotatedX, rotatedY, rotatedZ); + if (projected) { + const brightness = Math.min(1, 2000 / rotatedZ); + ctx.fillStyle = `rgba(255, 255, 255, ${brightness})`; + ctx.beginPath(); + ctx.arc(projected.x, projected.y, star.size * brightness, 0, Math.PI * 2); + ctx.fill(); + starsRendered++; + } + } + }); + + // Debug output + if (Math.random() < 0.01) { // Only log occasionally to avoid spam + console.log('Stars rendered:', starsRendered); + } + + // Draw planets + gameState.planets.forEach(planet => { + const projected = projectPoint(planet.x - player.x, planet.y - player.y, planet.z - player.z); + if (projected) { + const radius = planet.radius * projected.scale; + ctx.fillStyle = planet.color; + ctx.beginPath(); + ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2); + ctx.fill(); + } + }); + + // Draw enemy ships + gameState.enemyShips.forEach(ship => { + const projected = projectPoint(ship.x - player.x, ship.y - player.y, ship.z - player.z); + if (projected) { + const size = 20 * projected.scale; + ctx.fillStyle = '#ff0000'; + ctx.beginPath(); + ctx.moveTo(projected.x, projected.y - size); + ctx.lineTo(projected.x + size, projected.y + size); + ctx.lineTo(projected.x - size, projected.y + size); + ctx.closePath(); + ctx.fill(); + } + }); + + // Draw projectiles + gameState.projectiles.forEach(projectile => { + const projected = projectPoint(projectile.x - player.x, projectile.y - player.y, projectile.z - player.z); + if (projected) { + const size = 3 * projected.scale; + ctx.fillStyle = projectile.type === 'primary' ? '#ffff00' : '#00ffff'; + ctx.beginPath(); + ctx.arc(projected.x, projected.y, size, 0, Math.PI * 2); + ctx.fill(); + } + }); + + // Draw HUD elements + drawRadar(player, gameState, targetLock); + drawSpeedIndicator(player, targetLock); + drawTargetingReticle(player, gameState); + drawWeaponCooldowns(); +} \ No newline at end of file |