Refactor relief rendering and generation logic

- Migrate relief icon rendering from SVG to WebGL for improved performance.
- Introduce a new relief generator module to handle relief icon creation.
- Update event listeners in relief editor to use a consistent `byId` method.
- Synchronize relief data with the current SVG DOM when exiting edit mode.
- Enhance relief icon management by integrating new utility functions for generating and resolving relief icons.
- Clean up legacy code and improve overall structure for better maintainability.
This commit is contained in:
Azgaar 2026-03-10 03:05:07 +01:00
parent cbed9af783
commit bf22c5eaf6
11 changed files with 504 additions and 385 deletions

View file

@ -323,7 +323,8 @@ function editBiomes() {
}
function regenerateIcons() {
drawReliefIcons();
pack.relief = [];
drawRelief();
if (!layerIsOn("toggleRelief")) toggleRelief();
}

View file

@ -192,7 +192,7 @@ function drawLayers() {
if (layerIsOn("toggleCoordinates")) drawCoordinates();
if (layerIsOn("toggleCompass")) compass.style("display", "block");
if (layerIsOn("toggleRivers")) drawRivers();
if (layerIsOn("toggleRelief")) drawReliefIcons();
if (layerIsOn("toggleRelief")) drawRelief();
if (layerIsOn("toggleReligions")) drawReligions();
if (layerIsOn("toggleCultures")) drawCultures();
if (layerIsOn("toggleStates")) drawStates();
@ -699,22 +699,12 @@ function toggleCompass(event) {
function toggleRelief(event) {
if (!layerIsOn("toggleRelief")) {
turnButtonOn("toggleRelief");
if (!terrain.selectAll("*").size()) {
drawReliefIcons();
} else if (
terrain.selectAll("use").size() &&
!terrain.select("#terrainCanvasImage").size() &&
!terrain.select("#terrainGlFo").size()
) {
// Legacy SVG use elements present but no canvas/GL render yet migrate now
if (typeof migrateReliefFromSvg === "function") migrateReliefFromSvg();
}
$("#terrain").fadeIn();
drawRelief();
if (event && isCtrlClick(event)) editStyle("terrain");
} else {
if (event && isCtrlClick(event)) return editStyle("terrain");
$("#terrain").fadeOut();
turnButtonOff("toggleRelief");
undrawRelief();
}
}

View file

@ -2,11 +2,12 @@
function editReliefIcon() {
if (customization) return;
closeDialogs(".stable");
// Switch from WebGL to editable SVG <use> elements
undrawRelief();
drawRelief("svg");
if (!layerIsOn("toggleRelief")) toggleRelief();
// Switch from canvas image to editable SVG <use> elements
if (typeof enterReliefSvgEditMode === "function") enterReliefSvgEditMode();
terrain.selectAll("use").call(d3.drag().on("drag", dragReliefIcon)).classed("draggable", true);
// When called from the Tools button there is no d3 click event; fall back to the first <use>.
@ -31,30 +32,42 @@ function editReliefIcon() {
modules.editReliefIcon = true;
// add listeners
document.getElementById("reliefIndividual").addEventListener("click", enterIndividualMode);
document.getElementById("reliefBulkAdd").addEventListener("click", enterBulkAddMode);
document.getElementById("reliefBulkRemove").addEventListener("click", enterBulkRemoveMode);
byId("reliefIndividual").on("click", enterIndividualMode);
byId("reliefBulkAdd").on("click", enterBulkAddMode);
byId("reliefBulkRemove").on("click", enterBulkRemoveMode);
document.getElementById("reliefSize").addEventListener("input", changeIconSize);
document.getElementById("reliefSizeNumber").addEventListener("input", changeIconSize);
document.getElementById("reliefEditorSet").addEventListener("change", changeIconsSet);
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.addEventListener("click", changeIcon));
byId("reliefSize").on("input", changeIconSize);
byId("reliefSizeNumber").on("input", changeIconSize);
byId("reliefEditorSet").on("change", changeIconsSet);
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.on("click", changeIcon));
document.getElementById("reliefEditStyle").addEventListener("click", () => editStyle("terrain"));
document.getElementById("reliefCopy").addEventListener("click", copyIcon);
document.getElementById("reliefMoveFront").addEventListener("click", () => elSelected.raise());
document.getElementById("reliefMoveBack").addEventListener("click", () => elSelected.lower());
document.getElementById("reliefRemove").addEventListener("click", removeIcon);
byId("reliefEditStyle").on("click", () => editStyle("terrain"));
byId("reliefCopy").on("click", copyIcon);
byId("reliefMoveFront").on("click", () => elSelected.raise());
byId("reliefMoveBack").on("click", () => elSelected.lower());
byId("reliefRemove").on("click", removeIcon);
function dragReliefIcon() {
const dx = +this.getAttribute("x") - d3.event.x;
const dy = +this.getAttribute("y") - d3.event.y;
let newX;
let newY;
d3.event.on("drag", function () {
const x = d3.event.x,
y = d3.event.y;
this.setAttribute("x", dx + x);
this.setAttribute("y", dy + y);
newX = dx + d3.event.x;
newY = dy + d3.event.y;
this.setAttribute("x", newX);
this.setAttribute("y", newY);
});
d3.event.on("end", function () {
const id = this.dataset.id;
const icon = pack.relief.find(icon => icon.i === +id);
if (icon) {
icon.x = newX;
icon.y = newY;
}
});
}
@ -295,7 +308,23 @@ function editReliefIcon() {
removeCircle();
unselect();
clearMainTip();
// Read back edits and switch terrain to canvas rendering
if (typeof exitReliefSvgEditMode === "function") exitReliefSvgEditMode();
// Sync pack.relief from the current SVG DOM (captures all edits)
pack.relief = [];
terrain.selectAll("use").each(function () {
const href = this.getAttribute("href") || this.getAttribute("xlink:href") || "";
if (!href) return;
pack.relief.push({
i: pack.relief.length,
href,
x: +this.getAttribute("x"),
y: +this.getAttribute("y"),
s: +this.getAttribute("width")
});
});
// Switch from SVG edit mode back to WebGL rendering
undrawRelief();
drawRelief();
}
}

View file

@ -729,19 +729,22 @@ styleHeightmapCurve.on("change", e => {
styleReliefSet.on("change", e => {
terrain.attr("set", e.target.value);
drawReliefIcons();
pack.relief = [];
drawRelief();
if (!layerIsOn("toggleRelief")) toggleRelief();
});
styleReliefSize.on("change", e => {
terrain.attr("size", e.target.value);
drawReliefIcons();
pack.relief = [];
drawRelief();
if (!layerIsOn("toggleRelief")) toggleRelief();
});
styleReliefDensity.on("change", e => {
terrain.attr("density", e.target.value);
drawReliefIcons();
pack.relief = [];
drawRelief();
if (!layerIsOn("toggleRelief")) toggleRelief();
});

View file

@ -79,7 +79,7 @@ function processFeatureRegeneration(event, button) {
$("#labels").fadeIn();
drawStateLabels();
} else if (button === "regenerateReliefIcons") {
drawReliefIcons();
generateReliefIcons();
if (!layerIsOn("toggleRelief")) toggleRelief();
} else if (button === "regenerateRoutes") {
regenerateRoutes();
@ -327,8 +327,8 @@ function recreateStates() {
const type = nomadic
? "Nomadic"
: pack.cultures[culture].type === "Nomadic"
? "Generic"
: pack.cultures[culture].type;
? "Generic"
: pack.cultures[culture].type;
const expansionism = rn(Math.random() * byId("sizeVariety").value + 1, 1);
const cultureType = pack.cultures[culture].type;
@ -898,8 +898,8 @@ function configMarkersGeneration() {
<td><input class="type" value="${type}" /></td>
<td style="position: relative">
<img class="image" src="${isExternal ? icon : ""}" ${
isExternal ? "" : "hidden"
} style="width:1.2em; height:1.2em; vertical-align: middle;">
isExternal ? "" : "hidden"
} style="width:1.2em; height:1.2em; vertical-align: middle;">
<span class="emoji" style="font-size:1.2em">${isExternal ? "" : icon}</span>
<button class="changeIcon icon-pencil"></button>
</td>