mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-05 06:57:24 +02:00
refactor: Update relief icon handling and streamline texture atlas integration
This commit is contained in:
parent
b17b417d1b
commit
256f36015b
5 changed files with 46 additions and 58 deletions
|
|
@ -1122,10 +1122,11 @@ export function resolveVersionConflicts(mapVersion) {
|
||||||
terrainEl.querySelectorAll("use").forEach(u => {
|
terrainEl.querySelectorAll("use").forEach(u => {
|
||||||
const href = u.getAttribute("href") || u.getAttribute("xlink:href") || "";
|
const href = u.getAttribute("href") || u.getAttribute("xlink:href") || "";
|
||||||
if (!href) return;
|
if (!href) return;
|
||||||
|
const icon = href.replace("#", "");
|
||||||
const x = +u.getAttribute("x");
|
const x = +u.getAttribute("x");
|
||||||
const y = +u.getAttribute("y");
|
const y = +u.getAttribute("y");
|
||||||
const s = +u.getAttribute("width");
|
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 = "";
|
terrainEl.innerHTML = "";
|
||||||
pack.relief = relief;
|
pack.relief = relief;
|
||||||
|
|
|
||||||
|
|
@ -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 n = ids.length || 1;
|
||||||
const cols = Math.ceil(Math.sqrt(n));
|
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 = {
|
export const RELIEF_ATLASES = {
|
||||||
simple: createAtlas([
|
simple: createAtlas("simple", [
|
||||||
"relief-mount-1",
|
"relief-mount-1",
|
||||||
"relief-hill-1",
|
"relief-hill-1",
|
||||||
"relief-conifer-1",
|
"relief-conifer-1",
|
||||||
|
|
@ -16,7 +17,7 @@ export const RELIEF_SYMBOLS = {
|
||||||
"relief-swamp-1",
|
"relief-swamp-1",
|
||||||
"relief-dune-1",
|
"relief-dune-1",
|
||||||
]),
|
]),
|
||||||
gray: createAtlas([
|
gray: createAtlas("gray", [
|
||||||
"relief-mount-2-bw",
|
"relief-mount-2-bw",
|
||||||
"relief-mount-3-bw",
|
"relief-mount-3-bw",
|
||||||
"relief-mount-4-bw",
|
"relief-mount-4-bw",
|
||||||
|
|
@ -52,7 +53,7 @@ export const RELIEF_SYMBOLS = {
|
||||||
"relief-deciduous-2-bw",
|
"relief-deciduous-2-bw",
|
||||||
"relief-deciduous-3-bw",
|
"relief-deciduous-3-bw",
|
||||||
]),
|
]),
|
||||||
colored: createAtlas([
|
colored: createAtlas("colored", [
|
||||||
"relief-mount-2",
|
"relief-mount-2",
|
||||||
"relief-mount-3",
|
"relief-mount-3",
|
||||||
"relief-mount-4",
|
"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<string, [number, number]> = {
|
export const VARIANT_RANGES: Record<string, [number, number]> = {
|
||||||
mount: [2, 7],
|
mount: [2, 7],
|
||||||
mountSnow: [1, 6],
|
mountSnow: [1, 6],
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
|
|
||||||
export interface ReliefIcon {
|
export interface ReliefIcon {
|
||||||
i: number;
|
i: number;
|
||||||
href: string; // e.g. "#relief-mount-1"
|
icon: string; // e.g. "relief-mount-1"
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
s: number; // size (width = height in map units)
|
s: number; // size (width = height in map units)
|
||||||
|
|
@ -63,10 +63,10 @@ export function generateRelief(): ReliefIcon[] {
|
||||||
if (!polygonContains(polygon, [cx, cy])) continue;
|
if (!polygonContains(polygon, [cx, cy])) continue;
|
||||||
let h = (4 + Math.random()) * size;
|
let h = (4 + Math.random()) * size;
|
||||||
const icon = getBiomeIcon(i, biome);
|
const icon = getBiomeIcon(i, biome);
|
||||||
if (icon === "#relief-grass-1") h *= 1.2;
|
if (icon === "relief-grass-1") h *= 1.2;
|
||||||
reliefIcons.push({
|
reliefIcons.push({
|
||||||
i: reliefIcons.length,
|
i: reliefIcons.length,
|
||||||
href: icon,
|
icon,
|
||||||
x: rn(cx - h, 2),
|
x: rn(cx - h, 2),
|
||||||
y: rn(cy - h, 2),
|
y: rn(cy - h, 2),
|
||||||
s: rn(h * 2, 2),
|
s: rn(h * 2, 2),
|
||||||
|
|
@ -85,7 +85,7 @@ export function generateRelief(): ReliefIcon[] {
|
||||||
if (!polygonContains(polygon, [cx, cy])) continue;
|
if (!polygonContains(polygon, [cx, cy])) continue;
|
||||||
reliefIcons.push({
|
reliefIcons.push({
|
||||||
i: reliefIcons.length,
|
i: reliefIcons.length,
|
||||||
href: icon,
|
icon,
|
||||||
x: rn(cx - h, 2),
|
x: rn(cx - h, 2),
|
||||||
y: rn(cy - h, 2),
|
y: rn(cy - h, 2),
|
||||||
s: rn(h * 2, 2),
|
s: rn(h * 2, 2),
|
||||||
|
|
@ -125,9 +125,9 @@ function getVariant(type: string): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHref(type: string, set: string): string {
|
function getHref(type: string, set: string): string {
|
||||||
if (set === "colored") return `#relief-${type}-${getVariant(type)}`;
|
if (set === "colored") return `relief-${type}-${getVariant(type)}`;
|
||||||
if (set === "gray") return `#relief-${type}-${getVariant(type)}-bw`;
|
if (set === "gray") return `relief-${type}-${getVariant(type)}-bw`;
|
||||||
return `#relief-${COLORED_TO_SIMPLE_MAP[type] ?? type}-1`;
|
return `relief-${COLORED_TO_SIMPLE_MAP[type] ?? type}-1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.generateReliefIcons = generateRelief;
|
window.generateReliefIcons = generateRelief;
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,16 @@ import {
|
||||||
|
|
||||||
export interface AtlasConfig {
|
export interface AtlasConfig {
|
||||||
url: string;
|
url: string;
|
||||||
|
ids: string[];
|
||||||
cols: number;
|
cols: number;
|
||||||
rows: number;
|
rows: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AtlasQuad {
|
export interface AtlasItem {
|
||||||
atlasId: string;
|
icon: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
s: number;
|
s: number;
|
||||||
tileIndex: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextureAtlasLayer {
|
export class TextureAtlasLayer {
|
||||||
|
|
@ -47,25 +47,30 @@ export class TextureAtlasLayer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(quads: AtlasQuad[]) {
|
draw(items: AtlasItem[]) {
|
||||||
if (!this.group) return;
|
if (!this.group) return;
|
||||||
this.disposeGroup();
|
this.disposeGroup();
|
||||||
|
|
||||||
const byAtlas = new Map<string, AtlasQuad[]>();
|
const byAtlas = new Map<string, { item: AtlasItem; tileIndex: number }[]>();
|
||||||
for (const q of quads) {
|
for (const item of items) {
|
||||||
let arr = byAtlas.get(q.atlasId);
|
for (const [atlasId, config] of Object.entries(this.atlases)) {
|
||||||
if (!arr) {
|
const tileIndex = config.ids.indexOf(item.icon);
|
||||||
arr = [];
|
if (tileIndex === -1) continue;
|
||||||
byAtlas.set(q.atlasId, arr);
|
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 texture = this.textureCache.get(atlasId);
|
||||||
const config = this.atlases[atlasId];
|
const config = this.atlases[atlasId];
|
||||||
if (!texture || !config) continue;
|
if (!texture || !config) continue;
|
||||||
this.group.add(buildMesh(atlasQuads, config, texture));
|
this.group.add(buildMesh(entries, config, texture));
|
||||||
}
|
}
|
||||||
WebGLLayer.rerender();
|
WebGLLayer.rerender();
|
||||||
}
|
}
|
||||||
|
|
@ -113,20 +118,20 @@ export class TextureAtlasLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMesh(
|
function buildMesh(
|
||||||
quads: AtlasQuad[],
|
entries: Array<{ item: AtlasItem; tileIndex: number }>,
|
||||||
atlas: AtlasConfig,
|
atlas: AtlasConfig,
|
||||||
texture: Texture,
|
texture: Texture,
|
||||||
): Mesh {
|
): Mesh {
|
||||||
const { cols, rows } = atlas;
|
const { cols, rows } = atlas;
|
||||||
const positions = new Float32Array(quads.length * 4 * 3);
|
const positions = new Float32Array(entries.length * 4 * 3);
|
||||||
const uvs = new Float32Array(quads.length * 4 * 2);
|
const uvs = new Float32Array(entries.length * 4 * 2);
|
||||||
const indices = new Uint32Array(quads.length * 6);
|
const indices = new Uint32Array(entries.length * 6);
|
||||||
|
|
||||||
let vi = 0,
|
let vi = 0,
|
||||||
ii = 0;
|
ii = 0;
|
||||||
for (const q of quads) {
|
for (const { item: q, tileIndex } of entries) {
|
||||||
const col = q.tileIndex % cols;
|
const col = tileIndex % cols;
|
||||||
const row = Math.floor(q.tileIndex / cols);
|
const row = Math.floor(tileIndex / cols);
|
||||||
const u0 = col / cols,
|
const u0 = col / cols,
|
||||||
u1 = (col + 1) / cols;
|
u1 = (col + 1) / cols;
|
||||||
const v0 = row / rows,
|
const v0 = row / rows,
|
||||||
|
|
|
||||||
|
|
@ -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 type { ReliefIcon } from "../modules/relief-generator";
|
||||||
import { generateRelief } from "../modules/relief-generator";
|
import { generateRelief } from "../modules/relief-generator";
|
||||||
import { TextureAtlasLayer } from "../modules/texture-atlas-layer";
|
import { TextureAtlasLayer } from "../modules/texture-atlas-layer";
|
||||||
import { byId } from "../utils";
|
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 {
|
function drawSvg(icons: ReliefIcon[], parentEl: HTMLElement): void {
|
||||||
parentEl.innerHTML = icons
|
parentEl.innerHTML = icons
|
||||||
.map(
|
.map(
|
||||||
(r) =>
|
(r) =>
|
||||||
`<use href="${r.href}" data-id="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`,
|
`<use href="#${r.icon}" data-id="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
@ -29,24 +29,12 @@ window.drawRelief = (
|
||||||
if (type === "svg") {
|
if (type === "svg") {
|
||||||
drawSvg(icons, parentEl);
|
drawSvg(icons, parentEl);
|
||||||
} else {
|
} 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 = () => {
|
window.undrawRelief = () => {
|
||||||
terrainLayer.clear();
|
layer.clear();
|
||||||
const terrainEl = byId("terrain");
|
const terrainEl = byId("terrain");
|
||||||
if (terrainEl) terrainEl.innerHTML = "";
|
if (terrainEl) terrainEl.innerHTML = "";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue