From dc06f3d65c6e79984c103b2a5d5eeef3c722d408 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 10 Mar 2026 22:55:08 +0100 Subject: [PATCH] fix: streamline texture loading and preload logic in WebGL renderer --- src/config/relief-config.ts | 106 +++++++++++++++++++++++++ src/modules/relief-generator.ts | 122 +---------------------------- src/renderers/draw-relief-icons.ts | 49 ++++++------ 3 files changed, 130 insertions(+), 147 deletions(-) create mode 100644 src/config/relief-config.ts diff --git a/src/config/relief-config.ts b/src/config/relief-config.ts new file mode 100644 index 00000000..b591c10a --- /dev/null +++ b/src/config/relief-config.ts @@ -0,0 +1,106 @@ +export const RELIEF_SYMBOLS: Record = { + 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 = { + 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 = { + mountSnow: "mount", + vulcan: "mount", + coniferSnow: "conifer", + cactus: "dune", + deadTree: "dune", +}; diff --git a/src/modules/relief-generator.ts b/src/modules/relief-generator.ts index 230bec32..e3cc5383 100644 --- a/src/modules/relief-generator.ts +++ b/src/modules/relief-generator.ts @@ -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 = { - 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 = { - 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 = { - 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; diff --git a/src/renderers/draw-relief-icons.ts b/src/renderers/draw-relief-icons.ts index 237c38a2..e9b637dc 100644 --- a/src/renderers/draw-relief-icons.ts +++ b/src/renderers/draw-relief-icons.ts @@ -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(); // set name → THREE.Texture -// ── Texture ──────────────────────────────────────────────────────────────────── +function preloadTextures(): void { + for (const set of Object.keys(RELIEF_SYMBOLS)) loadTexture(set); +} function loadTexture(set: string): Promise { if (textureCache.has(set)) return Promise.resolve(textureCache.get(set)); @@ -30,7 +28,8 @@ function loadTexture(set: string): Promise { 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 { }); } -// ── 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;