"use strict"; // 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 }; } // Generate glaciers and icebergs based on temperature and height function generate() { clear(); const {cells, features} = grid; const {temp, h} = cells; Math.random = aleaPRNG(seed); const ICEBERG_MAX_TEMP = 0; const GLACIER_MAX_TEMP = -8; const minMaxTemp = d3.min(temp); // Generate glaciers on cold land { const type = "iceShield"; const getType = cellId => h[cellId] >= 20 && temp[cellId] <= GLACIER_MAX_TEMP ? type : null; const isolines = getIsolines(grid, getType, {polygons: true}); if (isolines[type]?.polygons) { isolines[type].polygons.forEach(points => { const clipped = clipPoly(points); pack.ice.glaciers.push({ points: clipped }); }); } } // Generate icebergs on cold water for (const cellId of grid.cells.i) { const t = temp[cellId]; if (h[cellId] >= 20) continue; // no icebergs on land if (t > ICEBERG_MAX_TEMP) continue; // too warm: no icebergs if (features[cells.f[cellId]].type === "lake") continue; // no icebergs on lakes if (P(0.8)) continue; // skip most of eligible cells const randomFactor = 0.8 + rand() * 0.4; // random size factor let baseSize = (1 - normalize(t, minMaxTemp, 1)) * 0.8; // size: 0 = zero, 1 = full if (cells.t[cellId] === -1) baseSize /= 1.3; // coastline: smaller icebergs const size = minmax(rn(baseSize * randomFactor, 2), 0.1, 1); const [cx, cy] = grid.points[cellId]; const points = getGridPolygon(cellId).map(([x, y]) => [ rn(lerp(cx, x, size), 2), rn(lerp(cy, y, size), 2) ]); pack.ice.icebergs.push({ cellId, size, points }); } } // Add a new iceberg (manual editing) function addIceberg(cellId, size) { const [cx, cy] = grid.points[cellId]; const points = getGridPolygon(cellId).map(([x, y]) => [ rn(lerp(cx, x, size), 2), rn(lerp(cy, y, size), 2) ]); pack.ice.icebergs.push({ cellId, size, points }); return pack.ice.icebergs.length - 1; // return index } // Remove ice element by index function removeIce(type, index) { if (type === "glacier" && pack.ice.glaciers[index]) { pack.ice.glaciers.splice(index, 1); } else if (type === "iceberg" && pack.ice.icebergs[index]) { pack.ice.icebergs.splice(index, 1); } } // Update iceberg points and size function updateIceberg(index, points, size) { if (pack.ice.icebergs[index]) { pack.ice.icebergs[index].points = points; pack.ice.icebergs[index].size = size; } } // Randomize iceberg shape function randomizeIcebergShape(index) { const iceberg = pack.ice.icebergs[index]; if (!iceberg) return; const cellId = iceberg.cellId; const size = iceberg.size; const [cx, cy] = grid.points[cellId]; // Get a different random cell for the polygon template const i = ra(grid.cells.i); const cn = grid.points[i]; const poly = getGridPolygon(i).map(p => [p[0] - cn[0], p[1] - cn[1]]); const points = poly.map(p => [ rn(cx + p[0] * size, 2), rn(cy + p[1] * size, 2) ]); iceberg.points = points; } // Change iceberg size and recalculate points function changeIcebergSize(index, newSize) { const iceberg = pack.ice.icebergs[index]; if (!iceberg) return; const cellId = iceberg.cellId; const [cx, cy] = grid.points[cellId]; const oldSize = iceberg.size; // Recalculate points based on new size const flat = iceberg.points.flat(); const pairs = []; while (flat.length) pairs.push(flat.splice(0, 2)); const poly = pairs.map(p => [(p[0] - cx) / oldSize, (p[1] - cy) / oldSize]); const points = poly.map(p => [ rn(cx + p[0] * newSize, 2), rn(cy + p[1] * newSize, 2) ]); iceberg.points = points; iceberg.size = newSize; } // Get all ice data function getData() { return pack.ice; } // Clear all ice function clear() { pack.ice.glaciers = []; pack.ice.icebergs = []; } return { initialize, generate, addIceberg, removeIce, updateIceberg, randomizeIcebergShape, changeIcebergSize, getData, clear }; })();