From ab7baf83fde7b2630505df4e6c0a561b1c6b8036 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 11 Mar 2026 00:11:15 +0100 Subject: [PATCH] feat: update relief rendering logic and version to 1.114.0 --- public/modules/dynamic/auto-update.js | 19 ++++++ public/modules/io/export.js | 32 ++++----- public/modules/io/load.js | 7 +- public/modules/io/save.js | 4 -- public/versioning.js | 6 +- src/renderers/draw-relief-icons.ts | 95 +++++---------------------- 6 files changed, 53 insertions(+), 110 deletions(-) diff --git a/public/modules/dynamic/auto-update.js b/public/modules/dynamic/auto-update.js index 775a76a6..00a425f0 100644 --- a/public/modules/dynamic/auto-update.js +++ b/public/modules/dynamic/auto-update.js @@ -1112,4 +1112,23 @@ export function resolveVersionConflicts(mapVersion) { zone.cells = unique(zone.cells); }); } + + if (isOlderThan("1.114.0")) { + // v1.114.0 add reliefIcon to pack data and changed rendering method to WebGL + const terrainEl = byId("terrain"); + if (!terrainEl) return; + const relief = []; + + terrainEl.querySelectorAll("use").forEach(u => { + const href = u.getAttribute("href") || u.getAttribute("xlink:href") || ""; + if (!href) return; + const x = +u.getAttribute("x"); + const y = +u.getAttribute("y"); + const s = +u.getAttribute("width"); + relief.push({i: relief.length, href, x, y, s}); + }); + terrainEl.innerHTML = ""; + pack.relief = relief; + if (layerIsOn("toggleRelief")) drawRelief(); + } } diff --git a/public/modules/io/export.js b/public/modules/io/export.js index 482cc2ad..1325dada 100644 --- a/public/modules/io/export.js +++ b/public/modules/io/export.js @@ -180,10 +180,7 @@ async function getMapURL( fullMap = false } = {} ) { - // Temporarily inject elements so the clone includes relief icon data - if (typeof prepareReliefForSave === "function") prepareReliefForSave(); const cloneEl = byId("map").cloneNode(true); // clone svg - if (typeof restoreReliefAfterSave === "function") restoreReliefAfterSave(); cloneEl.id = "fantasyMap"; document.body.appendChild(cloneEl); const clone = d3.select(cloneEl); @@ -261,6 +258,19 @@ async function getMapURL( cloneDefs.querySelector("#defs-emblems")?.remove(); } + { + // render relief icons in svg and add used icons to defs + const terrainEl = cloneEl.getElementById("terrain"); + if (terrainEl) drawRelief("svg", terrainEl); + + const uniqueElements = new Set(pack.relief?.map(r => r.href) || []); + const defsRelief = svgDefs.getElementById("defs-relief"); + for (const terrain of uniqueElements) { + const element = defsRelief.querySelector(terrain); + if (element) cloneDefs.appendChild(element.cloneNode(true)); + } + } + { // replace ocean pattern href to base64 const image = cloneEl.getElementById("oceanicPattern"); @@ -289,22 +299,6 @@ async function getMapURL( } } - // add relief icons (from elements – canvas is excluded) - if (cloneEl.getElementById("terrain")) { - const uniqueElements = new Set(); - const terrainUses = cloneEl.getElementById("terrain").querySelectorAll("use"); - for (let i = 0; i < terrainUses.length; i++) { - const href = terrainUses[i].getAttribute("href") || terrainUses[i].getAttribute("xlink:href"); - if (href && href.startsWith("#")) uniqueElements.add(href); - } - - const defsRelief = svgDefs.getElementById("defs-relief"); - for (const terrain of [...uniqueElements]) { - const element = defsRelief.querySelector(terrain); - if (element) cloneDefs.appendChild(element.cloneNode(true)); - } - } - // add wind rose if (cloneEl.getElementById("compass")) { const rose = svgDefs.getElementById("defs-compass-rose"); diff --git a/public/modules/io/load.js b/public/modules/io/load.js index eb334f10..415c231b 100644 --- a/public/modules/io/load.js +++ b/public/modules/io/load.js @@ -440,12 +440,7 @@ async function parseLoadedData(data, mapVersion) { if (hasChildren(coordinates)) turnOn("toggleCoordinates"); if (isVisible(compass) && hasChild(compass, "use")) turnOn("toggleCompass"); if (hasChildren(rivers)) turnOn("toggleRivers"); - if (isVisible(terrain) && hasChildren(terrain)) { - turnOn("toggleRelief"); - } - // Migrate any legacy SVG elements to canvas rendering - // (runs regardless of visibility to handle maps loaded with relief layer off) - if (typeof migrateReliefFromSvg === "function") migrateReliefFromSvg(); + if (hasChildren(terrain)) turnOn("toggleRelief"); if (hasChildren(relig)) turnOn("toggleReligions"); if (hasChildren(cults)) turnOn("toggleCultures"); if (hasChildren(statesBody)) turnOn("toggleStates"); diff --git a/public/modules/io/save.js b/public/modules/io/save.js index 488b0925..473e1c32 100644 --- a/public/modules/io/save.js +++ b/public/modules/io/save.js @@ -77,11 +77,7 @@ function prepareMapData() { const rulersString = rulers.toString(); const fonts = JSON.stringify(getUsedFonts(svg.node())); - // save svg - // Temporarily inject elements so the SVG snapshot includes relief icon data - if (typeof prepareReliefForSave === "function") prepareReliefForSave(); const cloneEl = document.getElementById("map").cloneNode(true); - if (typeof restoreReliefAfterSave === "function") restoreReliefAfterSave(); // reset transform values to default cloneEl.setAttribute("width", graphWidth); diff --git a/public/versioning.js b/public/versioning.js index 50fd1add..473e021e 100644 --- a/public/versioning.js +++ b/public/versioning.js @@ -16,7 +16,7 @@ * For the changes that may be interesting to end users, update the `latestPublicChanges` array below (new changes on top). */ -const VERSION = "1.113.5"; +const VERSION = "1.114.0"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { @@ -30,6 +30,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o } const latestPublicChanges = [ + "Relief icons: improved performance on huge maps", "Search input in Overview dialogs", "Custom burg grouping and icon selection", "Ability to set custom image as Marker or Regiment icon", @@ -41,8 +42,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o "New style preset: Dark Seas", "New routes generation algorithm", "Routes overview tool", - "Configurable longitude", - "Export zones to GeoJSON" + "Configurable longitude" ]; function showUpdateWindow() { diff --git a/src/renderers/draw-relief-icons.ts b/src/renderers/draw-relief-icons.ts index e9b637dc..33768d0a 100644 --- a/src/renderers/draw-relief-icons.ts +++ b/src/renderers/draw-relief-icons.ts @@ -232,14 +232,10 @@ function renderFrame(): void { renderer.render(scene, camera); } -function drawWebGl(icons: ReliefIcon[]): void { - const terrainEl = byId("terrain"); - if (!terrainEl) return; - if (!icons.length) return; - - terrainEl.innerHTML = ""; - terrainEl.dataset.mode = "webGL"; - const set = terrainEl.getAttribute("set") || "simple"; +function drawWebGl(icons: ReliefIcon[], parentEl: HTMLElement): void { + parentEl.innerHTML = ""; + parentEl.dataset.mode = "webGL"; + const set = parentEl.getAttribute("set") || "simple"; if (ensureRenderer()) { loadTexture(set).then(() => { @@ -251,23 +247,26 @@ function drawWebGl(icons: ReliefIcon[]): void { } } -function drawSvg(icons: ReliefIcon[]): void { - const terrainEl = byId("terrain"); - if (!terrainEl) return; - terrainEl.innerHTML = ""; - +function drawSvg(icons: ReliefIcon[], parentEl: HTMLElement): void { const html = icons.map( (r) => ``, ); - terrainEl.innerHTML = html.join(""); - terrainEl.dataset.mode = "svg"; + parentEl.innerHTML = html.join(""); + parentEl.dataset.mode = "svg"; } -window.drawRelief = (type: "svg" | "webGL" = "webGL") => { +window.drawRelief = ( + type: "svg" | "webGL" = "webGL", + parentEl: HTMLElement | undefined = byId("terrain"), +) => { + if (!parentEl) throw new Error("Relief: parent element not found"); + const icons = pack.relief?.length ? pack.relief : generateRelief(); - if (type === "svg") drawSvg(icons); - else drawWebGl(icons); + if (!icons.length) return; + + if (type === "svg") drawSvg(icons, parentEl); + else drawWebGl(icons, parentEl); }; window.undrawRelief = () => { @@ -294,68 +293,8 @@ window.undrawRelief = () => { // re-render the current WebGL frame (called on pan/zoom) window.rerenderReliefIcons = renderFrame; -// Migrate legacy saves: read elements from the terrain SVG into pack.relief, remove them from the DOM, then render via WebGL. -window.migrateReliefFromSvg = () => { - const terrainEl = byId("terrain"); - if (!terrainEl) return; - const relief: ReliefIcon[] = []; - - terrainEl.querySelectorAll("use").forEach((u) => { - const href = u.getAttribute("href") || u.getAttribute("xlink:href") || ""; - if (!href) return; - relief.push({ - i: relief.length, - href, - x: +u.getAttribute("x")!, - y: +u.getAttribute("y")!, - s: +u.getAttribute("width")!, - }); - }); - terrainEl.innerHTML = ""; - pack.relief = relief; - drawWebGl(relief); -}; - -let _reliefSvgInjectedForSave = false; - -/** - * Before SVG serialization: ensure elements are in the terrain group. - * In WebGL mode, temporarily injects them from pack.relief. - * In SVG edit mode, elements are already live in the DOM. - */ -window.prepareReliefForSave = () => { - const terrainEl = byId("terrain"); - if (!terrainEl) return; - if (terrainEl.querySelectorAll("use").length > 0) { - _reliefSvgInjectedForSave = false; - } else { - terrainEl.insertAdjacentHTML( - "afterbegin", - (pack.relief || []) - .map( - (r) => - ``, - ) - .join(""), - ); - _reliefSvgInjectedForSave = true; - } -}; - -/** Remove temporarily injected elements after serialization. */ -window.restoreReliefAfterSave = () => { - if (_reliefSvgInjectedForSave) { - for (const el of byId("terrain")?.querySelectorAll("use") ?? []) - el.remove(); - _reliefSvgInjectedForSave = false; - } -}; - declare global { var drawRelief: (type?: "svg" | "webGL") => void; var undrawRelief: () => void; var rerenderReliefIcons: () => void; - var migrateReliefFromSvg: () => void; - var prepareReliefForSave: () => void; - var restoreReliefAfterSave: () => void; }