From 256f36015b9acafcab382f393ec3911dfc34a1c5 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 12 Mar 2026 21:46:22 +0100 Subject: [PATCH] refactor: Update relief icon handling and streamline texture atlas integration --- public/modules/dynamic/auto-update.js | 3 +- src/config/relief-config.ts | 20 +++++------- src/modules/relief-generator.ts | 14 ++++----- src/modules/texture-atlas-layer.ts | 45 +++++++++++++++------------ src/renderers/draw-relief-icons.ts | 22 +++---------- 5 files changed, 46 insertions(+), 58 deletions(-) diff --git a/public/modules/dynamic/auto-update.js b/public/modules/dynamic/auto-update.js index 00a425f0..2a1c1045 100644 --- a/public/modules/dynamic/auto-update.js +++ b/public/modules/dynamic/auto-update.js @@ -1122,10 +1122,11 @@ export function resolveVersionConflicts(mapVersion) { terrainEl.querySelectorAll("use").forEach(u => { const href = u.getAttribute("href") || u.getAttribute("xlink:href") || ""; if (!href) return; + const icon = href.replace("#", ""); const x = +u.getAttribute("x"); const y = +u.getAttribute("y"); const s = +u.getAttribute("width"); - relief.push({i: relief.length, href, x, y, s}); + relief.push({i: relief.length, icon, x, y, s}); }); terrainEl.innerHTML = ""; pack.relief = relief; diff --git a/src/config/relief-config.ts b/src/config/relief-config.ts index 5db57607..aac28752 100644 --- a/src/config/relief-config.ts +++ b/src/config/relief-config.ts @@ -1,11 +1,12 @@ -function createAtlas(ids: string[]) { +function createAtlas(set: string, ids: string[]) { + const url = `images/relief/${set}.png`; const n = ids.length || 1; const cols = Math.ceil(Math.sqrt(n)); - return { ids, cols, rows: Math.ceil(n / cols) }; + return { url, ids, cols, rows: Math.ceil(n / cols) }; } -export const RELIEF_SYMBOLS = { - simple: createAtlas([ +export const RELIEF_ATLASES = { + simple: createAtlas("simple", [ "relief-mount-1", "relief-hill-1", "relief-conifer-1", @@ -16,7 +17,7 @@ export const RELIEF_SYMBOLS = { "relief-swamp-1", "relief-dune-1", ]), - gray: createAtlas([ + gray: createAtlas("gray", [ "relief-mount-2-bw", "relief-mount-3-bw", "relief-mount-4-bw", @@ -52,7 +53,7 @@ export const RELIEF_SYMBOLS = { "relief-deciduous-2-bw", "relief-deciduous-3-bw", ]), - colored: createAtlas([ + colored: createAtlas("colored", [ "relief-mount-2", "relief-mount-3", "relief-mount-4", @@ -90,13 +91,6 @@ export const RELIEF_SYMBOLS = { ]), }; -export const RELIEF_ATLASES = Object.fromEntries( - Object.entries(RELIEF_SYMBOLS).map(([set, { cols, rows }]) => [ - set, - { url: `images/relief/${set}.png`, cols, rows }, - ]), -); - export const VARIANT_RANGES: Record = { mount: [2, 7], mountSnow: [1, 6], diff --git a/src/modules/relief-generator.ts b/src/modules/relief-generator.ts index e3cc5383..9598d73d 100644 --- a/src/modules/relief-generator.ts +++ b/src/modules/relief-generator.ts @@ -11,7 +11,7 @@ import { export interface ReliefIcon { i: number; - href: string; // e.g. "#relief-mount-1" + icon: string; // e.g. "relief-mount-1" x: number; y: number; s: number; // size (width = height in map units) @@ -63,10 +63,10 @@ export function generateRelief(): ReliefIcon[] { if (!polygonContains(polygon, [cx, cy])) continue; let h = (4 + Math.random()) * size; const icon = getBiomeIcon(i, biome); - if (icon === "#relief-grass-1") h *= 1.2; + if (icon === "relief-grass-1") h *= 1.2; reliefIcons.push({ i: reliefIcons.length, - href: icon, + icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2), @@ -85,7 +85,7 @@ export function generateRelief(): ReliefIcon[] { if (!polygonContains(polygon, [cx, cy])) continue; reliefIcons.push({ i: reliefIcons.length, - href: icon, + icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2), @@ -125,9 +125,9 @@ function getVariant(type: string): number { } function getHref(type: string, set: string): string { - if (set === "colored") return `#relief-${type}-${getVariant(type)}`; - if (set === "gray") return `#relief-${type}-${getVariant(type)}-bw`; - return `#relief-${COLORED_TO_SIMPLE_MAP[type] ?? type}-1`; + if (set === "colored") return `relief-${type}-${getVariant(type)}`; + if (set === "gray") return `relief-${type}-${getVariant(type)}-bw`; + return `relief-${COLORED_TO_SIMPLE_MAP[type] ?? type}-1`; } window.generateReliefIcons = generateRelief; diff --git a/src/modules/texture-atlas-layer.ts b/src/modules/texture-atlas-layer.ts index df9423f2..b02c57d1 100644 --- a/src/modules/texture-atlas-layer.ts +++ b/src/modules/texture-atlas-layer.ts @@ -14,16 +14,16 @@ import { export interface AtlasConfig { url: string; + ids: string[]; cols: number; rows: number; } -export interface AtlasQuad { - atlasId: string; +export interface AtlasItem { + icon: string; x: number; y: number; s: number; - tileIndex: number; } export class TextureAtlasLayer { @@ -47,25 +47,30 @@ export class TextureAtlasLayer { }); } - draw(quads: AtlasQuad[]) { + draw(items: AtlasItem[]) { if (!this.group) return; this.disposeGroup(); - const byAtlas = new Map(); - for (const q of quads) { - let arr = byAtlas.get(q.atlasId); - if (!arr) { - arr = []; - byAtlas.set(q.atlasId, arr); + const byAtlas = new Map(); + for (const item of items) { + for (const [atlasId, config] of Object.entries(this.atlases)) { + const tileIndex = config.ids.indexOf(item.icon); + if (tileIndex === -1) continue; + let arr = byAtlas.get(atlasId); + if (!arr) { + arr = []; + byAtlas.set(atlasId, arr); + } + arr.push({ item, tileIndex }); + break; } - arr.push(q); } - for (const [atlasId, atlasQuads] of byAtlas) { + for (const [atlasId, entries] of byAtlas) { const texture = this.textureCache.get(atlasId); const config = this.atlases[atlasId]; if (!texture || !config) continue; - this.group.add(buildMesh(atlasQuads, config, texture)); + this.group.add(buildMesh(entries, config, texture)); } WebGLLayer.rerender(); } @@ -113,20 +118,20 @@ export class TextureAtlasLayer { } function buildMesh( - quads: AtlasQuad[], + entries: Array<{ item: AtlasItem; tileIndex: number }>, atlas: AtlasConfig, texture: Texture, ): Mesh { const { cols, rows } = atlas; - const positions = new Float32Array(quads.length * 4 * 3); - const uvs = new Float32Array(quads.length * 4 * 2); - const indices = new Uint32Array(quads.length * 6); + const positions = new Float32Array(entries.length * 4 * 3); + const uvs = new Float32Array(entries.length * 4 * 2); + const indices = new Uint32Array(entries.length * 6); let vi = 0, ii = 0; - for (const q of quads) { - const col = q.tileIndex % cols; - const row = Math.floor(q.tileIndex / cols); + for (const { item: q, tileIndex } of entries) { + const col = tileIndex % cols; + const row = Math.floor(tileIndex / cols); const u0 = col / cols, u1 = (col + 1) / cols; const v0 = row / rows, diff --git a/src/renderers/draw-relief-icons.ts b/src/renderers/draw-relief-icons.ts index 819e6fc3..cc78135c 100644 --- a/src/renderers/draw-relief-icons.ts +++ b/src/renderers/draw-relief-icons.ts @@ -1,16 +1,16 @@ -import { RELIEF_ATLASES, RELIEF_SYMBOLS } from "../config/relief-config"; +import { RELIEF_ATLASES } from "../config/relief-config"; import type { ReliefIcon } from "../modules/relief-generator"; import { generateRelief } from "../modules/relief-generator"; import { TextureAtlasLayer } from "../modules/texture-atlas-layer"; import { byId } from "../utils"; -const terrainLayer = new TextureAtlasLayer("terrain", RELIEF_ATLASES); +const layer = new TextureAtlasLayer("terrain", RELIEF_ATLASES); function drawSvg(icons: ReliefIcon[], parentEl: HTMLElement): void { parentEl.innerHTML = icons .map( (r) => - ``, + ``, ) .join(""); } @@ -29,24 +29,12 @@ window.drawRelief = ( if (type === "svg") { drawSvg(icons, parentEl); } else { - terrainLayer.draw(resolveQuads(icons)); + layer.draw(icons); } }; -function resolveQuads(icons: ReliefIcon[]) { - return icons.map((r) => { - const id = r.href.startsWith("#") ? r.href.slice(1) : r.href; - for (const [set, { ids }] of Object.entries(RELIEF_SYMBOLS)) { - const tileIndex = ids.indexOf(id); - if (tileIndex !== -1) - return { atlasId: set, x: r.x, y: r.y, s: r.s, tileIndex }; - } - throw new Error(`Relief: unknown symbol href "${r.href}"`); - }); -} - window.undrawRelief = () => { - terrainLayer.clear(); + layer.clear(); const terrainEl = byId("terrain"); if (terrainEl) terrainEl.innerHTML = ""; };