refactor: unify ice data structure and streamline ice element handling

This commit is contained in:
StempunkDev 2026-01-15 19:44:50 +01:00
parent a7d9fb3242
commit f2d98e5bc7
6 changed files with 105 additions and 113 deletions

View file

@ -1041,7 +1041,8 @@ export function resolveVersionConflicts(mapVersion) {
// v1.110.0 moved ice data from SVG to data model // v1.110.0 moved ice data from SVG to data model
// Migrate old ice SVG elements to new pack.ice structure // Migrate old ice SVG elements to new pack.ice structure
if (!pack.ice) { if (!pack.ice) {
pack.ice = { glaciers: [], icebergs: [] }; pack.ice = [];
let iceId = 0;
const iceLayer = document.getElementById("ice"); const iceLayer = document.getElementById("ice");
if (iceLayer) { if (iceLayer) {
@ -1051,15 +1052,15 @@ export function resolveVersionConflicts(mapVersion) {
const points = [...polygon.points].map(svgPoint => [svgPoint.x, svgPoint.y]); const points = [...polygon.points].map(svgPoint => [svgPoint.x, svgPoint.y]);
const transform = polygon.getAttribute("transform"); const transform = polygon.getAttribute("transform");
const iceElement = {
if (transform) { i: iceId++,
pack.ice.glaciers.push({
points, points,
offset: parseTransform(transform) type: "glacier"
}); };
} else { if (transform) {
pack.ice.glaciers.push({ points }); iceElement.offset = parseTransform(transform);
} }
pack.ice.push(iceElement);
}); });
// Migrate icebergs // Migrate icebergs
@ -1074,21 +1075,17 @@ export function resolveVersionConflicts(mapVersion) {
const points = [...polygon.points].map(svgPoint => [svgPoint.x, svgPoint.y]); const points = [...polygon.points].map(svgPoint => [svgPoint.x, svgPoint.y]);
const transform = polygon.getAttribute("transform"); const transform = polygon.getAttribute("transform");
const iceElement = {
i: iceId++,
points,
type: "iceberg",
cellId,
size
};
if (transform) { if (transform) {
pack.ice.icebergs.push({ iceElement.offset = parseTransform(transform);
cellId,
size,
points,
offset: parseTransform(transform)
});
} else {
pack.ice.icebergs.push({
cellId,
size,
points
});
} }
pack.ice.push(iceElement);
}); });
// Clear old SVG elements // Clear old SVG elements

View file

@ -2,17 +2,16 @@
// Ice layer data model - separates ice data from SVG rendering // Ice layer data model - separates ice data from SVG rendering
window.Ice = (function () { window.Ice = (function () {
// Initialize ice data structure
function initialize() { // Find next available id for new ice element
pack.ice = { function getNextId() {
glaciers: [], // auto-generated glaciers on cold land if (pack.ice.length === 0) return 0;
icebergs: [] // manually edited and auto-generated icebergs on cold water return Math.max(...pack.ice.map(element => element.i)) + 1;
};
} }
// Generate glaciers and icebergs based on temperature and height // Generate glaciers and icebergs based on temperature and height
function generate() { function generate() {
initialize(); clear();
const { cells, features } = grid; const { cells, features } = grid;
const { temp, h } = cells; const { temp, h } = cells;
Math.random = aleaPRNG(seed); Math.random = aleaPRNG(seed);
@ -31,8 +30,10 @@ window.Ice = (function () {
if (isolines[type]?.polygons) { if (isolines[type]?.polygons) {
isolines[type].polygons.forEach(points => { isolines[type].polygons.forEach(points => {
const clipped = clipPoly(points); const clipped = clipPoly(points);
pack.ice.glaciers.push({ pack.ice.push({
points: clipped i: getNextId(),
points: clipped,
type: "glacier"
}); });
}); });
} }
@ -57,10 +58,12 @@ window.Ice = (function () {
rn(lerp(cy, y, size), 2) rn(lerp(cy, y, size), 2)
]); ]);
pack.ice.icebergs.push({ pack.ice.push({
i: getNextId(),
points,
type: "iceberg",
cellId, cellId,
size, size
points
}); });
} }
} }
@ -71,44 +74,41 @@ window.Ice = (function () {
rn(lerp(cx, x, size), 2), rn(lerp(cx, x, size), 2),
rn(lerp(cy, y, size), 2) rn(lerp(cy, y, size), 2)
]); ]);
//here we use the lose equality to find the first undefined or empty or null slot const id = getNextId();
const nextIndex = pack.ice.icebergs.findIndex(iceberg => iceberg == undefined); pack.ice.push({
if (nextIndex !== -1) { i: id,
pack.ice.icebergs[nextIndex] = { points,
cellId, type: "iceberg",
size, cellId,
points size
}; });
redrawIceberg(nextIndex); redrawIceberg(id);
} else { }
pack.ice.icebergs.push({
cellId, function removeIce(id) {
size, const index = pack.ice.findIndex(element => element.i === id);
points if (index !== -1) {
}); const type = pack.ice.find(element => element.i === id).type;
redrawIceberg(pack.ice.icebergs.length - 1); pack.ice.splice(index, 1);
if (type === "glacier") {
redrawGlacier(id);
} else {
redrawIceberg(id);
}
} }
} }
function removeIce(type, index) { function updateIceberg(id, points, size) {
if (type === "glacier" && pack.ice.glaciers[index]) { const iceberg = pack.ice.find(element => element.i === id);
delete pack.ice.glaciers[index]; if (iceberg) {
redrawGlacier(index); iceberg.points = points;
} else if (type === "iceberg" && pack.ice.icebergs[index]) { iceberg.size = size;
delete pack.ice.icebergs[index];
redrawIceberg(index);
} }
} }
function updateIceberg(index, points, size) { function randomizeIcebergShape(id) {
if (pack.ice.icebergs[index]) { const iceberg = pack.ice.find(element => element.i === id);
pack.ice.icebergs[index].points = points;
pack.ice.icebergs[index].size = size;
}
}
function randomizeIcebergShape(index) {
const iceberg = pack.ice.icebergs[index];
if (!iceberg) return; if (!iceberg) return;
const cellId = iceberg.cellId; const cellId = iceberg.cellId;
@ -127,8 +127,8 @@ window.Ice = (function () {
iceberg.points = points; iceberg.points = points;
} }
function changeIcebergSize(index, newSize) { function changeIcebergSize(id, newSize) {
const iceberg = pack.ice.icebergs[index]; const iceberg = pack.ice.find(element => element.i === id);
if (!iceberg) return; if (!iceberg) return;
const cellId = iceberg.cellId; const cellId = iceberg.cellId;
@ -150,12 +150,10 @@ window.Ice = (function () {
// Clear all ice // Clear all ice
function clear() { function clear() {
pack.ice.glaciers = []; pack.ice = [];
pack.ice.icebergs = [];
} }
return { return {
initialize,
generate, generate,
addIceberg, addIceberg,
removeIce, removeIce,

View file

@ -406,7 +406,7 @@ async function parseLoadedData(data, mapVersion) {
pack.cells.province = data[27] ? Uint16Array.from(data[27].split(",")) : new Uint16Array(pack.cells.i.length); pack.cells.province = data[27] ? Uint16Array.from(data[27].split(",")) : new Uint16Array(pack.cells.i.length);
// data[28] had deprecated cells.crossroad // data[28] had deprecated cells.crossroad
pack.cells.routes = data[36] ? JSON.parse(data[36]) : {}; pack.cells.routes = data[36] ? JSON.parse(data[36]) : {};
pack.ice = data[39] ? JSON.parse(data[39]) : {glaciers: [], icebergs: []}; pack.ice = data[39] ? JSON.parse(data[39]) : [];
if (data[31]) { if (data[31]) {
const namesDL = data[31].split("/"); const namesDL = data[31].split("/");

View file

@ -32,7 +32,7 @@ async function saveMap(method) {
$(this).dialog("close"); $(this).dialog("close");
} }
}, },
position: {my: "center", at: "center", of: "svg"} position: { my: "center", at: "center", of: "svg" }
}); });
} }
} }
@ -90,8 +90,8 @@ function prepareMapData() {
const serializedSVG = new XMLSerializer().serializeToString(cloneEl); const serializedSVG = new XMLSerializer().serializeToString(cloneEl);
const {spacing, cellsX, cellsY, boundary, points, features, cellsDesired} = grid; const { spacing, cellsX, cellsY, boundary, points, features, cellsDesired } = grid;
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features, cellsDesired}); const gridGeneral = JSON.stringify({ spacing, cellsX, cellsY, boundary, points, features, cellsDesired });
const packFeatures = JSON.stringify(pack.features); const packFeatures = JSON.stringify(pack.features);
const cultures = JSON.stringify(pack.cultures); const cultures = JSON.stringify(pack.cultures);
const states = JSON.stringify(pack.states); const states = JSON.stringify(pack.states);
@ -103,10 +103,7 @@ function prepareMapData() {
const cellRoutes = JSON.stringify(pack.cells.routes); const cellRoutes = JSON.stringify(pack.cells.routes);
const routes = JSON.stringify(pack.routes); const routes = JSON.stringify(pack.routes);
const zones = JSON.stringify(pack.zones); const zones = JSON.stringify(pack.zones);
const ice = JSON.stringify(pack.ice);
const icebergs = pack.ice.icebergs.filter(iceberg => iceberg !== undefined);
const glaciers = pack.ice.glaciers.filter(glacier => glacier !== undefined);
const ice = JSON.stringify({icebergs, glaciers});
// store name array only if not the same as default // store name array only if not the same as default
const defaultNB = Names.getNameBases(); const defaultNB = Names.getNameBases();
@ -168,14 +165,14 @@ function prepareMapData() {
// save map file to indexedDB // save map file to indexedDB
async function saveToStorage(mapData, showTip = false) { async function saveToStorage(mapData, showTip = false) {
const blob = new Blob([mapData], {type: "text/plain"}); const blob = new Blob([mapData], { type: "text/plain" });
await ldb.set("lastMap", blob); await ldb.set("lastMap", blob);
showTip && tip("Map is saved to the browser storage", false, "success"); showTip && tip("Map is saved to the browser storage", false, "success");
} }
// download map file // download map file
function saveToMachine(mapData, filename) { function saveToMachine(mapData, filename) {
const blob = new Blob([mapData], {type: "text/plain"}); const blob = new Blob([mapData], { type: "text/plain" });
const URL = window.URL.createObjectURL(blob); const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement("a");

View file

@ -9,14 +9,13 @@ function drawIce() {
let html = ""; let html = "";
// Draw glaciers // Draw all ice elements
pack.ice.glaciers.forEach((glacier, index) => { pack.ice.forEach(iceElement => {
html += getGlacierHtml(glacier, index); if (iceElement.type === "glacier") {
}); html += getGlacierHtml(iceElement);
} else if (iceElement.type === "iceberg") {
// Draw icebergs html += getIcebergHtml(iceElement);
pack.ice.icebergs.forEach((iceberg, index) => { }
html += getIcebergHtml(iceberg, index);
}); });
ice.html(html); ice.html(html);
@ -24,18 +23,18 @@ function drawIce() {
TIME && console.timeEnd("drawIce"); TIME && console.timeEnd("drawIce");
} }
function redrawIceberg(index) { function redrawIceberg(id) {
TIME && console.time("redrawIceberg"); TIME && console.time("redrawIceberg");
const iceberg = pack.ice.icebergs[index]; const iceberg = pack.ice.find(element => element.i === id);
let el = ice.selectAll(`polygon[data-index="${index}"]:not([type="glacier"])`); let el = ice.selectAll(`polygon[data-id="${id}"]:not([type="glacier"])`);
if (!iceberg && !el.empty()) { if (!iceberg && !el.empty()) {
el.remove(); el.remove();
} else { } else {
if (el.empty()) { if (el.empty()) {
// Create new element if it doesn't exist // Create new element if it doesn't exist
const polygon = getIcebergHtml(iceberg, index); const polygon = getIcebergHtml(iceberg);
ice.node().insertAdjacentHTML("beforeend", polygon); ice.node().insertAdjacentHTML("beforeend", polygon);
el = ice.selectAll(`polygon[data-index="${index}"]:not([type="glacier"])`); el = ice.selectAll(`polygon[data-id="${id}"]:not([type="glacier"])`);
} }
el.attr("points", iceberg.points); el.attr("points", iceberg.points);
el.attr("transform", iceberg.offset ? `translate(${iceberg.offset[0]},${iceberg.offset[1]})` : null); el.attr("transform", iceberg.offset ? `translate(${iceberg.offset[0]},${iceberg.offset[1]})` : null);
@ -43,18 +42,18 @@ function redrawIceberg(index) {
TIME && console.timeEnd("redrawIceberg"); TIME && console.timeEnd("redrawIceberg");
} }
function redrawGlacier(index) { function redrawGlacier(id) {
TIME && console.time("redrawGlacier"); TIME && console.time("redrawGlacier");
const glacier = pack.ice.glaciers[index]; const glacier = pack.ice.find(element => element.i === id);
let el = ice.selectAll(`polygon[data-index="${index}"][type="glacier"]`); let el = ice.selectAll(`polygon[data-id="${id}"][type="glacier"]`);
if (!glacier && !el.empty()) { if (!glacier && !el.empty()) {
el.remove(); el.remove();
} else { } else {
if (el.empty()) { if (el.empty()) {
// Create new element if it doesn't exist // Create new element if it doesn't exist
const polygon = getGlacierHtml(glacier, index); const polygon = getGlacierHtml(glacier);
ice.node().insertAdjacentHTML("beforeend", polygon); ice.node().insertAdjacentHTML("beforeend", polygon);
el = ice.selectAll(`polygon[data-index="${index}"][type="glacier"]`); el = ice.selectAll(`polygon[data-id="${id}"][type="glacier"]`);
} }
el.attr("points", glacier.points); el.attr("points", glacier.points);
el.attr("transform", glacier.offset ? `translate(${glacier.offset[0]},${glacier.offset[1]})` : null); el.attr("transform", glacier.offset ? `translate(${glacier.offset[0]},${glacier.offset[1]})` : null);
@ -62,10 +61,10 @@ function redrawGlacier(index) {
TIME && console.timeEnd("redrawGlacier"); TIME && console.timeEnd("redrawGlacier");
} }
function getGlacierHtml(glacier, index) { function getGlacierHtml(glacier) {
return `<polygon points="${glacier.points}" type="glacier" data-index="${index}" ${glacier.offset ? `transform="translate(${glacier.offset[0]},${glacier.offset[1]})"` : ""}/>`; return `<polygon points="${glacier.points}" type="glacier" data-id="${glacier.i}" ${glacier.offset ? `transform="translate(${glacier.offset[0]},${glacier.offset[1]})"` : ""}/>`;
} }
function getIcebergHtml(iceberg, index) { function getIcebergHtml(iceberg) {
return `<polygon points="${iceberg.points}" data-index="${index}" ${iceberg.offset ? `transform="translate(${iceberg.offset[0]},${iceberg.offset[1]})"` : ""}/>`; return `<polygon points="${iceberg.points}" data-id="${iceberg.i}" ${iceberg.offset ? `transform="translate(${iceberg.offset[0]},${iceberg.offset[1]})"` : ""}/>`;
} }

View file

@ -7,13 +7,14 @@ function editIce(element) {
if (!layerIsOn("toggleIce")) toggleIce(); if (!layerIsOn("toggleIce")) toggleIce();
elSelected = d3.select(d3.event.target); elSelected = d3.select(d3.event.target);
const index = +elSelected.attr("data-index"); const id = +elSelected.attr("data-id");
const iceElement = pack.ice.find(el => el.i === id);
const isGlacier = elSelected.attr("type") === "glacier"; const isGlacier = elSelected.attr("type") === "glacier";
const type = isGlacier ? "Glacier" : "Iceberg"; const type = isGlacier ? "Glacier" : "Iceberg";
document.getElementById("iceRandomize").style.display = isGlacier ? "none" : "inline-block"; document.getElementById("iceRandomize").style.display = isGlacier ? "none" : "inline-block";
document.getElementById("iceSize").style.display = isGlacier ? "none" : "inline-block"; document.getElementById("iceSize").style.display = isGlacier ? "none" : "inline-block";
if (!isGlacier) document.getElementById("iceSize").value = isGlacier ? "" : pack.ice.icebergs[index].size; if (!isGlacier) document.getElementById("iceSize").value = iceElement?.size || "";
ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement)); ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement));
@ -35,16 +36,16 @@ function editIce(element) {
function randomizeShape() { function randomizeShape() {
const idx = +elSelected.attr("data-index"); const selectedId = +elSelected.attr("data-id");
Ice.randomizeIcebergShape(idx); Ice.randomizeIcebergShape(selectedId);
redrawIceberg(idx); redrawIceberg(selectedId);
} }
function changeSize() { function changeSize() {
const newSize = +this.value; const newSize = +this.value;
const idx = +elSelected.attr("data-index"); const selectedId = +elSelected.attr("data-id");
Ice.changeIcebergSize(idx, newSize); Ice.changeIcebergSize(selectedId, newSize);
redrawIceberg(idx); redrawIceberg(selectedId);
} }
function toggleAdd() { function toggleAdd() {
@ -77,7 +78,7 @@ function editIce(element) {
buttons: { buttons: {
Remove: function () { Remove: function () {
$(this).dialog("close"); $(this).dialog("close");
Ice.removeIce(type.toLowerCase(), +elSelected.attr("data-index")); Ice.removeIce(+elSelected.attr("data-id"));
$("#iceEditor").dialog("close"); $("#iceEditor").dialog("close");
}, },
Cancel: function () { Cancel: function () {
@ -88,7 +89,7 @@ function editIce(element) {
} }
function dragElement() { function dragElement() {
const idx = +elSelected.attr("data-index"); const selectedId = +elSelected.attr("data-id");
const initialTransform = parseTransform(this.getAttribute("transform")); const initialTransform = parseTransform(this.getAttribute("transform"));
const dx = initialTransform[0] - d3.event.x; const dx = initialTransform[0] - d3.event.x;
const dy = initialTransform[1] - d3.event.y; const dy = initialTransform[1] - d3.event.y;
@ -101,7 +102,7 @@ function editIce(element) {
// Update data model with new position // Update data model with new position
const offset = [dx + x, dy + y]; const offset = [dx + x, dy + y];
const iceData = elSelected.attr("type") === "glacier" ? pack.ice.glaciers[idx] : pack.ice.icebergs[idx]; const iceData = pack.ice.find(element => element.i === selectedId);
if (iceData) { if (iceData) {
// Store offset for visual positioning, actual geometry stays in points // Store offset for visual positioning, actual geometry stays in points
iceData.offset = offset; iceData.offset = offset;