feat: update relief rendering logic and version to 1.114.0

This commit is contained in:
Azgaar 2026-03-11 00:11:15 +01:00
parent dc06f3d65c
commit ab7baf83fd
6 changed files with 53 additions and 110 deletions

View file

@ -1112,4 +1112,23 @@ export function resolveVersionConflicts(mapVersion) {
zone.cells = unique(zone.cells); 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();
}
} }

View file

@ -180,10 +180,7 @@ async function getMapURL(
fullMap = false fullMap = false
} = {} } = {}
) { ) {
// Temporarily inject <use> elements so the clone includes relief icon data
if (typeof prepareReliefForSave === "function") prepareReliefForSave();
const cloneEl = byId("map").cloneNode(true); // clone svg const cloneEl = byId("map").cloneNode(true); // clone svg
if (typeof restoreReliefAfterSave === "function") restoreReliefAfterSave();
cloneEl.id = "fantasyMap"; cloneEl.id = "fantasyMap";
document.body.appendChild(cloneEl); document.body.appendChild(cloneEl);
const clone = d3.select(cloneEl); const clone = d3.select(cloneEl);
@ -261,6 +258,19 @@ async function getMapURL(
cloneDefs.querySelector("#defs-emblems")?.remove(); 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 // replace ocean pattern href to base64
const image = cloneEl.getElementById("oceanicPattern"); const image = cloneEl.getElementById("oceanicPattern");
@ -289,22 +299,6 @@ async function getMapURL(
} }
} }
// add relief icons (from <use> elements canvas <image> 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 // add wind rose
if (cloneEl.getElementById("compass")) { if (cloneEl.getElementById("compass")) {
const rose = svgDefs.getElementById("defs-compass-rose"); const rose = svgDefs.getElementById("defs-compass-rose");

View file

@ -440,12 +440,7 @@ async function parseLoadedData(data, mapVersion) {
if (hasChildren(coordinates)) turnOn("toggleCoordinates"); if (hasChildren(coordinates)) turnOn("toggleCoordinates");
if (isVisible(compass) && hasChild(compass, "use")) turnOn("toggleCompass"); if (isVisible(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
if (hasChildren(rivers)) turnOn("toggleRivers"); if (hasChildren(rivers)) turnOn("toggleRivers");
if (isVisible(terrain) && hasChildren(terrain)) { if (hasChildren(terrain)) turnOn("toggleRelief");
turnOn("toggleRelief");
}
// Migrate any legacy SVG <use> elements to canvas rendering
// (runs regardless of visibility to handle maps loaded with relief layer off)
if (typeof migrateReliefFromSvg === "function") migrateReliefFromSvg();
if (hasChildren(relig)) turnOn("toggleReligions"); if (hasChildren(relig)) turnOn("toggleReligions");
if (hasChildren(cults)) turnOn("toggleCultures"); if (hasChildren(cults)) turnOn("toggleCultures");
if (hasChildren(statesBody)) turnOn("toggleStates"); if (hasChildren(statesBody)) turnOn("toggleStates");

View file

@ -77,11 +77,7 @@ function prepareMapData() {
const rulersString = rulers.toString(); const rulersString = rulers.toString();
const fonts = JSON.stringify(getUsedFonts(svg.node())); const fonts = JSON.stringify(getUsedFonts(svg.node()));
// save svg
// Temporarily inject <use> elements so the SVG snapshot includes relief icon data
if (typeof prepareReliefForSave === "function") prepareReliefForSave();
const cloneEl = document.getElementById("map").cloneNode(true); const cloneEl = document.getElementById("map").cloneNode(true);
if (typeof restoreReliefAfterSave === "function") restoreReliefAfterSave();
// reset transform values to default // reset transform values to default
cloneEl.setAttribute("width", graphWidth); cloneEl.setAttribute("width", graphWidth);

View file

@ -16,7 +16,7 @@
* For the changes that may be interesting to end users, update the `latestPublicChanges` array below (new changes on top). * 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"); 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 = [ const latestPublicChanges = [
"Relief icons: improved performance on huge maps",
"Search input in Overview dialogs", "Search input in Overview dialogs",
"Custom burg grouping and icon selection", "Custom burg grouping and icon selection",
"Ability to set custom image as Marker or Regiment icon", "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 style preset: Dark Seas",
"New routes generation algorithm", "New routes generation algorithm",
"Routes overview tool", "Routes overview tool",
"Configurable longitude", "Configurable longitude"
"Export zones to GeoJSON"
]; ];
function showUpdateWindow() { function showUpdateWindow() {

View file

@ -232,14 +232,10 @@ function renderFrame(): void {
renderer.render(scene, camera); renderer.render(scene, camera);
} }
function drawWebGl(icons: ReliefIcon[]): void { function drawWebGl(icons: ReliefIcon[], parentEl: HTMLElement): void {
const terrainEl = byId("terrain"); parentEl.innerHTML = "";
if (!terrainEl) return; parentEl.dataset.mode = "webGL";
if (!icons.length) return; const set = parentEl.getAttribute("set") || "simple";
terrainEl.innerHTML = "";
terrainEl.dataset.mode = "webGL";
const set = terrainEl.getAttribute("set") || "simple";
if (ensureRenderer()) { if (ensureRenderer()) {
loadTexture(set).then(() => { loadTexture(set).then(() => {
@ -251,23 +247,26 @@ function drawWebGl(icons: ReliefIcon[]): void {
} }
} }
function drawSvg(icons: ReliefIcon[]): void { function drawSvg(icons: ReliefIcon[], parentEl: HTMLElement): void {
const terrainEl = byId("terrain");
if (!terrainEl) return;
terrainEl.innerHTML = "";
const html = icons.map( const html = icons.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.href}" data-id="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`,
); );
terrainEl.innerHTML = html.join(""); parentEl.innerHTML = html.join("");
terrainEl.dataset.mode = "svg"; 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(); const icons = pack.relief?.length ? pack.relief : generateRelief();
if (type === "svg") drawSvg(icons); if (!icons.length) return;
else drawWebGl(icons);
if (type === "svg") drawSvg(icons, parentEl);
else drawWebGl(icons, parentEl);
}; };
window.undrawRelief = () => { window.undrawRelief = () => {
@ -294,68 +293,8 @@ window.undrawRelief = () => {
// re-render the current WebGL frame (called on pan/zoom) // re-render the current WebGL frame (called on pan/zoom)
window.rerenderReliefIcons = renderFrame; window.rerenderReliefIcons = renderFrame;
// Migrate legacy saves: read <use> 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<SVGUseElement>("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 <use> 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) =>
`<use href="${r.href}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`,
)
.join(""),
);
_reliefSvgInjectedForSave = true;
}
};
/** Remove temporarily injected <use> elements after serialization. */
window.restoreReliefAfterSave = () => {
if (_reliefSvgInjectedForSave) {
for (const el of byId("terrain")?.querySelectorAll("use") ?? [])
el.remove();
_reliefSvgInjectedForSave = false;
}
};
declare global { declare global {
var drawRelief: (type?: "svg" | "webGL") => void; var drawRelief: (type?: "svg" | "webGL") => void;
var undrawRelief: () => void; var undrawRelief: () => void;
var rerenderReliefIcons: () => void; var rerenderReliefIcons: () => void;
var migrateReliefFromSvg: () => void;
var prepareReliefForSave: () => void;
var restoreReliefAfterSave: () => void;
} }