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

View file

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

View file

@ -32,7 +32,7 @@ async function saveMap(method) {
$(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 {spacing, cellsX, cellsY, boundary, points, features, cellsDesired} = grid;
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features, cellsDesired});
const { spacing, cellsX, cellsY, boundary, points, features, cellsDesired } = grid;
const gridGeneral = JSON.stringify({ spacing, cellsX, cellsY, boundary, points, features, cellsDesired });
const packFeatures = JSON.stringify(pack.features);
const cultures = JSON.stringify(pack.cultures);
const states = JSON.stringify(pack.states);
@ -103,10 +103,7 @@ function prepareMapData() {
const cellRoutes = JSON.stringify(pack.cells.routes);
const routes = JSON.stringify(pack.routes);
const zones = JSON.stringify(pack.zones);
const icebergs = pack.ice.icebergs.filter(iceberg => iceberg !== undefined);
const glaciers = pack.ice.glaciers.filter(glacier => glacier !== undefined);
const ice = JSON.stringify({icebergs, glaciers});
const ice = JSON.stringify(pack.ice);
// store name array only if not the same as default
const defaultNB = Names.getNameBases();
@ -168,14 +165,14 @@ function prepareMapData() {
// save map file to indexedDB
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);
showTip && tip("Map is saved to the browser storage", false, "success");
}
// download map file
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 link = document.createElement("a");

View file

@ -9,14 +9,13 @@ function drawIce() {
let html = "";
// Draw glaciers
pack.ice.glaciers.forEach((glacier, index) => {
html += getGlacierHtml(glacier, index);
});
// Draw icebergs
pack.ice.icebergs.forEach((iceberg, index) => {
html += getIcebergHtml(iceberg, index);
// Draw all ice elements
pack.ice.forEach(iceElement => {
if (iceElement.type === "glacier") {
html += getGlacierHtml(iceElement);
} else if (iceElement.type === "iceberg") {
html += getIcebergHtml(iceElement);
}
});
ice.html(html);
@ -24,18 +23,18 @@ function drawIce() {
TIME && console.timeEnd("drawIce");
}
function redrawIceberg(index) {
function redrawIceberg(id) {
TIME && console.time("redrawIceberg");
const iceberg = pack.ice.icebergs[index];
let el = ice.selectAll(`polygon[data-index="${index}"]:not([type="glacier"])`);
const iceberg = pack.ice.find(element => element.i === id);
let el = ice.selectAll(`polygon[data-id="${id}"]:not([type="glacier"])`);
if (!iceberg && !el.empty()) {
el.remove();
} else {
if (el.empty()) {
// Create new element if it doesn't exist
const polygon = getIcebergHtml(iceberg, index);
const polygon = getIcebergHtml(iceberg);
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("transform", iceberg.offset ? `translate(${iceberg.offset[0]},${iceberg.offset[1]})` : null);
@ -43,18 +42,18 @@ function redrawIceberg(index) {
TIME && console.timeEnd("redrawIceberg");
}
function redrawGlacier(index) {
function redrawGlacier(id) {
TIME && console.time("redrawGlacier");
const glacier = pack.ice.glaciers[index];
let el = ice.selectAll(`polygon[data-index="${index}"][type="glacier"]`);
const glacier = pack.ice.find(element => element.i === id);
let el = ice.selectAll(`polygon[data-id="${id}"][type="glacier"]`);
if (!glacier && !el.empty()) {
el.remove();
} else {
if (el.empty()) {
// Create new element if it doesn't exist
const polygon = getGlacierHtml(glacier, index);
const polygon = getGlacierHtml(glacier);
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("transform", glacier.offset ? `translate(${glacier.offset[0]},${glacier.offset[1]})` : null);
@ -62,10 +61,10 @@ function redrawGlacier(index) {
TIME && console.timeEnd("redrawGlacier");
}
function getGlacierHtml(glacier, index) {
return `<polygon points="${glacier.points}" type="glacier" data-index="${index}" ${glacier.offset ? `transform="translate(${glacier.offset[0]},${glacier.offset[1]})"` : ""}/>`;
function getGlacierHtml(glacier) {
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) {
return `<polygon points="${iceberg.points}" data-index="${index}" ${iceberg.offset ? `transform="translate(${iceberg.offset[0]},${iceberg.offset[1]})"` : ""}/>`;
function getIcebergHtml(iceberg) {
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();
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 type = isGlacier ? "Glacier" : "Iceberg";
document.getElementById("iceRandomize").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));
@ -35,16 +36,16 @@ function editIce(element) {
function randomizeShape() {
const idx = +elSelected.attr("data-index");
Ice.randomizeIcebergShape(idx);
redrawIceberg(idx);
const selectedId = +elSelected.attr("data-id");
Ice.randomizeIcebergShape(selectedId);
redrawIceberg(selectedId);
}
function changeSize() {
const newSize = +this.value;
const idx = +elSelected.attr("data-index");
Ice.changeIcebergSize(idx, newSize);
redrawIceberg(idx);
const selectedId = +elSelected.attr("data-id");
Ice.changeIcebergSize(selectedId, newSize);
redrawIceberg(selectedId);
}
function toggleAdd() {
@ -77,7 +78,7 @@ function editIce(element) {
buttons: {
Remove: function () {
$(this).dialog("close");
Ice.removeIce(type.toLowerCase(), +elSelected.attr("data-index"));
Ice.removeIce(+elSelected.attr("data-id"));
$("#iceEditor").dialog("close");
},
Cancel: function () {
@ -88,7 +89,7 @@ function editIce(element) {
}
function dragElement() {
const idx = +elSelected.attr("data-index");
const selectedId = +elSelected.attr("data-id");
const initialTransform = parseTransform(this.getAttribute("transform"));
const dx = initialTransform[0] - d3.event.x;
const dy = initialTransform[1] - d3.event.y;
@ -101,7 +102,7 @@ function editIce(element) {
// Update data model with new position
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) {
// Store offset for visual positioning, actual geometry stays in points
iceData.offset = offset;