Files
hexifyer/src/map/hex-layer.ts
Axel Meyer f144063db9 Fix sidebar init, client-side first, coastline rework
- Fix: move terrainPickerUI declaration before createToolbar to avoid
  "can't access lexical declaration before initialization" error
- Hex size is now fixed per map (not adjustable at runtime)
- Client-side first: localStorage for persistence, no server needed
  for editing. Added Export/Import JSON buttons in sidebar.
- Removed server dependency from main.ts init flow
- Coastline rework: routes edge-to-edge like road/river (bezier),
  fills one side with water color (the side away from hex center).
  No longer draws along hex edges — it's a proper dividing curve.
- Simplified map-settings to just grid toggle + opacity slider

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:59:29 +00:00

104 lines
3.1 KiB
TypeScript

import L from 'leaflet';
import type { AxialCoord } from '../../core/types.js';
import { axialToPixel, computeHexGeometry } from '../../core/coords.js';
import { getHexesInBounds, type PixelBounds } from '../../core/hex-grid.js';
import type { HexMap } from '../../core/hex-map.js';
import { renderHex } from '../svg/renderer.js';
export interface HexLayerOptions {
hexSize: number;
hexMap: HexMap;
origin?: { x: number; y: number };
selectedHex?: AxialCoord | null;
showGrid?: boolean;
opacity?: number;
}
/**
* Leaflet GridLayer that renders the hex overlay using Canvas.
* Uses L.GridLayer.extend() for compatibility with Leaflet's class system.
*/
export function createHexLayer(options: HexLayerOptions): L.GridLayer & {
setSelectedHex: (coord: AxialCoord | null) => void;
setShowGrid: (show: boolean) => void;
setHexOpacity: (opacity: number) => void;
} {
let hexSize = options.hexSize;
let hexMap = options.hexMap;
const origin = options.origin ?? { x: 0, y: 0 };
let selectedHex: AxialCoord | null = options.selectedHex ?? null;
let showGrid = options.showGrid ?? true;
let hexOpacity = options.opacity ?? 0.7;
const HexLayer = L.GridLayer.extend({
createTile(coords: L.Coords): HTMLCanvasElement {
const canvas = document.createElement('canvas');
const tileSize = this.getTileSize();
canvas.width = tileSize.x;
canvas.height = tileSize.y;
const ctx = canvas.getContext('2d')!;
const nwPoint = coords.scaleBy(tileSize);
const sePoint = nwPoint.add(tileSize);
const zoom = coords.z;
const maxZoom = this._map?.getMaxZoom() ?? 6;
const scale = Math.pow(2, maxZoom - zoom);
const bounds: PixelBounds = {
minX: nwPoint.x * scale,
minY: nwPoint.y * scale,
maxX: sePoint.x * scale,
maxY: sePoint.y * scale,
};
const hexCoords = getHexesInBounds(bounds, hexSize, origin);
for (const coord of hexCoords) {
const terrain = hexMap.getTerrain(coord);
const pixelCenter = axialToPixel(coord, hexSize, origin);
const localX = (pixelCenter.x - bounds.minX) / scale;
const localY = (pixelCenter.y - bounds.minY) / scale;
const localSize = hexSize / scale;
const geom = computeHexGeometry(localX, localY, localSize);
const isSelected = selectedHex !== null &&
selectedHex.q === coord.q &&
selectedHex.r === coord.r;
renderHex(ctx, geom, terrain, {
opacity: hexOpacity,
showGrid,
selected: isSelected,
});
}
return canvas;
},
});
const layer = new HexLayer() as L.GridLayer & {
setSelectedHex: (coord: AxialCoord | null) => void;
setShowGrid: (show: boolean) => void;
setHexOpacity: (opacity: number) => void;
};
layer.setSelectedHex = (coord: AxialCoord | null) => {
selectedHex = coord;
layer.redraw();
};
layer.setShowGrid = (show: boolean) => {
showGrid = show;
layer.redraw();
};
layer.setHexOpacity = (opacity: number) => {
hexOpacity = opacity;
layer.redraw();
};
return layer;
}