- 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>
104 lines
3.1 KiB
TypeScript
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;
|
|
}
|