fix: streamline texture loading and preload logic in WebGL renderer

This commit is contained in:
Azgaar 2026-03-10 22:55:08 +01:00
parent 4515232e93
commit dc06f3d65c
3 changed files with 130 additions and 147 deletions

106
src/config/relief-config.ts Normal file
View file

@ -0,0 +1,106 @@
export const RELIEF_SYMBOLS: Record<string, string[]> = {
simple: [
"relief-mount-1",
"relief-hill-1",
"relief-conifer-1",
"relief-deciduous-1",
"relief-acacia-1",
"relief-palm-1",
"relief-grass-1",
"relief-swamp-1",
"relief-dune-1",
],
gray: [
"relief-mount-2-bw",
"relief-mount-3-bw",
"relief-mount-4-bw",
"relief-mount-5-bw",
"relief-mount-6-bw",
"relief-mount-7-bw",
"relief-mountSnow-1-bw",
"relief-mountSnow-2-bw",
"relief-mountSnow-3-bw",
"relief-mountSnow-4-bw",
"relief-mountSnow-5-bw",
"relief-mountSnow-6-bw",
"relief-hill-2-bw",
"relief-hill-3-bw",
"relief-hill-4-bw",
"relief-hill-5-bw",
"relief-conifer-2-bw",
"relief-coniferSnow-1-bw",
"relief-swamp-2-bw",
"relief-swamp-3-bw",
"relief-cactus-1-bw",
"relief-cactus-2-bw",
"relief-cactus-3-bw",
"relief-deadTree-1-bw",
"relief-deadTree-2-bw",
"relief-vulcan-1-bw",
"relief-vulcan-2-bw",
"relief-vulcan-3-bw",
"relief-dune-2-bw",
"relief-grass-2-bw",
"relief-acacia-2-bw",
"relief-palm-2-bw",
"relief-deciduous-2-bw",
"relief-deciduous-3-bw",
],
colored: [
"relief-mount-2",
"relief-mount-3",
"relief-mount-4",
"relief-mount-5",
"relief-mount-6",
"relief-mount-7",
"relief-mountSnow-1",
"relief-mountSnow-2",
"relief-mountSnow-3",
"relief-mountSnow-4",
"relief-mountSnow-5",
"relief-mountSnow-6",
"relief-hill-2",
"relief-hill-3",
"relief-hill-4",
"relief-hill-5",
"relief-conifer-2",
"relief-coniferSnow-1",
"relief-swamp-2",
"relief-swamp-3",
"relief-cactus-1",
"relief-cactus-2",
"relief-cactus-3",
"relief-deadTree-1",
"relief-deadTree-2",
"relief-vulcan-1",
"relief-vulcan-2",
"relief-vulcan-3",
"relief-dune-2",
"relief-grass-2",
"relief-acacia-2",
"relief-palm-2",
"relief-deciduous-2",
"relief-deciduous-3",
],
};
export const VARIANT_RANGES: Record<string, [number, number]> = {
mount: [2, 7],
mountSnow: [1, 6],
hill: [2, 5],
conifer: [2, 2],
coniferSnow: [1, 1],
swamp: [2, 3],
cactus: [1, 3],
deadTree: [1, 2],
vulcan: [1, 3],
deciduous: [2, 3],
};
export const COLORED_TO_SIMPLE_MAP: Record<string, string> = {
mountSnow: "mount",
vulcan: "mount",
coniferSnow: "conifer",
cactus: "dune",
deadTree: "dune",
};

View file

@ -1,4 +1,5 @@
import { extent, polygonContains } from "d3";
import { COLORED_TO_SIMPLE_MAP, VARIANT_RANGES } from "../config/relief-config";
import {
byId,
getPackPolygon,
@ -118,127 +119,6 @@ export function generateRelief(): ReliefIcon[] {
}
}
// ── Utilities ─────────────────────────────────────────────────────────
export const RELIEF_SYMBOLS: Record<string, string[]> = {
simple: [
"relief-mount-1",
"relief-hill-1",
"relief-conifer-1",
"relief-deciduous-1",
"relief-acacia-1",
"relief-palm-1",
"relief-grass-1",
"relief-swamp-1",
"relief-dune-1",
],
gray: [
"relief-mount-2-bw",
"relief-mount-3-bw",
"relief-mount-4-bw",
"relief-mount-5-bw",
"relief-mount-6-bw",
"relief-mount-7-bw",
"relief-mountSnow-1-bw",
"relief-mountSnow-2-bw",
"relief-mountSnow-3-bw",
"relief-mountSnow-4-bw",
"relief-mountSnow-5-bw",
"relief-mountSnow-6-bw",
"relief-hill-2-bw",
"relief-hill-3-bw",
"relief-hill-4-bw",
"relief-hill-5-bw",
"relief-conifer-2-bw",
"relief-coniferSnow-1-bw",
"relief-swamp-2-bw",
"relief-swamp-3-bw",
"relief-cactus-1-bw",
"relief-cactus-2-bw",
"relief-cactus-3-bw",
"relief-deadTree-1-bw",
"relief-deadTree-2-bw",
"relief-vulcan-1-bw",
"relief-vulcan-2-bw",
"relief-vulcan-3-bw",
"relief-dune-2-bw",
"relief-grass-2-bw",
"relief-acacia-2-bw",
"relief-palm-2-bw",
"relief-deciduous-2-bw",
"relief-deciduous-3-bw",
],
colored: [
"relief-mount-2",
"relief-mount-3",
"relief-mount-4",
"relief-mount-5",
"relief-mount-6",
"relief-mount-7",
"relief-mountSnow-1",
"relief-mountSnow-2",
"relief-mountSnow-3",
"relief-mountSnow-4",
"relief-mountSnow-5",
"relief-mountSnow-6",
"relief-hill-2",
"relief-hill-3",
"relief-hill-4",
"relief-hill-5",
"relief-conifer-2",
"relief-coniferSnow-1",
"relief-swamp-2",
"relief-swamp-3",
"relief-cactus-1",
"relief-cactus-2",
"relief-cactus-3",
"relief-deadTree-1",
"relief-deadTree-2",
"relief-vulcan-1",
"relief-vulcan-2",
"relief-vulcan-3",
"relief-dune-2",
"relief-grass-2",
"relief-acacia-2",
"relief-palm-2",
"relief-deciduous-2",
"relief-deciduous-3",
],
};
// map a symbol href to its atlas set and tile index
export function resolveSprite(symbolHref: string): {
set: string;
tileIndex: number;
} {
const id = symbolHref.startsWith("#") ? symbolHref.slice(1) : symbolHref;
for (const [set, ids] of Object.entries(RELIEF_SYMBOLS)) {
const idx = ids.indexOf(id);
if (idx !== -1) return { set, tileIndex: idx };
}
throw new Error(`Relief: unknown symbol href "${symbolHref}"`);
}
const VARIANT_RANGES: Record<string, [number, number]> = {
mount: [2, 7],
mountSnow: [1, 6],
hill: [2, 5],
conifer: [2, 2],
coniferSnow: [1, 1],
swamp: [2, 3],
cactus: [1, 3],
deadTree: [1, 2],
vulcan: [1, 3],
deciduous: [2, 3],
};
const COLORED_TO_SIMPLE_MAP: Record<string, string> = {
mountSnow: "mount",
vulcan: "mount",
coniferSnow: "conifer",
cactus: "dune",
deadTree: "dune",
};
function getVariant(type: string): number {
const range = VARIANT_RANGES[type];
return range ? rand(...range) : 2;

View file

@ -1,13 +1,9 @@
import * as THREE from "three";
import { RELIEF_SYMBOLS } from "../config/relief-config";
import type { ReliefIcon } from "../modules/relief-generator";
import {
generateRelief,
RELIEF_SYMBOLS,
resolveSprite,
} from "../modules/relief-generator";
import { generateRelief } from "../modules/relief-generator";
import { byId } from "../utils";
// ── Module state ───────────────────────────────────────────────────────────────
let fo: SVGForeignObjectElement | null = null;
let renderer: any = null; // THREE.WebGLRenderer
let camera: any = null; // THREE.OrthographicCamera
@ -15,7 +11,9 @@ let scene: any = null; // THREE.Scene
const textureCache = new Map<string, any>(); // set name → THREE.Texture
// ── Texture ────────────────────────────────────────────────────────────────────
function preloadTextures(): void {
for (const set of Object.keys(RELIEF_SYMBOLS)) loadTexture(set);
}
function loadTexture(set: string): Promise<any> {
if (textureCache.has(set)) return Promise.resolve(textureCache.get(set));
@ -30,7 +28,8 @@ function loadTexture(set: string): Promise<any> {
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = true;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
if (renderer)
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
textureCache.set(set, texture);
resolve(texture);
},
@ -43,8 +42,6 @@ function loadTexture(set: string): Promise<any> {
});
}
// ── WebGL bootstrap ────────────────────────────────────────────────────────────
function ensureRenderer(): boolean {
const terrainEl = byId("terrain");
if (!terrainEl) return false;
@ -58,7 +55,6 @@ function ensureRenderer(): boolean {
camera = null;
scene = null;
disposeTextureCache();
// fall through to recreate
} else {
if (fo && !fo.isConnected) terrainEl.appendChild(fo);
return true;
@ -66,10 +62,7 @@ function ensureRenderer(): boolean {
}
// foreignObject hosts the WebGL canvas inside the SVG.
fo = document.createElementNS(
"http://www.w3.org/2000/svg",
"foreignObject",
) as unknown as SVGForeignObjectElement;
fo = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
fo.id = "terrainFo";
fo.setAttribute("x", "0");
fo.setAttribute("y", "0");
@ -102,19 +95,25 @@ function ensureRenderer(): boolean {
// Camera in SVG coordinate space: top=0, bottom=H puts map y=0 at screen-top.
camera = new THREE.OrthographicCamera(0, graphWidth, 0, graphHeight, -1, 1);
scene = new THREE.Scene();
preloadTextures();
return true;
}
// ── Scene / geometry ───────────────────────────────────────────────────────────
// map a symbol href to its atlas set and tile index
function resolveSprite(symbolHref: string): {
set: string;
tileIndex: number;
} {
const id = symbolHref.startsWith("#") ? symbolHref.slice(1) : symbolHref;
for (const [set, ids] of Object.entries(RELIEF_SYMBOLS)) {
const idx = ids.indexOf(id);
if (idx !== -1) return { set, tileIndex: idx };
}
throw new Error(`Relief: unknown symbol href "${symbolHref}"`);
}
/**
* Build a BufferGeometry with all icon quads for one atlas set.
* Geometry is painter's-order sorted so depth is correct without depth testing.
*
* UV layout (texture.flipY = false v=0 is top of image):
* u = col/cols (col+1)/cols
* v = row/rows (row+1)/rows
*/
// Build a BufferGeometry with all icon quads for one atlas set.
function buildSetMesh(icons: ReliefIcon[], set: string, texture: any): any {
const ids = RELIEF_SYMBOLS[set] ?? [];
const n = ids.length || 1;
@ -233,8 +232,6 @@ function renderFrame(): void {
renderer.render(scene, camera);
}
// ── Private draw / clear ───────────────────────────────────────────────────────
function drawWebGl(icons: ReliefIcon[]): void {
const terrainEl = byId("terrain");
if (!terrainEl) return;