Browse Source

Fix fog of war to work correctly with 3D pitch/rotation

- Restore 3D building effect (pitch: 45, maxPitch: 60)
- Add drawProjectedCircle() helper that samples 48 points and projects each to screen coords
- Replace ctx.arc() circles with projected polygons for both homebase and player explore radius
- Fog circles now correctly render as ellipses when map is tilted

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
master
HikeMap User 4 weeks ago
parent
commit
8530f008d6
  1. 70
      index.html

70
index.html

@ -4480,8 +4480,8 @@
zoom: 13, zoom: 13,
maxZoom: 22, maxZoom: 22,
bearing: 0, bearing: 0,
pitch: 0, // Disable pitch to keep fog of war circles correct
maxPitch: 0, // Prevent user from tilting map (fog doesn't work with perspective)
pitch: 45, // 3D building perspective
maxPitch: 60,
doubleClickZoom: false doubleClickZoom: false
}); });
@ -5125,35 +5125,42 @@
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.6)'; fogCtx.fillStyle = 'rgba(0, 0, 0, 0.6)';
fogCtx.fillRect(0, 0, width, height); fogCtx.fillRect(0, 0, width, height);
// Helper function to draw a projected circle (handles rotation AND pitch)
function drawProjectedCircle(ctx, centerLngLat, radiusMeters, numPoints = 48) {
const points = [];
for (let i = 0; i < numPoints; i++) {
const angle = (i / numPoints) * 360;
const edgeLngLat = destinationPoint(centerLngLat, radiusMeters, angle);
const edgePoint = map.project(edgeLngLat);
points.push(edgePoint);
}
// Draw the projected shape
ctx.beginPath();
points.forEach((pt, i) => {
if (i === 0) ctx.moveTo(pt.x, pt.y);
else ctx.lineTo(pt.x, pt.y);
});
ctx.closePath();
}
// Homebase reveal (only if homebase exists) // Homebase reveal (only if homebase exists)
if (playerStats && playerStats.homeBaseLat != null && playerStats.homeBaseLng != null) { if (playerStats && playerStats.homeBaseLat != null && playerStats.homeBaseLng != null) {
// Calculate reveal circle center using container coordinates (viewport-relative)
// MapLibre uses [lng, lat] order
const homeLngLat = [playerStats.homeBaseLng, playerStats.homeBaseLat]; const homeLngLat = [playerStats.homeBaseLng, playerStats.homeBaseLat];
const centerPoint = map.project(homeLngLat);
// Calculate radius in pixels using map projection (with multiplier)
// Use actual distance between projected points to handle rotation correctly
const effectiveHomebaseRadius = playerRevealRadius * homebaseRadiusMultiplier; const effectiveHomebaseRadius = playerRevealRadius * homebaseRadiusMultiplier;
const edgeLngLat = destinationPoint(homeLngLat, effectiveHomebaseRadius, 90);
const edgePoint = map.project(edgeLngLat);
const radiusPixels = Math.hypot(edgePoint.x - centerPoint.x, edgePoint.y - centerPoint.y);
// Cut out revealed area using composite operation // Cut out revealed area using composite operation
fogCtx.save(); fogCtx.save();
fogCtx.globalCompositeOperation = 'destination-out'; fogCtx.globalCompositeOperation = 'destination-out';
// Create gradient for soft edge
const gradient = fogCtx.createRadialGradient(
centerPoint.x, centerPoint.y, radiusPixels * 0.85,
centerPoint.x, centerPoint.y, radiusPixels
);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
// Draw outer edge with fade (slightly larger, semi-transparent)
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
drawProjectedCircle(fogCtx, homeLngLat, effectiveHomebaseRadius * 1.05);
fogCtx.fill();
fogCtx.beginPath();
fogCtx.arc(centerPoint.x, centerPoint.y, radiusPixels, 0, Math.PI * 2);
fogCtx.fillStyle = gradient;
// Draw main revealed area (solid cutout)
fogCtx.fillStyle = 'rgba(0, 0, 0, 1)';
drawProjectedCircle(fogCtx, homeLngLat, effectiveHomebaseRadius * 0.95);
fogCtx.fill(); fogCtx.fill();
fogCtx.restore(); fogCtx.restore();
@ -5175,26 +5182,19 @@
} }
if (isOutsideHomebase) { if (isOutsideHomebase) {
const playerPoint = map.project(playerLngLat);
const effectiveExploreRadius = playerExploreRadius * exploreRadiusMultiplier; const effectiveExploreRadius = playerExploreRadius * exploreRadiusMultiplier;
const playerEdge = destinationPoint(playerLngLat, effectiveExploreRadius, 90);
const playerEdgePoint = map.project(playerEdge);
// Use actual distance to handle rotation correctly
const playerRadiusPixels = Math.hypot(playerEdgePoint.x - playerPoint.x, playerEdgePoint.y - playerPoint.y);
fogCtx.save(); fogCtx.save();
fogCtx.globalCompositeOperation = 'destination-out'; fogCtx.globalCompositeOperation = 'destination-out';
const playerGradient = fogCtx.createRadialGradient(
playerPoint.x, playerPoint.y, playerRadiusPixels * 0.85,
playerPoint.x, playerPoint.y, playerRadiusPixels
);
playerGradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
playerGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
// Draw outer edge with fade (slightly larger, semi-transparent)
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
drawProjectedCircle(fogCtx, playerLngLat, effectiveExploreRadius * 1.1);
fogCtx.fill();
fogCtx.beginPath();
fogCtx.arc(playerPoint.x, playerPoint.y, playerRadiusPixels, 0, Math.PI * 2);
fogCtx.fillStyle = playerGradient;
// Draw main revealed area (solid cutout)
fogCtx.fillStyle = 'rgba(0, 0, 0, 1)';
drawProjectedCircle(fogCtx, playerLngLat, effectiveExploreRadius * 0.85);
fogCtx.fill(); fogCtx.fill();
fogCtx.restore(); fogCtx.restore();

Loading…
Cancel
Save