mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
make depressions resolve elevation change not that big
This commit is contained in:
parent
ab065da5d2
commit
67235bc41e
8 changed files with 1765 additions and 1264 deletions
|
|
@ -104,7 +104,7 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
#biomes {
|
#biomes {
|
||||||
stroke-width: .7;
|
stroke-width: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
#landmass {
|
#landmass {
|
||||||
|
|
@ -285,6 +285,12 @@ i.icon-lock {
|
||||||
animation: dash 80s linear backwards;
|
animation: dash 80s linear backwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
marker-end: url(#end-arrow-small);
|
||||||
|
stroke: #555;
|
||||||
|
stroke-width: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes dash {
|
@keyframes dash {
|
||||||
to {
|
to {
|
||||||
stroke-dashoffset: 0;
|
stroke-dashoffset: 0;
|
||||||
|
|
|
||||||
|
|
@ -3451,6 +3451,9 @@
|
||||||
<marker id="end-arrow" viewBox="0 -5 10 10" refX="6" markerWidth="7" markerHeight="7" orient="auto">
|
<marker id="end-arrow" viewBox="0 -5 10 10" refX="6" markerWidth="7" markerHeight="7" orient="auto">
|
||||||
<path d="M0,-5L10,0L0,5" fill="#000"></path>
|
<path d="M0,-5L10,0L0,5" fill="#000"></path>
|
||||||
</marker>
|
</marker>
|
||||||
|
<marker id="end-arrow-small" viewBox="0 -5 10 10" refX="6" markerWidth="2" markerHeight="2" orient="auto">
|
||||||
|
<path d="M0,-5L10,0L0,5" fill="#555"></path>
|
||||||
|
</marker>
|
||||||
|
|
||||||
<symbol id="icon-store" viewBox="0 0 616 512">
|
<symbol id="icon-store" viewBox="0 0 616 512">
|
||||||
<path d="M602 118.6L537.1 15C531.3 5.7 521 0 510 0H106C95 0 84.7 5.7 78.9 15L14 118.6c-33.5 53.5-3.8 127.9 58.8 136.4 4.5.6 9.1.9 13.7.9 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18.1 20.1 44.3 33.1 73.8 33.1 4.7 0 9.2-.3 13.7-.9 62.8-8.4 92.6-82.8 59-136.4zM529.5 288c-10 0-19.9-1.5-29.5-3.8V384H116v-99.8c-9.6 2.2-19.5 3.8-29.5 3.8-6 0-12.1-.4-18-1.2-5.6-.8-11.1-2.1-16.4-3.6V480c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32V283.2c-5.4 1.6-10.8 2.9-16.4 3.6-6.1.8-12.1 1.2-18.2 1.2z"></path>
|
<path d="M602 118.6L537.1 15C531.3 5.7 521 0 510 0H106C95 0 84.7 5.7 78.9 15L14 118.6c-33.5 53.5-3.8 127.9 58.8 136.4 4.5.6 9.1.9 13.7.9 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18.1 20.1 44.3 33.1 73.8 33.1 4.7 0 9.2-.3 13.7-.9 62.8-8.4 92.6-82.8 59-136.4zM529.5 288c-10 0-19.9-1.5-29.5-3.8V384H116v-99.8c-9.6 2.2-19.5 3.8-29.5 3.8-6 0-12.1-.4-18-1.2-5.6-.8-11.1-2.1-16.4-3.6V480c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32V283.2c-5.4 1.6-10.8 2.9-16.4 3.6-6.1.8-12.1 1.2-18.2 1.2z"></path>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
(function (global, factory) {
|
(function (global, factory) {
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Lakes = factory());
|
||||||
typeof define === 'function' && define.amd ? define(factory) :
|
})(this, function () {
|
||||||
(global.Lakes = factory());
|
"use strict";
|
||||||
}(this, (function () {'use strict';
|
|
||||||
|
|
||||||
const setClimateData = function(h) {
|
const setClimateData = function (h) {
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
const lakeOutCells = new Uint16Array(cells.i.length);
|
const lakeOutCells = new Uint16Array(cells.i.length);
|
||||||
|
|
||||||
|
|
@ -17,24 +16,46 @@ const setClimateData = function(h) {
|
||||||
// temperature and evaporation to detect closed lakes
|
// temperature and evaporation to detect closed lakes
|
||||||
f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
|
f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
|
||||||
const height = (f.height - 18) ** heightExponentInput.value; // height in meters
|
const height = (f.height - 18) ** heightExponentInput.value; // height in meters
|
||||||
const evaporation = (700 * (f.temp + .006 * height) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
|
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
|
||||||
f.evaporation = rn(evaporation * f.cells);
|
f.evaporation = rn(evaporation * f.cells);
|
||||||
|
|
||||||
|
// no outlet for lakes in depressed areas
|
||||||
|
if (f.closed) return;
|
||||||
|
|
||||||
// lake outlet cell
|
// lake outlet cell
|
||||||
f.outCell = f.shoreline[d3.scan(f.shoreline, (a,b) => h[a] - h[b])];
|
f.outCell = f.shoreline[d3.scan(f.shoreline, (a, b) => h[a] - h[b])];
|
||||||
lakeOutCells[f.outCell] = f.i;
|
lakeOutCells[f.outCell] = f.i;
|
||||||
});
|
});
|
||||||
|
|
||||||
return lakeOutCells;
|
return lakeOutCells;
|
||||||
}
|
};
|
||||||
|
|
||||||
const cleanupLakeData = function() {
|
// get array of land cells aroound lake
|
||||||
|
const getShoreline = function (lake) {
|
||||||
|
const queue = [lake.firstCell];
|
||||||
|
const used = [queue[0]];
|
||||||
|
const landCellsAround = [];
|
||||||
|
while (queue.length) {
|
||||||
|
const q = queue.pop();
|
||||||
|
for (const c of pack.cells.c[q]) {
|
||||||
|
if (used[c]) continue;
|
||||||
|
used[c] = true;
|
||||||
|
if (pack.cells.f[c] === lake.i) queue.push(c);
|
||||||
|
if (pack.cells.h[c] >= 20) landCellsAround.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lake.shoreline = landCellsAround;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanupLakeData = function () {
|
||||||
for (const feature of pack.features) {
|
for (const feature of pack.features) {
|
||||||
if (feature.type !== "lake") continue;
|
if (feature.type !== "lake") continue;
|
||||||
delete feature.river;
|
delete feature.river;
|
||||||
delete feature.enteringFlux;
|
delete feature.enteringFlux;
|
||||||
delete feature.shoreline;
|
delete feature.shoreline;
|
||||||
delete feature.outCell;
|
delete feature.outCell;
|
||||||
|
delete feature.closed;
|
||||||
feature.height = rn(feature.height);
|
feature.height = rn(feature.height);
|
||||||
|
|
||||||
const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r));
|
const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r));
|
||||||
|
|
@ -44,9 +65,9 @@ const cleanupLakeData = function() {
|
||||||
const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet);
|
const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet);
|
||||||
if (!outlet) delete feature.outlet;
|
if (!outlet) delete feature.outlet;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const defineGroup = function() {
|
const defineGroup = function () {
|
||||||
for (const feature of pack.features) {
|
for (const feature of pack.features) {
|
||||||
if (feature.type !== "lake") continue;
|
if (feature.type !== "lake") continue;
|
||||||
const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node();
|
const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node();
|
||||||
|
|
@ -55,23 +76,23 @@ const defineGroup = function() {
|
||||||
feature.group = getGroup(feature);
|
feature.group = getGroup(feature);
|
||||||
document.getElementById(feature.group).appendChild(lakeEl);
|
document.getElementById(feature.group).appendChild(lakeEl);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const generateName = function() {
|
const generateName = function () {
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
for (const feature of pack.features) {
|
for (const feature of pack.features) {
|
||||||
if (feature.type !== "lake") continue;
|
if (feature.type !== "lake") continue;
|
||||||
feature.name = getName(feature);
|
feature.name = getName(feature);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getName = function(feature) {
|
const getName = function (feature) {
|
||||||
const landCell = pack.cells.c[feature.firstCell].find(c => pack.cells.h[c] >= 20);
|
const landCell = pack.cells.c[feature.firstCell].find(c => pack.cells.h[c] >= 20);
|
||||||
const culture = pack.cells.culture[landCell];
|
const culture = pack.cells.culture[landCell];
|
||||||
return Names.getCulture(culture);
|
return Names.getCulture(culture);
|
||||||
}
|
};
|
||||||
|
|
||||||
function getGroup(feature) {
|
function getGroup(feature) {
|
||||||
if (feature.temp < -3) return "frozen";
|
if (feature.temp < -3) return "frozen";
|
||||||
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava";
|
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava";
|
||||||
|
|
||||||
|
|
@ -83,8 +104,7 @@ function getGroup(feature) {
|
||||||
if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
|
if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
|
||||||
|
|
||||||
return "freshwater";
|
return "freshwater";
|
||||||
}
|
}
|
||||||
|
|
||||||
return {setClimateData, cleanupLakeData, defineGroup, generateName, getName};
|
return {setClimateData, cleanupLakeData, defineGroup, generateName, getName, getShoreline};
|
||||||
|
});
|
||||||
})));
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
(function (global, factory) {
|
(function (global, factory) {
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Rivers = factory());
|
||||||
typeof define === 'function' && define.amd ? define(factory) :
|
})(this, function () {
|
||||||
(global.Rivers = factory());
|
"use strict";
|
||||||
}(this, (function () {'use strict';
|
|
||||||
|
|
||||||
const generate = function(changeHeights = true) {
|
const generate = function (changeHeights = true) {
|
||||||
TIME && console.time('generateRivers');
|
TIME && console.time("generateRivers");
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
const cells = pack.cells, p = cells.p, features = pack.features;
|
const cells = pack.cells,
|
||||||
|
p = cells.p,
|
||||||
|
features = pack.features;
|
||||||
|
|
||||||
const riversData = []; // rivers data
|
const riversData = []; // rivers data
|
||||||
cells.fl = new Uint16Array(cells.i.length); // water flux array
|
cells.fl = new Uint16Array(cells.i.length); // water flux array
|
||||||
|
|
@ -16,44 +17,38 @@ const generate = function(changeHeights = true) {
|
||||||
let riverNext = 1; // first river id is 1
|
let riverNext = 1; // first river id is 1
|
||||||
|
|
||||||
const h = alterHeights();
|
const h = alterHeights();
|
||||||
removeStoredLakeData();
|
prepareLakeData();
|
||||||
resolveDepressions(h);
|
resolveDepressions(h, 200);
|
||||||
drainWater();
|
drainWater();
|
||||||
defineRivers();
|
defineRivers();
|
||||||
Lakes.cleanupLakeData();
|
Lakes.cleanupLakeData();
|
||||||
|
|
||||||
if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one
|
if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one
|
||||||
|
|
||||||
TIME && console.timeEnd('generateRivers');
|
TIME && console.timeEnd("generateRivers");
|
||||||
|
|
||||||
// height with added t value to make map less depressed
|
function prepareLakeData() {
|
||||||
function alterHeights() {
|
|
||||||
const h = Array.from(cells.h)
|
|
||||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)
|
|
||||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000);
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeStoredLakeData() {
|
|
||||||
features.forEach(f => {
|
features.forEach(f => {
|
||||||
|
if (f.type !== "lake") return;
|
||||||
delete f.flux;
|
delete f.flux;
|
||||||
delete f.inlets;
|
delete f.inlets;
|
||||||
delete f.outlet;
|
delete f.outlet;
|
||||||
delete f.height;
|
delete f.height;
|
||||||
|
!f.shoreline && Lakes.getShoreline(f);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drainWater() {
|
function drainWater() {
|
||||||
const MIN_FLUX_TO_FORM_RIVER = 30;
|
const MIN_FLUX_TO_FORM_RIVER = 30;
|
||||||
const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]);
|
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
|
||||||
const lakeOutCells = Lakes.setClimateData(h);
|
const lakeOutCells = Lakes.setClimateData(h);
|
||||||
|
|
||||||
land.forEach(function(i) {
|
land.forEach(function (i) {
|
||||||
cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation
|
cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation
|
||||||
const x = p[i][0], y = p[i][1];
|
const [x, y] = p[i];
|
||||||
|
|
||||||
// create lake outlet if flux > evaporation
|
// create lake outlet if lake is not in deep depression and flux > evaporation
|
||||||
const lakes = !lakeOutCells[i] ? [] : features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation);
|
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||||
for (const lake of lakes) {
|
for (const lake of lakes) {
|
||||||
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
||||||
|
|
||||||
|
|
@ -78,25 +73,23 @@ const generate = function(changeHeights = true) {
|
||||||
|
|
||||||
// assign all tributary rivers to outlet basin
|
// assign all tributary rivers to outlet basin
|
||||||
for (let outlet = lakes[0]?.outlet, l = 0; l < lakes.length; l++) {
|
for (let outlet = lakes[0]?.outlet, l = 0; l < lakes.length; l++) {
|
||||||
lakes[l].inlets?.forEach(fork => riversData.find(r => r.river === fork).parent = outlet);
|
lakes[l].inlets?.forEach(fork => (riversData.find(r => r.river === fork).parent = outlet));
|
||||||
}
|
}
|
||||||
|
|
||||||
// near-border cell: pour water out of the screen
|
// near-border cell: pour water out of the screen
|
||||||
if (cells.b[i] && cells.r[i]) {
|
if (cells.b[i] && cells.r[i]) {
|
||||||
const to = [];
|
let to = [];
|
||||||
const min = Math.min(y, graphHeight - y, x, graphWidth - x);
|
const min = Math.min(y, graphHeight - y, x, graphWidth - x);
|
||||||
if (min === y) {to[0] = x; to[1] = 0;} else
|
if (min === y) to = [x, 0];
|
||||||
if (min === graphHeight - y) {to[0] = x; to[1] = graphHeight;} else
|
else if (min === graphHeight - y) to = [x, graphHeight];
|
||||||
if (min === x) {to[0] = 0; to[1] = y;} else
|
else if (min === x) to = [0, y];
|
||||||
if (min === graphWidth - x) {to[0] = graphWidth; to[1] = y;}
|
else if (min === graphWidth - x) to = [graphWidth, y];
|
||||||
riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1], flux: cells.fl[i]});
|
riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1], flux: cells.fl[i]});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// downhill cell (make sure it's not in the source lake)
|
// downhill cell (make sure it's not in the source lake)
|
||||||
const min = lakeOutCells[i]
|
const min = lakeOutCells[i] ? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])).sort((a, b) => h[a] - h[b])[0] : cells.c[i].sort((a, b) => h[a] - h[b])[0];
|
||||||
? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])).sort((a, b) => h[a] - h[b])[0]
|
|
||||||
: cells.c[i].sort((a, b) => h[a] - h[b])[0];
|
|
||||||
|
|
||||||
if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) {
|
if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) {
|
||||||
if (h[min] >= 20) cells.fl[min] += cells.fl[i];
|
if (h[min] >= 20) cells.fl[min] += cells.fl[i];
|
||||||
|
|
@ -139,7 +132,7 @@ const generate = function(changeHeights = true) {
|
||||||
waterBody.enteringFlux = fromFlux;
|
waterBody.enteringFlux = fromFlux;
|
||||||
}
|
}
|
||||||
waterBody.flux = waterBody.flux + fromFlux;
|
waterBody.flux = waterBody.flux + fromFlux;
|
||||||
waterBody.inlets ? waterBody.inlets.push(river) : waterBody.inlets = [river];
|
waterBody.inlets ? waterBody.inlets.push(river) : (waterBody.inlets = [river]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// propagate flux and add next river segment
|
// propagate flux and add next river segment
|
||||||
|
|
@ -165,77 +158,111 @@ const generate = function(changeHeights = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = riverSegments[0].cell;
|
const source = riverSegments[0].cell;
|
||||||
const mouth = riverSegments[riverSegments.length-2].cell;
|
const mouth = riverSegments[riverSegments.length - 2].cell;
|
||||||
|
|
||||||
const widthFactor = rn(.8 + Math.random() * .4, 1); // river width modifier [.8, 1.2]
|
const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2]
|
||||||
const sourceWidth = cells.h[source] >= 20 ? .1 : rn(Math.min(Math.max((cells.fl[source] / 500) ** .4, .5), 1.7), 2);
|
const sourceWidth = cells.h[source] >= 20 ? 0.1 : rn(Math.min(Math.max((cells.fl[source] / 500) ** 0.4, 0.5), 1.7), 2);
|
||||||
|
|
||||||
const riverMeandered = addMeandering(riverSegments, sourceWidth * 10, .5);
|
const riverMeandered = addMeandering(riverSegments, sourceWidth * 10, 0.5);
|
||||||
const [path, length, offset] = getPath(riverMeandered, widthFactor, sourceWidth);
|
const [path, length, offset] = getPath(riverMeandered, widthFactor, sourceWidth);
|
||||||
riverPaths.push([path, r]);
|
riverPaths.push([path, r]);
|
||||||
|
|
||||||
const parent = riverSegments[0].parent || 0;
|
const parent = riverSegments[0].parent || 0;
|
||||||
const width = rn(offset ** 2, 2); // mounth width in km
|
const width = rn(offset ** 2, 2); // mounth width in km
|
||||||
const discharge = last(riverSegments).flux; // in m3/s
|
const discharge = last(riverSegments).flux; // in m3/s
|
||||||
pack.rivers.push({i:r, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent});
|
pack.rivers.push({i: r, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent});
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw rivers
|
// draw rivers
|
||||||
rivers.html(riverPaths.map(d => `<path id="river${d[1]}" d="${d[0]}"/>`).join(""));
|
rivers.html(riverPaths.map(d => `<path id="river${d[1]}" d="${d[0]}"/>`).join(""));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// depression filling algorithm (for a correct water flux modeling)
|
// add distance to water value to land cells to make map less depressed
|
||||||
const resolveDepressions = function(h) {
|
function alterHeights() {
|
||||||
|
const cells = pack.cells;
|
||||||
|
return Array.from(cells.h).map((h, i) => {
|
||||||
|
if (h < 20 || cells.t[i] < 1) return h;
|
||||||
|
return h + cells.t[i] / 100 + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// depression filling algorithm (for a correct water flux modeling)
|
||||||
|
const resolveDepressions = function (h, maxIterations) {
|
||||||
const {cells, features} = pack;
|
const {cells, features} = pack;
|
||||||
const ITERATIONS = 150;
|
const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell
|
||||||
|
|
||||||
const lakes = features.filter(f => f.type === "lake");
|
const lakes = features.filter(f => f.type === "lake");
|
||||||
lakes.forEach(l => {
|
|
||||||
const uniqueCells = new Set();
|
|
||||||
l.vertices.forEach(v => pack.vertices.c[v].forEach(c => cells.h[c] >= 20 && uniqueCells.add(c)));
|
|
||||||
l.shoreline = [...uniqueCells];
|
|
||||||
});
|
|
||||||
|
|
||||||
const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
|
const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
|
||||||
land.sort((a,b) => h[b] - h[a]); // highest cells go first
|
land.sort((a, b) => h[b] - h[a]); // highest cells go first
|
||||||
|
|
||||||
|
const progress = [];
|
||||||
let depressions = Infinity;
|
let depressions = Infinity;
|
||||||
for (let l = 0; depressions && l < ITERATIONS; l++) {
|
let prevDepressions = null;
|
||||||
|
for (let iteration = 0; depressions && iteration < maxIterations; iteration++) {
|
||||||
|
if (progress.length > 5 && d3.sum(progress) > 0) {
|
||||||
|
// bad progress, abort and set heights back
|
||||||
|
h = alterHeights();
|
||||||
|
depressions = progress[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
depressions = 0;
|
depressions = 0;
|
||||||
|
|
||||||
|
if (iteration < 180) {
|
||||||
for (const l of lakes) {
|
for (const l of lakes) {
|
||||||
|
if (l.closed) continue;
|
||||||
const minHeight = d3.min(l.shoreline.map(s => h[s]));
|
const minHeight = d3.min(l.shoreline.map(s => h[s]));
|
||||||
if (minHeight >= 100 || l.height > minHeight) continue;
|
if (minHeight >= 100 || l.height > minHeight) continue;
|
||||||
l.height = minHeight + 1;
|
|
||||||
|
if (iteration > 150) {
|
||||||
|
l.shoreline.forEach(i => (h[i] = cells.h[i]));
|
||||||
|
l.height = d3.min(l.shoreline.map(s => h[s])) - 1;
|
||||||
|
l.closed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
depressions++;
|
depressions++;
|
||||||
|
l.height = minHeight + 0.2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const i of land) {
|
for (const i of land) {
|
||||||
const minHeight = d3.min(cells.c[i].map(c => cells.t[c] > 0 ? h[c] : pack.features[cells.f[c]].height || h[c]));
|
const minHeight = d3.min(cells.c[i].map(c => height(c)));
|
||||||
if (minHeight >= 100 || h[i] > minHeight) continue;
|
if (minHeight >= 100 || h[i] > minHeight) continue;
|
||||||
h[i] = minHeight + 1;
|
|
||||||
depressions++;
|
depressions++;
|
||||||
}
|
h[i] = minHeight + 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
depressions && ERROR && console.error("Heightmap is depressed. Issues with rivers expected. Remove depressed areas to resolve");
|
prevDepressions !== null && progress.push(depressions - prevDepressions);
|
||||||
}
|
prevDepressions = depressions;
|
||||||
|
}
|
||||||
|
|
||||||
// add more river points on 1/3 and 2/3 of length
|
if (!depressions) return;
|
||||||
const addMeandering = function(segments, width = 1, meandering = .5) {
|
WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`);
|
||||||
|
//const flow = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length);
|
||||||
|
//flow[i] = min;
|
||||||
|
//debug.append("path").attr("class", "arrow").attr("d", `M${cells.p[i][0]},${cells.p[i][1]}L${cells.p[min][0]},${cells.p[min][1]}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// add more river points on 1/3 and 2/3 of length
|
||||||
|
const addMeandering = function (segments, width = 1, meandering = 0.5) {
|
||||||
const riverMeandered = []; // to store enhanced segments
|
const riverMeandered = []; // to store enhanced segments
|
||||||
|
|
||||||
for (let s = 0; s < segments.length; s++, width++) {
|
for (let s = 0; s < segments.length; s++, width++) {
|
||||||
const sX = segments[s].x, sY = segments[s].y; // segment start coordinates
|
const sX = segments[s].x,
|
||||||
|
sY = segments[s].y; // segment start coordinates
|
||||||
const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence
|
const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence
|
||||||
riverMeandered.push([sX, sY, c]);
|
riverMeandered.push([sX, sY, c]);
|
||||||
|
|
||||||
if (s+1 === segments.length) break; // do not meander last segment
|
if (s + 1 === segments.length) break; // do not meander last segment
|
||||||
|
|
||||||
const eX = segments[s+1].x, eY = segments[s+1].y; // segment end coordinates
|
const eX = segments[s + 1].x,
|
||||||
|
eY = segments[s + 1].y; // segment end coordinates
|
||||||
const angle = Math.atan2(eY - sY, eX - sX);
|
const angle = Math.atan2(eY - sY, eX - sX);
|
||||||
const sin = Math.sin(angle), cos = Math.cos(angle);
|
const sin = Math.sin(angle),
|
||||||
|
cos = Math.cos(angle);
|
||||||
|
|
||||||
const meander = meandering + 1 / width + Math.random() * Math.max(meandering - width / 100, 0);
|
const meander = meandering + 1 / width + Math.random() * Math.max(meandering - width / 100, 0);
|
||||||
const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; // square distance between segment start and end
|
const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; // square distance between segment start and end
|
||||||
|
|
@ -253,53 +280,61 @@ const addMeandering = function(segments, width = 1, meandering = .5) {
|
||||||
const p1y = (sY + eY) / 2 + cos * meander;
|
const p1y = (sY + eY) / 2 + cos * meander;
|
||||||
riverMeandered.push([p1x, p1y]);
|
riverMeandered.push([p1x, p1y]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return riverMeandered;
|
return riverMeandered;
|
||||||
}
|
};
|
||||||
|
|
||||||
const getPath = function(points, widthFactor = 1, sourceWidth = .1) {
|
const getPath = function (points, widthFactor = 1, sourceWidth = 0.1) {
|
||||||
let offset, extraOffset = sourceWidth; // starting river width (to make river source visible)
|
let offset,
|
||||||
const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i-1][0], v[1] - p[i-1][1]) : 0), 0); // summ of segments length
|
extraOffset = sourceWidth; // starting river width (to make river source visible)
|
||||||
|
const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0); // summ of segments length
|
||||||
const widening = 1000 + riverLength * 30;
|
const widening = 1000 + riverLength * 30;
|
||||||
const riverPointsLeft = [], riverPointsRight = []; // store points on both sides to build a valid polygon
|
const riverPointsLeft = [],
|
||||||
|
riverPointsRight = []; // store points on both sides to build a valid polygon
|
||||||
const last = points.length - 1;
|
const last = points.length - 1;
|
||||||
const factor = riverLength / points.length;
|
const factor = riverLength / points.length;
|
||||||
|
|
||||||
// first point
|
// first point
|
||||||
let x = points[0][0], y = points[0][1], c;
|
let x = points[0][0],
|
||||||
|
y = points[0][1],
|
||||||
|
c;
|
||||||
let angle = Math.atan2(y - points[1][1], x - points[1][0]);
|
let angle = Math.atan2(y - points[1][1], x - points[1][0]);
|
||||||
let sin = Math.sin(angle), cos = Math.cos(angle);
|
let sin = Math.sin(angle),
|
||||||
let xLeft = x + -sin * extraOffset, yLeft = y + cos * extraOffset;
|
cos = Math.cos(angle);
|
||||||
|
let xLeft = x + -sin * extraOffset,
|
||||||
|
yLeft = y + cos * extraOffset;
|
||||||
riverPointsLeft.push([xLeft, yLeft]);
|
riverPointsLeft.push([xLeft, yLeft]);
|
||||||
let xRight = x + sin * extraOffset, yRight = y + -cos * extraOffset;
|
let xRight = x + sin * extraOffset,
|
||||||
|
yRight = y + -cos * extraOffset;
|
||||||
riverPointsRight.unshift([xRight, yRight]);
|
riverPointsRight.unshift([xRight, yRight]);
|
||||||
|
|
||||||
// middle points
|
// middle points
|
||||||
for (let p = 1; p < last; p++) {
|
for (let p = 1; p < last; p++) {
|
||||||
x = points[p][0], y = points[p][1], c = points[p][2] || 0;
|
(x = points[p][0]), (y = points[p][1]), (c = points[p][2] || 0);
|
||||||
const xPrev = points[p-1][0], yPrev = points[p - 1][1];
|
const xPrev = points[p - 1][0],
|
||||||
const xNext = points[p+1][0], yNext = points[p + 1][1];
|
yPrev = points[p - 1][1];
|
||||||
|
const xNext = points[p + 1][0],
|
||||||
|
yNext = points[p + 1][1];
|
||||||
angle = Math.atan2(yPrev - yNext, xPrev - xNext);
|
angle = Math.atan2(yPrev - yNext, xPrev - xNext);
|
||||||
sin = Math.sin(angle), cos = Math.cos(angle);
|
(sin = Math.sin(angle)), (cos = Math.cos(angle));
|
||||||
offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2 * widthFactor) + extraOffset;
|
offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2) * widthFactor + extraOffset;
|
||||||
const confOffset = Math.atan(c * 5 / widening);
|
const confOffset = Math.atan((c * 5) / widening);
|
||||||
extraOffset += confOffset;
|
extraOffset += confOffset;
|
||||||
xLeft = x + -sin * offset, yLeft = y + cos * (offset + confOffset);
|
(xLeft = x + -sin * offset), (yLeft = y + cos * (offset + confOffset));
|
||||||
riverPointsLeft.push([xLeft, yLeft]);
|
riverPointsLeft.push([xLeft, yLeft]);
|
||||||
xRight = x + sin * offset, yRight = y + -cos * offset;
|
(xRight = x + sin * offset), (yRight = y + -cos * offset);
|
||||||
riverPointsRight.unshift([xRight, yRight]);
|
riverPointsRight.unshift([xRight, yRight]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// end point
|
// end point
|
||||||
x = points[last][0], y = points[last][1], c = points[last][2];
|
(x = points[last][0]), (y = points[last][1]), (c = points[last][2]);
|
||||||
if (c) extraOffset += Math.atan(c * 10 / widening); // add extra width on river confluence
|
if (c) extraOffset += Math.atan((c * 10) / widening); // add extra width on river confluence
|
||||||
angle = Math.atan2(points[last-1][1] - y, points[last-1][0] - x);
|
angle = Math.atan2(points[last - 1][1] - y, points[last - 1][0] - x);
|
||||||
sin = Math.sin(angle), cos = Math.cos(angle);
|
(sin = Math.sin(angle)), (cos = Math.cos(angle));
|
||||||
xLeft = x + -sin * offset, yLeft = y + cos * offset;
|
(xLeft = x + -sin * offset), (yLeft = y + cos * offset);
|
||||||
riverPointsLeft.push([xLeft, yLeft]);
|
riverPointsLeft.push([xLeft, yLeft]);
|
||||||
xRight = x + sin * offset, yRight = y + -cos * offset;
|
(xRight = x + sin * offset), (yRight = y + -cos * offset);
|
||||||
riverPointsRight.unshift([xRight, yRight]);
|
riverPointsRight.unshift([xRight, yRight]);
|
||||||
|
|
||||||
// generate polygon path and return
|
// generate polygon path and return
|
||||||
|
|
@ -308,33 +343,33 @@ const getPath = function(points, widthFactor = 1, sourceWidth = .1) {
|
||||||
let left = lineGen(riverPointsLeft);
|
let left = lineGen(riverPointsLeft);
|
||||||
left = left.substring(left.indexOf("C"));
|
left = left.substring(left.indexOf("C"));
|
||||||
return [round(right + left, 2), rn(riverLength, 2), offset];
|
return [round(right + left, 2), rn(riverLength, 2), offset];
|
||||||
}
|
};
|
||||||
|
|
||||||
const specify = function() {
|
const specify = function () {
|
||||||
const rivers = pack.rivers;
|
const rivers = pack.rivers;
|
||||||
if (!rivers.length) return;
|
if (!rivers.length) return;
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
const tresholdElement = Math.ceil(rivers.length * .15);
|
const tresholdElement = Math.ceil(rivers.length * 0.15);
|
||||||
const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a-b)[tresholdElement];
|
const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a - b)[tresholdElement];
|
||||||
const smallType = {"Creek":9, "River":3, "Brook":3, "Stream":1}; // weighted small river types
|
const smallType = {Creek: 9, River: 3, Brook: 3, Stream: 1}; // weighted small river types
|
||||||
|
|
||||||
for (const r of rivers) {
|
for (const r of rivers) {
|
||||||
r.basin = getBasin(r.i);
|
r.basin = getBasin(r.i);
|
||||||
r.name = getName(r.mouth);
|
r.name = getName(r.mouth);
|
||||||
const small = r.length < smallLength;
|
const small = r.length < smallLength;
|
||||||
r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River";
|
r.type = r.parent && !(r.i % 6) ? (small ? "Branch" : "Fork") : small ? rw(smallType) : "River";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getName = function(cell) {
|
const getName = function (cell) {
|
||||||
return Names.getCulture(pack.cells.culture[cell]);
|
return Names.getCulture(pack.cells.culture[cell]);
|
||||||
}
|
};
|
||||||
|
|
||||||
// remove river and all its tributaries
|
// remove river and all its tributaries
|
||||||
const remove = function(id) {
|
const remove = function (id) {
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
const riversToRemove = pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i);
|
const riversToRemove = pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i);
|
||||||
riversToRemove.forEach(r => rivers.select("#river"+r).remove());
|
riversToRemove.forEach(r => rivers.select("#river" + r).remove());
|
||||||
cells.r.forEach((r, i) => {
|
cells.r.forEach((r, i) => {
|
||||||
if (!r || !riversToRemove.includes(r)) return;
|
if (!r || !riversToRemove.includes(r)) return;
|
||||||
cells.r[i] = 0;
|
cells.r[i] = 0;
|
||||||
|
|
@ -342,14 +377,13 @@ const remove = function(id) {
|
||||||
cells.conf[i] = 0;
|
cells.conf[i] = 0;
|
||||||
});
|
});
|
||||||
pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
|
pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
|
||||||
}
|
};
|
||||||
|
|
||||||
const getBasin = function(r) {
|
const getBasin = function (r) {
|
||||||
const parent = pack.rivers.find(river => river.i === r)?.parent;
|
const parent = pack.rivers.find(river => river.i === r)?.parent;
|
||||||
if (!parent || r === parent) return r;
|
if (!parent || r === parent) return r;
|
||||||
return getBasin(parent);
|
return getBasin(parent);
|
||||||
}
|
};
|
||||||
|
|
||||||
return {generate, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove};
|
return {generate, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove};
|
||||||
|
});
|
||||||
})));
|
|
||||||
|
|
|
||||||
|
|
@ -27,19 +27,19 @@ async function savePNG() {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = url;
|
img.src = url;
|
||||||
|
|
||||||
img.onload = function() {
|
img.onload = function () {
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
link.download = getFileName() + ".png";
|
link.download = getFileName() + ".png";
|
||||||
canvas.toBlob(function(blob) {
|
canvas.toBlob(function (blob) {
|
||||||
link.href = window.URL.createObjectURL(blob);
|
link.href = window.URL.createObjectURL(blob);
|
||||||
link.click();
|
link.click();
|
||||||
window.setTimeout(function() {
|
window.setTimeout(function () {
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
window.URL.revokeObjectURL(link.href);
|
window.URL.revokeObjectURL(link.href);
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
TIME && console.timeEnd("savePNG");
|
TIME && console.timeEnd("savePNG");
|
||||||
}
|
}
|
||||||
|
|
@ -55,9 +55,9 @@ async function saveJPEG() {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = url;
|
img.src = url;
|
||||||
|
|
||||||
img.onload = async function() {
|
img.onload = async function () {
|
||||||
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
|
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), .92);
|
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
||||||
const URL = await canvas.toDataURL("image/jpeg", quality);
|
const URL = await canvas.toDataURL("image/jpeg", quality);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.download = getFileName() + ".jpeg";
|
link.download = getFileName() + ".jpeg";
|
||||||
|
|
@ -65,7 +65,7 @@ async function saveJPEG() {
|
||||||
link.click();
|
link.click();
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||||
}
|
};
|
||||||
|
|
||||||
TIME && console.timeEnd("saveJPEG");
|
TIME && console.timeEnd("saveJPEG");
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +81,7 @@ async function getMapURL(type, subtype) {
|
||||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||||
const svgDefs = document.getElementById("defElements");
|
const svgDefs = document.getElementById("defElements");
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove();
|
if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove();
|
||||||
if (subtype === "globe") clone.select("#scaleBar").remove();
|
if (subtype === "globe") clone.select("#scaleBar").remove();
|
||||||
if (subtype === "noWater") {
|
if (subtype === "noWater") {
|
||||||
|
|
@ -99,32 +99,35 @@ async function getMapURL(type, subtype) {
|
||||||
|
|
||||||
// remove unused filters
|
// remove unused filters
|
||||||
const filters = cloneEl.querySelectorAll("filter");
|
const filters = cloneEl.querySelectorAll("filter");
|
||||||
for (let i=0; i < filters.length; i++) {
|
for (let i = 0; i < filters.length; i++) {
|
||||||
const id = filters[i].id;
|
const id = filters[i].id;
|
||||||
if (cloneEl.querySelector("[filter='url(#"+id+")']")) continue;
|
if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue;
|
||||||
if (cloneEl.getAttribute("filter") === "url(#"+id+")") continue;
|
if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue;
|
||||||
filters[i].remove();
|
filters[i].remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove unused patterns
|
// remove unused patterns
|
||||||
const patterns = cloneEl.querySelectorAll("pattern");
|
const patterns = cloneEl.querySelectorAll("pattern");
|
||||||
for (let i=0; i < patterns.length; i++) {
|
for (let i = 0; i < patterns.length; i++) {
|
||||||
const id = patterns[i].id;
|
const id = patterns[i].id;
|
||||||
if (cloneEl.querySelector("[fill='url(#"+id+")']")) continue;
|
if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue;
|
||||||
patterns[i].remove();
|
patterns[i].remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove unused symbols
|
// remove unused symbols
|
||||||
const symbols = cloneEl.querySelectorAll("symbol");
|
const symbols = cloneEl.querySelectorAll("symbol");
|
||||||
for (let i=0; i < symbols.length; i++) {
|
for (let i = 0; i < symbols.length; i++) {
|
||||||
const id = symbols[i].id;
|
const id = symbols[i].id;
|
||||||
if (cloneEl.querySelector("use[*|href='#"+id+"']")) continue;
|
if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue;
|
||||||
symbols[i].remove();
|
symbols[i].remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add displayed emblems
|
// add displayed emblems
|
||||||
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) {
|
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) {
|
||||||
cloneEl.getElementById("emblems")?.querySelectorAll("use").forEach(el => {
|
cloneEl
|
||||||
|
.getElementById("emblems")
|
||||||
|
?.querySelectorAll("use")
|
||||||
|
.forEach(el => {
|
||||||
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||||||
if (!href) return;
|
if (!href) return;
|
||||||
const emblem = document.getElementById(href.slice(1));
|
const emblem = document.getElementById(href.slice(1));
|
||||||
|
|
@ -150,7 +153,7 @@ async function getMapURL(type, subtype) {
|
||||||
if (cloneEl.getElementById("terrain")) {
|
if (cloneEl.getElementById("terrain")) {
|
||||||
const uniqueElements = new Set();
|
const uniqueElements = new Set();
|
||||||
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
|
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
|
||||||
for (let i=0; i < terrainNodes.length; i++) {
|
for (let i = 0; i < terrainNodes.length; i++) {
|
||||||
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
|
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
|
||||||
uniqueElements.add(href);
|
uniqueElements.add(href);
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +180,7 @@ async function getMapURL(type, subtype) {
|
||||||
// add grid pattern
|
// add grid pattern
|
||||||
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) {
|
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) {
|
||||||
const type = cloneEl.getElementById("gridOverlay").getAttribute("type");
|
const type = cloneEl.getElementById("gridOverlay").getAttribute("type");
|
||||||
const pattern = svgDefs.getElementById("pattern_"+type);
|
const pattern = svgDefs.getElementById("pattern_" + type);
|
||||||
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,11 +193,11 @@ async function getMapURL(type, subtype) {
|
||||||
if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>");
|
if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>");
|
||||||
|
|
||||||
const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts
|
const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts
|
||||||
if (fontStyle) clone.select("defs").append("style").text(fontStyle.join('\n')); // add font to style
|
if (fontStyle) clone.select("defs").append("style").text(fontStyle.join("\n")); // add font to style
|
||||||
clone.remove();
|
clone.remove();
|
||||||
|
|
||||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + (new XMLSerializer()).serializeToString(cloneEl);
|
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
|
||||||
const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'});
|
const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
||||||
return url;
|
return url;
|
||||||
|
|
@ -205,10 +208,13 @@ function removeUnusedElements(clone) {
|
||||||
if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove();
|
if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove();
|
||||||
if (markers.style("display") === "none") clone.select("#defs-markers").remove();
|
if (markers.style("display") === "none") clone.select("#defs-markers").remove();
|
||||||
|
|
||||||
for (let empty = 1; empty;) {
|
for (let empty = 1; empty; ) {
|
||||||
empty = 0;
|
empty = 0;
|
||||||
clone.selectAll("g").each(function() {
|
clone.selectAll("g").each(function () {
|
||||||
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {empty++; this.remove();}
|
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {
|
||||||
|
empty++;
|
||||||
|
this.remove();
|
||||||
|
}
|
||||||
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
|
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -218,8 +224,14 @@ function updateMeshCells(clone) {
|
||||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
||||||
const scheme = getColorScheme();
|
const scheme = getColorScheme();
|
||||||
clone.select("#heights").attr("filter", "url(#blur1)");
|
clone.select("#heights").attr("filter", "url(#blur1)");
|
||||||
clone.select("#heights").selectAll("polygon").data(data).join("polygon").attr("points", d => getGridPolygon(d))
|
clone
|
||||||
.attr("id", d => "cell"+d).attr("stroke", d => getColor(grid.cells.h[d], scheme));
|
.select("#heights")
|
||||||
|
.selectAll("polygon")
|
||||||
|
.data(data)
|
||||||
|
.join("polygon")
|
||||||
|
.attr("points", d => getGridPolygon(d))
|
||||||
|
.attr("id", d => "cell" + d)
|
||||||
|
.attr("stroke", d => getColor(grid.cells.h[d], scheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each g element get inline style
|
// for each g element get inline style
|
||||||
|
|
@ -227,11 +239,11 @@ function inlineStyle(clone) {
|
||||||
const emptyG = clone.append("g").node();
|
const emptyG = clone.append("g").node();
|
||||||
const defaultStyles = window.getComputedStyle(emptyG);
|
const defaultStyles = window.getComputedStyle(emptyG);
|
||||||
|
|
||||||
clone.selectAll("g, #ruler *, #scaleBar > text").each(function() {
|
clone.selectAll("g, #ruler *, #scaleBar > text").each(function () {
|
||||||
const compStyle = window.getComputedStyle(this);
|
const compStyle = window.getComputedStyle(this);
|
||||||
let style = "";
|
let style = "";
|
||||||
|
|
||||||
for (let i=0; i < compStyle.length; i++) {
|
for (let i = 0; i < compStyle.length; i++) {
|
||||||
const key = compStyle[i];
|
const key = compStyle[i];
|
||||||
const value = compStyle.getPropertyValue(key);
|
const value = compStyle.getPropertyValue(key);
|
||||||
|
|
||||||
|
|
@ -244,7 +256,7 @@ function inlineStyle(clone) {
|
||||||
if (key === "cursor") continue; // cursor should be default
|
if (key === "cursor") continue; // cursor should be default
|
||||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||||
style += key + ':' + value + ';';
|
style += key + ":" + value + ";";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in compStyle) {
|
for (const key in compStyle) {
|
||||||
|
|
@ -253,10 +265,10 @@ function inlineStyle(clone) {
|
||||||
if (key === "cursor") continue; // cursor should be default
|
if (key === "cursor") continue; // cursor should be default
|
||||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||||
style += key + ':' + value + ';';
|
style += key + ":" + value + ";";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style != "") this.setAttribute('style', style);
|
if (style != "") this.setAttribute("style", style);
|
||||||
});
|
});
|
||||||
|
|
||||||
emptyG.remove();
|
emptyG.remove();
|
||||||
|
|
@ -267,7 +279,7 @@ function getFontsToLoad(clone) {
|
||||||
const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch
|
const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch
|
||||||
|
|
||||||
const fontsInUse = new Set(); // to store fonts currently in use
|
const fontsInUse = new Set(); // to store fonts currently in use
|
||||||
clone.selectAll("#labels > g").each(function() {
|
clone.selectAll("#labels > g").each(function () {
|
||||||
if (!this.hasChildNodes()) return;
|
if (!this.hasChildNodes()) return;
|
||||||
const font = this.dataset.font;
|
const font = this.dataset.font;
|
||||||
if (!font || webSafe.includes(font)) return;
|
if (!font || webSafe.includes(font)) return;
|
||||||
|
|
@ -285,16 +297,16 @@ function GFontToDataURI(url) {
|
||||||
return fetch(url) // first fecth the embed stylesheet page
|
return fetch(url) // first fecth the embed stylesheet page
|
||||||
.then(resp => resp.text()) // we only need the text of it
|
.then(resp => resp.text()) // we only need the text of it
|
||||||
.then(text => {
|
.then(text => {
|
||||||
let s = document.createElement('style');
|
let s = document.createElement("style");
|
||||||
s.innerHTML = text;
|
s.innerHTML = text;
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
|
const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
|
||||||
|
|
||||||
const FontRule = rule => {
|
const FontRule = rule => {
|
||||||
const src = rule.style.getPropertyValue('src');
|
const src = rule.style.getPropertyValue("src");
|
||||||
const url = src ? src.split('url(')[1].split(')')[0] : "";
|
const url = src ? src.split("url(")[1].split(")")[0] : "";
|
||||||
return {rule, src, url: url.substring(url.length - 1, 1)};
|
return {rule, src, url: url.substring(url.length - 1, 1)};
|
||||||
}
|
};
|
||||||
const fontProms = [];
|
const fontProms = [];
|
||||||
|
|
||||||
for (const r of styleSheet.cssRules) {
|
for (const r of styleSheet.cssRules) {
|
||||||
|
|
@ -309,10 +321,10 @@ function GFontToDataURI(url) {
|
||||||
let f = new FileReader();
|
let f = new FileReader();
|
||||||
f.onload = e => resolve(f.result);
|
f.onload = e => resolve(f.result);
|
||||||
f.readAsDataURL(blob);
|
f.readAsDataURL(blob);
|
||||||
})
|
});
|
||||||
})
|
})
|
||||||
.then(dataURL => fR.rule.cssText.replace(fR.url, dataURL))
|
.then(dataURL => fR.rule.cssText.replace(fR.url, dataURL))
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
document.head.removeChild(s); // clean up
|
document.head.removeChild(s); // clean up
|
||||||
return Promise.all(fontProms); // wait for all this has been done
|
return Promise.all(fontProms); // wait for all this has been done
|
||||||
|
|
@ -328,13 +340,7 @@ function getMapData() {
|
||||||
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
||||||
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
||||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
|
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
|
||||||
const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value,
|
const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate.value, urbanization.value, mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(options), mapName.value].join("|");
|
||||||
heightUnit.value, heightExponentInput.value, temperatureScale.value,
|
|
||||||
barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value,
|
|
||||||
barPosX.value, barPosY.value, populationRate.value, urbanization.value,
|
|
||||||
mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value,
|
|
||||||
temperaturePoleOutput.value, precOutput.value, JSON.stringify(options),
|
|
||||||
mapName.value].join("|");
|
|
||||||
const coords = JSON.stringify(mapCoordinates);
|
const coords = JSON.stringify(mapCoordinates);
|
||||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
||||||
const notesData = JSON.stringify(notes);
|
const notesData = JSON.stringify(notes);
|
||||||
|
|
@ -351,9 +357,9 @@ function getMapData() {
|
||||||
// always remove rulers
|
// always remove rulers
|
||||||
cloneEl.querySelector("#ruler").innerHTML = "";
|
cloneEl.querySelector("#ruler").innerHTML = "";
|
||||||
|
|
||||||
const svg_xml = (new XMLSerializer()).serializeToString(cloneEl);
|
const svg_xml = new XMLSerializer().serializeToString(cloneEl);
|
||||||
|
|
||||||
const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features});
|
const gridGeneral = JSON.stringify({spacing: grid.spacing, cellsX: grid.cellsX, cellsY: grid.cellsY, boundary: grid.boundary, points: grid.points, features: grid.features});
|
||||||
const features = JSON.stringify(pack.features);
|
const features = 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);
|
||||||
|
|
@ -364,22 +370,18 @@ function getMapData() {
|
||||||
|
|
||||||
// store name array only if it is not the same as default
|
// store name array only if it is not the same as default
|
||||||
const defaultNB = Names.getNameBases();
|
const defaultNB = Names.getNameBases();
|
||||||
const namesData = nameBases.map((b,i) => {
|
const namesData = nameBases
|
||||||
|
.map((b, i) => {
|
||||||
const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b;
|
const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b;
|
||||||
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
||||||
}).join("/");
|
})
|
||||||
|
.join("/");
|
||||||
|
|
||||||
// round population to save resources
|
// round population to save resources
|
||||||
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
||||||
|
|
||||||
// data format as below
|
// data format as below
|
||||||
const data = [params, settings, coords, biomes, notesData, svg_xml,
|
const data = [params, settings, coords, biomes, notesData, svg_xml, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, features, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n");
|
||||||
gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp,
|
|
||||||
features, cultures, states, burgs,
|
|
||||||
pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl,
|
|
||||||
pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state,
|
|
||||||
pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces,
|
|
||||||
namesData, rivers, rulersString].join("\r\n");
|
|
||||||
const blob = new Blob([data], {type: "text/plain"});
|
const blob = new Blob([data], {type: "text/plain"});
|
||||||
|
|
||||||
TIME && console.timeEnd("createMapDataBlob");
|
TIME && console.timeEnd("createMapDataBlob");
|
||||||
|
|
@ -389,7 +391,10 @@ function getMapData() {
|
||||||
|
|
||||||
// Download .map file
|
// Download .map file
|
||||||
async function saveMap() {
|
async function saveMap() {
|
||||||
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
|
if (customization) {
|
||||||
|
tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
closeDialogs("#alert");
|
closeDialogs("#alert");
|
||||||
|
|
||||||
const blob = await getMapData();
|
const blob = await getMapData();
|
||||||
|
|
@ -405,8 +410,11 @@ async function saveMap() {
|
||||||
function saveGeoJSON_Cells() {
|
function saveGeoJSON_Cells() {
|
||||||
const json = {type: "FeatureCollection", features: []};
|
const json = {type: "FeatureCollection", features: []};
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
const getPopulation = i => {const [r, u] = getCellPopulation(i); return rn(r+u)};
|
const getPopulation = i => {
|
||||||
const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]]));
|
const [r, u] = getCellPopulation(i);
|
||||||
|
return rn(r + u);
|
||||||
|
};
|
||||||
|
const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
||||||
|
|
||||||
cells.i.forEach(i => {
|
cells.i.forEach(i => {
|
||||||
const coordinates = getCellCoordinates(cells.v[i]);
|
const coordinates = getCellCoordinates(cells.v[i]);
|
||||||
|
|
@ -420,7 +428,7 @@ function saveGeoJSON_Cells() {
|
||||||
const religion = cells.religion[i];
|
const religion = cells.religion[i];
|
||||||
const neighbors = cells.c[i];
|
const neighbors = cells.c[i];
|
||||||
|
|
||||||
const properties = {id:i, height, biome, type, population, state, province, culture, religion, neighbors}
|
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
||||||
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
|
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
|
||||||
json.features.push(feature);
|
json.features.push(feature);
|
||||||
});
|
});
|
||||||
|
|
@ -432,7 +440,7 @@ function saveGeoJSON_Cells() {
|
||||||
function saveGeoJSON_Routes() {
|
function saveGeoJSON_Routes() {
|
||||||
const json = {type: "FeatureCollection", features: []};
|
const json = {type: "FeatureCollection", features: []};
|
||||||
|
|
||||||
routes.selectAll("g > path").each(function() {
|
routes.selectAll("g > path").each(function () {
|
||||||
const coordinates = getRoutePoints(this);
|
const coordinates = getRoutePoints(this);
|
||||||
const id = this.id;
|
const id = this.id;
|
||||||
const type = this.parentElement.id;
|
const type = this.parentElement.id;
|
||||||
|
|
@ -448,7 +456,7 @@ function saveGeoJSON_Routes() {
|
||||||
function saveGeoJSON_Rivers() {
|
function saveGeoJSON_Rivers() {
|
||||||
const json = {type: "FeatureCollection", features: []};
|
const json = {type: "FeatureCollection", features: []};
|
||||||
|
|
||||||
rivers.selectAll("path").each(function() {
|
rivers.selectAll("path").each(function () {
|
||||||
const coordinates = getRiverPoints(this);
|
const coordinates = getRiverPoints(this);
|
||||||
const id = this.id;
|
const id = this.id;
|
||||||
const width = +this.dataset.increment;
|
const width = +this.dataset.increment;
|
||||||
|
|
@ -470,10 +478,10 @@ function saveGeoJSON_Rivers() {
|
||||||
function saveGeoJSON_Markers() {
|
function saveGeoJSON_Markers() {
|
||||||
const json = {type: "FeatureCollection", features: []};
|
const json = {type: "FeatureCollection", features: []};
|
||||||
|
|
||||||
markers.selectAll("use").each(function() {
|
markers.selectAll("use").each(function () {
|
||||||
const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
|
const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
|
||||||
const id = this.id;
|
const id = this.id;
|
||||||
const type = (this.dataset.id).substring(1);
|
const type = this.dataset.id.substring(1);
|
||||||
const icon = document.getElementById(type).textContent;
|
const icon = document.getElementById(type).textContent;
|
||||||
const note = notes.length ? notes.find(note => note.id === this.id) : null;
|
const note = notes.length ? notes.find(note => note.id === this.id) : null;
|
||||||
const name = note ? note.name : "";
|
const name = note ? note.name : "";
|
||||||
|
|
@ -497,7 +505,7 @@ function getRoutePoints(node) {
|
||||||
let points = [];
|
let points = [];
|
||||||
const l = node.getTotalLength();
|
const l = node.getTotalLength();
|
||||||
const increment = l / Math.ceil(l / 2);
|
const increment = l / Math.ceil(l / 2);
|
||||||
for (let i=0; i <= l; i += increment) {
|
for (let i = 0; i <= l; i += increment) {
|
||||||
const p = node.getPointAtLength(i);
|
const p = node.getPointAtLength(i);
|
||||||
points.push(getQGIScoordinates(p.x, p.y));
|
points.push(getQGIScoordinates(p.x, p.y));
|
||||||
}
|
}
|
||||||
|
|
@ -508,17 +516,20 @@ function getRiverPoints(node) {
|
||||||
let points = [];
|
let points = [];
|
||||||
const l = node.getTotalLength() / 2; // half-length
|
const l = node.getTotalLength() / 2; // half-length
|
||||||
const increment = 0.25; // defines density of points
|
const increment = 0.25; // defines density of points
|
||||||
for (let i=l, c=i; i >= 0; i -= increment, c += increment) {
|
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
||||||
const p1 = node.getPointAtLength(i);
|
const p1 = node.getPointAtLength(i);
|
||||||
const p2 = node.getPointAtLength(c);
|
const p2 = node.getPointAtLength(c);
|
||||||
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||||
points.push([x,y]);
|
points.push([x, y]);
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function quickSave() {
|
async function quickSave() {
|
||||||
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
|
if (customization) {
|
||||||
|
tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const blob = await getMapData();
|
const blob = await getMapData();
|
||||||
if (blob) ldb.set("lastMap", blob); // auto-save map
|
if (blob) ldb.set("lastMap", blob); // auto-save map
|
||||||
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
|
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
|
||||||
|
|
@ -537,14 +548,24 @@ function quickLoad() {
|
||||||
|
|
||||||
function loadMapPrompt(blob) {
|
function loadMapPrompt(blob) {
|
||||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||||
if (workingTime < 5) {loadLastSavedMap(); return;}
|
if (workingTime < 5) {
|
||||||
|
loadLastSavedMap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
alertMessage.innerHTML = `Are you sure you want to load saved map?<br>
|
alertMessage.innerHTML = `Are you sure you want to load saved map?<br>
|
||||||
All unsaved changes made to the current map will be lost`;
|
All unsaved changes made to the current map will be lost`;
|
||||||
$("#alert").dialog({resizable: false, title: "Load saved map",
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Load saved map",
|
||||||
buttons: {
|
buttons: {
|
||||||
Cancel: function() {$(this).dialog("close");},
|
Cancel: function () {
|
||||||
Load: function() {loadLastSavedMap(); $(this).dialog("close");}
|
$(this).dialog("close");
|
||||||
|
},
|
||||||
|
Load: function () {
|
||||||
|
loadLastSavedMap();
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -552,31 +573,23 @@ function loadMapPrompt(blob) {
|
||||||
WARN && console.warn("Load last saved map");
|
WARN && console.warn("Load last saved map");
|
||||||
try {
|
try {
|
||||||
uploadMap(blob);
|
uploadMap(blob);
|
||||||
}
|
} catch (error) {
|
||||||
catch(error) {
|
|
||||||
ERROR && console.error(error);
|
ERROR && console.error(error);
|
||||||
tip("Cannot load last saved map", true, "error", 2000);
|
tip("Cannot load last saved map", true, "error", 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveReminder = function() {
|
const saveReminder = function () {
|
||||||
if (localStorage.getItem("noReminder")) return;
|
if (localStorage.getItem("noReminder")) return;
|
||||||
const message = ["Please don't forget to save your work as a .map file",
|
const message = ["Please don't forget to save your work as a .map file", "Please remember to save work as a .map file", "Saving in .map format will ensure your data won't be lost in case of issues", "Safety is number one priority. Please save the map", "Don't forget to save your map on a regular basis!", "Just a gentle reminder for you to save the map", "Please don't forget to save your progress (saving as .map is the best option)", "Don't want to be reminded about need to save? Press CTRL+Q"];
|
||||||
"Please remember to save work as a .map file",
|
|
||||||
"Saving in .map format will ensure your data won't be lost in case of issues",
|
|
||||||
"Safety is number one priority. Please save the map",
|
|
||||||
"Don't forget to save your map on a regular basis!",
|
|
||||||
"Just a gentle reminder for you to save the map",
|
|
||||||
"Please don't forget to save your progress (saving as .map is the best option)",
|
|
||||||
"Don't want to be reminded about need to save? Press CTRL+Q"];
|
|
||||||
|
|
||||||
saveReminder.reminder = setInterval(() => {
|
saveReminder.reminder = setInterval(() => {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
tip(ra(message), true, "warn", 2500);
|
tip(ra(message), true, "warn", 2500);
|
||||||
}, 1e6);
|
}, 1e6);
|
||||||
saveReminder.status = 1;
|
saveReminder.status = 1;
|
||||||
}
|
};
|
||||||
|
|
||||||
saveReminder();
|
saveReminder();
|
||||||
|
|
||||||
|
|
@ -597,7 +610,7 @@ function uploadMap(file, callback) {
|
||||||
uploadMap.timeStart = performance.now();
|
uploadMap.timeStart = performance.now();
|
||||||
|
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
fileReader.onload = function(fileLoadedEvent) {
|
fileReader.onload = function (fileLoadedEvent) {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
|
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
|
||||||
|
|
||||||
|
|
@ -605,11 +618,15 @@ function uploadMap(file, callback) {
|
||||||
const data = dataLoaded.split("\r\n");
|
const data = dataLoaded.split("\r\n");
|
||||||
|
|
||||||
const mapVersion = data[0].split("|")[0] || data[0];
|
const mapVersion = data[0].split("|")[0] || data[0];
|
||||||
if (mapVersion === version) {parseLoadedData(data); return;}
|
if (mapVersion === version) {
|
||||||
|
parseLoadedData(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const archive = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "archived version");
|
const archive = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "archived version");
|
||||||
const parsed = parseFloat(mapVersion);
|
const parsed = parseFloat(mapVersion);
|
||||||
let message = "", load = false;
|
let message = "",
|
||||||
|
load = false;
|
||||||
if (isNaN(parsed) || data.length < 26 || !data[5]) {
|
if (isNaN(parsed) || data.length < 26 || !data[5]) {
|
||||||
message = `The file you are trying to load is outdated or not a valid .map file.
|
message = `The file you are trying to load is outdated or not a valid .map file.
|
||||||
<br>Please try to open it using an ${archive}`;
|
<br>Please try to open it using an ${archive}`;
|
||||||
|
|
@ -622,9 +639,16 @@ function uploadMap(file, callback) {
|
||||||
<br>Click OK to get map <b>auto-updated</b>. In case of issues please keep using an ${archive} of the Generator`;
|
<br>Click OK to get map <b>auto-updated</b>. In case of issues please keep using an ${archive} of the Generator`;
|
||||||
}
|
}
|
||||||
alertMessage.innerHTML = message;
|
alertMessage.innerHTML = message;
|
||||||
$("#alert").dialog({title: "Version conflict", width: "38em", buttons: {
|
$("#alert").dialog({
|
||||||
OK: function() {$(this).dialog("close"); if (load) parseLoadedData(data);}
|
title: "Version conflict",
|
||||||
}});
|
width: "38em",
|
||||||
|
buttons: {
|
||||||
|
OK: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
if (load) parseLoadedData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
fileReader.readAsText(file, "UTF-8");
|
fileReader.readAsText(file, "UTF-8");
|
||||||
|
|
@ -640,17 +664,20 @@ function parseLoadedData(data) {
|
||||||
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
|
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
|
||||||
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
|
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
|
||||||
|
|
||||||
void function parseParameters() {
|
void (function parseParameters() {
|
||||||
const params = data[0].split("|");
|
const params = data[0].split("|");
|
||||||
if (params[3]) {seed = params[3]; optionsSeed.value = seed;}
|
if (params[3]) {
|
||||||
|
seed = params[3];
|
||||||
|
optionsSeed.value = seed;
|
||||||
|
}
|
||||||
if (params[4]) graphWidth = +params[4];
|
if (params[4]) graphWidth = +params[4];
|
||||||
if (params[5]) graphHeight = +params[5];
|
if (params[5]) graphHeight = +params[5];
|
||||||
mapId = params[6] ? +params[6] : Date.now();
|
mapId = params[6] ? +params[6] : Date.now();
|
||||||
}()
|
})();
|
||||||
|
|
||||||
INFO && console.group("Loaded Map " + seed);
|
INFO && console.group("Loaded Map " + seed);
|
||||||
|
|
||||||
void function parseSettings() {
|
void (function parseSettings() {
|
||||||
const settings = data[1].split("|");
|
const settings = data[1].split("|");
|
||||||
if (settings[0]) applyOption(distanceUnitInput, settings[0]);
|
if (settings[0]) applyOption(distanceUnitInput, settings[0]);
|
||||||
if (settings[1]) distanceScaleInput.value = distanceScaleOutput.value = settings[1];
|
if (settings[1]) distanceScaleInput.value = distanceScaleOutput.value = settings[1];
|
||||||
|
|
@ -673,9 +700,9 @@ function parseLoadedData(data) {
|
||||||
if (settings[18]) precInput.value = precOutput.value = settings[18];
|
if (settings[18]) precInput.value = precOutput.value = settings[18];
|
||||||
if (settings[19]) options = JSON.parse(settings[19]);
|
if (settings[19]) options = JSON.parse(settings[19]);
|
||||||
if (settings[20]) mapName.value = settings[20];
|
if (settings[20]) mapName.value = settings[20];
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function parseConfiguration() {
|
void (function parseConfiguration() {
|
||||||
if (data[2]) mapCoordinates = JSON.parse(data[2]);
|
if (data[2]) mapCoordinates = JSON.parse(data[2]);
|
||||||
if (data[4]) notes = JSON.parse(data[4]);
|
if (data[4]) notes = JSON.parse(data[4]);
|
||||||
if (data[33]) rulers.fromString(data[33]);
|
if (data[33]) rulers.fromString(data[33]);
|
||||||
|
|
@ -687,20 +714,20 @@ function parseLoadedData(data) {
|
||||||
biomesData.name = biomes[2].split(",");
|
biomesData.name = biomes[2].split(",");
|
||||||
|
|
||||||
// push custom biomes if any
|
// push custom biomes if any
|
||||||
for (let i=biomesData.i.length; i < biomesData.name.length; i++) {
|
for (let i = biomesData.i.length; i < biomesData.name.length; i++) {
|
||||||
biomesData.i.push(biomesData.i.length);
|
biomesData.i.push(biomesData.i.length);
|
||||||
biomesData.iconsDensity.push(0);
|
biomesData.iconsDensity.push(0);
|
||||||
biomesData.icons.push([]);
|
biomesData.icons.push([]);
|
||||||
biomesData.cost.push(50);
|
biomesData.cost.push(50);
|
||||||
}
|
}
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function replaceSVG() {
|
void (function replaceSVG() {
|
||||||
svg.remove();
|
svg.remove();
|
||||||
document.body.insertAdjacentHTML("afterbegin", data[5]);
|
document.body.insertAdjacentHTML("afterbegin", data[5]);
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function redefineElements() {
|
void (function redefineElements() {
|
||||||
svg = d3.select("#map");
|
svg = d3.select("#map");
|
||||||
defs = svg.select("#deftemp");
|
defs = svg.select("#deftemp");
|
||||||
viewbox = svg.select("#viewbox");
|
viewbox = svg.select("#viewbox");
|
||||||
|
|
@ -750,9 +777,9 @@ function parseLoadedData(data) {
|
||||||
fogging = viewbox.select("#fogging");
|
fogging = viewbox.select("#fogging");
|
||||||
debug = viewbox.select("#debug");
|
debug = viewbox.select("#debug");
|
||||||
burgLabels = labels.select("#burgLabels");
|
burgLabels = labels.select("#burgLabels");
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function parseGridData() {
|
void (function parseGridData() {
|
||||||
grid = JSON.parse(data[6]);
|
grid = JSON.parse(data[6]);
|
||||||
calculateVoronoi(grid, grid.points);
|
calculateVoronoi(grid, grid.points);
|
||||||
grid.cells.h = Uint8Array.from(data[7].split(","));
|
grid.cells.h = Uint8Array.from(data[7].split(","));
|
||||||
|
|
@ -760,9 +787,9 @@ function parseLoadedData(data) {
|
||||||
grid.cells.f = Uint16Array.from(data[9].split(","));
|
grid.cells.f = Uint16Array.from(data[9].split(","));
|
||||||
grid.cells.t = Int8Array.from(data[10].split(","));
|
grid.cells.t = Int8Array.from(data[10].split(","));
|
||||||
grid.cells.temp = Int8Array.from(data[11].split(","));
|
grid.cells.temp = Int8Array.from(data[11].split(","));
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function parsePackData() {
|
void (function parsePackData() {
|
||||||
pack = {};
|
pack = {};
|
||||||
reGraph();
|
reGraph();
|
||||||
reMarkFeatures();
|
reMarkFeatures();
|
||||||
|
|
@ -795,19 +822,22 @@ function parseLoadedData(data) {
|
||||||
const e = d.split("|");
|
const e = d.split("|");
|
||||||
if (!e.length) return;
|
if (!e.length) return;
|
||||||
const b = e[5].split(",").length > 2 || !nameBases[i] ? e[5] : nameBases[i].b;
|
const b = e[5].split(",").length > 2 || !nameBases[i] ? e[5] : nameBases[i].b;
|
||||||
nameBases[i] = {name:e[0], min:e[1], max:e[2], d:e[3], m:e[4], b};
|
nameBases[i] = {name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}()
|
})();
|
||||||
|
|
||||||
const notHidden = selection => selection.node() && selection.style("display") !== "none";
|
const notHidden = selection => selection.node() && selection.style("display") !== "none";
|
||||||
const hasChildren = selection => selection.node()?.hasChildNodes();
|
const hasChildren = selection => selection.node()?.hasChildNodes();
|
||||||
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
|
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
|
||||||
const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
|
const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
|
||||||
|
|
||||||
void function restoreLayersState() {
|
void (function restoreLayersState() {
|
||||||
// turn all layers off
|
// turn all layers off
|
||||||
document.getElementById("mapLayers").querySelectorAll("li").forEach(el => el.classList.add("buttonoff"));
|
document
|
||||||
|
.getElementById("mapLayers")
|
||||||
|
.querySelectorAll("li")
|
||||||
|
.forEach(el => el.classList.add("buttonoff"));
|
||||||
|
|
||||||
// turn on active layers
|
// turn on active layers
|
||||||
if (notHidden(texture) && hasChild(texture, "image")) turnOn("toggleTexture");
|
if (notHidden(texture) && hasChild(texture, "image")) turnOn("toggleTexture");
|
||||||
|
|
@ -839,14 +869,14 @@ function parseLoadedData(data) {
|
||||||
if (notHidden(scaleBar)) turnOn("toggleScaleBar");
|
if (notHidden(scaleBar)) turnOn("toggleScaleBar");
|
||||||
|
|
||||||
getCurrentPreset();
|
getCurrentPreset();
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function restoreEvents() {
|
void (function restoreEvents() {
|
||||||
scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
|
scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
|
||||||
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
|
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
|
||||||
}()
|
})();
|
||||||
|
|
||||||
void function resolveVersionConflicts() {
|
void (function resolveVersionConflicts() {
|
||||||
const version = parseFloat(data[0].split("|")[0]);
|
const version = parseFloat(data[0].split("|")[0]);
|
||||||
if (version < 0.9) {
|
if (version < 0.9) {
|
||||||
// 0.9 has additional relief icons to be included into older maps
|
// 0.9 has additional relief icons to be included into older maps
|
||||||
|
|
@ -860,19 +890,17 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
// 1.0 adds a legend box
|
// 1.0 adds a legend box
|
||||||
legend = svg.append("g").attr("id", "legend");
|
legend = svg.append("g").attr("id", "legend");
|
||||||
legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC")
|
legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93).attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round");
|
||||||
.attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93)
|
|
||||||
.attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round");
|
|
||||||
|
|
||||||
// 1.0 separated drawBorders fron drawStates()
|
// 1.0 separated drawBorders fron drawStates()
|
||||||
stateBorders = borders.append("g").attr("id", "stateBorders");
|
stateBorders = borders.append("g").attr("id", "stateBorders");
|
||||||
provinceBorders = borders.append("g").attr("id", "provinceBorders");
|
provinceBorders = borders.append("g").attr("id", "provinceBorders");
|
||||||
borders.attr("opacity", null).attr("stroke", null).attr("stroke-width", null).attr("stroke-dasharray", null).attr("stroke-linecap", null).attr("filter", null);
|
borders.attr("opacity", null).attr("stroke", null).attr("stroke-width", null).attr("stroke-dasharray", null).attr("stroke-linecap", null).attr("filter", null);
|
||||||
stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
|
stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
|
||||||
provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
|
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
|
||||||
|
|
||||||
// 1.0 adds state relations, provinces, forms and full names
|
// 1.0 adds state relations, provinces, forms and full names
|
||||||
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6);
|
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
|
||||||
BurgsAndStates.collectStatistics();
|
BurgsAndStates.collectStatistics();
|
||||||
BurgsAndStates.generateCampaigns();
|
BurgsAndStates.generateCampaigns();
|
||||||
BurgsAndStates.generateDiplomacy();
|
BurgsAndStates.generateDiplomacy();
|
||||||
|
|
@ -880,7 +908,7 @@ function parseLoadedData(data) {
|
||||||
drawStates();
|
drawStates();
|
||||||
BurgsAndStates.generateProvinces();
|
BurgsAndStates.generateProvinces();
|
||||||
drawBorders();
|
drawBorders();
|
||||||
if (!layerIsOn("toggleBorders")) $('#borders').fadeOut();
|
if (!layerIsOn("toggleBorders")) $("#borders").fadeOut();
|
||||||
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
||||||
|
|
||||||
// 1.0 adds hatching
|
// 1.0 adds hatching
|
||||||
|
|
@ -888,9 +916,12 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
// 1.0 adds zones layer
|
// 1.0 adds zones layer
|
||||||
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
|
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
|
||||||
zones.attr("opacity", .6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
|
zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
|
||||||
addZones();
|
addZones();
|
||||||
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
|
if (!markers.selectAll("*").size()) {
|
||||||
|
addMarkers();
|
||||||
|
turnButtonOn("toggleMarkers");
|
||||||
|
}
|
||||||
|
|
||||||
// 1.0 add fogging layer (state focus)
|
// 1.0 add fogging layer (state focus)
|
||||||
fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
|
fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
|
||||||
|
|
@ -904,7 +935,7 @@ function parseLoadedData(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.0 changed labels to multi-lined
|
// 1.0 changed labels to multi-lined
|
||||||
labels.selectAll("textPath").each(function() {
|
labels.selectAll("textPath").each(function () {
|
||||||
const text = this.textContent;
|
const text = this.textContent;
|
||||||
const shift = this.getComputedTextLength() / -1.5;
|
const shift = this.getComputedTextLength() / -1.5;
|
||||||
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
|
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
|
||||||
|
|
@ -923,7 +954,7 @@ function parseLoadedData(data) {
|
||||||
// v 1.0 initially has Sympathy status then relaced with Friendly
|
// v 1.0 initially has Sympathy status then relaced with Friendly
|
||||||
for (const s of pack.states) {
|
for (const s of pack.states) {
|
||||||
if (!s.diplomacy) continue;
|
if (!s.diplomacy) continue;
|
||||||
s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r);
|
s.diplomacy = s.diplomacy.map(r => (r === "Sympathy" ? "Friendly" : r));
|
||||||
}
|
}
|
||||||
|
|
||||||
// labels should be toggled via style attribute, so remove display attribute
|
// labels should be toggled via style attribute, so remove display attribute
|
||||||
|
|
@ -931,7 +962,9 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
// v 1.0 added religions heirarchy tree
|
// v 1.0 added religions heirarchy tree
|
||||||
if (pack.religions[1] && !pack.religions[1].code) {
|
if (pack.religions[1] && !pack.religions[1].code) {
|
||||||
pack.religions.filter(r => r.i).forEach(r => {
|
pack.religions
|
||||||
|
.filter(r => r.i)
|
||||||
|
.forEach(r => {
|
||||||
r.origin = 0;
|
r.origin = 0;
|
||||||
r.code = r.name.slice(0, 2);
|
r.code = r.name.slice(0, 2);
|
||||||
});
|
});
|
||||||
|
|
@ -939,12 +972,12 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
if (!document.getElementById("freshwater")) {
|
if (!document.getElementById("freshwater")) {
|
||||||
lakes.append("g").attr("id", "freshwater");
|
lakes.append("g").attr("id", "freshwater");
|
||||||
lakes.select("#freshwater").attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", .7).attr("filter", null);
|
lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!document.getElementById("salt")) {
|
if (!document.getElementById("salt")) {
|
||||||
lakes.append("g").attr("id", "salt");
|
lakes.append("g").attr("id", "salt");
|
||||||
lakes.select("#salt").attr("opacity", .5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", .7).attr("filter", null);
|
lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// v 1.1 added new lake and coast groups
|
// v 1.1 added new lake and coast groups
|
||||||
|
|
@ -952,14 +985,14 @@ function parseLoadedData(data) {
|
||||||
lakes.append("g").attr("id", "sinkhole");
|
lakes.append("g").attr("id", "sinkhole");
|
||||||
lakes.append("g").attr("id", "frozen");
|
lakes.append("g").attr("id", "frozen");
|
||||||
lakes.append("g").attr("id", "lava");
|
lakes.append("g").attr("id", "lava");
|
||||||
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", .7).attr("filter", null);
|
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
lakes.select("#frozen").attr("opacity", .95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
|
lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
|
||||||
lakes.select("#lava").attr("opacity", .7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
|
lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
|
||||||
|
|
||||||
coastline.append("g").attr("id", "sea_island");
|
coastline.append("g").attr("id", "sea_island");
|
||||||
coastline.append("g").attr("id", "lake_island");
|
coastline.append("g").attr("id", "lake_island");
|
||||||
coastline.select("#sea_island").attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("filter", "url(#dropShadow)");
|
coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("filter", "url(#dropShadow)");
|
||||||
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", .35).attr("filter", null);
|
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// v 1.1 features stores more data
|
// v 1.1 features stores more data
|
||||||
|
|
@ -979,7 +1012,9 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
// v 1.11 added cultures heirarchy tree
|
// v 1.11 added cultures heirarchy tree
|
||||||
if (pack.cultures[1] && !pack.cultures[1].code) {
|
if (pack.cultures[1] && !pack.cultures[1].code) {
|
||||||
pack.cultures.filter(c => c.i).forEach(c => {
|
pack.cultures
|
||||||
|
.filter(c => c.i)
|
||||||
|
.forEach(c => {
|
||||||
c.origin = 0;
|
c.origin = 0;
|
||||||
c.code = c.name.slice(0, 2);
|
c.code = c.name.slice(0, 2);
|
||||||
});
|
});
|
||||||
|
|
@ -991,12 +1026,12 @@ function parseLoadedData(data) {
|
||||||
// v 1.2 added new terrain attributes
|
// v 1.2 added new terrain attributes
|
||||||
if (!terrain.attr("set")) terrain.attr("set", "simple");
|
if (!terrain.attr("set")) terrain.attr("set", "simple");
|
||||||
if (!terrain.attr("size")) terrain.attr("size", 1);
|
if (!terrain.attr("size")) terrain.attr("size", 1);
|
||||||
if (!terrain.attr("density")) terrain.attr("density", .4);
|
if (!terrain.attr("density")) terrain.attr("density", 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 1.21) {
|
if (version < 1.21) {
|
||||||
// v 1.11 replaced "display" attribute by "display" style
|
// v 1.11 replaced "display" attribute by "display" style
|
||||||
viewbox.selectAll("g").each(function() {
|
viewbox.selectAll("g").each(function () {
|
||||||
if (this.hasAttribute("display")) {
|
if (this.hasAttribute("display")) {
|
||||||
this.removeAttribute("display");
|
this.removeAttribute("display");
|
||||||
this.style.display = "none";
|
this.style.display = "none";
|
||||||
|
|
@ -1005,16 +1040,17 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
// v 1.21 added rivers data to pack
|
// v 1.21 added rivers data to pack
|
||||||
pack.rivers = []; // rivers data
|
pack.rivers = []; // rivers data
|
||||||
rivers.selectAll("path").each(function() {
|
rivers.selectAll("path").each(function () {
|
||||||
const i = +this.id.slice(5);
|
const i = +this.id.slice(5);
|
||||||
const length = this.getTotalLength() / 2;
|
const length = this.getTotalLength() / 2;
|
||||||
const s = this.getPointAtLength(length), e = this.getPointAtLength(0);
|
const s = this.getPointAtLength(length),
|
||||||
const source = findCell(s.x, s.y), mouth = findCell(e.x, e.y);
|
e = this.getPointAtLength(0);
|
||||||
|
const source = findCell(s.x, s.y),
|
||||||
|
mouth = findCell(e.x, e.y);
|
||||||
const name = Rivers.getName(mouth);
|
const name = Rivers.getName(mouth);
|
||||||
const type = length < 25 ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
|
const type = length < 25 ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River";
|
||||||
pack.rivers.push({i, parent:0, length, source, mouth, basin:i, name, type});
|
pack.rivers.push({i, parent: 0, length, source, mouth, basin: i, name, type});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 1.22) {
|
if (version < 1.22) {
|
||||||
|
|
@ -1026,7 +1062,7 @@ function parseLoadedData(data) {
|
||||||
// v 1.3 added global options object
|
// v 1.3 added global options object
|
||||||
const winds = options.slice(); // previostly wind was saved in settings[19]
|
const winds = options.slice(); // previostly wind was saved in settings[19]
|
||||||
const year = rand(100, 2000);
|
const year = rand(100, 2000);
|
||||||
const era = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era";
|
const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era";
|
||||||
const eraShort = era[0] + "E";
|
const eraShort = era[0] + "E";
|
||||||
const military = Military.getDefaultOptions();
|
const military = Military.getDefaultOptions();
|
||||||
options = {winds, year, era, eraShort, military};
|
options = {winds, year, era, eraShort, military};
|
||||||
|
|
@ -1036,7 +1072,7 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
// v 1.3 added militry layer
|
// v 1.3 added militry layer
|
||||||
armies = viewbox.insert("g", "#icons").attr("id", "armies");
|
armies = viewbox.insert("g", "#icons").attr("id", "armies");
|
||||||
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3);
|
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3);
|
||||||
turnButtonOn("toggleMilitary");
|
turnButtonOn("toggleMilitary");
|
||||||
Military.generate();
|
Military.generate();
|
||||||
}
|
}
|
||||||
|
|
@ -1045,7 +1081,7 @@ function parseLoadedData(data) {
|
||||||
// v 1.35 added dry lakes
|
// v 1.35 added dry lakes
|
||||||
if (!lakes.select("#dry").size()) {
|
if (!lakes.select("#dry").size()) {
|
||||||
lakes.append("g").attr("id", "dry");
|
lakes.append("g").attr("id", "dry");
|
||||||
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", .7).attr("filter", null);
|
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// v 1.4 added ice layer
|
// v 1.4 added ice layer
|
||||||
|
|
@ -1071,7 +1107,7 @@ function parseLoadedData(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.4 added state reference for regiments
|
// 1.4 added state reference for regiments
|
||||||
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => r.state = s.i));
|
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 1.5) {
|
if (version < 1.5) {
|
||||||
|
|
@ -1103,7 +1139,7 @@ function parseLoadedData(data) {
|
||||||
toggleEmblems();
|
toggleEmblems();
|
||||||
|
|
||||||
// v 1.5 changed releif icons data
|
// v 1.5 changed releif icons data
|
||||||
terrain.selectAll("use").each(function() {
|
terrain.selectAll("use").each(function () {
|
||||||
const type = this.getAttribute("data-type") || this.getAttribute("xlink:href");
|
const type = this.getAttribute("data-type") || this.getAttribute("xlink:href");
|
||||||
this.removeAttribute("xlink:href");
|
this.removeAttribute("xlink:href");
|
||||||
this.removeAttribute("data-type");
|
this.removeAttribute("data-type");
|
||||||
|
|
@ -1115,14 +1151,14 @@ function parseLoadedData(data) {
|
||||||
if (version < 1.6) {
|
if (version < 1.6) {
|
||||||
// v 1.6 changed rivers data
|
// v 1.6 changed rivers data
|
||||||
for (const river of pack.rivers) {
|
for (const river of pack.rivers) {
|
||||||
const el = document.getElementById("river"+river.i);
|
const el = document.getElementById("river" + river.i);
|
||||||
if (el) {
|
if (el) {
|
||||||
river.widthFactor = +el.getAttribute("data-width");
|
river.widthFactor = +el.getAttribute("data-width");
|
||||||
el.removeAttribute("data-width");
|
el.removeAttribute("data-width");
|
||||||
el.removeAttribute("data-increment");
|
el.removeAttribute("data-increment");
|
||||||
river.discharge = pack.cells.fl[river.mouth] || 1;
|
river.discharge = pack.cells.fl[river.mouth] || 1;
|
||||||
river.width = rn(river.length / 100, 2);
|
river.width = rn(river.length / 100, 2);
|
||||||
river.sourceWidth = .1;
|
river.sourceWidth = 0.1;
|
||||||
} else {
|
} else {
|
||||||
Rivers.remove(river.i);
|
Rivers.remove(river.i);
|
||||||
}
|
}
|
||||||
|
|
@ -1137,7 +1173,7 @@ function parseLoadedData(data) {
|
||||||
f.temp = grid.cells.temp[pack.cells.g[f.firstCell]];
|
f.temp = grid.cells.temp[pack.cells.g[f.firstCell]];
|
||||||
f.height = f.height || d3.min(pack.cells.c[f.firstCell].map(c => pack.cells.h[c]).filter(h => h >= 20));
|
f.height = f.height || d3.min(pack.cells.c[f.firstCell].map(c => pack.cells.h[c]).filter(h => h >= 20));
|
||||||
const height = (f.height - 18) ** heightExponentInput.value;
|
const height = (f.height - 18) ** heightExponentInput.value;
|
||||||
const evaporation = (700 * (f.temp + .006 * height) / 50 + 75) / (80 - f.temp);
|
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp);
|
||||||
f.evaporation = rn(evaporation * f.cells);
|
f.evaporation = rn(evaporation * f.cells);
|
||||||
f.name = f.name || Lakes.getName(f);
|
f.name = f.name || Lakes.getName(f);
|
||||||
delete f.river;
|
delete f.river;
|
||||||
|
|
@ -1149,31 +1185,34 @@ function parseLoadedData(data) {
|
||||||
ruler.style("display", null);
|
ruler.style("display", null);
|
||||||
rulers = new Rulers();
|
rulers = new Rulers();
|
||||||
|
|
||||||
ruler.selectAll(".ruler > .white").each(function() {
|
ruler.selectAll(".ruler > .white").each(function () {
|
||||||
const x1 = +this.getAttribute("x1");
|
const x1 = +this.getAttribute("x1");
|
||||||
const y1 = +this.getAttribute("y1");
|
const y1 = +this.getAttribute("y1");
|
||||||
const x2 = +this.getAttribute("x2");
|
const x2 = +this.getAttribute("x2");
|
||||||
const y2 = +this.getAttribute("y2");
|
const y2 = +this.getAttribute("y2");
|
||||||
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) return;
|
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) return;
|
||||||
const points = [[x1, y1], [x2, y2]];
|
const points = [
|
||||||
|
[x1, y1],
|
||||||
|
[x2, y2]
|
||||||
|
];
|
||||||
rulers.create(Ruler, points);
|
rulers.create(Ruler, points);
|
||||||
});
|
});
|
||||||
|
|
||||||
ruler.selectAll("g.opisometer").each(function() {
|
ruler.selectAll("g.opisometer").each(function () {
|
||||||
const pointsString = this.dataset.points;
|
const pointsString = this.dataset.points;
|
||||||
if (!pointsString) return;
|
if (!pointsString) return;
|
||||||
const points = JSON.parse(pointsString);
|
const points = JSON.parse(pointsString);
|
||||||
rulers.create(Opisometer, points);
|
rulers.create(Opisometer, points);
|
||||||
});
|
});
|
||||||
|
|
||||||
ruler.selectAll("path.planimeter").each(function() {
|
ruler.selectAll("path.planimeter").each(function () {
|
||||||
const length = this.getTotalLength();
|
const length = this.getTotalLength();
|
||||||
if (length < 30) return;
|
if (length < 30) return;
|
||||||
|
|
||||||
const step = length > 1000 ? 40 : length > 400 ? 20 : 10;
|
const step = length > 1000 ? 40 : length > 400 ? 20 : 10;
|
||||||
const increment = length / Math.ceil(length / step);
|
const increment = length / Math.ceil(length / step);
|
||||||
const points = [];
|
const points = [];
|
||||||
for (let i=0; i <= length; i += increment) {
|
for (let i = 0; i <= length; i += increment) {
|
||||||
const point = this.getPointAtLength(i);
|
const point = this.getPointAtLength(i);
|
||||||
points.push([point.x | 0, point.y | 0]);
|
points.push([point.x | 0, point.y | 0]);
|
||||||
}
|
}
|
||||||
|
|
@ -1193,16 +1232,16 @@ function parseLoadedData(data) {
|
||||||
const filter = pattern.firstElementChild.getAttribute("filter");
|
const filter = pattern.firstElementChild.getAttribute("filter");
|
||||||
const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : "";
|
const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : "";
|
||||||
pattern.innerHTML = `<image id="oceanicPattern" href=${href} width="100" height="100"></image>`;
|
pattern.innerHTML = `<image id="oceanicPattern" href=${href} width="100" height="100"></image>`;
|
||||||
document.getElementById("oceanPattern").setAttribute("opacity", .2);
|
document.getElementById("oceanPattern").setAttribute("opacity", 0.2);
|
||||||
}
|
}
|
||||||
}()
|
})();
|
||||||
|
|
||||||
if (version < 1.62) {
|
if (version < 1.62) {
|
||||||
// v 1.62 changed grid data
|
// v 1.62 changed grid data
|
||||||
gridOverlay.attr("size", null);
|
gridOverlay.attr("size", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void function checkDataIntegrity() {
|
void (function checkDataIntegrity() {
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
|
|
||||||
if (pack.cells.i.length !== pack.cells.state.length) {
|
if (pack.cells.i.length !== pack.cells.state.length) {
|
||||||
|
|
@ -1212,28 +1251,28 @@ function parseLoadedData(data) {
|
||||||
const invalidStates = [...new Set(cells.state)].filter(s => !pack.states[s] || pack.states[s].removed);
|
const invalidStates = [...new Set(cells.state)].filter(s => !pack.states[s] || pack.states[s].removed);
|
||||||
invalidStates.forEach(s => {
|
invalidStates.forEach(s => {
|
||||||
const invalidCells = cells.i.filter(i => cells.state[i] === s);
|
const invalidCells = cells.i.filter(i => cells.state[i] === s);
|
||||||
invalidCells.forEach(i => cells.state[i] = 0);
|
invalidCells.forEach(i => (cells.state[i] = 0));
|
||||||
ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
|
ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
|
||||||
});
|
});
|
||||||
|
|
||||||
const invalidProvinces = [...new Set(cells.province)].filter(p => p && (!pack.provinces[p] || pack.provinces[p].removed));
|
const invalidProvinces = [...new Set(cells.province)].filter(p => p && (!pack.provinces[p] || pack.provinces[p].removed));
|
||||||
invalidProvinces.forEach(p => {
|
invalidProvinces.forEach(p => {
|
||||||
const invalidCells = cells.i.filter(i => cells.province[i] === p);
|
const invalidCells = cells.i.filter(i => cells.province[i] === p);
|
||||||
invalidCells.forEach(i => cells.province[i] = 0);
|
invalidCells.forEach(i => (cells.province[i] = 0));
|
||||||
ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
|
ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
|
||||||
});
|
});
|
||||||
|
|
||||||
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
|
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
|
||||||
invalidCultures.forEach(c => {
|
invalidCultures.forEach(c => {
|
||||||
const invalidCells = cells.i.filter(i => cells.culture[i] === c);
|
const invalidCells = cells.i.filter(i => cells.culture[i] === c);
|
||||||
invalidCells.forEach(i => cells.province[i] = 0);
|
invalidCells.forEach(i => (cells.province[i] = 0));
|
||||||
ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
|
ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
|
||||||
});
|
});
|
||||||
|
|
||||||
const invalidReligions = [...new Set(cells.religion)].filter(r => !pack.religions[r] || pack.religions[r].removed);
|
const invalidReligions = [...new Set(cells.religion)].filter(r => !pack.religions[r] || pack.religions[r].removed);
|
||||||
invalidReligions.forEach(r => {
|
invalidReligions.forEach(r => {
|
||||||
const invalidCells = cells.i.filter(i => cells.religion[i] === r);
|
const invalidCells = cells.i.filter(i => cells.religion[i] === r);
|
||||||
invalidCells.forEach(i => cells.religion[i] = 0);
|
invalidCells.forEach(i => (cells.religion[i] = 0));
|
||||||
ERROR && console.error("Data Integrity Check. Invalid religion", c, "is assigned to cells", invalidCells);
|
ERROR && console.error("Data Integrity Check. Invalid religion", c, "is assigned to cells", invalidCells);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1247,26 +1286,29 @@ function parseLoadedData(data) {
|
||||||
const invalidBurgs = [...new Set(cells.burg)].filter(b => b && (!pack.burgs[b] || pack.burgs[b].removed));
|
const invalidBurgs = [...new Set(cells.burg)].filter(b => b && (!pack.burgs[b] || pack.burgs[b].removed));
|
||||||
invalidBurgs.forEach(b => {
|
invalidBurgs.forEach(b => {
|
||||||
const invalidCells = cells.i.filter(i => cells.burg[i] === b);
|
const invalidCells = cells.i.filter(i => cells.burg[i] === b);
|
||||||
invalidCells.forEach(i => cells.burg[i] = 0);
|
invalidCells.forEach(i => (cells.burg[i] = 0));
|
||||||
ERROR && console.error("Data Integrity Check. Invalid burg", b, "is assigned to cells", invalidCells);
|
ERROR && console.error("Data Integrity Check. Invalid burg", b, "is assigned to cells", invalidCells);
|
||||||
});
|
});
|
||||||
|
|
||||||
const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r));
|
const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r));
|
||||||
invalidRivers.forEach(r => {
|
invalidRivers.forEach(r => {
|
||||||
const invalidCells = cells.i.filter(i => cells.r[i] === r);
|
const invalidCells = cells.i.filter(i => cells.r[i] === r);
|
||||||
invalidCells.forEach(i => cells.r[i] = 0);
|
invalidCells.forEach(i => (cells.r[i] = 0));
|
||||||
rivers.select("river"+r).remove();
|
rivers.select("river" + r).remove();
|
||||||
ERROR && console.error("Data Integrity Check. Invalid river", r, "is assigned to cells", invalidCells);
|
ERROR && console.error("Data Integrity Check. Invalid river", r, "is assigned to cells", invalidCells);
|
||||||
});
|
});
|
||||||
|
|
||||||
pack.burgs.forEach(b => {
|
pack.burgs.forEach(b => {
|
||||||
if (!b.i || b.removed) return;
|
if (!b.i || b.removed) return;
|
||||||
if (b.port < 0) {ERROR && console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port); b.port = 0;}
|
if (b.port < 0) {
|
||||||
|
ERROR && console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port);
|
||||||
|
b.port = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (b.cell >= cells.i.length) {
|
if (b.cell >= cells.i.length) {
|
||||||
ERROR && console.error("Data Integrity Check. Burg", b.i, "is linked to invalid cell", b.cell);
|
ERROR && console.error("Data Integrity Check. Burg", b.i, "is linked to invalid cell", b.cell);
|
||||||
b.cell = findCell(b.x, b.y);
|
b.cell = findCell(b.x, b.y);
|
||||||
cells.i.filter(i => cells.burg[i] === b.i).forEach(i => cells.burg[i] = 0);
|
cells.i.filter(i => cells.burg[i] === b.i).forEach(i => (cells.burg[i] = 0));
|
||||||
cells.burg[b.cell] = b.i;
|
cells.burg[b.cell] = b.i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1282,7 +1324,7 @@ function parseLoadedData(data) {
|
||||||
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
|
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
|
||||||
p.removed = true; // remove incorrect province
|
p.removed = true; // remove incorrect province
|
||||||
});
|
});
|
||||||
}()
|
})();
|
||||||
|
|
||||||
changeMapSize();
|
changeMapSize();
|
||||||
|
|
||||||
|
|
@ -1301,12 +1343,11 @@ function parseLoadedData(data) {
|
||||||
focusOn(); // based on searchParams focus on point, cell or burg
|
focusOn(); // based on searchParams focus on point, cell or burg
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
|
|
||||||
WARN && console.warn(`TOTAL: ${rn((performance.now()-uploadMap.timeStart)/1000,2)}s`);
|
WARN && console.warn(`TOTAL: ${rn((performance.now() - uploadMap.timeStart) / 1000, 2)}s`);
|
||||||
showStatistics();
|
showStatistics();
|
||||||
INFO && console.groupEnd("Loaded Map " + seed);
|
INFO && console.groupEnd("Loaded Map " + seed);
|
||||||
tip("Map is successfully loaded", true, "success", 7000);
|
tip("Map is successfully loaded", true, "success", 7000);
|
||||||
}
|
} catch (error) {
|
||||||
catch(error) {
|
|
||||||
ERROR && console.error(error);
|
ERROR && console.error(error);
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
|
|
||||||
|
|
@ -1314,12 +1355,23 @@ function parseLoadedData(data) {
|
||||||
<br>generate a new random map or cancel the loading
|
<br>generate a new random map or cancel the loading
|
||||||
<p id="errorBox">${parseError(error)}</p>`;
|
<p id="errorBox">${parseError(error)}</p>`;
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
resizable: false, title: "Loading error", maxWidth:"50em", buttons: {
|
resizable: false,
|
||||||
"Select file": function() {$(this).dialog("close"); mapToLoad.click();},
|
title: "Loading error",
|
||||||
"New map": function() {$(this).dialog("close"); regenerateMap();},
|
maxWidth: "50em",
|
||||||
Cancel: function() {$(this).dialog("close")}
|
buttons: {
|
||||||
}, position: {my: "center", at: "center", of: "svg"}
|
"Select file": function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
mapToLoad.click();
|
||||||
|
},
|
||||||
|
"New map": function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
regenerateMap();
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
position: {my: "center", at: "center", of: "svg"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function editHeightmap() {
|
function editHeightmap() {
|
||||||
void function selectEditMode() {
|
void (function selectEditMode() {
|
||||||
alertMessage.innerHTML = `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
|
alertMessage.innerHTML = `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
|
||||||
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.
|
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.
|
||||||
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
||||||
|
|
@ -11,15 +11,26 @@ function editHeightmap() {
|
||||||
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
||||||
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
||||||
|
|
||||||
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Edit Heightmap",
|
||||||
|
width: "28em",
|
||||||
buttons: {
|
buttons: {
|
||||||
Erase: function() {enterHeightmapEditMode("erase");},
|
Erase: function () {
|
||||||
Keep: function() {enterHeightmapEditMode("keep");},
|
enterHeightmapEditMode("erase");
|
||||||
Risk: function() {enterHeightmapEditMode("risk");},
|
},
|
||||||
Cancel: function() {$(this).dialog("close");}
|
Keep: function () {
|
||||||
|
enterHeightmapEditMode("keep");
|
||||||
|
},
|
||||||
|
Risk: function () {
|
||||||
|
enterHeightmapEditMode("risk");
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}()
|
})();
|
||||||
|
|
||||||
restartHistory();
|
restartHistory();
|
||||||
viewbox.insert("g", "#terrs").attr("id", "heights");
|
viewbox.insert("g", "#terrs").attr("id", "heights");
|
||||||
|
|
@ -35,8 +46,8 @@ function editHeightmap() {
|
||||||
document.getElementById("heightmap3DView").addEventListener("click", changeViewMode);
|
document.getElementById("heightmap3DView").addEventListener("click", changeViewMode);
|
||||||
document.getElementById("finalizeHeightmap").addEventListener("click", finalizeHeightmap);
|
document.getElementById("finalizeHeightmap").addEventListener("click", finalizeHeightmap);
|
||||||
document.getElementById("renderOcean").addEventListener("click", mockHeightmap);
|
document.getElementById("renderOcean").addEventListener("click", mockHeightmap);
|
||||||
document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n-1));
|
document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n - 1));
|
||||||
document.getElementById("templateRedo").addEventListener("click", () => restoreHistory(edits.n+1));
|
document.getElementById("templateRedo").addEventListener("click", () => restoreHistory(edits.n + 1));
|
||||||
|
|
||||||
function enterHeightmapEditMode(type) {
|
function enterHeightmapEditMode(type) {
|
||||||
editHeightmap.layers = Array.from(mapLayers.querySelectorAll("li:not(.buttonoff)")).map(node => node.id); // store layers preset
|
editHeightmap.layers = Array.from(mapLayers.querySelectorAll("li:not(.buttonoff)")).map(node => node.id); // store layers preset
|
||||||
|
|
@ -77,9 +88,7 @@ function editHeightmap() {
|
||||||
exitCustomization.style.bottom = svgHeight / 2 + "px";
|
exitCustomization.style.bottom = svgHeight / 2 + "px";
|
||||||
exitCustomization.style.transform = "scale(2)";
|
exitCustomization.style.transform = "scale(2)";
|
||||||
exitCustomization.style.display = "block";
|
exitCustomization.style.display = "block";
|
||||||
d3.select("#exitCustomization")
|
d3.select("#exitCustomization").transition().duration(1000).style("opacity", 1).transition().duration(2000).ease(d3.easeSinInOut).style("right", "10px").style("bottom", "10px").style("transform", "scale(1)");
|
||||||
.transition().duration(1000).style("opacity", 1)
|
|
||||||
.transition().duration(2000).ease(d3.easeSinInOut).style("right", "10px").style("bottom", "10px").style("transform", "scale(1)");
|
|
||||||
} else exitCustomization.style.display = "block";
|
} else exitCustomization.style.display = "block";
|
||||||
|
|
||||||
openBrushesPanel();
|
openBrushesPanel();
|
||||||
|
|
@ -91,7 +100,8 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveCursor() {
|
function moveCursor() {
|
||||||
const p = d3.mouse(this), cell = findGridCell(p[0], p[1]);
|
const p = d3.mouse(this),
|
||||||
|
cell = findGridCell(p[0], p[1]);
|
||||||
heightmapInfoX.innerHTML = rn(p[0]);
|
heightmapInfoX.innerHTML = rn(p[0]);
|
||||||
heightmapInfoY.innerHTML = rn(p[1]);
|
heightmapInfoY.innerHTML = rn(p[1]);
|
||||||
heightmapInfoCell.innerHTML = cell;
|
heightmapInfoCell.innerHTML = cell;
|
||||||
|
|
@ -108,12 +118,13 @@ function editHeightmap() {
|
||||||
function getHeight(h) {
|
function getHeight(h) {
|
||||||
const unit = heightUnit.value;
|
const unit = heightUnit.value;
|
||||||
let unitRatio = 3.281; // default calculations are in feet
|
let unitRatio = 3.281; // default calculations are in feet
|
||||||
if (unit === "m") unitRatio = 1; // if meter
|
if (unit === "m") unitRatio = 1;
|
||||||
|
// if meter
|
||||||
else if (unit === "f") unitRatio = 0.5468; // if fathom
|
else if (unit === "f") unitRatio = 0.5468; // if fathom
|
||||||
|
|
||||||
let height = -990;
|
let height = -990;
|
||||||
if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value);
|
if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value);
|
||||||
else if (h < 20 && h > 0) height = (h - 20) / h * 50;
|
else if (h < 20 && h > 0) height = ((h - 20) / h) * 50;
|
||||||
|
|
||||||
return rn(height * unitRatio) + " " + unit;
|
return rn(height * unitRatio) + " " + unit;
|
||||||
}
|
}
|
||||||
|
|
@ -156,8 +167,12 @@ function editHeightmap() {
|
||||||
//viewbox.select("#heights").remove();
|
//viewbox.select("#heights").remove();
|
||||||
document.getElementById("heights").remove();
|
document.getElementById("heights").remove();
|
||||||
turnButtonOff("toggleHeight");
|
turnButtonOff("toggleHeight");
|
||||||
document.getElementById("mapLayers").querySelectorAll("li").forEach(function(e) {
|
document
|
||||||
if (editHeightmap.layers.includes(e.id) && !layerIsOn(e.id)) e.click(); // turn on
|
.getElementById("mapLayers")
|
||||||
|
.querySelectorAll("li")
|
||||||
|
.forEach(function (e) {
|
||||||
|
if (editHeightmap.layers.includes(e.id) && !layerIsOn(e.id)) e.click();
|
||||||
|
// turn on
|
||||||
else if (!editHeightmap.layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off
|
else if (!editHeightmap.layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off
|
||||||
});
|
});
|
||||||
getCurrentPreset();
|
getCurrentPreset();
|
||||||
|
|
@ -169,7 +184,7 @@ function editHeightmap() {
|
||||||
|
|
||||||
const change = changeHeights.checked;
|
const change = changeHeights.checked;
|
||||||
markFeatures();
|
markFeatures();
|
||||||
getSignedDistanceField();
|
markupGridOcean();
|
||||||
if (change) openNearSeaLakes();
|
if (change) openNearSeaLakes();
|
||||||
OceanLayers();
|
OceanLayers();
|
||||||
calculateTemperatures();
|
calculateTemperatures();
|
||||||
|
|
@ -275,7 +290,7 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// recalculate zones to grid
|
// recalculate zones to grid
|
||||||
zones.selectAll("g").each(function() {
|
zones.selectAll("g").each(function () {
|
||||||
const zone = d3.select(this);
|
const zone = d3.select(this);
|
||||||
const dataCells = zone.attr("data-cells");
|
const dataCells = zone.attr("data-cells");
|
||||||
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||||
|
|
@ -285,7 +300,7 @@ function editHeightmap() {
|
||||||
});
|
});
|
||||||
|
|
||||||
markFeatures();
|
markFeatures();
|
||||||
getSignedDistanceField();
|
markupGridOcean();
|
||||||
OceanLayers();
|
OceanLayers();
|
||||||
calculateTemperatures();
|
calculateTemperatures();
|
||||||
generatePrecipitation();
|
generatePrecipitation();
|
||||||
|
|
@ -339,14 +354,12 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// find closest land cell to burg
|
// find closest land cell to burg
|
||||||
const findBurgCell = function(x, y) {
|
const findBurgCell = function (x, y) {
|
||||||
let i = findCell(x, y);
|
let i = findCell(x, y);
|
||||||
if (pack.cells.h[i] >= 20) return i;
|
if (pack.cells.h[i] >= 20) return i;
|
||||||
const dist = pack.cells.c[i].map(c =>
|
const dist = pack.cells.c[i].map(c => (pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2));
|
||||||
pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2
|
|
||||||
);
|
|
||||||
return pack.cells.c[i][d3.scan(dist)];
|
return pack.cells.c[i][d3.scan(dist)];
|
||||||
}
|
};
|
||||||
|
|
||||||
// find best cell for burgs
|
// find best cell for burgs
|
||||||
for (const b of pack.burgs) {
|
for (const b of pack.burgs) {
|
||||||
|
|
@ -372,7 +385,10 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.burg && !pack.burgs[p.burg].removed) p.center = pack.burgs[p.burg].cell;
|
if (p.burg && !pack.burgs[p.burg].removed) p.center = pack.burgs[p.burg].cell;
|
||||||
else {p.center = provCells[0]; p.burg = pack.cells.burg[p.center];}
|
else {
|
||||||
|
p.center = provCells[0];
|
||||||
|
p.burg = pack.cells.burg[p.center];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const c of pack.cultures) {
|
for (const c of pack.cultures) {
|
||||||
|
|
@ -390,7 +406,7 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore zones from grid
|
// restore zones from grid
|
||||||
zones.selectAll("g").each(function() {
|
zones.selectAll("g").each(function () {
|
||||||
const zone = d3.select(this);
|
const zone = d3.select(this);
|
||||||
const g = zone.attr("data-cells");
|
const g = zone.attr("data-cells");
|
||||||
const gCells = g ? g.split(",").map(i => +i) : [];
|
const gCells = g ? g.split(",").map(i => +i) : [];
|
||||||
|
|
@ -399,8 +415,13 @@ function editHeightmap() {
|
||||||
zone.attr("data-cells", cells);
|
zone.attr("data-cells", cells);
|
||||||
zone.selectAll("*").remove();
|
zone.selectAll("*").remove();
|
||||||
const base = zone.attr("id") + "_"; // id generic part
|
const base = zone.attr("id") + "_"; // id generic part
|
||||||
zone.selectAll("*").data(cells).enter().append("polygon")
|
zone
|
||||||
.attr("points", d => getPackPolygon(d)).attr("id", d => base + d);
|
.selectAll("*")
|
||||||
|
.data(cells)
|
||||||
|
.enter()
|
||||||
|
.append("polygon")
|
||||||
|
.attr("points", d => getPackPolygon(d))
|
||||||
|
.attr("id", d => base + d);
|
||||||
});
|
});
|
||||||
|
|
||||||
TIME && console.timeEnd("restoreRiskedData");
|
TIME && console.timeEnd("restoreRiskedData");
|
||||||
|
|
@ -410,7 +431,7 @@ function editHeightmap() {
|
||||||
// trigger heightmap redraw and history update if at least 1 cell is changed
|
// trigger heightmap redraw and history update if at least 1 cell is changed
|
||||||
function updateHeightmap() {
|
function updateHeightmap() {
|
||||||
const prev = last(edits);
|
const prev = last(edits);
|
||||||
const changed = grid.cells.h.reduce((s, h, i) => h !== prev[i] ? s+1 : s, 0);
|
const changed = grid.cells.h.reduce((s, h, i) => (h !== prev[i] ? s + 1 : s), 0);
|
||||||
tip("Cells changed: " + changed);
|
tip("Cells changed: " + changed);
|
||||||
if (!changed) return;
|
if (!changed) return;
|
||||||
|
|
||||||
|
|
@ -429,8 +450,13 @@ function editHeightmap() {
|
||||||
function mockHeightmap() {
|
function mockHeightmap() {
|
||||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
||||||
const scheme = getColorScheme();
|
const scheme = getColorScheme();
|
||||||
viewbox.select("#heights").selectAll("polygon").data(data).join("polygon")
|
viewbox
|
||||||
.attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d)
|
.select("#heights")
|
||||||
|
.selectAll("polygon")
|
||||||
|
.data(data)
|
||||||
|
.join("polygon")
|
||||||
|
.attr("points", d => getGridPolygon(d))
|
||||||
|
.attr("id", d => "cell" + d)
|
||||||
.attr("fill", d => getColor(grid.cells.h[d], scheme));
|
.attr("fill", d => getColor(grid.cells.h[d], scheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,17 +465,25 @@ function editHeightmap() {
|
||||||
const ocean = renderOcean.checked;
|
const ocean = renderOcean.checked;
|
||||||
const scheme = getColorScheme();
|
const scheme = getColorScheme();
|
||||||
|
|
||||||
selection.forEach(function(i) {
|
selection.forEach(function (i) {
|
||||||
let cell = viewbox.select("#heights").select("#cell"+i);
|
let cell = viewbox.select("#heights").select("#cell" + i);
|
||||||
if (!ocean && grid.cells.h[i] < 20) {cell.remove(); return;}
|
if (!ocean && grid.cells.h[i] < 20) {
|
||||||
if (!cell.size()) cell = viewbox.select("#heights").append("polygon").attr("points", getGridPolygon(i)).attr("id", "cell"+i);
|
cell.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!cell.size())
|
||||||
|
cell = viewbox
|
||||||
|
.select("#heights")
|
||||||
|
.append("polygon")
|
||||||
|
.attr("points", getGridPolygon(i))
|
||||||
|
.attr("id", "cell" + i);
|
||||||
cell.attr("fill", getColor(grid.cells.h[i], scheme));
|
cell.attr("fill", getColor(grid.cells.h[i], scheme));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStatistics() {
|
function updateStatistics() {
|
||||||
const landCells = grid.cells.h.reduce((s, h) => h >= 20 ? s+1 : s);
|
const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s));
|
||||||
landmassCounter.innerHTML = `${landCells} (${rn(landCells/grid.cells.i.length*100)}%)`;
|
landmassCounter.innerHTML = `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`;
|
||||||
landmassAverage.innerHTML = rn(d3.mean(grid.cells.h));
|
landmassAverage.innerHTML = rn(d3.mean(grid.cells.h));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,10 +527,13 @@ function editHeightmap() {
|
||||||
|
|
||||||
function openBrushesPanel() {
|
function openBrushesPanel() {
|
||||||
if ($("#brushesPanel").is(":visible")) return;
|
if ($("#brushesPanel").is(":visible")) return;
|
||||||
$("#brushesPanel").dialog({
|
$("#brushesPanel")
|
||||||
title: "Paint Brushes", resizable: false,
|
.dialog({
|
||||||
|
title: "Paint Brushes",
|
||||||
|
resizable: false,
|
||||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||||
}).on('dialogclose', exitBrushMode);
|
})
|
||||||
|
.on("dialogclose", exitBrushMode);
|
||||||
|
|
||||||
if (modules.openBrushesPanel) return;
|
if (modules.openBrushesPanel) return;
|
||||||
modules.openBrushesPanel = true;
|
modules.openBrushesPanel = true;
|
||||||
|
|
@ -504,8 +541,8 @@ function editHeightmap() {
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("brushesButtons").addEventListener("click", e => toggleBrushMode(e));
|
document.getElementById("brushesButtons").addEventListener("click", e => toggleBrushMode(e));
|
||||||
document.getElementById("changeOnlyLand").addEventListener("click", e => changeOnlyLandClick(e));
|
document.getElementById("changeOnlyLand").addEventListener("click", e => changeOnlyLandClick(e));
|
||||||
document.getElementById("undo").addEventListener("click", () => restoreHistory(edits.n-1));
|
document.getElementById("undo").addEventListener("click", () => restoreHistory(edits.n - 1));
|
||||||
document.getElementById("redo").addEventListener("click", () => restoreHistory(edits.n+1));
|
document.getElementById("redo").addEventListener("click", () => restoreHistory(edits.n + 1));
|
||||||
document.getElementById("rescaleShow").addEventListener("click", () => {
|
document.getElementById("rescaleShow").addEventListener("click", () => {
|
||||||
document.getElementById("modifyButtons").style.display = "none";
|
document.getElementById("modifyButtons").style.display = "none";
|
||||||
document.getElementById("rescaleSection").style.display = "block";
|
document.getElementById("rescaleSection").style.display = "block";
|
||||||
|
|
@ -514,7 +551,7 @@ function editHeightmap() {
|
||||||
document.getElementById("modifyButtons").style.display = "block";
|
document.getElementById("modifyButtons").style.display = "block";
|
||||||
document.getElementById("rescaleSection").style.display = "none";
|
document.getElementById("rescaleSection").style.display = "none";
|
||||||
});
|
});
|
||||||
document.getElementById("rescaler").addEventListener("change", (e) => rescale(e.target.valueAsNumber));
|
document.getElementById("rescaler").addEventListener("change", e => rescale(e.target.valueAsNumber));
|
||||||
document.getElementById("rescaleCondShow").addEventListener("click", () => {
|
document.getElementById("rescaleCondShow").addEventListener("click", () => {
|
||||||
document.getElementById("modifyButtons").style.display = "none";
|
document.getElementById("modifyButtons").style.display = "none";
|
||||||
document.getElementById("rescaleCondSection").style.display = "block";
|
document.getElementById("rescaleCondSection").style.display = "block";
|
||||||
|
|
@ -539,7 +576,10 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleBrushMode(e) {
|
function toggleBrushMode(e) {
|
||||||
if (e.target.classList.contains("pressed")) {exitBrushMode(); return;}
|
if (e.target.classList.contains("pressed")) {
|
||||||
|
exitBrushMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
exitBrushMode();
|
exitBrushMode();
|
||||||
document.getElementById("brushesSliders").style.display = "block";
|
document.getElementById("brushesSliders").style.display = "block";
|
||||||
e.target.classList.add("pressed");
|
e.target.classList.add("pressed");
|
||||||
|
|
@ -568,17 +608,19 @@ function editHeightmap() {
|
||||||
const power = brushPower.valueAsNumber;
|
const power = brushPower.valueAsNumber;
|
||||||
const interpolate = d3.interpolateRound(power, 1);
|
const interpolate = d3.interpolateRound(power, 1);
|
||||||
const land = changeOnlyLand.checked;
|
const land = changeOnlyLand.checked;
|
||||||
function lim(v) {return Math.max(Math.min(v, 100), land ? 20 : 0);}
|
function lim(v) {
|
||||||
|
return Math.max(Math.min(v, 100), land ? 20 : 0);
|
||||||
|
}
|
||||||
const h = grid.cells.h;
|
const h = grid.cells.h;
|
||||||
|
|
||||||
const brush = document.querySelector("#brushesButtons > button.pressed").id;
|
const brush = document.querySelector("#brushesButtons > button.pressed").id;
|
||||||
if (brush === "brushRaise") s.forEach(i => h[i] = h[i] < 20 ? 20 : lim(h[i] + power)); else
|
if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power)));
|
||||||
if (brush === "brushElevate") s.forEach((i,d) => h[i] = lim(h[i] + interpolate(d/Math.max(s.length-1, 1)))); else
|
else if (brush === "brushElevate") s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1)))));
|
||||||
if (brush === "brushLower") s.forEach(i => h[i] = lim(h[i] - power)); else
|
else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
|
||||||
if (brush === "brushDepress") s.forEach((i,d) => h[i] = lim(h[i] - interpolate(d/Math.max(s.length-1, 1)))); else
|
else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
|
||||||
if (brush === "brushAlign") s.forEach(i => h[i] = lim(h[start])); else
|
else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
|
||||||
if (brush === "brushSmooth") s.forEach(i => h[i] = rn((d3.mean(grid.cells.c[i].filter(i => land ? h[i] >= 20 : 1).map(c => h[c])) + h[i]*(10-power) + .6) / (11-power),1)); else
|
else if (brush === "brushSmooth") s.forEach(i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1)));
|
||||||
if (brush === "brushDisrupt") s.forEach(i => h[i] = h[i] < 15 ? h[i] : lim(h[i] + power/1.6 - Math.random()*power));
|
else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
||||||
|
|
||||||
mockHeightmapSelection(s);
|
mockHeightmapSelection(s);
|
||||||
// updateHistory(); uncomment to update history every step
|
// updateHistory(); uncomment to update history every step
|
||||||
|
|
@ -592,7 +634,7 @@ function editHeightmap() {
|
||||||
|
|
||||||
function rescale(v) {
|
function rescale(v) {
|
||||||
const land = changeOnlyLand.checked;
|
const land = changeOnlyLand.checked;
|
||||||
grid.cells.h = grid.cells.h.map(h => land && (h < 20 || h+v < 20) ? h : lim(h+v));
|
grid.cells.h = grid.cells.h.map(h => (land && (h < 20 || h + v < 20) ? h : lim(h + v)));
|
||||||
updateHeightmap();
|
updateHeightmap();
|
||||||
document.getElementById("rescaler").value = 0;
|
document.getElementById("rescaler").value = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -601,14 +643,20 @@ function editHeightmap() {
|
||||||
const range = rescaleLower.value + "-" + rescaleHigher.value;
|
const range = rescaleLower.value + "-" + rescaleHigher.value;
|
||||||
const operator = conditionSign.value;
|
const operator = conditionSign.value;
|
||||||
const operand = rescaleModifier.valueAsNumber;
|
const operand = rescaleModifier.valueAsNumber;
|
||||||
if (Number.isNaN(operand)) {tip("Operand should be a number", false, "error"); return;}
|
if (Number.isNaN(operand)) {
|
||||||
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) {tip("Operand should be an integer", false, "error"); return;}
|
tip("Operand should be a number", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) {
|
||||||
|
tip("Operand should be an integer", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0); else
|
if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0);
|
||||||
if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0); else
|
else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0);
|
||||||
if (operator === "add") HeightmapGenerator.modify(range, operand, 1, 0); else
|
else if (operator === "add") HeightmapGenerator.modify(range, operand, 1, 0);
|
||||||
if (operator === "subtract") HeightmapGenerator.modify(range, -1 * operand, 1, 0); else
|
else if (operator === "subtract") HeightmapGenerator.modify(range, -1 * operand, 1, 0);
|
||||||
if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand);
|
else if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand);
|
||||||
|
|
||||||
updateHeightmap();
|
updateHeightmap();
|
||||||
}
|
}
|
||||||
|
|
@ -619,19 +667,24 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function disruptAllHeights() {
|
function disruptAllHeights() {
|
||||||
grid.cells.h = grid.cells.h.map(h => h < 15 ? h : lim(h + 2.5 - Math.random() * 4));
|
grid.cells.h = grid.cells.h.map(h => (h < 15 ? h : lim(h + 2.5 - Math.random() * 4)));
|
||||||
updateHeightmap();
|
updateHeightmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
function startFromScratch() {
|
function startFromScratch() {
|
||||||
if (changeOnlyLand.checked) {tip("Not allowed when 'Change only land cells' mode is set", false, "error"); return;}
|
if (changeOnlyLand.checked) {
|
||||||
|
tip("Not allowed when 'Change only land cells' mode is set", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const someHeights = grid.cells.h.some(h => h);
|
const someHeights = grid.cells.h.some(h => h);
|
||||||
if (!someHeights) {tip("Heightmap is already cleared, please do not click twice if not required", false, "error"); return;}
|
if (!someHeights) {
|
||||||
|
tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
||||||
viewbox.select("#heights").selectAll("*").remove();
|
viewbox.select("#heights").selectAll("*").remove();
|
||||||
updateHistory();
|
updateHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTemplateEditor() {
|
function openTemplateEditor() {
|
||||||
|
|
@ -639,7 +692,10 @@ function editHeightmap() {
|
||||||
const body = document.getElementById("templateBody");
|
const body = document.getElementById("templateBody");
|
||||||
|
|
||||||
$("#templateEditor").dialog({
|
$("#templateEditor").dialog({
|
||||||
title: "Template Editor", minHeight: "auto", width: "fit-content", resizable: false,
|
title: "Template Editor",
|
||||||
|
minHeight: "auto",
|
||||||
|
width: "fit-content",
|
||||||
|
resizable: false,
|
||||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -649,12 +705,12 @@ function editHeightmap() {
|
||||||
$("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"});
|
$("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"});
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
body.addEventListener("click", function(ev) {
|
body.addEventListener("click", function (ev) {
|
||||||
const el = ev.target;
|
const el = ev.target;
|
||||||
if (el.classList.contains("icon-check")) {
|
if (el.classList.contains("icon-check")) {
|
||||||
el.classList.remove("icon-check");
|
el.classList.remove("icon-check");
|
||||||
el.classList.add("icon-check-empty");
|
el.classList.add("icon-check-empty");
|
||||||
el.parentElement.style.opacity = .5;
|
el.parentElement.style.opacity = 0.5;
|
||||||
body.dataset.changed = 1;
|
body.dataset.changed = 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -665,7 +721,8 @@ function editHeightmap() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (el.classList.contains("icon-trash-empty")) {
|
if (el.classList.contains("icon-trash-empty")) {
|
||||||
el.parentElement.remove(); return;
|
el.parentElement.remove();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -674,7 +731,9 @@ function editHeightmap() {
|
||||||
document.getElementById("templateRun").addEventListener("click", executeTemplate);
|
document.getElementById("templateRun").addEventListener("click", executeTemplate);
|
||||||
document.getElementById("templateSave").addEventListener("click", downloadTemplate);
|
document.getElementById("templateSave").addEventListener("click", downloadTemplate);
|
||||||
document.getElementById("templateLoad").addEventListener("click", () => templateToLoad.click());
|
document.getElementById("templateLoad").addEventListener("click", () => templateToLoad.click());
|
||||||
document.getElementById("templateToLoad").addEventListener("change", function() {uploadFile(this, uploadTemplate)});
|
document.getElementById("templateToLoad").addEventListener("change", function () {
|
||||||
|
uploadFile(this, uploadTemplate);
|
||||||
|
});
|
||||||
|
|
||||||
function addStepOnClick(e) {
|
function addStepOnClick(e) {
|
||||||
if (e.target.tagName !== "BUTTON") return;
|
if (e.target.tagName !== "BUTTON") return;
|
||||||
|
|
@ -689,7 +748,9 @@ function editHeightmap() {
|
||||||
const elDist = body.querySelector("div:last-child").querySelector(".templateDist");
|
const elDist = body.querySelector("div:last-child").querySelector(".templateDist");
|
||||||
if (elDist) elDist.addEventListener("change", setRange);
|
if (elDist) elDist.addEventListener("change", setRange);
|
||||||
if (dist && elDist && elDist.tagName === "SELECT") {
|
if (dist && elDist && elDist.tagName === "SELECT") {
|
||||||
for (const o of elDist.options) {if (o.value === dist) elDist.value = dist;}
|
for (const o of elDist.options) {
|
||||||
|
if (o.value === dist) elDist.value = dist;
|
||||||
|
}
|
||||||
if (elDist.value !== dist) {
|
if (elDist.value !== dist) {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = opt.innerHTML = dist;
|
opt.value = opt.innerHTML = dist;
|
||||||
|
|
@ -705,23 +766,23 @@ function editHeightmap() {
|
||||||
const Reorder = `<i class="icon-resize-vertical" data-tip="Drag to reorder"></i>`;
|
const Reorder = `<i class="icon-resize-vertical" data-tip="Drag to reorder"></i>`;
|
||||||
const common = `<div data-type="${type}">${Hide}<div style="width:4em">${type}</div>${Trash}${Reorder}`;
|
const common = `<div data-type="${type}">${Hide}<div style="width:4em">${type}</div>${Trash}${Reorder}`;
|
||||||
|
|
||||||
const TempY = `<span>y:<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5||"20-80"}></span>`;
|
const TempY = `<span>y:<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5 || "20-80"}></span>`;
|
||||||
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4||"15-85"}></span>`;
|
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || "15-85"}></span>`;
|
||||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3||"40-50"}></span>`;
|
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || "40-50"}></span>`;
|
||||||
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count||"1-2"}></span>`;
|
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || "1-2"}></span>`;
|
||||||
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
||||||
|
|
||||||
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return blob;
|
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return blob;
|
||||||
if (type === "Strait") return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${count||"2-7"}></span></div>`;
|
if (type === "Strait") return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${count || "2-7"}></span></div>`;
|
||||||
if (type === "Add") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${count||-10} min=-100 max=100 step=1></span></div>`;
|
if (type === "Add") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${count || -10} min=-100 max=100 step=1></span></div>`;
|
||||||
if (type === "Multiply") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${count||1.1} min=0 max=10 step=.1></span></div>`;
|
if (type === "Multiply") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${count || 1.1} min=0 max=10 step=.1></span></div>`;
|
||||||
if (type === "Smooth") return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${count||2}></span></div>`;
|
if (type === "Smooth") return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${count || 2}></span></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRange(event) {
|
function setRange(event) {
|
||||||
if (event.target.value !== "interval") return;
|
if (event.target.value !== "interval") return;
|
||||||
|
|
||||||
prompt("Set a height interval. Avoid space, use hyphen as a separator", {default:"17-20"}, v => {
|
prompt("Set a height interval. Avoid space, use hyphen as a separator", {default: "17-20"}, v => {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = opt.innerHTML = v;
|
opt.value = opt.innerHTML = v;
|
||||||
event.target.add(opt);
|
event.target.add(opt);
|
||||||
|
|
@ -734,13 +795,24 @@ function editHeightmap() {
|
||||||
const steps = body.querySelectorAll("div").length;
|
const steps = body.querySelectorAll("div").length;
|
||||||
const changed = +body.getAttribute("data-changed");
|
const changed = +body.getAttribute("data-changed");
|
||||||
const template = e.target.value;
|
const template = e.target.value;
|
||||||
if (!steps || !changed) {changeTemplate(template); return;}
|
if (!steps || !changed) {
|
||||||
|
changeTemplate(template);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
alertMessage.innerHTML = "Are you sure you want to select a different template? All changes will be lost.";
|
alertMessage.innerHTML = "Are you sure you want to select a different template? All changes will be lost.";
|
||||||
$("#alert").dialog({resizable: false, title: "Change Template",
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Change Template",
|
||||||
buttons: {
|
buttons: {
|
||||||
Change: function() {changeTemplate(template); $(this).dialog("close");},
|
Change: function () {
|
||||||
Cancel: function() {$(this).dialog("close");}}
|
changeTemplate(template);
|
||||||
|
$(this).dialog("close");
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -751,15 +823,13 @@ function editHeightmap() {
|
||||||
|
|
||||||
if (template === "templateVolcano") {
|
if (template === "templateVolcano") {
|
||||||
addStep("Hill", "1", "90-100", "44-56", "40-60");
|
addStep("Hill", "1", "90-100", "44-56", "40-60");
|
||||||
addStep("Multiply", .8, "50-100");
|
addStep("Multiply", 0.8, "50-100");
|
||||||
addStep("Range", "1.5", "30-55", "45-55", "40-60");
|
addStep("Range", "1.5", "30-55", "45-55", "40-60");
|
||||||
addStep("Smooth", 2);
|
addStep("Smooth", 2);
|
||||||
addStep("Hill", "1.5", "25-35", "25-30", "20-75");
|
addStep("Hill", "1.5", "25-35", "25-30", "20-75");
|
||||||
addStep("Hill", "1", "25-35", "75-80", "25-75");
|
addStep("Hill", "1", "25-35", "75-80", "25-75");
|
||||||
addStep("Hill", "0.5", "20-25", "10-15", "20-25");
|
addStep("Hill", "0.5", "20-25", "10-15", "20-25");
|
||||||
}
|
} else if (template === "templateHighIsland") {
|
||||||
|
|
||||||
else if (template === "templateHighIsland") {
|
|
||||||
addStep("Hill", "1", "90-100", "65-75", "47-53");
|
addStep("Hill", "1", "90-100", "65-75", "47-53");
|
||||||
addStep("Add", 5, "all");
|
addStep("Add", 5, "all");
|
||||||
addStep("Hill", "6", "20-23", "25-55", "45-55");
|
addStep("Hill", "6", "20-23", "25-55", "45-55");
|
||||||
|
|
@ -769,13 +839,11 @@ function editHeightmap() {
|
||||||
addStep("Trough", "2-3", "20-30", "60-80", "70-80");
|
addStep("Trough", "2-3", "20-30", "60-80", "70-80");
|
||||||
addStep("Hill", "1", "10-15", "60-60", "50-50");
|
addStep("Hill", "1", "10-15", "60-60", "50-50");
|
||||||
addStep("Hill", "1.5", "13-16", "15-20", "20-75");
|
addStep("Hill", "1.5", "13-16", "15-20", "20-75");
|
||||||
addStep("Multiply", .8, "20-100");
|
addStep("Multiply", 0.8, "20-100");
|
||||||
addStep("Range", "1.5", "30-40", "15-85", "30-40");
|
addStep("Range", "1.5", "30-40", "15-85", "30-40");
|
||||||
addStep("Range", "1.5", "30-40", "15-85", "60-70");
|
addStep("Range", "1.5", "30-40", "15-85", "60-70");
|
||||||
addStep("Pit", "2-3", "10-15", "15-85", "20-80");
|
addStep("Pit", "2-3", "10-15", "15-85", "20-80");
|
||||||
}
|
} else if (template === "templateLowIsland") {
|
||||||
|
|
||||||
else if (template === "templateLowIsland") {
|
|
||||||
addStep("Hill", "1", "90-99", "60-80", "45-55");
|
addStep("Hill", "1", "90-99", "60-80", "45-55");
|
||||||
addStep("Hill", "4-5", "25-35", "20-65", "40-60");
|
addStep("Hill", "4-5", "25-35", "20-65", "40-60");
|
||||||
addStep("Range", "1", "40-50", "45-55", "45-55");
|
addStep("Range", "1", "40-50", "45-55", "45-55");
|
||||||
|
|
@ -785,13 +853,11 @@ function editHeightmap() {
|
||||||
addStep("Hill", "1.5", "10-15", "5-15", "20-80");
|
addStep("Hill", "1.5", "10-15", "5-15", "20-80");
|
||||||
addStep("Hill", "1", "10-15", "85-95", "70-80");
|
addStep("Hill", "1", "10-15", "85-95", "70-80");
|
||||||
addStep("Pit", "3-5", "10-15", "15-85", "20-80");
|
addStep("Pit", "3-5", "10-15", "15-85", "20-80");
|
||||||
addStep("Multiply", .4, "20-100");
|
addStep("Multiply", 0.4, "20-100");
|
||||||
}
|
} else if (template === "templateContinents") {
|
||||||
|
|
||||||
else if (template === "templateContinents") {
|
|
||||||
addStep("Hill", "1", "80-85", "75-80", "40-60");
|
addStep("Hill", "1", "80-85", "75-80", "40-60");
|
||||||
addStep("Hill", "1", "80-85", "20-25", "40-60");
|
addStep("Hill", "1", "80-85", "20-25", "40-60");
|
||||||
addStep("Multiply", .22, "20-100");
|
addStep("Multiply", 0.22, "20-100");
|
||||||
addStep("Hill", "5-6", "15-20", "25-75", "20-82");
|
addStep("Hill", "5-6", "15-20", "25-75", "20-82");
|
||||||
addStep("Range", ".8", "30-60", "5-15", "20-45");
|
addStep("Range", ".8", "30-60", "5-15", "20-45");
|
||||||
addStep("Range", ".8", "30-60", "5-15", "55-80");
|
addStep("Range", ".8", "30-60", "5-15", "55-80");
|
||||||
|
|
@ -802,9 +868,7 @@ function editHeightmap() {
|
||||||
addStep("Trough", "1-2", "5-10", "45-55", "45-55");
|
addStep("Trough", "1-2", "5-10", "45-55", "45-55");
|
||||||
addStep("Pit", "3-4", "10-15", "15-85", "20-80");
|
addStep("Pit", "3-4", "10-15", "15-85", "20-80");
|
||||||
addStep("Hill", "1", "5-10", "40-60", "40-60");
|
addStep("Hill", "1", "5-10", "40-60", "40-60");
|
||||||
}
|
} else if (template === "templateArchipelago") {
|
||||||
|
|
||||||
else if (template === "templateArchipelago") {
|
|
||||||
addStep("Add", 11, "all");
|
addStep("Add", 11, "all");
|
||||||
addStep("Range", "2-3", "40-60", "20-80", "20-80");
|
addStep("Range", "2-3", "40-60", "20-80", "20-80");
|
||||||
addStep("Hill", "5", "15-20", "10-90", "30-70");
|
addStep("Hill", "5", "15-20", "10-90", "30-70");
|
||||||
|
|
@ -814,18 +878,14 @@ function editHeightmap() {
|
||||||
addStep("Trough", "10", "20-30", "5-95", "5-95");
|
addStep("Trough", "10", "20-30", "5-95", "5-95");
|
||||||
addStep("Strait", "2", "vertical");
|
addStep("Strait", "2", "vertical");
|
||||||
addStep("Strait", "2", "horizontal");
|
addStep("Strait", "2", "horizontal");
|
||||||
}
|
} else if (template === "templateAtoll") {
|
||||||
|
|
||||||
else if (template === "templateAtoll") {
|
|
||||||
addStep("Hill", "1", "75-80", "50-60", "45-55");
|
addStep("Hill", "1", "75-80", "50-60", "45-55");
|
||||||
addStep("Hill", "1.5", "30-50", "25-75", "30-70");
|
addStep("Hill", "1.5", "30-50", "25-75", "30-70");
|
||||||
addStep("Hill", ".5", "30-50", "25-35", "30-70");
|
addStep("Hill", ".5", "30-50", "25-35", "30-70");
|
||||||
addStep("Smooth", 1);
|
addStep("Smooth", 1);
|
||||||
addStep("Multiply", .2, "25-100");
|
addStep("Multiply", 0.2, "25-100");
|
||||||
addStep("Hill", ".5", "10-20", "50-55", "48-52");
|
addStep("Hill", ".5", "10-20", "50-55", "48-52");
|
||||||
}
|
} else if (template === "templateMediterranean") {
|
||||||
|
|
||||||
else if (template === "templateMediterranean") {
|
|
||||||
addStep("Range", "3-4", "30-50", "0-100", "0-10");
|
addStep("Range", "3-4", "30-50", "0-100", "0-10");
|
||||||
addStep("Range", "3-4", "30-50", "0-100", "90-100");
|
addStep("Range", "3-4", "30-50", "0-100", "90-100");
|
||||||
addStep("Hill", "5-6", "30-70", "0-100", "0-5");
|
addStep("Hill", "5-6", "30-70", "0-100", "0-5");
|
||||||
|
|
@ -833,12 +893,10 @@ function editHeightmap() {
|
||||||
addStep("Smooth", 1);
|
addStep("Smooth", 1);
|
||||||
addStep("Hill", "2-3", "30-70", "0-5", "20-80");
|
addStep("Hill", "2-3", "30-70", "0-5", "20-80");
|
||||||
addStep("Hill", "2-3", "30-70", "95-100", "20-80");
|
addStep("Hill", "2-3", "30-70", "95-100", "20-80");
|
||||||
addStep("Multiply", .8, "land");
|
addStep("Multiply", 0.8, "land");
|
||||||
addStep("Trough", "3-5", "40-50", "0-100", "0-10");
|
addStep("Trough", "3-5", "40-50", "0-100", "0-10");
|
||||||
addStep("Trough", "3-5", "40-50", "0-100", "90-100");
|
addStep("Trough", "3-5", "40-50", "0-100", "90-100");
|
||||||
}
|
} else if (template === "templatePeninsula") {
|
||||||
|
|
||||||
else if (template === "templatePeninsula") {
|
|
||||||
addStep("Range", "2-3", "20-35", "40-50", "0-15");
|
addStep("Range", "2-3", "20-35", "40-50", "0-15");
|
||||||
addStep("Add", 5, "all");
|
addStep("Add", 5, "all");
|
||||||
addStep("Hill", "1", "90-100", "10-90", "0-5");
|
addStep("Hill", "1", "90-100", "10-90", "0-5");
|
||||||
|
|
@ -847,22 +905,18 @@ function editHeightmap() {
|
||||||
addStep("Hill", "1-2", "3-5", "5-95", "40-60");
|
addStep("Hill", "1-2", "3-5", "5-95", "40-60");
|
||||||
addStep("Trough", "5-6", "10-25", "5-95", "5-95");
|
addStep("Trough", "5-6", "10-25", "5-95", "5-95");
|
||||||
addStep("Smooth", 3);
|
addStep("Smooth", 3);
|
||||||
}
|
} else if (template === "templatePangea") {
|
||||||
|
|
||||||
else if (template === "templatePangea") {
|
|
||||||
addStep("Hill", "1-2", "25-40", "15-50", "0-10");
|
addStep("Hill", "1-2", "25-40", "15-50", "0-10");
|
||||||
addStep("Hill", "1-2", "5-40", "50-85", "0-10");
|
addStep("Hill", "1-2", "5-40", "50-85", "0-10");
|
||||||
addStep("Hill", "1-2", "25-40", "50-85", "90-100");
|
addStep("Hill", "1-2", "25-40", "50-85", "90-100");
|
||||||
addStep("Hill", "1-2", "5-40", "15-50", "90-100");
|
addStep("Hill", "1-2", "5-40", "15-50", "90-100");
|
||||||
addStep("Hill", "8-12", "20-40", "20-80", "48-52");
|
addStep("Hill", "8-12", "20-40", "20-80", "48-52");
|
||||||
addStep("Smooth", 2);
|
addStep("Smooth", 2);
|
||||||
addStep("Multiply", .7, "land");
|
addStep("Multiply", 0.7, "land");
|
||||||
addStep("Trough", "3-4", "25-35", "5-95", "10-20");
|
addStep("Trough", "3-4", "25-35", "5-95", "10-20");
|
||||||
addStep("Trough", "3-4", "25-35", "5-95", "80-90");
|
addStep("Trough", "3-4", "25-35", "5-95", "80-90");
|
||||||
addStep("Range", "5-6", "30-40", "10-90", "35-65");
|
addStep("Range", "5-6", "30-40", "10-90", "35-65");
|
||||||
}
|
} else if (template === "templateIsthmus") {
|
||||||
|
|
||||||
else if (template === "templateIsthmus") {
|
|
||||||
addStep("Hill", "5-10", "15-30", "0-30", "0-20");
|
addStep("Hill", "5-10", "15-30", "0-30", "0-20");
|
||||||
addStep("Hill", "5-10", "15-30", "10-50", "20-40");
|
addStep("Hill", "5-10", "15-30", "10-50", "20-40");
|
||||||
addStep("Hill", "5-10", "15-30", "30-70", "40-60");
|
addStep("Hill", "5-10", "15-30", "30-70", "40-60");
|
||||||
|
|
@ -874,15 +928,12 @@ function editHeightmap() {
|
||||||
addStep("Trough", "4-8", "15-30", "30-70", "40-60");
|
addStep("Trough", "4-8", "15-30", "30-70", "40-60");
|
||||||
addStep("Trough", "4-8", "15-30", "50-90", "60-80");
|
addStep("Trough", "4-8", "15-30", "50-90", "60-80");
|
||||||
addStep("Trough", "4-8", "15-30", "70-100", "80-100");
|
addStep("Trough", "4-8", "15-30", "70-100", "80-100");
|
||||||
}
|
} else if (template === "templateShattered") {
|
||||||
|
|
||||||
else if (template === "templateShattered") {
|
|
||||||
addStep("Hill", "8", "35-40", "15-85", "30-70");
|
addStep("Hill", "8", "35-40", "15-85", "30-70");
|
||||||
addStep("Trough", "10-20", "40-50", "5-95", "5-95");
|
addStep("Trough", "10-20", "40-50", "5-95", "5-95");
|
||||||
addStep("Range", "5-7", "30-40", "10-90", "20-80");
|
addStep("Range", "5-7", "30-40", "10-90", "20-80");
|
||||||
addStep("Pit", "12-20", "30-40", "15-85", "20-80");
|
addStep("Pit", "12-20", "30-40", "15-85", "20-80");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeTemplate() {
|
function executeTemplate() {
|
||||||
|
|
@ -893,7 +944,7 @@ function editHeightmap() {
|
||||||
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
|
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
|
||||||
|
|
||||||
for (const s of steps) {
|
for (const s of steps) {
|
||||||
if (s.style.opacity == .5) continue;
|
if (s.style.opacity == 0.5) continue;
|
||||||
const type = s.getAttribute("data-type");
|
const type = s.getAttribute("data-type");
|
||||||
const elCount = s.querySelector(".templateCount") || "";
|
const elCount = s.querySelector(".templateCount") || "";
|
||||||
const elHeight = s.querySelector(".templateHeight") || "";
|
const elHeight = s.querySelector(".templateHeight") || "";
|
||||||
|
|
@ -906,14 +957,14 @@ function editHeightmap() {
|
||||||
const templateY = s.querySelector(".templateY");
|
const templateY = s.querySelector(".templateY");
|
||||||
const y = templateY ? templateY.value : null;
|
const y = templateY ? templateY.value : null;
|
||||||
|
|
||||||
if (type === "Hill") HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y); else
|
if (type === "Hill") HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y);
|
||||||
if (type === "Pit") HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y); else
|
else if (type === "Pit") HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y);
|
||||||
if (type === "Range") HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y); else
|
else if (type === "Range") HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y);
|
||||||
if (type === "Trough") HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y); else
|
else if (type === "Trough") HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y);
|
||||||
if (type === "Strait") HeightmapGenerator.addStrait(elCount.value, dist); else
|
else if (type === "Strait") HeightmapGenerator.addStrait(elCount.value, dist);
|
||||||
if (type === "Add") HeightmapGenerator.modify(dist, +elCount.value, 1); else
|
else if (type === "Add") HeightmapGenerator.modify(dist, +elCount.value, 1);
|
||||||
if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +elCount.value); else
|
else if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +elCount.value);
|
||||||
if (type === "Smooth") HeightmapGenerator.smooth(+elCount.value);
|
else if (type === "Smooth") HeightmapGenerator.smooth(+elCount.value);
|
||||||
|
|
||||||
updateHistory("noStat"); // update history every step
|
updateHistory("noStat"); // update history every step
|
||||||
}
|
}
|
||||||
|
|
@ -932,7 +983,7 @@ function editHeightmap() {
|
||||||
|
|
||||||
let data = "";
|
let data = "";
|
||||||
for (const s of steps) {
|
for (const s of steps) {
|
||||||
if (s.style.opacity == .5) continue;
|
if (s.style.opacity == 0.5) continue;
|
||||||
const type = s.getAttribute("data-type");
|
const type = s.getAttribute("data-type");
|
||||||
const elCount = s.querySelector(".templateCount");
|
const elCount = s.querySelector(".templateCount");
|
||||||
const count = elCount ? elCount.value : "0";
|
const count = elCount ? elCount.value : "0";
|
||||||
|
|
@ -952,14 +1003,19 @@ function editHeightmap() {
|
||||||
|
|
||||||
function uploadTemplate(dataLoaded) {
|
function uploadTemplate(dataLoaded) {
|
||||||
const steps = dataLoaded.split("\r\n");
|
const steps = dataLoaded.split("\r\n");
|
||||||
if (!steps.length) {tip("Cannot parse the template, please check the file", false, "error"); return;}
|
if (!steps.length) {
|
||||||
|
tip("Cannot parse the template, please check the file", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
templateBody.innerHTML = "";
|
templateBody.innerHTML = "";
|
||||||
for (const s of steps) {
|
for (const s of steps) {
|
||||||
const step = s.split(" ");
|
const step = s.split(" ");
|
||||||
if (step.length !== 5) {ERROR && console.error("Cannot parse step, wrong arguments count", s); continue;}
|
if (step.length !== 5) {
|
||||||
|
ERROR && console.error("Cannot parse step, wrong arguments count", s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
addStep(step[0], step[1], step[2], step[3], step[4]);
|
addStep(step[0], step[1], step[2], step[3], step[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -969,7 +1025,10 @@ function editHeightmap() {
|
||||||
closeDialogs("#imageConverter");
|
closeDialogs("#imageConverter");
|
||||||
|
|
||||||
$("#imageConverter").dialog({
|
$("#imageConverter").dialog({
|
||||||
title: "Image Converter", maxHeight: svgHeight*.8, minHeight: "auto", width: "20em",
|
title: "Image Converter",
|
||||||
|
maxHeight: svgHeight * 0.8,
|
||||||
|
minHeight: "auto",
|
||||||
|
width: "20em",
|
||||||
position: {my: "right top", at: "right-10 top+10", of: "svg"},
|
position: {my: "right top", at: "right-10 top+10", of: "svg"},
|
||||||
beforeClose: closeImageConverter
|
beforeClose: closeImageConverter
|
||||||
});
|
});
|
||||||
|
|
@ -983,7 +1042,7 @@ function editHeightmap() {
|
||||||
|
|
||||||
setOverlayOpacity(0);
|
setOverlayOpacity(0);
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
tip('Image Converter is opened. Upload image and assign height value for each color', false, "warn"); // main tip
|
tip("Image Converter is opened. Upload image and assign height value for each color", false, "warn"); // main tip
|
||||||
|
|
||||||
// remove all heights
|
// remove all heights
|
||||||
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
||||||
|
|
@ -994,13 +1053,18 @@ function editHeightmap() {
|
||||||
modules.openImageConverter = true;
|
modules.openImageConverter = true;
|
||||||
|
|
||||||
// add color pallete
|
// add color pallete
|
||||||
void function createColorPallete() {
|
void (function createColorPallete() {
|
||||||
d3.select("#imageConverterPalette").selectAll("div").data(d3.range(101))
|
d3.select("#imageConverterPalette")
|
||||||
.enter().append("div").attr("data-color", i => i)
|
.selectAll("div")
|
||||||
.style("background-color", i => color(1-(i < 20 ? i-5 : i) / 100))
|
.data(d3.range(101))
|
||||||
.style("width", i => i < 40 || i > 68 ? ".2em" : ".1em")
|
.enter()
|
||||||
.on("touchmove mousemove", showPalleteHeight).on("click", assignHeight);
|
.append("div")
|
||||||
}()
|
.attr("data-color", i => i)
|
||||||
|
.style("background-color", i => color(1 - (i < 20 ? i - 5 : i) / 100))
|
||||||
|
.style("width", i => (i < 40 || i > 68 ? ".2em" : ".1em"))
|
||||||
|
.on("touchmove mousemove", showPalleteHeight)
|
||||||
|
.on("click", assignHeight);
|
||||||
|
})();
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("convertImageLoad").addEventListener("click", () => imageToLoad.click());
|
document.getElementById("convertImageLoad").addEventListener("click", () => imageToLoad.click());
|
||||||
|
|
@ -1011,14 +1075,18 @@ function editHeightmap() {
|
||||||
document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber);
|
document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber);
|
||||||
document.getElementById("convertComplete").addEventListener("click", applyConversion);
|
document.getElementById("convertComplete").addEventListener("click", applyConversion);
|
||||||
document.getElementById("convertCancel").addEventListener("click", cancelConversion);
|
document.getElementById("convertCancel").addEventListener("click", cancelConversion);
|
||||||
document.getElementById("convertOverlay").addEventListener("input", function() {setOverlayOpacity(this.value)});
|
document.getElementById("convertOverlay").addEventListener("input", function () {
|
||||||
document.getElementById("convertOverlayNumber").addEventListener("input", function() {setOverlayOpacity(this.value)});
|
setOverlayOpacity(this.value);
|
||||||
|
});
|
||||||
|
document.getElementById("convertOverlayNumber").addEventListener("input", function () {
|
||||||
|
setOverlayOpacity(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
function showPalleteHeight() {
|
function showPalleteHeight() {
|
||||||
const height = +this.getAttribute("data-color");
|
const height = +this.getAttribute("data-color");
|
||||||
colorsSelectValue.innerHTML = height;
|
colorsSelectValue.innerHTML = height;
|
||||||
colorsSelectFriendly.innerHTML = getHeight(height);
|
colorsSelectFriendly.innerHTML = getHeight(height);
|
||||||
const former = imageConverterPalette.querySelector(".hoveredColor")
|
const former = imageConverterPalette.querySelector(".hoveredColor");
|
||||||
if (former) former.className = "";
|
if (former) former.className = "";
|
||||||
this.className = "hoveredColor";
|
this.className = "hoveredColor";
|
||||||
}
|
}
|
||||||
|
|
@ -1028,15 +1096,15 @@ function editHeightmap() {
|
||||||
this.value = ""; // reset input value to get triggered if the file is re-uploaded
|
this.value = ""; // reset input value to get triggered if the file is re-uploaded
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
const img = new Image;
|
const img = new Image();
|
||||||
img.onload = function() {
|
img.onload = function () {
|
||||||
const ctx = document.getElementById("canvas").getContext("2d");
|
const ctx = document.getElementById("canvas").getContext("2d");
|
||||||
ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
|
ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
|
||||||
heightsFromImage(+convertColors.value);
|
heightsFromImage(+convertColors.value);
|
||||||
resetZoom();
|
resetZoom();
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.onloadend = () => img.src = reader.result;
|
reader.onloadend = () => (img.src = reader.result);
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1045,9 +1113,9 @@ function editHeightmap() {
|
||||||
const sampleCanvas = document.createElement("canvas");
|
const sampleCanvas = document.createElement("canvas");
|
||||||
sampleCanvas.width = grid.cellsX;
|
sampleCanvas.width = grid.cellsX;
|
||||||
sampleCanvas.height = grid.cellsY;
|
sampleCanvas.height = grid.cellsY;
|
||||||
sampleCanvas.getContext('2d').drawImage(sourceImage, 0, 0, grid.cellsX, grid.cellsY);
|
sampleCanvas.getContext("2d").drawImage(sourceImage, 0, 0, grid.cellsX, grid.cellsY);
|
||||||
|
|
||||||
const q = new RgbQuant({colors:count});
|
const q = new RgbQuant({colors: count});
|
||||||
q.sample(sampleCanvas);
|
q.sample(sampleCanvas);
|
||||||
const data = q.reduce(sampleCanvas);
|
const data = q.reduce(sampleCanvas);
|
||||||
const pallete = q.palette(true);
|
const pallete = q.palette(true);
|
||||||
|
|
@ -1059,15 +1127,26 @@ function editHeightmap() {
|
||||||
colorsAssigned.style.display = "none";
|
colorsAssigned.style.display = "none";
|
||||||
sampleCanvas.remove(); // no need to keep
|
sampleCanvas.remove(); // no need to keep
|
||||||
|
|
||||||
viewbox.select("#heights").selectAll("polygon").data(grid.cells.i).join("polygon")
|
viewbox
|
||||||
.attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d)
|
.select("#heights")
|
||||||
.attr("fill", d => `rgb(${data[d*4]}, ${data[d*4+1]}, ${data[d*4+2]})`)
|
.selectAll("polygon")
|
||||||
|
.data(grid.cells.i)
|
||||||
|
.join("polygon")
|
||||||
|
.attr("points", d => getGridPolygon(d))
|
||||||
|
.attr("id", d => "cell" + d)
|
||||||
|
.attr("fill", d => `rgb(${data[d * 4]}, ${data[d * 4 + 1]}, ${data[d * 4 + 2]})`)
|
||||||
.on("click", mapClicked);
|
.on("click", mapClicked);
|
||||||
|
|
||||||
const colors = pallete.map(p => `rgb(${p[0]}, ${p[1]}, ${p[2]})`);
|
const colors = pallete.map(p => `rgb(${p[0]}, ${p[1]}, ${p[2]})`);
|
||||||
d3.select("#colorsUnassigned").selectAll("div").data(colors).enter().append("div")
|
d3.select("#colorsUnassigned")
|
||||||
.attr("data-color", i => i).style("background-color", i => i)
|
.selectAll("div")
|
||||||
.attr("class", "color-div").on("click", colorClicked);
|
.data(colors)
|
||||||
|
.enter()
|
||||||
|
.append("div")
|
||||||
|
.attr("data-color", i => i)
|
||||||
|
.style("background-color", i => i)
|
||||||
|
.attr("class", "color-div")
|
||||||
|
.on("click", colorClicked);
|
||||||
|
|
||||||
document.getElementById("colorsUnassignedNumber").innerHTML = colors.length;
|
document.getElementById("colorsUnassignedNumber").innerHTML = colors.length;
|
||||||
}
|
}
|
||||||
|
|
@ -1100,18 +1179,24 @@ function editHeightmap() {
|
||||||
|
|
||||||
const color = this.getAttribute("data-color");
|
const color = this.getAttribute("data-color");
|
||||||
viewbox.select("#heights").selectAll("polygon.selectedCell").classed("selectedCell", 0);
|
viewbox.select("#heights").selectAll("polygon.selectedCell").classed("selectedCell", 0);
|
||||||
viewbox.select("#heights").selectAll("polygon[fill='" + color + "']").classed("selectedCell", 1);
|
viewbox
|
||||||
|
.select("#heights")
|
||||||
|
.selectAll("polygon[fill='" + color + "']")
|
||||||
|
.classed("selectedCell", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignHeight() {
|
function assignHeight() {
|
||||||
const height = +this.dataset.color;
|
const height = +this.dataset.color;
|
||||||
const rgb = color(1 - (height < 20 ? height-5 : height) / 100);
|
const rgb = color(1 - (height < 20 ? height - 5 : height) / 100);
|
||||||
const selectedColor = imageConverter.querySelector("div.selectedColor");
|
const selectedColor = imageConverter.querySelector("div.selectedColor");
|
||||||
selectedColor.style.backgroundColor = rgb;
|
selectedColor.style.backgroundColor = rgb;
|
||||||
selectedColor.setAttribute("data-color", rgb);
|
selectedColor.setAttribute("data-color", rgb);
|
||||||
selectedColor.setAttribute("data-height", height);
|
selectedColor.setAttribute("data-height", height);
|
||||||
|
|
||||||
viewbox.select("#heights").selectAll(".selectedCell").each(function() {
|
viewbox
|
||||||
|
.select("#heights")
|
||||||
|
.selectAll(".selectedCell")
|
||||||
|
.each(function () {
|
||||||
this.setAttribute("fill", rgb);
|
this.setAttribute("fill", rgb);
|
||||||
this.setAttribute("data-height", height);
|
this.setAttribute("data-height", height);
|
||||||
});
|
});
|
||||||
|
|
@ -1123,7 +1208,6 @@ function editHeightmap() {
|
||||||
document.getElementById("colorsUnassignedNumber").innerHTML = colorsUnassigned.childElementCount - 2;
|
document.getElementById("colorsUnassignedNumber").innerHTML = colorsUnassigned.childElementCount - 2;
|
||||||
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
|
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto assign color based on luminosity or hue
|
// auto assign color based on luminosity or hue
|
||||||
|
|
@ -1138,37 +1222,44 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getHeightByHue = function(color) {
|
const getHeightByHue = function (color) {
|
||||||
let hue = d3.hsl(color).h;
|
let hue = d3.hsl(color).h;
|
||||||
if (hue > 300) hue -= 360;
|
if (hue > 300) hue -= 360;
|
||||||
if (hue > 170) return Math.abs(hue-250) / 3 |0; // water
|
if (hue > 170) return (Math.abs(hue - 250) / 3) | 0; // water
|
||||||
return Math.abs(hue-250+20) / 3 |0; // land
|
return (Math.abs(hue - 250 + 20) / 3) | 0; // land
|
||||||
}
|
};
|
||||||
|
|
||||||
const getHeightByLum = function(color) {
|
const getHeightByLum = function (color) {
|
||||||
let lum = d3.lab(color).l;
|
let lum = d3.lab(color).l;
|
||||||
if (lum < 13) return lum / 13 * 20 |0; // water
|
if (lum < 13) return ((lum / 13) * 20) | 0; // water
|
||||||
return lum|0; // land
|
return lum | 0; // land
|
||||||
}
|
};
|
||||||
|
|
||||||
const scheme = d3.range(101).map(i => getColor(i, color()));
|
const scheme = d3.range(101).map(i => getColor(i, color()));
|
||||||
const hues = scheme.map(rgb => d3.hsl(rgb).h|0);
|
const hues = scheme.map(rgb => d3.hsl(rgb).h | 0);
|
||||||
const getHeightByScheme = function(color) {
|
const getHeightByScheme = function (color) {
|
||||||
let height = scheme.indexOf(color);
|
let height = scheme.indexOf(color);
|
||||||
if (height !== -1) return height; // exact match
|
if (height !== -1) return height; // exact match
|
||||||
const hue = d3.hsl(color).h;
|
const hue = d3.hsl(color).h;
|
||||||
const closest = hues.reduce((prev, curr) => (Math.abs(curr - hue) < Math.abs(prev - hue) ? curr : prev));
|
const closest = hues.reduce((prev, curr) => (Math.abs(curr - hue) < Math.abs(prev - hue) ? curr : prev));
|
||||||
return hues.indexOf(closest);
|
return hues.indexOf(closest);
|
||||||
}
|
};
|
||||||
|
|
||||||
const assinged = []; // store assigned heights
|
const assinged = []; // store assigned heights
|
||||||
unassigned.forEach(el => {
|
unassigned.forEach(el => {
|
||||||
const clr = el.dataset.color;
|
const clr = el.dataset.color;
|
||||||
const height = type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr);
|
const height = type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr);
|
||||||
const colorTo = color(1 - (height < 20 ? (height-5) / 100 : height / 100));
|
const colorTo = color(1 - (height < 20 ? (height - 5) / 100 : height / 100));
|
||||||
viewbox.select("#heights").selectAll("polygon[fill='" + clr + "']").attr("fill", colorTo).attr("data-height", height);
|
viewbox
|
||||||
|
.select("#heights")
|
||||||
|
.selectAll("polygon[fill='" + clr + "']")
|
||||||
|
.attr("fill", colorTo)
|
||||||
|
.attr("data-height", height);
|
||||||
|
|
||||||
if (assinged[height]) {el.remove(); return;} // if color is already added, remove it
|
if (assinged[height]) {
|
||||||
|
el.remove();
|
||||||
|
return;
|
||||||
|
} // if color is already added, remove it
|
||||||
el.style.backgroundColor = el.dataset.color = colorTo;
|
el.style.backgroundColor = el.dataset.color = colorTo;
|
||||||
el.dataset.height = height;
|
el.dataset.height = height;
|
||||||
colorsAssigned.appendChild(el);
|
colorsAssigned.appendChild(el);
|
||||||
|
|
@ -1186,8 +1277,7 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConvertColorsNumber() {
|
function setConvertColorsNumber() {
|
||||||
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`,
|
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`, {default: +convertColors.value, step: 1, min: 3, max: 255}, number => {
|
||||||
{default:+convertColors.value, step:1, min:3, max:255}, number => {
|
|
||||||
convertColors.value = number;
|
convertColors.value = number;
|
||||||
heightsFromImage(number);
|
heightsFromImage(number);
|
||||||
});
|
});
|
||||||
|
|
@ -1204,7 +1294,10 @@ function editHeightmap() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewbox.select("#heights").selectAll("polygon").each(function() {
|
viewbox
|
||||||
|
.select("#heights")
|
||||||
|
.selectAll("polygon")
|
||||||
|
.each(function () {
|
||||||
const height = +this.dataset.height || 0;
|
const height = +this.dataset.height || 0;
|
||||||
const i = +this.id.slice(4);
|
const i = +this.id.slice(4);
|
||||||
grid.cells.h[i] = height;
|
grid.cells.h[i] = height;
|
||||||
|
|
@ -1218,7 +1311,7 @@ function editHeightmap() {
|
||||||
function cancelConversion() {
|
function cancelConversion() {
|
||||||
restoreImageConverterState();
|
restoreImageConverterState();
|
||||||
viewbox.select("#heights").selectAll("polygon").remove();
|
viewbox.select("#heights").selectAll("polygon").remove();
|
||||||
restoreHistory(edits.n-1);
|
restoreHistory(edits.n - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreImageConverterState() {
|
function restoreImageConverterState() {
|
||||||
|
|
@ -1244,20 +1337,22 @@ function editHeightmap() {
|
||||||
Click "Complete" to apply the conversion.
|
Click "Complete" to apply the conversion.
|
||||||
Click "Close" to exit conversion mode and restore previous heightmap`;
|
Click "Close" to exit conversion mode and restore previous heightmap`;
|
||||||
|
|
||||||
$("#alert").dialog({resizable: false, title: "Close Image Converter",
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Close Image Converter",
|
||||||
buttons: {
|
buttons: {
|
||||||
Cancel: function() {
|
Cancel: function () {
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
},
|
},
|
||||||
Complete: function() {
|
Complete: function () {
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
applyConversion();
|
applyConversion();
|
||||||
},
|
},
|
||||||
Close: function() {
|
Close: function () {
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
restoreImageConverterState();
|
restoreImageConverterState();
|
||||||
viewbox.select("#heights").selectAll("polygon").remove();
|
viewbox.select("#heights").selectAll("polygon").remove();
|
||||||
restoreHistory(edits.n-1);
|
restoreHistory(edits.n - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1285,11 +1380,11 @@ function editHeightmap() {
|
||||||
|
|
||||||
grid.cells.h.forEach((height, i) => {
|
grid.cells.h.forEach((height, i) => {
|
||||||
let h = height < 20 ? Math.max(height / 1.5, 0) : height;
|
let h = height < 20 ? Math.max(height / 1.5, 0) : height;
|
||||||
const v = h / 100 * 255;
|
const v = (h / 100) * 255;
|
||||||
imageData.data[i*4] = v;
|
imageData.data[i * 4] = v;
|
||||||
imageData.data[i*4 + 1] = v;
|
imageData.data[i * 4 + 1] = v;
|
||||||
imageData.data[i*4 + 2] = v;
|
imageData.data[i * 4 + 2] = v;
|
||||||
imageData.data[i*4 + 3] = 255;
|
imageData.data[i * 4 + 3] = 255;
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|
@ -1302,7 +1397,7 @@ function editHeightmap() {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = dataURL;
|
img.src = dataURL;
|
||||||
|
|
||||||
img.onload = function() {
|
img.onload = function () {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
canvas.width = svgWidth;
|
canvas.width = svgWidth;
|
||||||
|
|
@ -1315,7 +1410,6 @@ function editHeightmap() {
|
||||||
link.href = imgBig;
|
link.href = imgBig;
|
||||||
link.click();
|
link.click();
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,57 @@
|
||||||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
toolsContent.addEventListener("click", function(event) {
|
toolsContent.addEventListener("click", function (event) {
|
||||||
if (customization) {tip("Please exit the customization mode first", false, "warning"); return;}
|
if (customization) {
|
||||||
|
tip("Please exit the customization mode first", false, "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.target.tagName !== "BUTTON") return;
|
if (event.target.tagName !== "BUTTON") return;
|
||||||
const button = event.target.id;
|
const button = event.target.id;
|
||||||
|
|
||||||
// Click to open Editor buttons
|
// Click to open Editor buttons
|
||||||
if (button === "editHeightmapButton") editHeightmap(); else
|
if (button === "editHeightmapButton") editHeightmap();
|
||||||
if (button === "editBiomesButton") editBiomes(); else
|
else if (button === "editBiomesButton") editBiomes();
|
||||||
if (button === "editStatesButton") editStates(); else
|
else if (button === "editStatesButton") editStates();
|
||||||
if (button === "editProvincesButton") editProvinces(); else
|
else if (button === "editProvincesButton") editProvinces();
|
||||||
if (button === "editDiplomacyButton") editDiplomacy(); else
|
else if (button === "editDiplomacyButton") editDiplomacy();
|
||||||
if (button === "editCulturesButton") editCultures(); else
|
else if (button === "editCulturesButton") editCultures();
|
||||||
if (button === "editReligions") editReligions(); else
|
else if (button === "editReligions") editReligions();
|
||||||
if (button === "editEmblemButton") openEmblemEditor(); else
|
else if (button === "editEmblemButton") openEmblemEditor();
|
||||||
if (button === "editNamesBaseButton") editNamesbase(); else
|
else if (button === "editNamesBaseButton") editNamesbase();
|
||||||
if (button === "editUnitsButton") editUnits(); else
|
else if (button === "editUnitsButton") editUnits();
|
||||||
if (button === "editNotesButton") editNotes(); else
|
else if (button === "editNotesButton") editNotes();
|
||||||
if (button === "editZonesButton") editZones(); else
|
else if (button === "editZonesButton") editZones();
|
||||||
if (button === "overviewBurgsButton") overviewBurgs(); else
|
else if (button === "overviewBurgsButton") overviewBurgs();
|
||||||
if (button === "overviewRiversButton") overviewRivers(); else
|
else if (button === "overviewRiversButton") overviewRivers();
|
||||||
if (button === "overviewMilitaryButton") overviewMilitary(); else
|
else if (button === "overviewMilitaryButton") overviewMilitary();
|
||||||
if (button === "overviewCellsButton") viewCellDetails();
|
else if (button === "overviewCellsButton") viewCellDetails();
|
||||||
|
|
||||||
// Click to Regenerate buttons
|
// Click to Regenerate buttons
|
||||||
if (event.target.parentNode.id === "regenerateFeature") {
|
if (event.target.parentNode.id === "regenerateFeature") {
|
||||||
if (sessionStorage.getItem("regenerateFeatureDontAsk")) {processFeatureRegeneration(event, button); return;}
|
if (sessionStorage.getItem("regenerateFeatureDontAsk")) {
|
||||||
|
processFeatureRegeneration(event, button);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.<br><br>Are you sure you want to proceed?`
|
alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.<br><br>Are you sure you want to proceed?`;
|
||||||
$("#alert").dialog({resizable: false, title: "Regenerate element",
|
$("#alert").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Regenerate element",
|
||||||
buttons: {
|
buttons: {
|
||||||
Proceed: function() {processFeatureRegeneration(event, button); $(this).dialog("close");},
|
Proceed: function () {
|
||||||
Cancel: function() {$(this).dialog("close");}
|
processFeatureRegeneration(event, button);
|
||||||
|
$(this).dialog("close");
|
||||||
},
|
},
|
||||||
open: function() {
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open: function () {
|
||||||
const pane = $(this).dialog("widget").find(".ui-dialog-buttonpane");
|
const pane = $(this).dialog("widget").find(".ui-dialog-buttonpane");
|
||||||
$('<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>').prependTo(pane);
|
$('<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>').prependTo(pane);
|
||||||
},
|
},
|
||||||
close: function() {
|
close: function () {
|
||||||
const box = $(this).dialog("widget").find(".checkbox")[0];
|
const box = $(this).dialog("widget").find(".checkbox")[0];
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
if (box.checked) sessionStorage.setItem("regenerateFeatureDontAsk", true);
|
if (box.checked) sessionStorage.setItem("regenerateFeatureDontAsk", true);
|
||||||
|
|
@ -48,29 +61,35 @@ toolsContent.addEventListener("click", function(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click to Add buttons
|
// Click to Add buttons
|
||||||
if (button === "addLabel") toggleAddLabel(); else
|
if (button === "addLabel") toggleAddLabel();
|
||||||
if (button === "addBurgTool") toggleAddBurg(); else
|
else if (button === "addBurgTool") toggleAddBurg();
|
||||||
if (button === "addRiver") toggleAddRiver(); else
|
else if (button === "addRiver") toggleAddRiver();
|
||||||
if (button === "addRoute") toggleAddRoute(); else
|
else if (button === "addRoute") toggleAddRoute();
|
||||||
if (button === "addMarker") toggleAddMarker();
|
else if (button === "addMarker") toggleAddMarker();
|
||||||
});
|
});
|
||||||
|
|
||||||
function processFeatureRegeneration(event, button) {
|
function processFeatureRegeneration(event, button) {
|
||||||
if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else
|
if (button === "regenerateStateLabels") {
|
||||||
if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else
|
BurgsAndStates.drawStateLabels();
|
||||||
if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else
|
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||||
if (button === "regenerateRivers") regenerateRivers(); else
|
} else if (button === "regenerateReliefIcons") {
|
||||||
if (button === "regeneratePopulation") recalculatePopulation(); else
|
ReliefIcons();
|
||||||
if (button === "regenerateStates") regenerateStates(); else
|
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||||
if (button === "regenerateProvinces") regenerateProvinces(); else
|
} else if (button === "regenerateRoutes") {
|
||||||
if (button === "regenerateBurgs") regenerateBurgs(); else
|
Routes.regenerate();
|
||||||
if (button === "regenerateEmblems") regenerateEmblems(); else
|
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||||
if (button === "regenerateReligions") regenerateReligions(); else
|
} else if (button === "regenerateRivers") regenerateRivers();
|
||||||
if (button === "regenerateCultures") regenerateCultures(); else
|
else if (button === "regeneratePopulation") recalculatePopulation();
|
||||||
if (button === "regenerateMilitary") regenerateMilitary(); else
|
else if (button === "regenerateStates") regenerateStates();
|
||||||
if (button === "regenerateIce") regenerateIce(); else
|
else if (button === "regenerateProvinces") regenerateProvinces();
|
||||||
if (button === "regenerateMarkers") regenerateMarkers(event); else
|
else if (button === "regenerateBurgs") regenerateBurgs();
|
||||||
if (button === "regenerateZones") regenerateZones(event);
|
else if (button === "regenerateEmblems") regenerateEmblems();
|
||||||
|
else if (button === "regenerateReligions") regenerateReligions();
|
||||||
|
else if (button === "regenerateCultures") regenerateCultures();
|
||||||
|
else if (button === "regenerateMilitary") regenerateMilitary();
|
||||||
|
else if (button === "regenerateIce") regenerateIce();
|
||||||
|
else if (button === "regenerateMarkers") regenerateMarkers(event);
|
||||||
|
else if (button === "regenerateZones") regenerateZones(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openEmblemEditor() {
|
async function openEmblemEditor() {
|
||||||
|
|
@ -106,10 +125,10 @@ function recalculatePopulation() {
|
||||||
if (!b.i || b.removed || b.lock) return;
|
if (!b.i || b.removed || b.lock) return;
|
||||||
const i = b.cell;
|
const i = b.cell;
|
||||||
|
|
||||||
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + (i % 100) / 1000, 0.1), 3);
|
||||||
if (b.capital) b.population = b.population * 1.3; // increase capital population
|
if (b.capital) b.population = b.population * 1.3; // increase capital population
|
||||||
if (b.port) b.population = b.population * 1.3; // increase port population
|
if (b.port) b.population = b.population * 1.3; // increase port population
|
||||||
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
|
b.population = rn(b.population * gauss(2, 3, 0.6, 20, 3), 3);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,11 +145,16 @@ function regenerateStates() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// burg local ids sorted by a bit randomized population:
|
// burg local ids sorted by a bit randomized population:
|
||||||
const sorted = burgs.map((b, i) => [i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]);
|
const sorted = burgs
|
||||||
|
.map((b, i) => [i, b.population * Math.random()])
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.map(b => b[0]);
|
||||||
const capitalsTree = d3.quadtree();
|
const capitalsTree = d3.quadtree();
|
||||||
|
|
||||||
// turn all old capitals into towns
|
// turn all old capitals into towns
|
||||||
burgs.filter(b => b.capital).forEach(b => {
|
burgs
|
||||||
|
.filter(b => b.capital)
|
||||||
|
.forEach(b => {
|
||||||
moveBurgToGroup(b.i, "towns");
|
moveBurgToGroup(b.i, "towns");
|
||||||
b.capital = 0;
|
b.capital = 0;
|
||||||
});
|
});
|
||||||
|
|
@ -145,7 +169,7 @@ function regenerateStates() {
|
||||||
// if desired states number is 0
|
// if desired states number is 0
|
||||||
if (regionsInput.value == 0) {
|
if (regionsInput.value == 0) {
|
||||||
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, "warn");
|
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, "warn");
|
||||||
pack.states = pack.states.slice(0,1); // remove all except of neutrals
|
pack.states = pack.states.slice(0, 1); // remove all except of neutrals
|
||||||
pack.states[0].diplomacy = []; // clear diplomacy
|
pack.states[0].diplomacy = []; // clear diplomacy
|
||||||
pack.provinces = [0]; // remove all provinces
|
pack.provinces = [0]; // remove all provinces
|
||||||
pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data
|
pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data
|
||||||
|
|
@ -165,10 +189,12 @@ function regenerateStates() {
|
||||||
pack.states = d3.range(count).map(i => {
|
pack.states = d3.range(count).map(i => {
|
||||||
if (!i) return {i, name: neutral};
|
if (!i) return {i, name: neutral};
|
||||||
|
|
||||||
let capital = null, x = 0, y = 0;
|
let capital = null,
|
||||||
|
x = 0,
|
||||||
|
y = 0;
|
||||||
for (const i of sorted) {
|
for (const i of sorted) {
|
||||||
capital = burgs[i];
|
capital = burgs[i];
|
||||||
x = capital.x, y = capital.y;
|
(x = capital.x), (y = capital.y);
|
||||||
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
||||||
spacing = Math.max(spacing - 1, 1);
|
spacing = Math.max(spacing - 1, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -178,17 +204,17 @@ function regenerateStates() {
|
||||||
moveBurgToGroup(capital.i, "cities");
|
moveBurgToGroup(capital.i, "cities");
|
||||||
|
|
||||||
const culture = capital.culture;
|
const culture = capital.culture;
|
||||||
const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
|
const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
|
||||||
const name = Names.getState(basename, culture);
|
const name = Names.getState(basename, culture);
|
||||||
const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]);
|
const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]);
|
||||||
const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type;
|
const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type;
|
||||||
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
|
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
|
||||||
|
|
||||||
const cultureType = pack.cultures[culture].type;
|
const cultureType = pack.cultures[culture].type;
|
||||||
const coa = COA.generate(capital.coa, .3, null, cultureType);
|
const coa = COA.generate(capital.coa, 0.3, null, cultureType);
|
||||||
coa.shield = capital.coa.shield;
|
coa.shield = capital.coa.shield;
|
||||||
|
|
||||||
return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism, coa};
|
return {i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa};
|
||||||
});
|
});
|
||||||
|
|
||||||
BurgsAndStates.expandStates();
|
BurgsAndStates.expandStates();
|
||||||
|
|
@ -199,8 +225,10 @@ function regenerateStates() {
|
||||||
BurgsAndStates.generateDiplomacy();
|
BurgsAndStates.generateDiplomacy();
|
||||||
BurgsAndStates.defineStateForms();
|
BurgsAndStates.defineStateForms();
|
||||||
BurgsAndStates.generateProvinces(true);
|
BurgsAndStates.generateProvinces(true);
|
||||||
if (!layerIsOn("toggleStates")) toggleStates(); else drawStates();
|
if (!layerIsOn("toggleStates")) toggleStates();
|
||||||
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
|
else drawStates();
|
||||||
|
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||||
|
else drawBorders();
|
||||||
BurgsAndStates.drawStateLabels();
|
BurgsAndStates.drawStateLabels();
|
||||||
Military.generate();
|
Military.generate();
|
||||||
if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems
|
if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems
|
||||||
|
|
@ -224,43 +252,52 @@ function regenerateProvinces() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function regenerateBurgs() {
|
function regenerateBurgs() {
|
||||||
const cells = pack.cells, states = pack.states, Lockedburgs = pack.burgs.filter(b =>b.lock);
|
const cells = pack.cells,
|
||||||
|
states = pack.states,
|
||||||
|
Lockedburgs = pack.burgs.filter(b => b.lock);
|
||||||
rankCells();
|
rankCells();
|
||||||
cells.burg = new Uint16Array(cells.i.length);
|
cells.burg = new Uint16Array(cells.i.length);
|
||||||
const burgs = pack.burgs = [0]; // clear burgs array
|
const burgs = (pack.burgs = [0]); // clear burgs array
|
||||||
states.filter(s => s.i).forEach(s => s.capital = 0); // clear state capitals
|
states.filter(s => s.i).forEach(s => (s.capital = 0)); // clear state capitals
|
||||||
pack.provinces.filter(p => p.i).forEach(p => p.burg = 0); // clear province capitals
|
pack.provinces.filter(p => p.i).forEach(p => (p.burg = 0)); // clear province capitals
|
||||||
const burgsTree = d3.quadtree();
|
const burgsTree = d3.quadtree();
|
||||||
|
|
||||||
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
|
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
|
||||||
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||||
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** .8) + states.length : +manorsInput.value + states.length;
|
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length;
|
||||||
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** .7 / 66); // base min distance between towns
|
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns
|
||||||
|
|
||||||
//clear locked list since ids will change
|
//clear locked list since ids will change
|
||||||
//burglock.selectAll("text").remove();
|
//burglock.selectAll("text").remove();
|
||||||
for (let j=0; j < Lockedburgs.length; j++) {
|
for (let j = 0; j < Lockedburgs.length; j++) {
|
||||||
const id = burgs.length;
|
const id = burgs.length;
|
||||||
const oldBurg = Lockedburgs[j];
|
const oldBurg = Lockedburgs[j];
|
||||||
oldBurg.i = id;
|
oldBurg.i = id;
|
||||||
burgs.push(oldBurg);
|
burgs.push(oldBurg);
|
||||||
burgsTree.add([oldBurg.x, oldBurg.y]);
|
burgsTree.add([oldBurg.x, oldBurg.y]);
|
||||||
cells.burg[oldBurg.cell] = id;
|
cells.burg[oldBurg.cell] = id;
|
||||||
if (oldBurg.capital) {states[oldBurg.state].capital = id; states[oldBurg.state].center = oldBurg.cell;}
|
if (oldBurg.capital) {
|
||||||
|
states[oldBurg.state].capital = id;
|
||||||
|
states[oldBurg.state].center = oldBurg.cell;
|
||||||
|
}
|
||||||
//burglock.append("text").attr("data-id", id);
|
//burglock.append("text").attr("data-id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) {
|
for (let i = 0; i < sorted.length && burgs.length < burgsCount; i++) {
|
||||||
const id = burgs.length;
|
const id = burgs.length;
|
||||||
const cell = sorted[i];
|
const cell = sorted[i];
|
||||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
const x = cells.p[cell][0],
|
||||||
|
y = cells.p[cell][1];
|
||||||
|
|
||||||
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make the placement not uniform
|
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
|
||||||
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
||||||
|
|
||||||
const state = cells.state[cell];
|
const state = cells.state[cell];
|
||||||
const capital = state && !states[state].capital; // if state doesn't have capital, make this burg a capital, no capital for neutral lands
|
const capital = state && !states[state].capital; // if state doesn't have capital, make this burg a capital, no capital for neutral lands
|
||||||
if (capital) {states[state].capital = id; states[state].center = cell;}
|
if (capital) {
|
||||||
|
states[state].capital = id;
|
||||||
|
states[state].center = cell;
|
||||||
|
}
|
||||||
|
|
||||||
const culture = cells.culture[cell];
|
const culture = cells.culture[cell];
|
||||||
const name = Names.getCulture(culture);
|
const name = Names.getCulture(culture);
|
||||||
|
|
@ -270,7 +307,9 @@ function regenerateBurgs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add a capital at former place for states without added capitals
|
// add a capital at former place for states without added capitals
|
||||||
states.filter(s => s.i && !s.removed && !s.capital).forEach(s => {
|
states
|
||||||
|
.filter(s => s.i && !s.removed && !s.capital)
|
||||||
|
.forEach(s => {
|
||||||
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
|
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
|
||||||
s.capital = burg;
|
s.capital = burg;
|
||||||
s.center = pack.burgs[burg].cell;
|
s.center = pack.burgs[burg].cell;
|
||||||
|
|
@ -279,7 +318,9 @@ function regenerateBurgs() {
|
||||||
moveBurgToGroup(burg, "cities");
|
moveBurgToGroup(burg, "cities");
|
||||||
});
|
});
|
||||||
|
|
||||||
pack.features.forEach(f => {if (f.port) f.port = 0}); // reset features ports counter
|
pack.features.forEach(f => {
|
||||||
|
if (f.port) f.port = 0;
|
||||||
|
}); // reset features ports counter
|
||||||
BurgsAndStates.specifyBurgs();
|
BurgsAndStates.specifyBurgs();
|
||||||
BurgsAndStates.defineBurgFeatures();
|
BurgsAndStates.defineBurgFeatures();
|
||||||
BurgsAndStates.drawBurgs();
|
BurgsAndStates.drawBurgs();
|
||||||
|
|
@ -313,10 +354,10 @@ function regenerateEmblems() {
|
||||||
if (!burg.i || burg.removed) return;
|
if (!burg.i || burg.removed) return;
|
||||||
const state = pack.states[burg.state];
|
const state = pack.states[burg.state];
|
||||||
|
|
||||||
let kinship = state ? .25 : 0;
|
let kinship = state ? 0.25 : 0;
|
||||||
if (burg.capital) kinship += .1;
|
if (burg.capital) kinship += 0.1;
|
||||||
else if (burg.port) kinship -= .1;
|
else if (burg.port) kinship -= 0.1;
|
||||||
if (state && burg.culture !== state.culture) kinship -= .25;
|
if (state && burg.culture !== state.culture) kinship -= 0.25;
|
||||||
burg.coa = COA.generate(state ? state.coa : null, kinship, null, burg.type);
|
burg.coa = COA.generate(state ? state.coa : null, kinship, null, burg.type);
|
||||||
burg.coa.shield = COA.getShield(burg.culture, state ? burg.state : 0);
|
burg.coa.shield = COA.getShield(burg.culture, state ? burg.state : 0);
|
||||||
});
|
});
|
||||||
|
|
@ -327,16 +368,16 @@ function regenerateEmblems() {
|
||||||
|
|
||||||
let dominion = false;
|
let dominion = false;
|
||||||
if (!province.burg) {
|
if (!province.burg) {
|
||||||
dominion = P(.2);
|
dominion = P(0.2);
|
||||||
if (province.formName === "Colony") dominion = P(.95); else
|
if (province.formName === "Colony") dominion = P(0.95);
|
||||||
if (province.formName === "Island") dominion = P(.6); else
|
else if (province.formName === "Island") dominion = P(0.6);
|
||||||
if (province.formName === "Islands") dominion = P(.5); else
|
else if (province.formName === "Islands") dominion = P(0.5);
|
||||||
if (province.formName === "Territory") dominion = P(.4); else
|
else if (province.formName === "Territory") dominion = P(0.4);
|
||||||
if (province.formName === "Land") dominion = P(.3);
|
else if (province.formName === "Land") dominion = P(0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameByBurg = province.burg && province.name.slice(0, 3) === parent.name.slice(0, 3);
|
const nameByBurg = province.burg && province.name.slice(0, 3) === parent.name.slice(0, 3);
|
||||||
const kinship = dominion ? 0 : nameByBurg ? .8 : .4;
|
const kinship = dominion ? 0 : nameByBurg ? 0.8 : 0.4;
|
||||||
const culture = pack.cells.culture[province.center];
|
const culture = pack.cells.culture[province.center];
|
||||||
const type = BurgsAndStates.getType(province.center, parent.port);
|
const type = BurgsAndStates.getType(province.center, parent.port);
|
||||||
province.coa = COA.generate(parent.coa, kinship, dominion, type);
|
province.coa = COA.generate(parent.coa, kinship, dominion, type);
|
||||||
|
|
@ -348,7 +389,8 @@ function regenerateEmblems() {
|
||||||
|
|
||||||
function regenerateReligions() {
|
function regenerateReligions() {
|
||||||
Religions.generate();
|
Religions.generate();
|
||||||
if (!layerIsOn("toggleReligions")) toggleReligions(); else drawReligions();
|
if (!layerIsOn("toggleReligions")) toggleReligions();
|
||||||
|
else drawReligions();
|
||||||
}
|
}
|
||||||
|
|
||||||
function regenerateCultures() {
|
function regenerateCultures() {
|
||||||
|
|
@ -356,7 +398,8 @@ function regenerateCultures() {
|
||||||
Cultures.expand();
|
Cultures.expand();
|
||||||
BurgsAndStates.updateCultures();
|
BurgsAndStates.updateCultures();
|
||||||
Religions.updateCultures();
|
Religions.updateCultures();
|
||||||
if (!layerIsOn("toggleCultures")) toggleCultures(); else drawCultures();
|
if (!layerIsOn("toggleCultures")) toggleCultures();
|
||||||
|
else drawCultures();
|
||||||
refreshAllEditors();
|
refreshAllEditors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,15 +416,18 @@ function regenerateIce() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function regenerateMarkers(event) {
|
function regenerateMarkers(event) {
|
||||||
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfMarkers(v));
|
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v => addNumberOfMarkers(v));
|
||||||
else addNumberOfMarkers(gauss(1, .5, .3, 5, 2));
|
else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2));
|
||||||
|
|
||||||
function addNumberOfMarkers(number) {
|
function addNumberOfMarkers(number) {
|
||||||
// remove existing markers and assigned notes
|
// remove existing markers and assigned notes
|
||||||
markers.selectAll("use").each(function() {
|
markers
|
||||||
|
.selectAll("use")
|
||||||
|
.each(function () {
|
||||||
const index = notes.findIndex(n => n.id === this.id);
|
const index = notes.findIndex(n => n.id === this.id);
|
||||||
if (index != -1) notes.splice(index, 1);
|
if (index != -1) notes.splice(index, 1);
|
||||||
}).remove();
|
})
|
||||||
|
.remove();
|
||||||
|
|
||||||
addMarkers(number);
|
addMarkers(number);
|
||||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||||
|
|
@ -389,8 +435,8 @@ function regenerateMarkers(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function regenerateZones(event) {
|
function regenerateZones(event) {
|
||||||
if (isCtrlClick(event)) prompt("Please provide zones number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfZones(v));
|
if (isCtrlClick(event)) prompt("Please provide zones number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v => addNumberOfZones(v));
|
||||||
else addNumberOfZones(gauss(1, .5, .6, 5, 2));
|
else addNumberOfZones(gauss(1, 0.5, 0.6, 5, 2));
|
||||||
|
|
||||||
function addNumberOfZones(number) {
|
function addNumberOfZones(number) {
|
||||||
zones.selectAll("g").remove(); // remove existing zones
|
zones.selectAll("g").remove(); // remove existing zones
|
||||||
|
|
@ -408,10 +454,13 @@ function unpressClickToAddButton() {
|
||||||
|
|
||||||
function toggleAddLabel() {
|
function toggleAddLabel() {
|
||||||
const pressed = document.getElementById("addLabel").classList.contains("pressed");
|
const pressed = document.getElementById("addLabel").classList.contains("pressed");
|
||||||
if (pressed) {unpressClickToAddButton(); return;}
|
if (pressed) {
|
||||||
|
unpressClickToAddButton();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||||
addLabel.classList.add('pressed');
|
addLabel.classList.add("pressed");
|
||||||
closeDialogs(".stable");
|
closeDialogs(".stable");
|
||||||
viewbox.style("cursor", "crosshair").on("click", addLabelOnClick);
|
viewbox.style("cursor", "crosshair").on("click", addLabelOnClick);
|
||||||
tip("Click on map to place label. Hold Shift to add multiple", true);
|
tip("Click on map to place label. Hold Shift to add multiple", true);
|
||||||
|
|
@ -428,10 +477,7 @@ function addLabelOnClick() {
|
||||||
const id = getNextId("label");
|
const id = getNextId("label");
|
||||||
|
|
||||||
let group = labels.select("#addedLabels");
|
let group = labels.select("#addedLabels");
|
||||||
if (!group.size()) group = labels.append("g").attr("id", "addedLabels")
|
if (!group.size()) group = labels.append("g").attr("id", "addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null);
|
||||||
.attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a")
|
|
||||||
.attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC")
|
|
||||||
.attr("font-size", 18).attr("data-size", 18).attr("filter", null);
|
|
||||||
|
|
||||||
const example = group.append("text").attr("x", 0).attr("x", 0).text(name);
|
const example = group.append("text").attr("x", 0).attr("x", 0).text(name);
|
||||||
const width = example.node().getBBox().width;
|
const width = example.node().getBBox().width;
|
||||||
|
|
@ -439,13 +485,22 @@ function addLabelOnClick() {
|
||||||
example.remove();
|
example.remove();
|
||||||
|
|
||||||
group.classed("hidden", false);
|
group.classed("hidden", false);
|
||||||
group.append("text").attr("id", id)
|
group
|
||||||
.append("textPath").attr("xlink:href", "#textPath_"+id).attr("startOffset", "50%").attr("font-size", "100%")
|
.append("text")
|
||||||
.append("tspan").attr("x", x).text(name);
|
.attr("id", id)
|
||||||
|
.append("textPath")
|
||||||
|
.attr("xlink:href", "#textPath_" + id)
|
||||||
|
.attr("startOffset", "50%")
|
||||||
|
.attr("font-size", "100%")
|
||||||
|
.append("tspan")
|
||||||
|
.attr("x", x)
|
||||||
|
.text(name);
|
||||||
|
|
||||||
defs.select("#textPaths")
|
defs
|
||||||
.append("path").attr("id", "textPath_"+id)
|
.select("#textPaths")
|
||||||
.attr("d", `M${point[0]-width},${point[1]} h${width*2}`);
|
.append("path")
|
||||||
|
.attr("id", "textPath_" + id)
|
||||||
|
.attr("d", `M${point[0] - width},${point[1]} h${width * 2}`);
|
||||||
|
|
||||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||||
}
|
}
|
||||||
|
|
@ -466,7 +521,7 @@ function toggleAddRiver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||||
addRiver.classList.add('pressed');
|
addRiver.classList.add("pressed");
|
||||||
document.getElementById("addNewRiver").classList.add("pressed");
|
document.getElementById("addNewRiver").classList.add("pressed");
|
||||||
closeDialogs(".stable");
|
closeDialogs(".stable");
|
||||||
viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
|
viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
|
||||||
|
|
@ -486,22 +541,27 @@ function addRiverOnClick() {
|
||||||
|
|
||||||
// height with added t value to make map less depressed
|
// height with added t value to make map less depressed
|
||||||
const h = Array.from(cells.h)
|
const h = Array.from(cells.h)
|
||||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)
|
.map((h, i) => (h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100))
|
||||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000);
|
.map((h, i) => (h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000));
|
||||||
Rivers.resolveDepressions(h);
|
Rivers.resolveDepressions(h, 200);
|
||||||
|
|
||||||
while (i) {
|
while (i) {
|
||||||
cells.r[i] = river;
|
cells.r[i] = river;
|
||||||
const x = cells.p[i][0], y = cells.p[i][1];
|
const x = cells.p[i][0],
|
||||||
dataRiver.push({x, y, cell:i});
|
y = cells.p[i][1];
|
||||||
|
dataRiver.push({x, y, cell: i});
|
||||||
|
|
||||||
const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
|
const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
|
||||||
if (h[i] <= h[min]) {tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); return;}
|
if (h[i] <= h[min]) {
|
||||||
const tx = cells.p[min][0], ty = cells.p[min][1];
|
tip(`Cell ${i} is depressed, river cannot flow further`, false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tx = cells.p[min][0],
|
||||||
|
ty = cells.p[min][1];
|
||||||
|
|
||||||
if (h[min] < 20) {
|
if (h[min] < 20) {
|
||||||
// pour to water body
|
// pour to water body
|
||||||
dataRiver.push({x: tx, y: ty, cell:i});
|
dataRiver.push({x: tx, y: ty, cell: i});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,19 +586,22 @@ function addRiverOnClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// extend old river
|
// extend old river
|
||||||
rivers.select("#river"+r).remove();
|
rivers.select("#river" + r).remove();
|
||||||
cells.i.filter(i => cells.r[i] === river).forEach(i => cells.r[i] = r);
|
cells.i.filter(i => cells.r[i] === river).forEach(i => (cells.r[i] = r));
|
||||||
riverCells.forEach(i => cells.r[i] = 0);
|
riverCells.forEach(i => (cells.r[i] = 0));
|
||||||
river = r;
|
river = r;
|
||||||
cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]];
|
cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]];
|
||||||
i = min;
|
i = min;
|
||||||
}
|
}
|
||||||
|
|
||||||
const points = Rivers.addMeandering(dataRiver, 1, .5);
|
const points = Rivers.addMeandering(dataRiver, 1, 0.5);
|
||||||
const widthFactor = rn(.8 + Math.random() * .4, 1); // river width modifier [.8, 1.2]
|
const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2]
|
||||||
const sourceWidth = .1;
|
const sourceWidth = 0.1;
|
||||||
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
||||||
rivers.append("path").attr("d", path).attr("id", "river"+river);
|
rivers
|
||||||
|
.append("path")
|
||||||
|
.attr("d", path)
|
||||||
|
.attr("id", "river" + river);
|
||||||
|
|
||||||
// add new river to data or change extended river attributes
|
// add new river to data or change extended river attributes
|
||||||
const r = pack.rivers.find(r => r.i === river);
|
const r = pack.rivers.find(r => r.i === river);
|
||||||
|
|
@ -555,10 +618,10 @@ function addRiverOnClick() {
|
||||||
const source = dataRiver[0].cell;
|
const source = dataRiver[0].cell;
|
||||||
const width = rn(offset ** 2, 2); // mounth width in km
|
const width = rn(offset ** 2, 2); // mounth width in km
|
||||||
const name = Rivers.getName(mouth);
|
const name = Rivers.getName(mouth);
|
||||||
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
|
const smallLength = pack.rivers.map(r => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)];
|
||||||
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
|
const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River";
|
||||||
|
|
||||||
pack.rivers.push({i:river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
|
pack.rivers.push({i: river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d3.event.shiftKey === false) {
|
if (d3.event.shiftKey === false) {
|
||||||
|
|
@ -570,10 +633,13 @@ function addRiverOnClick() {
|
||||||
|
|
||||||
function toggleAddRoute() {
|
function toggleAddRoute() {
|
||||||
const pressed = document.getElementById("addRoute").classList.contains("pressed");
|
const pressed = document.getElementById("addRoute").classList.contains("pressed");
|
||||||
if (pressed) {unpressClickToAddButton(); return;}
|
if (pressed) {
|
||||||
|
unpressClickToAddButton();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||||
addRoute.classList.add('pressed');
|
addRoute.classList.add("pressed");
|
||||||
closeDialogs(".stable");
|
closeDialogs(".stable");
|
||||||
viewbox.style("cursor", "crosshair").on("click", addRouteOnClick);
|
viewbox.style("cursor", "crosshair").on("click", addRouteOnClick);
|
||||||
tip("Click on map to add a first control point", true);
|
tip("Click on map to add a first control point", true);
|
||||||
|
|
@ -590,10 +656,13 @@ function addRouteOnClick() {
|
||||||
|
|
||||||
function toggleAddMarker() {
|
function toggleAddMarker() {
|
||||||
const pressed = document.getElementById("addMarker").classList.contains("pressed");
|
const pressed = document.getElementById("addMarker").classList.contains("pressed");
|
||||||
if (pressed) {unpressClickToAddButton(); return;}
|
if (pressed) {
|
||||||
|
unpressClickToAddButton();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||||
addMarker.classList.add('pressed');
|
addMarker.classList.add("pressed");
|
||||||
closeDialogs(".stable");
|
closeDialogs(".stable");
|
||||||
viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
|
viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
|
||||||
tip("Click on map to add a marker. Hold Shift to add multiple", true);
|
tip("Click on map to add a marker. Hold Shift to add multiple", true);
|
||||||
|
|
@ -602,27 +671,44 @@ function toggleAddMarker() {
|
||||||
|
|
||||||
function addMarkerOnClick() {
|
function addMarkerOnClick() {
|
||||||
const point = d3.mouse(this);
|
const point = d3.mouse(this);
|
||||||
const x = rn(point[0], 2), y = rn(point[1], 2);
|
const x = rn(point[0], 2),
|
||||||
|
y = rn(point[1], 2);
|
||||||
const id = getNextId("markerElement");
|
const id = getNextId("markerElement");
|
||||||
|
|
||||||
const selected = markerSelectGroup.value;
|
const selected = markerSelectGroup.value;
|
||||||
const valid = selected && d3.select("#defs-markers").select("#"+selected).size();
|
const valid =
|
||||||
const symbol = valid ? "#"+selected : "#marker0";
|
selected &&
|
||||||
|
d3
|
||||||
|
.select("#defs-markers")
|
||||||
|
.select("#" + selected)
|
||||||
|
.size();
|
||||||
|
const symbol = valid ? "#" + selected : "#marker0";
|
||||||
const added = markers.select("[data-id='" + symbol + "']").size();
|
const added = markers.select("[data-id='" + symbol + "']").size();
|
||||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1;
|
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1;
|
||||||
if (isNaN(desired)) desired = 1;
|
if (isNaN(desired)) desired = 1;
|
||||||
const size = desired * 5 + 25 / scale;
|
const size = desired * 5 + 25 / scale;
|
||||||
|
|
||||||
markers.append("use").attr("id", id).attr("xlink:href", symbol).attr("data-id", symbol)
|
markers
|
||||||
.attr("data-x", x).attr("data-y", y).attr("x", x - size / 2).attr("y", y - size)
|
.append("use")
|
||||||
.attr("data-size", desired).attr("width", size).attr("height", size);
|
.attr("id", id)
|
||||||
|
.attr("xlink:href", symbol)
|
||||||
|
.attr("data-id", symbol)
|
||||||
|
.attr("data-x", x)
|
||||||
|
.attr("data-y", y)
|
||||||
|
.attr("x", x - size / 2)
|
||||||
|
.attr("y", y - size)
|
||||||
|
.attr("data-size", desired)
|
||||||
|
.attr("width", size)
|
||||||
|
.attr("height", size);
|
||||||
|
|
||||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewCellDetails() {
|
function viewCellDetails() {
|
||||||
$("#cellInfo").dialog({
|
$("#cellInfo").dialog({
|
||||||
resizable: false, width: "22em", title: "Cell Details",
|
resizable: false,
|
||||||
|
width: "22em",
|
||||||
|
title: "Cell Details",
|
||||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue