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>
This commit is contained in:
@@ -5,7 +5,7 @@ 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 extends L.GridLayerOptions {
|
||||
export interface HexLayerOptions {
|
||||
hexSize: number;
|
||||
hexMap: HexMap;
|
||||
origin?: { x: number; y: number };
|
||||
@@ -16,84 +16,88 @@ export interface HexLayerOptions extends L.GridLayerOptions {
|
||||
|
||||
/**
|
||||
* Leaflet GridLayer that renders the hex overlay using Canvas.
|
||||
* Delegates all hex rendering to svg/renderer.ts.
|
||||
* Uses L.GridLayer.extend() for compatibility with Leaflet's class system.
|
||||
*/
|
||||
export class HexOverlayLayer extends L.GridLayer {
|
||||
private hexSize: number;
|
||||
private hexMap: HexMap;
|
||||
private origin: { x: number; y: number };
|
||||
private _selectedHex: AxialCoord | null = null;
|
||||
private _showGrid = true;
|
||||
private _hexOpacity = 0.7;
|
||||
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;
|
||||
|
||||
constructor(options: HexLayerOptions) {
|
||||
super(options);
|
||||
this.hexSize = options.hexSize;
|
||||
this.hexMap = options.hexMap;
|
||||
this.origin = options.origin ?? { x: 0, y: 0 };
|
||||
this._selectedHex = options.selectedHex ?? null;
|
||||
this._showGrid = options.showGrid ?? true;
|
||||
this._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')!;
|
||||
|
||||
setSelectedHex(coord: AxialCoord | null): void {
|
||||
this._selectedHex = coord;
|
||||
this.redraw();
|
||||
}
|
||||
const nwPoint = coords.scaleBy(tileSize);
|
||||
const sePoint = nwPoint.add(tileSize);
|
||||
|
||||
setShowGrid(show: boolean): void {
|
||||
this._showGrid = show;
|
||||
this.redraw();
|
||||
}
|
||||
const zoom = coords.z;
|
||||
const maxZoom = this._map?.getMaxZoom() ?? 6;
|
||||
const scale = Math.pow(2, maxZoom - zoom);
|
||||
|
||||
setHexOpacity(opacity: number): void {
|
||||
this._hexOpacity = opacity;
|
||||
this.redraw();
|
||||
}
|
||||
const bounds: PixelBounds = {
|
||||
minX: nwPoint.x * scale,
|
||||
minY: nwPoint.y * scale,
|
||||
maxX: sePoint.x * scale,
|
||||
maxY: sePoint.y * scale,
|
||||
};
|
||||
|
||||
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 hexCoords = getHexesInBounds(bounds, hexSize, origin);
|
||||
|
||||
const nwPoint = coords.scaleBy(tileSize);
|
||||
const sePoint = nwPoint.add(tileSize);
|
||||
for (const coord of hexCoords) {
|
||||
const terrain = hexMap.getTerrain(coord);
|
||||
const pixelCenter = axialToPixel(coord, hexSize, origin);
|
||||
|
||||
const zoom = coords.z;
|
||||
const maxZoom = this._map?.getMaxZoom() ?? 6;
|
||||
const scale = Math.pow(2, maxZoom - zoom);
|
||||
const localX = (pixelCenter.x - bounds.minX) / scale;
|
||||
const localY = (pixelCenter.y - bounds.minY) / scale;
|
||||
const localSize = hexSize / scale;
|
||||
|
||||
const bounds: PixelBounds = {
|
||||
minX: nwPoint.x * scale,
|
||||
minY: nwPoint.y * scale,
|
||||
maxX: sePoint.x * scale,
|
||||
maxY: sePoint.y * scale,
|
||||
};
|
||||
const geom = computeHexGeometry(localX, localY, localSize);
|
||||
const isSelected = selectedHex !== null &&
|
||||
selectedHex.q === coord.q &&
|
||||
selectedHex.r === coord.r;
|
||||
|
||||
const hexCoords = getHexesInBounds(bounds, this.hexSize, this.origin);
|
||||
renderHex(ctx, geom, terrain, {
|
||||
opacity: hexOpacity,
|
||||
showGrid,
|
||||
selected: isSelected,
|
||||
});
|
||||
}
|
||||
|
||||
for (const coord of hexCoords) {
|
||||
const terrain = this.hexMap.getTerrain(coord);
|
||||
const pixelCenter = axialToPixel(coord, this.hexSize, this.origin);
|
||||
return canvas;
|
||||
},
|
||||
});
|
||||
|
||||
const localX = (pixelCenter.x - bounds.minX) / scale;
|
||||
const localY = (pixelCenter.y - bounds.minY) / scale;
|
||||
const localSize = this.hexSize / scale;
|
||||
const layer = new HexLayer() as L.GridLayer & {
|
||||
setSelectedHex: (coord: AxialCoord | null) => void;
|
||||
setShowGrid: (show: boolean) => void;
|
||||
setHexOpacity: (opacity: number) => void;
|
||||
};
|
||||
|
||||
const geom = computeHexGeometry(localX, localY, localSize);
|
||||
const isSelected = this._selectedHex !== null &&
|
||||
this._selectedHex.q === coord.q &&
|
||||
this._selectedHex.r === coord.r;
|
||||
layer.setSelectedHex = (coord: AxialCoord | null) => {
|
||||
selectedHex = coord;
|
||||
layer.redraw();
|
||||
};
|
||||
|
||||
renderHex(ctx, geom, terrain, {
|
||||
opacity: this._hexOpacity,
|
||||
showGrid: this._showGrid,
|
||||
selected: isSelected,
|
||||
});
|
||||
}
|
||||
layer.setShowGrid = (show: boolean) => {
|
||||
showGrid = show;
|
||||
layer.redraw();
|
||||
};
|
||||
|
||||
return canvas;
|
||||
}
|
||||
layer.setHexOpacity = (opacity: number) => {
|
||||
hexOpacity = opacity;
|
||||
layer.redraw();
|
||||
};
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user