mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
commit
16ee659f2f
13 changed files with 460 additions and 181 deletions
16
index.css
16
index.css
|
|
@ -114,7 +114,7 @@ button, select, a, .pointer {
|
||||||
fill-rule: evenodd;
|
fill-rule: evenodd;
|
||||||
}
|
}
|
||||||
|
|
||||||
#oceanLayers {
|
#oceanLayers, #terrs {
|
||||||
fill-rule: evenodd;
|
fill-rule: evenodd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1028,10 +1028,10 @@ div#regimentSelectorBody > div > div {
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-div {
|
.color-div {
|
||||||
width: 2.5em;
|
width: 3em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: .1em .2em;
|
margin: 0 .16em;
|
||||||
border: 1px #c5c5c5 groove;
|
border: 1px #c5c5c5 groove;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
@ -2101,6 +2101,16 @@ svg.button {
|
||||||
text-shadow: 0px 1px 4px #4c3a35;
|
text-shadow: 0px 1px 4px #4c3a35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.epgrid line {
|
||||||
|
stroke: lightgrey;
|
||||||
|
stroke-opacity: .7;
|
||||||
|
shape-rendering: crispEdges;
|
||||||
|
}
|
||||||
|
|
||||||
|
.epgrid path {
|
||||||
|
stroke-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#debug {
|
#debug {
|
||||||
font-size: 1px;
|
font-size: 1px;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
|
|
|
||||||
43
index.html
43
index.html
|
|
@ -1883,8 +1883,8 @@
|
||||||
<button data-tip="Display brushes panel" id="paintBrushes">Paint Brushes</button>
|
<button data-tip="Display brushes panel" id="paintBrushes">Paint Brushes</button>
|
||||||
<button data-tip="Open template editor" id="applyTemplate" style="display: none">Template Editor</button>
|
<button data-tip="Open template editor" id="applyTemplate" style="display: none">Template Editor</button>
|
||||||
<button data-tip="Open Image Converter" id="convertImage" style="display: none">Image Converter</button>
|
<button data-tip="Open Image Converter" id="convertImage" style="display: none">Image Converter</button>
|
||||||
<button data-tip="Render heightmap data as a small monochrome image" id="heightmapPreview">Heightmap Preview</button>
|
<button data-tip="Render heightmap data as a small monochrome image" id="heightmapPreview">Preview</button>
|
||||||
<button data-tip="Preview heightmap in 3D scene" id="heightmap3DView">3D</button>
|
<button data-tip="Preview heightmap in 3D scene" id="heightmap3DView">3D scene</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="customizeOptions">
|
<div id="customizeOptions">
|
||||||
|
|
@ -2135,6 +2135,22 @@
|
||||||
|
|
||||||
<div id="elevationProfile" class="dialog" style="display: none" width="100%">
|
<div id="elevationProfile" class="dialog" style="display: none" width="100%">
|
||||||
<div id="elevationGraph" data-tip="Elevation profile"></div>
|
<div id="elevationGraph" data-tip="Elevation profile"></div>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<div id="epControls">
|
||||||
|
<span data-tip="Set height scale">Height scale: <input id="epScaleRange" type="range" min=1 max=100 value=50></span>
|
||||||
|
<span data-tip="Set curve profile">Curve:
|
||||||
|
<select id="epCurve">
|
||||||
|
<option>Linear</option>
|
||||||
|
<option selected>Basis spline</option>
|
||||||
|
<option>Bundle</option>
|
||||||
|
<option>Cubic Catmull-Rom</option>
|
||||||
|
<option>Monotone X</option>
|
||||||
|
<option>Natural</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<span><button id="epSave" data-tip="Download the chart data as a CSV file" class="icon-download"></button></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="routeEditor" class="dialog" style="display: none">
|
<div id="routeEditor" class="dialog" style="display: none">
|
||||||
|
|
@ -2492,8 +2508,8 @@
|
||||||
<button id="battleAddRegiment" data-tip="Add regiment to the battle" class="icon-user-plus"></button>
|
<button id="battleAddRegiment" data-tip="Add regiment to the battle" class="icon-user-plus"></button>
|
||||||
<button id="battleRoll" data-tip="Roll dice to update random factor" class="icon-die"></button>
|
<button id="battleRoll" data-tip="Roll dice to update random factor" class="icon-die"></button>
|
||||||
<button id="battleRun" data-tip="Iterate battle" class="icon-play"></button>
|
<button id="battleRun" data-tip="Iterate battle" class="icon-play"></button>
|
||||||
<button id="battleApply" data-tip="Apply battle results and close the screen" class="icon-check"></button>
|
<button id="battleApply" data-tip="End battle: apply current results and close the screen" class="icon-check"></button>
|
||||||
<button id="battleCancel" data-tip="Cancel battle results and close the screen" class="icon-cancel"></button>
|
<button id="battleCancel" data-tip="Cancel battle: roll back results and close the screen" class="icon-cancel"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2658,17 +2674,18 @@
|
||||||
|
|
||||||
<div id="convertImageButtons">
|
<div id="convertImageButtons">
|
||||||
<button id="convertImageLoad" data-tip="Load image to convert" class="icon-upload"></button>
|
<button id="convertImageLoad" data-tip="Load image to convert" class="icon-upload"></button>
|
||||||
<button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good to monochrome images)" class="icon-adjust"></button>
|
<button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good for monochrome images)" class="icon-adjust"></button>
|
||||||
<button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good to colored images)" class="icon-brush"></button>
|
<button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good for colored images)" class="icon-paint-roller"></button>
|
||||||
|
<button id="convertAutoFMG" data-tip="Auto-assign colors using generator scheme (for exported colored heightmaps)" class="icon-layer-group"></button>
|
||||||
<button id="convertColorsButton" data-tip="Set maximum number of colors" class="icon-signal"></button>
|
<button id="convertColorsButton" data-tip="Set maximum number of colors" class="icon-signal"></button>
|
||||||
<input id="convertColors" value="128" style="display: none"/>
|
<input id="convertColors" value="100" style="display: none"/>
|
||||||
<button id="convertComplete" data-tip="Complete the conversion. All unassigned colors will be considered as ocean" class="icon-check"></button>
|
<button id="convertComplete" data-tip="Complete the conversion. All unassigned colors will be considered as ocean" class="icon-check"></button>
|
||||||
<button id="convertCancel" data-tip="Cancel the conversion. Previous heightmap will be restored" class="icon-cancel"></button>
|
<button id="convertCancel" data-tip="Cancel the conversion. Previous heightmap will be restored" class="icon-cancel"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="Set opacity of the loaded image" style="padding-top: 4px"><i>Overlay opacity:</i><br>
|
<div data-tip="Set opacity of the loaded image" style="padding-top: .4em"><i>Overlay opacity:</i><br>
|
||||||
<input id="convertOverlay" type="range" min=0 max=1 step=.01 value=0 style="width: 11.5em">
|
<input id="convertOverlay" type="range" min=0 max=1 step=.01 value=0 style="width: 12.6em">
|
||||||
<input id="convertOverlayNumber" type="number" min=0 max=1 step=.01 value=0 style="width: 3.5em">
|
<input id="convertOverlayNumber" type="number" min=0 max=1 step=.01 value=0 style="width: 4.2em">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="Select a color below and assign a height value for it" id="colorsSelect" style="display: none">
|
<div data-tip="Select a color below and assign a height value for it" id="colorsSelect" style="display: none">
|
||||||
|
|
@ -2679,11 +2696,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="Select a color to re-assign the height value" id="colorsAssigned" style="display: none">
|
<div data-tip="Select a color to re-assign the height value" id="colorsAssigned" style="display: none">
|
||||||
<i>Assigned colors: </i><br>
|
<i>Assigned colors (<span id="colorsAssignedNumber"></span>):</i><br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="Select a color to assign a height value" id="colorsUnassigned" style="display: none">
|
<div data-tip="Select a color to assign a height value" id="colorsUnassigned" style="display: none">
|
||||||
<i>Unassigned colors: </i><br>
|
<i>Unassigned colors (<span id="colorsUnassignedNumber"></span>):</i><br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2853,7 +2870,7 @@
|
||||||
<option data-form="Union" value="United States">United States</option>
|
<option data-form="Union" value="United States">United States</option>
|
||||||
<option data-form="Wild" value="United Tribes">United Tribes</option>
|
<option data-form="Wild" value="United Tribes">United Tribes</option>
|
||||||
</select>
|
</select>
|
||||||
<input id="stateNameEditorCustomForm" placeholder="type form name" data-tip="Create custom state form name" style="display: none; width: 11em;">
|
<input id="stateNameEditorCustomForm" placeholder="type form name" data-tip="Enter custom form name" style="display: none; width: 11em;">
|
||||||
<span id="stateNameEditorAddForm" data-tip="Click to add custom state form name to the list" class="icon-plus pointer"></span>
|
<span id="stateNameEditorAddForm" data-tip="Click to add custom state form name to the list" class="icon-plus pointer"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
3
main.js
3
main.js
|
|
@ -343,7 +343,8 @@ function showWelcomeMessage() {
|
||||||
<ul>${post}
|
<ul>${post}
|
||||||
<li>Battle simulation</li>
|
<li>Battle simulation</li>
|
||||||
<li>Ice layer and Ice editor</li>
|
<li>Ice layer and Ice editor</li>
|
||||||
<li>Route Elevation profile</li>
|
<li>Route and River Elevation profile</li>
|
||||||
|
<li>Image Converter enhancement</li>
|
||||||
<li>Name generator improvement</li>
|
<li>Name generator improvement</li>
|
||||||
<li>Fogging style change</li>
|
<li>Fogging style change</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@
|
||||||
const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : "";
|
const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : "";
|
||||||
|
|
||||||
const composition = r.a ? Object.keys(r.u).map(t => `— ${t}: ${r.u[t]}`).join("\r\n") : null;
|
const composition = r.a ? Object.keys(r.u).map(t => `— ${t}: ${r.u[t]}`).join("\r\n") : null;
|
||||||
const troops = composition ? `\r\n\r\nRegiment composition:\r\n${composition}.` : "";
|
const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : "";
|
||||||
|
|
||||||
const campaign = s.campaigns ? ra(s.campaigns) : null;
|
const campaign = s.campaigns ? ra(s.campaigns) : null;
|
||||||
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year-100, 150, 1, options.year-6);
|
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year-100, 150, 1, options.year-6);
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,8 @@ function saveGeoJSON_Cells() {
|
||||||
data += " \"state\": \""+cells.state[i]+"\",\n";
|
data += " \"state\": \""+cells.state[i]+"\",\n";
|
||||||
data += " \"province\": \""+cells.province[i]+"\",\n";
|
data += " \"province\": \""+cells.province[i]+"\",\n";
|
||||||
data += " \"culture\": \""+cells.culture[i]+"\",\n";
|
data += " \"culture\": \""+cells.culture[i]+"\",\n";
|
||||||
data += " \"religion\": \""+cells.religion[i]+"\"\n";
|
data += " \"religion\": \""+cells.religion[i]+"\",\n";
|
||||||
|
data += " \"neighbors\": ["+cells.c[i]+"]\n";
|
||||||
data +=" }\n},\n";
|
data +=" }\n},\n";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ class Battle {
|
||||||
this.y = defender.y;
|
this.y = defender.y;
|
||||||
this.name = this.getBattleName();
|
this.name = this.getBattleName();
|
||||||
this.iteration = 0;
|
this.iteration = 0;
|
||||||
this.attackers = {regiments:[], distances:[], morale:100};
|
this.attackers = {regiments:[], distances:[], morale:100, casualties:0};
|
||||||
this.defenders = {regiments:[], distances:[], morale:100};
|
this.defenders = {regiments:[], distances:[], morale:100, casualties:0};
|
||||||
|
|
||||||
this.addHeaders();
|
this.addHeaders();
|
||||||
this.addRegiment("attackers", attacker);
|
this.addRegiment("attackers", attacker);
|
||||||
|
|
@ -260,6 +260,8 @@ class Battle {
|
||||||
|
|
||||||
this.calculateCasualties("attackers", casualtiesA);
|
this.calculateCasualties("attackers", casualtiesA);
|
||||||
this.calculateCasualties("defenders", casualtiesD);
|
this.calculateCasualties("defenders", casualtiesD);
|
||||||
|
this.attackers.casualties += casualtiesA;
|
||||||
|
this.defenders.casualties += casualtiesD;
|
||||||
|
|
||||||
// change morale
|
// change morale
|
||||||
this.attackers.morale = Math.max(this.attackers.morale - casualtiesA * 100, 0);
|
this.attackers.morale = Math.max(this.attackers.morale - casualtiesA * 100, 0);
|
||||||
|
|
@ -326,12 +328,79 @@ class Battle {
|
||||||
}
|
}
|
||||||
|
|
||||||
applyResults() {
|
applyResults() {
|
||||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => {
|
const battleName = this.name;
|
||||||
|
const maxCasualties = Math.max(this.attackers.casualties, this.attackers.casualties);
|
||||||
|
const relativeCasualties = this.defenders.casualties / (this.attackers.casualties + this.attackers.casualties);
|
||||||
|
const battleStatus = getBattleStatus(relativeCasualties, maxCasualties);
|
||||||
|
function getBattleStatus(relative, max) {
|
||||||
|
if (isNaN(relative)) return ["standoff", "standoff"]; // if no casualties at all
|
||||||
|
if (max < .05) return ["minor skirmishes", "minor skirmishes"];
|
||||||
|
if (relative > 95) return ["attackers flawless victory", "disorderly retreat of defenders"];
|
||||||
|
if (relative > .7) return ["attackers decisive victory", "defenders disastrous defeat"];
|
||||||
|
if (relative > .6) return ["attackers victory", "defenders defeat"];
|
||||||
|
if (relative > .4) return ["stalemate", "stalemate"];
|
||||||
|
if (relative > .3) return ["attackers defeat", "defenders victory"];
|
||||||
|
if (relative > 0.5) return ["attackers disastrous defeat", "decisive victory of defenders"];
|
||||||
|
if (relative >= 0) return ["attackers disorderly retreat", "flawless victory of defenders"];
|
||||||
|
return ["stalemate", "stalemate"]; // exception
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attackers.regiments.forEach(r => applyResultForSide(r, "attackers"));
|
||||||
|
this.defenders.regiments.forEach(r => applyResultForSide(r, "defenders"));
|
||||||
|
|
||||||
|
function applyResultForSide(r, side) {
|
||||||
|
const id = "regiment" + r.state + "-" + r.i;
|
||||||
|
|
||||||
|
// add result to regiment note
|
||||||
|
const note = notes.find(n => n.id === id);
|
||||||
|
if (note) {
|
||||||
|
const status = side === "attackers" ? battleStatus[0] : battleStatus[1];
|
||||||
|
const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1;
|
||||||
|
const regStatus =
|
||||||
|
losses === 1 ? "is destroyed" :
|
||||||
|
losses > .8 ? "is almost completely destroyed" :
|
||||||
|
losses > .5 ? "suffered terrible losses" :
|
||||||
|
losses > .3 ? "suffered severe losses" :
|
||||||
|
losses > .2 ? "suffered heavy losses" :
|
||||||
|
losses > .05 ? "suffered significant losses" :
|
||||||
|
losses > 0 ? "suffered unsignificant losses" :
|
||||||
|
"left the battle without loss";
|
||||||
|
const casualties = Object.keys(r.casualties).map(t => r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null).filter(c => c).join(", ");
|
||||||
|
const casualtiesText = casualties ? " Casualties: " + casualties : "";
|
||||||
|
const legend = `\r\n\r\n${battleName} (${options.year} ${options.eraShort}): ${status}. The regiment ${regStatus}.${casualtiesText}`;
|
||||||
|
note.legend += legend;
|
||||||
|
}
|
||||||
|
|
||||||
r.u = Object.assign({}, r.survivors);
|
r.u = Object.assign({}, r.survivors);
|
||||||
r.a = d3.sum(Object.values(r.u)); // reg total
|
r.a = d3.sum(Object.values(r.u)); // reg total
|
||||||
armies.select(`g#regiment${r.state}-${r.i} > text`).text(Military.getTotal(r)); // update reg box
|
armies.select(`g#${id} > text`).text(Military.getTotal(r)); // update reg box
|
||||||
Military.moveRegiment(r, r.x + rand(30) - 15, r.y + rand(30) - 15);
|
Military.moveRegiment(r, r.x + rand(20) - 10, r.y + rand(20) - 10);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// append battlefield marker
|
||||||
|
void function addMarkerSymbol() {
|
||||||
|
if (svg.select("#defs-markers").select("#marker_battlefield").size()) return;
|
||||||
|
const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30");
|
||||||
|
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||||
|
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||||
|
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0)
|
||||||
|
.attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||||
|
}()
|
||||||
|
|
||||||
|
const getSide = (regs, n) => regs.length > 1 ?
|
||||||
|
`${n ? "regiments" : "forces"} of ${[... new Set(regs.map(r => pack.states[r.state].name))].join(", ")}` :
|
||||||
|
getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name;
|
||||||
|
const getLosses = casualties => Math.min(rn(casualties * 100), 100);
|
||||||
|
|
||||||
|
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(this.defenders.regiments, 0)}. The battle ended in ${battleStatus[+P(.7)]}.
|
||||||
|
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
|
||||||
|
const id = getNextId("markerElement");
|
||||||
|
notes.push({id, name:this.name, legend});
|
||||||
|
|
||||||
|
markers.append("use").attr("id", id)
|
||||||
|
.attr("xlink:href", "#marker_battlefield").attr("data-id", "#marker_battlefield")
|
||||||
|
.attr("data-x", this.x).attr("data-y", this.y).attr("x", this.x - 15).attr("y", this.y - 30)
|
||||||
|
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||||
|
|
||||||
$("#battleScreen").dialog("destroy");
|
$("#battleScreen").dialog("destroy");
|
||||||
this.cleanData();
|
this.cleanData();
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,17 @@ function editBurg(id) {
|
||||||
if (deg < 0) deg += 360;
|
if (deg < 0) deg += 360;
|
||||||
let norm = rn(normalize(deg, 0, 360) * 8) / 4;
|
let norm = rn(normalize(deg, 0, 360) * 8) / 4;
|
||||||
if (norm === 2) norm = 0;
|
if (norm === 2) norm = 0;
|
||||||
return "sea="+norm;
|
switch(norm) {
|
||||||
|
case 0 : return "&southSea=1";
|
||||||
|
case 0.25 : return "&southSea=1&westSea=1";
|
||||||
|
case 0.50 : return "&westSea=1";
|
||||||
|
case 0.75 : return "&westSea=1&northSea=1";
|
||||||
|
case 1 : return "&northSea=1";
|
||||||
|
case 1.25 : return "&northSea=1&eastSea=1";
|
||||||
|
case 1.5 : return "&eastSea=1";
|
||||||
|
case 1.75 : return "&eastSea=1&southSea=1";
|
||||||
|
}
|
||||||
|
return "&sea="+norm;
|
||||||
// debug.selectAll("*").remove();
|
// debug.selectAll("*").remove();
|
||||||
// pack.burgs.filter(b => b.port).forEach(b => {
|
// pack.burgs.filter(b => b.port).forEach(b => {
|
||||||
// var p1 = pack.cells.p[b.cell];
|
// var p1 = pack.cells.p[b.cell];
|
||||||
|
|
@ -450,4 +460,4 @@ function editBurg(id) {
|
||||||
unselect();
|
unselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function showEPForRoute(node) {
|
function showEPForRoute(node) {
|
||||||
const points = [];
|
const points = [];
|
||||||
debug.select("#controlPoints").selectAll("circle").each(function() {
|
debug.select("#controlPoints").selectAll("circle").each(function() {
|
||||||
|
|
@ -21,19 +22,20 @@ function showEPForRiver(node) {
|
||||||
showElevationProfile(points, riverLen, true);
|
showElevationProfile(points, riverLen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeElevationProfile() {
|
|
||||||
}
|
|
||||||
|
|
||||||
function showElevationProfile(data, routeLen, isRiver) {
|
function showElevationProfile(data, routeLen, isRiver) {
|
||||||
// data is an array of cell indexes, routeLen is the distance, isRiver should be true for rivers, false otherwise
|
// data is an array of cell indexes, routeLen is the distance (in actual metres/feet), isRiver should be true for rivers, false otherwise
|
||||||
document.getElementById("elevationGraph").innerHTML = "";
|
|
||||||
|
document.getElementById("epScaleRange").addEventListener("change", draw);
|
||||||
|
document.getElementById("epCurve").addEventListener("change", draw);
|
||||||
|
document.getElementById("epSave").addEventListener("click", downloadCSV);
|
||||||
|
|
||||||
$("#elevationProfile").dialog({
|
$("#elevationProfile").dialog({
|
||||||
title: "Elevation profile", resizable: false, width: window.width,
|
title: "Elevation profile", resizable: false, width: window.width,
|
||||||
position: {my: "left top", at: "left+20 bottom-240", of: window, collision: "fit"}
|
close: closeElevationProfile,
|
||||||
|
position: {my: "left top", at: "left+20 bottom-500", of: window, collision: "fit"}
|
||||||
});
|
});
|
||||||
|
|
||||||
// prevent river graphs from showing rivers as flowing uphill
|
// prevent river graphs from showing rivers as flowing uphill - remember the general slope
|
||||||
var slope = 0;
|
var slope = 0;
|
||||||
if (isRiver) {
|
if (isRiver) {
|
||||||
if (pack.cells.h[data[0]] < pack.cells.h[data[data.length-1]]) {
|
if (pack.cells.h[data[0]] < pack.cells.h[data[data.length-1]]) {
|
||||||
|
|
@ -43,12 +45,22 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const points = [];
|
const chartWidth = window.innerWidth-180;
|
||||||
var prevB=0, prevH=-1, i=0, j=0, cell=0, b=0, ma=0, mi=100, h=0;
|
const chartHeight = 300; // height of our land/sea profile, excluding the biomes data below
|
||||||
for (var i=0; i<data.length; i++) {
|
|
||||||
cell = data[i];
|
|
||||||
|
|
||||||
h = pack.cells.h[cell];
|
const xOffset = 80;
|
||||||
|
const yOffset = 80; // this is our drawing starting point from top-left (y = 0) of SVG
|
||||||
|
|
||||||
|
const biomesHeight = 40;
|
||||||
|
|
||||||
|
let lastBurgIndex = 0;
|
||||||
|
let lastBurgCell = 0;
|
||||||
|
let burgCount = 0;
|
||||||
|
let chartData = {biome:[], burg:[], cell:[], height:[], mi:1000000, ma:0, mih: 100, mah: 0, points:[] }
|
||||||
|
for (let i=0, prevB=0, prevH=-1; i<data.length; i++) {
|
||||||
|
let cell = data[i];
|
||||||
|
|
||||||
|
let h = pack.cells.h[cell];
|
||||||
if (h < 20) h = 20;
|
if (h < 20) h = 20;
|
||||||
|
|
||||||
// check for river up-hill
|
// check for river up-hill
|
||||||
|
|
@ -62,83 +74,214 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
prevH = h;
|
prevH = h;
|
||||||
// river up-hill checks stop here
|
// river up-hill checks stop here
|
||||||
|
|
||||||
mi = Math.min(mi, h);
|
let b = pack.cells.burg[cell];
|
||||||
ma = Math.max(ma, h);
|
|
||||||
|
|
||||||
b = pack.cells.burg[cell];
|
|
||||||
if (b == prevB) b = 0;
|
if (b == prevB) b = 0;
|
||||||
else prevB = b;
|
else prevB = b;
|
||||||
points.push({x:j, y:h, b:b});
|
if (b) { burgCount++; lastBurgIndex = i; lastBurgCell = cell; }
|
||||||
j++;
|
|
||||||
|
chartData.biome[i] = pack.cells.biome[cell];
|
||||||
|
chartData.burg[i] = b;
|
||||||
|
chartData.cell[i] = cell;
|
||||||
|
let sh = getHeight(h);
|
||||||
|
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(' ')));
|
||||||
|
chartData.mih = Math.min(chartData.mih, h);
|
||||||
|
chartData.mah = Math.max(chartData.mah, h);
|
||||||
|
chartData.mi = Math.min(chartData.mi, chartData.height[i]);
|
||||||
|
chartData.ma = Math.max(chartData.ma, chartData.height[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = window.innerWidth-280;
|
if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length-1] && lastBurgIndex < data.length) {
|
||||||
h = 100;
|
chartData.burg[data.length-1] = chartData.burg[lastBurgIndex];
|
||||||
|
chartData.burg[lastBurgIndex] = 0;
|
||||||
const xOffset = 100;
|
|
||||||
const yOffset = 80;
|
|
||||||
|
|
||||||
var chart = d3.select("#elevationGraph").append("svg").attr("width", w+200).attr("height", h+yOffset).attr("id", "elevationGraph");
|
|
||||||
// arrow-head definition
|
|
||||||
chart.append("defs").append("marker").attr("id", "arrowhead").attr("orient", "auto").attr("markerWidth", "2").attr("markerHeight", "4").attr("refX", "0.1").attr("refY", "2").append("path").attr("d", "M0,0 V4 L2,2 Z");
|
|
||||||
|
|
||||||
// main graph line
|
|
||||||
var lineFunc = d3.line().x(d => d.x * w / points.length + xOffset).y(d => h-d.y + yOffset);
|
|
||||||
chart.append("path").attr("d", lineFunc(points)).attr("stroke", "purple").attr("fill", "none").attr("id", "elevationLine");
|
|
||||||
|
|
||||||
// y-axis labels for starting and ending heights
|
|
||||||
chart.append("text").attr("id", "epy0").attr("x", xOffset-10).attr("y", h-points[0].y + yOffset).attr("text-anchor", "end");
|
|
||||||
document.getElementById("epy0").innerHTML = getHeight(points[0].y);
|
|
||||||
chart.append("text").attr("id", "epy1").attr("x", w+100).attr("y", h-points[points.length-1].y + yOffset).attr("text-anchor", "start");
|
|
||||||
document.getElementById("epy1").innerHTML = getHeight(points[points.length-1].y);
|
|
||||||
|
|
||||||
// y-axis labels for minimum and maximum heights (if not too close to start/end heights)
|
|
||||||
if (Math.abs(ma - points[0].y) > 3 && Math.abs(ma - points[points.length-1].y) > 3) {
|
|
||||||
chart.append("text").attr("id", "epy2").attr("x", xOffset-10).attr("y", h-ma + yOffset).attr("text-anchor", "end");
|
|
||||||
document.getElementById("epy2").innerHTML = getHeight(ma);
|
|
||||||
}
|
}
|
||||||
if (Math.abs(mi - points[0].y) > 3 && Math.abs(mi - points[points.length-1].y) > 3) {
|
|
||||||
chart.append("text").attr("id", "epy3").attr("x", xOffset-10).attr("y", h-mi + yOffset).attr("text-anchor", "end");
|
|
||||||
document.getElementById("epy3").innerHTML = getHeight(mi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// x-axis label for start, quarter, halfway and three-quarter, and end
|
|
||||||
chart.append("text").attr("id", "epx1").attr("x", xOffset).attr("y", h+yOffset).attr("text-anchor", "middle");
|
|
||||||
chart.append("text").attr("id", "epx2").attr("x", w / 4 + xOffset).attr("y", h+yOffset).attr("text-anchor", "middle");
|
|
||||||
chart.append("text").attr("id", "epx3").attr("x", w / 2 + xOffset).attr("y", h+yOffset).attr("text-anchor", "middle");
|
|
||||||
chart.append("text").attr("id", "epx4").attr("x", w / 4*3 + xOffset).attr("y", h+yOffset).attr("text-anchor", "middle");
|
|
||||||
chart.append("text").attr("id", "epx5").attr("x", w + xOffset).attr("y", h+yOffset).attr("text-anchor", "middle");
|
|
||||||
document.getElementById("epx1").innerHTML = "0 " + distanceUnitInput.value;
|
|
||||||
document.getElementById("epx2").innerHTML = rn(routeLen / 4) + " " + distanceUnitInput.value;
|
|
||||||
document.getElementById("epx3").innerHTML = rn(routeLen / 2) + " " + distanceUnitInput.value;
|
|
||||||
document.getElementById("epx4").innerHTML = rn(routeLen / 4*3) + " " + distanceUnitInput.value;
|
|
||||||
document.getElementById("epx5").innerHTML = rn(routeLen) + " " + distanceUnitInput.value;
|
|
||||||
|
|
||||||
chart.append("path").attr("id", "epx11").attr("d", "M" + (xOffset).toString() + ",0L" + (xOffset).toString() +"," + (h+yOffset-15).toString()).attr("stroke", "lightgray").attr("stroke-width", "1");
|
draw();
|
||||||
chart.append("path").attr("id", "epx12").attr("d", "M" + (w / 4 + xOffset).toString() + "," + (h+yOffset-15).toString() + "L" + (w / 4 + xOffset).toString() + ",0").attr("stroke", "lightgray").attr("stroke-width", "1");
|
|
||||||
chart.append("path").attr("id", "epx13").attr("d", "M" + (w / 2 + xOffset).toString() + "," + (h+yOffset-15).toString() + "L" + (w / 2 + xOffset).toString() + ",0").attr("stroke", "lightgray").attr("stroke-width", "1");
|
|
||||||
chart.append("path").attr("id", "epx14").attr("d", "M" + (w / 4*3 + xOffset).toString() + "," + (h+yOffset-15).toString() + "L" + (w / 4*3 + xOffset).toString() + ",0").attr("stroke", "lightgray").attr("stroke-width", "1");
|
|
||||||
chart.append("path").attr("id", "epx15").attr("d", "M" + (w + xOffset).toString() + ",0L" + (w + xOffset).toString() +"," + (h+yOffset-15).toString()).attr("stroke", "lightgray").attr("stroke-width", "1");
|
|
||||||
|
|
||||||
// draw city labels - try to avoid putting labels over one another
|
function downloadCSV() {
|
||||||
var y1 = 0;
|
let data = "Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n"; // headers
|
||||||
var add = 15;
|
|
||||||
points.forEach(function(p) {
|
|
||||||
if (p.b > 0) {
|
|
||||||
var x1 = p.x * w / points.length + xOffset;
|
|
||||||
y1+=add;
|
|
||||||
if (y1 >= yOffset) { y1 = add; }
|
|
||||||
var d1 = 0;
|
|
||||||
|
|
||||||
// burg name
|
for (let k=0; k<chartData.points.length; k++) {
|
||||||
chart.append("text").attr("id", "ep" + p.b).attr("x", x1).attr("y", y1).attr("text-anchor", "middle");
|
let cell = chartData.cell[k];
|
||||||
document.getElementById("ep" + p.b).innerHTML = pack.burgs[p.b].name;
|
let burg = pack.cells.burg[cell];
|
||||||
|
let biome = pack.cells.biome[cell];
|
||||||
|
let culture = pack.cells.culture[cell];
|
||||||
|
let religion = pack.cells.religion[cell];
|
||||||
|
let province = pack.cells.province[cell];
|
||||||
|
let state = pack.cells.state[cell];
|
||||||
|
let pop = pack.cells.pop[cell];
|
||||||
|
let h = pack.cells.h[cell];
|
||||||
|
|
||||||
// arrow from burg name to graph line
|
data += k+1 + ",";
|
||||||
chart.append("path").attr("id", "eparrow" + p.b).attr("d", "M" + x1.toString() + "," + (y1).toString() + "L" + x1.toString() + "," + parseInt(h-p.y-3+yOffset).toString()).attr("stroke", "black").attr("stroke-width", "1").attr("marker-end", "url(#arrowhead)");
|
data += chartData.points[k][0] + ",";
|
||||||
|
data += chartData.points[k][1] + ",";
|
||||||
|
data += cell + ",";
|
||||||
|
data += getHeight(h) + ",";
|
||||||
|
data += h + ",";
|
||||||
|
data += rn(pop * populationRate.value) + ",";
|
||||||
|
if (burg) {
|
||||||
|
data += pack.burgs[burg].name + ",";
|
||||||
|
data += (pack.burgs[burg].population * populationRate.value * urbanization.value) + ",";
|
||||||
|
} else {
|
||||||
|
data += ",0,";
|
||||||
|
}
|
||||||
|
data += biomesData.name[biome] + ",";
|
||||||
|
data += biomesData.color[biome] + ",";
|
||||||
|
data += pack.cultures[culture].name + ",";
|
||||||
|
data += pack.cultures[culture].color + ",";
|
||||||
|
data += pack.religions[religion].name + ",";
|
||||||
|
data += pack.religions[religion].color + ",";
|
||||||
|
data += pack.provinces[province].name + ",";
|
||||||
|
data += pack.provinces[province].color + ",";
|
||||||
|
data += pack.states[state].name + ",";
|
||||||
|
data += pack.states[state].color + ",";
|
||||||
|
|
||||||
|
data = data + "\n";
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
const name = getFileName("elevation profile") + ".csv";
|
||||||
|
downloadFile(data, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
chartData.points = [];
|
||||||
|
let heightScale = 100 / parseInt(epScaleRange.value);
|
||||||
|
|
||||||
|
heightScale *= 0.90; // curves cause the heights to go slightly higher, adjust here
|
||||||
|
|
||||||
|
const xscale = d3.scaleLinear().domain([0, data.length]).range([0, chartWidth]);
|
||||||
|
const yscale = d3.scaleLinear().domain([0, chartData.ma * heightScale]).range([chartHeight, 0]);
|
||||||
|
|
||||||
|
for (let i=0; i<data.length; i++) {
|
||||||
|
chartData.points.push([xscale(i) + xOffset, yscale(chartData.height[i]) + yOffset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("elevationGraph").innerHTML = "";
|
||||||
|
|
||||||
|
const chart = d3.select("#elevationGraph").append("svg").attr("width", chartWidth+120).attr("height", chartHeight+yOffset+biomesHeight).attr("id", "elevationSVG").attr("class", "epbackground");
|
||||||
|
// arrow-head definition
|
||||||
|
chart.append("defs").append("marker").attr("id", "arrowhead").attr("orient", "auto").attr("markerWidth", "2").attr("markerHeight", "4").attr("refX", "0.1").attr("refY", "2").append("path").attr("d", "M0,0 V4 L2,2 Z").attr("fill", "darkgray");
|
||||||
|
|
||||||
|
let colors = getColorScheme();
|
||||||
|
var landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
||||||
|
|
||||||
|
if (chartData.mah == chartData.mih) {
|
||||||
|
landdef.append("stop").attr("offset", "0%").attr("style", "stop-color:" + getColor(chartData.mih, colors) + ";stop-opacity:1");
|
||||||
|
landdef.append("stop").attr("offset", "100%").attr("style", "stop-color:" + getColor(chartData.mah, colors) + ";stop-opacity:1");
|
||||||
|
} else {
|
||||||
|
for (let k=chartData.mah; k >= chartData.mih; k--) {
|
||||||
|
let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih);
|
||||||
|
landdef.append("stop").attr("offset", perc*100 + "%").attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// land
|
||||||
|
let curve = d3.line().curve(d3.curveBasis); // see https://github.com/d3/d3-shape#curves
|
||||||
|
let epCurveIndex = parseInt(epCurve.selectedIndex);
|
||||||
|
switch(epCurveIndex) {
|
||||||
|
case 0 : curve = d3.line().curve(d3.curveLinear); break;
|
||||||
|
case 1 : curve = d3.line().curve(d3.curveBasis); break;
|
||||||
|
case 2 : curve = d3.line().curve(d3.curveBundle.beta(1)); break;
|
||||||
|
case 3 : curve = d3.line().curve(d3.curveCatmullRom.alpha(0.5)); break;
|
||||||
|
case 4 : curve = d3.line().curve(d3.curveMonotoneX); break;
|
||||||
|
case 5 : curve = d3.line().curve(d3.curveNatural); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the points so that we can add extra straight pieces, else we get curves at the ends of the chart
|
||||||
|
let extra = chartData.points.slice();
|
||||||
|
var path = curve(extra);
|
||||||
|
// this completes the right-hand side and bottom of our land "polygon"
|
||||||
|
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length-1][1]);
|
||||||
|
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||||
|
path += " L" + parseInt(xscale(0) + +xOffset) +"," + parseInt(yscale(0) + +yOffset);
|
||||||
|
path += "Z";
|
||||||
|
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)");
|
||||||
|
|
||||||
|
// biome / heights
|
||||||
|
let g = chart.append("g").attr("id", "epbiomes");
|
||||||
|
const hu = heightUnit.value;
|
||||||
|
for(var k=0; k<chartData.points.length; k++) {
|
||||||
|
const x = chartData.points[k][0];
|
||||||
|
const y = yOffset + chartHeight;
|
||||||
|
const c = biomesData.color[chartData.biome[k]];
|
||||||
|
const dataTip = biomesData.name[chartData.biome[k]]+" (" + chartData.height[k] + " " + hu + ", cell " + chartData.cell[k] + ")";
|
||||||
|
|
||||||
|
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip);
|
||||||
|
}
|
||||||
|
|
||||||
|
const xAxis = d3.axisBottom(xscale).ticks(10).tickFormat(function(d){ return (rn(d / chartData.points.length * routeLen) + " " + distanceUnitInput.value);});
|
||||||
|
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(function(d) { return d + " " + hu; });
|
||||||
|
|
||||||
|
const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat("");
|
||||||
|
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat("");
|
||||||
|
|
||||||
|
chart.append("g")
|
||||||
|
.attr("id", "epxaxis")
|
||||||
|
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset + 20) + ")")
|
||||||
|
.call(xAxis)
|
||||||
|
.selectAll("text")
|
||||||
|
.style("text-anchor", "center")
|
||||||
|
.attr("transform", function(d) {
|
||||||
|
return "rotate(0)" // used to rotate labels, - anti-clockwise, + clockwise
|
||||||
|
});
|
||||||
|
|
||||||
|
chart.append("g")
|
||||||
|
.attr("id", "epyaxis")
|
||||||
|
.attr("transform", "translate(" + parseInt(+xOffset-10) + "," + parseInt(+yOffset) + ")")
|
||||||
|
.call(yAxis);
|
||||||
|
|
||||||
|
// add the X gridlines
|
||||||
|
chart.append("g")
|
||||||
|
.attr("id", "epxgrid")
|
||||||
|
.attr("class", "epgrid")
|
||||||
|
.attr("stroke-dasharray", "4 1")
|
||||||
|
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset) + ")")
|
||||||
|
.call(xGrid);
|
||||||
|
|
||||||
|
// add the Y gridlines
|
||||||
|
chart.append("g")
|
||||||
|
.attr("id", "epygrid")
|
||||||
|
.attr("class", "epgrid")
|
||||||
|
.attr("stroke-dasharray", "4 1")
|
||||||
|
.attr("transform", "translate(" + xOffset + "," + yOffset + ")")
|
||||||
|
.call(yGrid);
|
||||||
|
|
||||||
|
// draw city labels - try to avoid putting labels over one another
|
||||||
|
g = chart.append("g").attr("id", "epburglabels");
|
||||||
|
var y1 = 0;
|
||||||
|
var add = 15;
|
||||||
|
|
||||||
|
let xwidth = chartData.points[1][0] - chartData.points[0][0];
|
||||||
|
for (let k=0; k<chartData.points.length; k++)
|
||||||
|
{
|
||||||
|
if (chartData.burg[k] > 0) {
|
||||||
|
let b = chartData.burg[k];
|
||||||
|
|
||||||
|
let x1 = chartData.points[k][0]; // left side of graph by default
|
||||||
|
if (k > 0) x1 += xwidth/2; // center it if not first
|
||||||
|
if (k == chartData.points.length-1)
|
||||||
|
x1 = chartWidth + xOffset; // right part of graph
|
||||||
|
y1+=add;
|
||||||
|
if (y1 >= yOffset) { y1 = add; }
|
||||||
|
var d1 = 0;
|
||||||
|
|
||||||
|
// burg name
|
||||||
|
g.append("text").attr("id", "ep" + b).attr("class", "epburglabel").attr("x", x1).attr("y", y1).attr("text-anchor", "middle");
|
||||||
|
document.getElementById("ep" + b).innerHTML = pack.burgs[b].name;
|
||||||
|
|
||||||
|
// arrow from burg name to graph line
|
||||||
|
g.append("path").attr("id", "eparrow" + b).attr("d", "M" + x1.toString() + "," + (y1+3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1]-3).toString()).attr("stroke", "darkgray").attr("fill", "lightgray").attr("stroke-width", "1").attr("marker-end", "url(#arrowhead)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeElevationProfile() {
|
||||||
|
document.getElementById("epScaleRange").removeEventListener("change", draw);
|
||||||
|
document.getElementById("epCurve").removeEventListener("change", draw);
|
||||||
|
document.getElementById("epSave").removeEventListener("click", downloadCSV);
|
||||||
|
|
||||||
|
document.getElementById("elevationGraph").innerHTML = "";
|
||||||
|
|
||||||
|
modules.elevation = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
|
|
||||||
function editHeightmap() {
|
function editHeightmap() {
|
||||||
void function selectEditMode() {
|
void function selectEditMode() {
|
||||||
alertMessage.innerHTML = `<span>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.</span>
|
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.
|
||||||
<p>You can also <i>keep</i> all the data, but you won't be able to change the coastline.</p>
|
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
||||||
<p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option.
|
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
||||||
The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p>
|
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
||||||
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>
|
<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>`;
|
||||||
|
|
||||||
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
|
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
|
||||||
buttons: {
|
buttons: {
|
||||||
|
|
@ -61,9 +61,9 @@ function editHeightmap() {
|
||||||
changeOnlyLand.checked = false;
|
changeOnlyLand.checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide convert and template buttons for the Keep mode
|
// show convert and template buttons for Erase mode only
|
||||||
applyTemplate.style.display = type === "keep" ? "none" : "inline-block";
|
applyTemplate.style.display = type === "erase" ? "inline-block" : "none";
|
||||||
convertImage.style.display = type === "keep" ? "none" : "inline-block";
|
convertImage.style.display = type === "erase" ? "inline-block" : "none";
|
||||||
|
|
||||||
// hide erosion checkbox if mode is Keep
|
// hide erosion checkbox if mode is Keep
|
||||||
changeHeightsBox.style.display = type === "keep" ? "none" : "inline-block";
|
changeHeightsBox.style.display = type === "keep" ? "none" : "inline-block";
|
||||||
|
|
@ -963,10 +963,11 @@ function editHeightmap() {
|
||||||
|
|
||||||
function openImageConverter() {
|
function openImageConverter() {
|
||||||
if ($("#imageConverter").is(":visible")) return;
|
if ($("#imageConverter").is(":visible")) return;
|
||||||
|
imageToLoad.click();
|
||||||
closeDialogs("#imageConverter");
|
closeDialogs("#imageConverter");
|
||||||
|
|
||||||
$("#imageConverter").dialog({
|
$("#imageConverter").dialog({
|
||||||
title: "Image Converter", maxHeight: svgHeight*.75, minHeight: "auto", width: "19.5em", resizable: false,
|
title: "Image Converter", maxHeight: svgHeight*.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
|
||||||
});
|
});
|
||||||
|
|
@ -978,15 +979,9 @@ function editHeightmap() {
|
||||||
canvas.height = graphHeight;
|
canvas.height = graphHeight;
|
||||||
document.body.insertBefore(canvas, optionsContainer);
|
document.body.insertBefore(canvas, optionsContainer);
|
||||||
|
|
||||||
const img = new Image;
|
|
||||||
img.id = "image";
|
|
||||||
img.style.display = "none";
|
|
||||||
document.body.appendChild(img);
|
|
||||||
|
|
||||||
setOverlayOpacity(0);
|
setOverlayOpacity(0);
|
||||||
|
clearMainTip();
|
||||||
document.getElementById("convertImageLoad").classList.add("glow"); // add glow effect
|
tip('Image Converter is opened. Upload image and assign height value for each color', false, "warn"); // main tip
|
||||||
tip('Image Converter is opened. Upload the image and assign height value for each of the colors', true, "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);
|
||||||
|
|
@ -1001,7 +996,7 @@ function editHeightmap() {
|
||||||
d3.select("#imageConverterPalette").selectAll("div").data(d3.range(101))
|
d3.select("#imageConverterPalette").selectAll("div").data(d3.range(101))
|
||||||
.enter().append("div").attr("data-color", i => i)
|
.enter().append("div").attr("data-color", i => i)
|
||||||
.style("background-color", i => color(1-(i < 20 ? i-5 : i) / 100))
|
.style("background-color", i => color(1-(i < 20 ? i-5 : i) / 100))
|
||||||
.style("width", i => i < 20 || i > 70 ? ".2em" : ".1em")
|
.style("width", i => i < 40 || i > 68 ? ".2em" : ".1em")
|
||||||
.on("touchmove mousemove", showPalleteHeight).on("click", assignHeight);
|
.on("touchmove mousemove", showPalleteHeight).on("click", assignHeight);
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -1010,6 +1005,7 @@ function editHeightmap() {
|
||||||
document.getElementById("imageToLoad").addEventListener("change", loadImage);
|
document.getElementById("imageToLoad").addEventListener("change", loadImage);
|
||||||
document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum"));
|
document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum"));
|
||||||
document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue"));
|
document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue"));
|
||||||
|
document.getElementById("convertAutoFMG").addEventListener("click", () => autoAssing("scheme"));
|
||||||
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);
|
||||||
|
|
@ -1030,12 +1026,12 @@ 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;
|
||||||
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();
|
||||||
convertImageLoad.classList.remove("glow");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.onloadend = () => img.src = reader.result;
|
reader.onloadend = () => img.src = reader.result;
|
||||||
|
|
@ -1043,37 +1039,35 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function heightsFromImage(count) {
|
function heightsFromImage(count) {
|
||||||
const ctx = document.getElementById("canvas").getContext("2d");
|
const sourceImage = document.getElementById("canvas");
|
||||||
|
const sampleCanvas = document.createElement("canvas");
|
||||||
|
sampleCanvas.width = grid.cellsX;
|
||||||
|
sampleCanvas.height = 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(ctx);
|
q.sample(sampleCanvas);
|
||||||
const data = q.reduce(ctx);
|
const data = q.reduce(sampleCanvas);
|
||||||
|
const pallete = q.palette(true);
|
||||||
|
|
||||||
viewbox.select("#heights").selectAll("*").remove();
|
viewbox.select("#heights").selectAll("*").remove();
|
||||||
d3.select("#imageConverter").selectAll("div.color-div").remove();
|
d3.select("#imageConverter").selectAll("div.color-div").remove();
|
||||||
colorsSelect.style.display = "block";
|
colorsSelect.style.display = "block";
|
||||||
colorsUnassigned.style.display = "block";
|
colorsUnassigned.style.display = "block";
|
||||||
colorsAssigned.style.display = "none";
|
colorsAssigned.style.display = "none";
|
||||||
|
sampleCanvas.remove(); // no need to keep
|
||||||
let usedColors = new Set();
|
|
||||||
let gridColors = grid.points.map(p => {
|
|
||||||
const x = Math.floor(p[0]-.01), y = Math.floor(p[1]-.01);
|
|
||||||
const i = (x + y * graphWidth) * 4;
|
|
||||||
const r = data[i], g = data[i+1], b = data[i+2];
|
|
||||||
usedColors.add(`rgb(${r},${g},${b})`);
|
|
||||||
return [r, g, b];
|
|
||||||
});
|
|
||||||
|
|
||||||
viewbox.select("#heights").selectAll("polygon").data(grid.cells.i).join("polygon")
|
viewbox.select("#heights").selectAll("polygon").data(grid.cells.i).join("polygon")
|
||||||
.attr("points", d => getGridPolygon(d))
|
.attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d)
|
||||||
.attr("id", d => "cell"+d).attr("fill", d => `rgb(${gridColors[d].join(",")})`)
|
.attr("fill", d => `rgb(${data[d*4]}, ${data[d*4+1]}, ${data[d*4+2]})`)
|
||||||
.on("click", mapClicked);
|
.on("click", mapClicked);
|
||||||
|
|
||||||
const unassigned = [...usedColors].sort((a, b) => d3.lab(a).l - d3.lab(b).l);
|
const colors = pallete.map(p => `rgb(${p[0]}, ${p[1]}, ${p[2]})`);
|
||||||
d3.select("#colorsUnassigned").selectAll("div").data(unassigned).enter().append("div")
|
d3.select("#colorsUnassigned").selectAll("div").data(colors).enter().append("div")
|
||||||
.attr("data-color", i => i).style("background-color", i => i)
|
.attr("data-color", i => i).style("background-color", i => i)
|
||||||
.attr("class", "color-div").on("click", colorClicked);
|
.attr("class", "color-div").on("click", colorClicked);
|
||||||
|
|
||||||
convertColors.value = unassigned.length;
|
document.getElementById("colorsUnassignedNumber").innerHTML = colors.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapClicked() {
|
function mapClicked() {
|
||||||
|
|
@ -1123,34 +1117,60 @@ function editHeightmap() {
|
||||||
if (selectedColor.parentNode.id === "colorsUnassigned") {
|
if (selectedColor.parentNode.id === "colorsUnassigned") {
|
||||||
colorsAssigned.appendChild(selectedColor);
|
colorsAssigned.appendChild(selectedColor);
|
||||||
colorsAssigned.style.display = "block";
|
colorsAssigned.style.display = "block";
|
||||||
|
|
||||||
|
document.getElementById("colorsUnassignedNumber").innerHTML = colorsUnassigned.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
|
||||||
function autoAssing(type) {
|
function autoAssing(type) {
|
||||||
const unassigned = colorsUnassigned.querySelectorAll("div");
|
let unassigned = colorsUnassigned.querySelectorAll("div");
|
||||||
if (!unassigned.length) {tip("No unassigned colors. Please load an image and click the button again", false, "error"); return;}
|
if (!unassigned.length) {
|
||||||
|
heightsFromImage(+convertColors.value);
|
||||||
|
unassigned = colorsUnassigned.querySelectorAll("div");
|
||||||
|
if (!unassigned.length) {
|
||||||
|
tip("No unassigned colors. Please load an image and click the button again", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const assinged = []; // assigned heights
|
const getHeightByHue = function(color) {
|
||||||
|
let hue = d3.hsl(color).h;
|
||||||
|
if (hue > 300) hue -= 360;
|
||||||
|
if (hue > 170) return Math.abs(hue-250) / 3 |0; // water
|
||||||
|
return Math.abs(hue-250+20) / 3 |0; // land
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHeightByLum = function(color) {
|
||||||
|
let lum = d3.lab(color).l;
|
||||||
|
if (lum < 13) return lum / 13 * 20 |0; // water
|
||||||
|
return lum|0; // land
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheme = d3.range(101).map(i => getColor(i, color()));
|
||||||
|
const hues = scheme.map(rgb => d3.hsl(rgb).h|0);
|
||||||
|
const getHeightByScheme = function(color) {
|
||||||
|
let height = scheme.indexOf(color);
|
||||||
|
if (height !== -1) return height; // exact match
|
||||||
|
const hue = d3.hsl(color).h;
|
||||||
|
const closest = hues.reduce((prev, curr) => (Math.abs(curr - hue) < Math.abs(prev - hue) ? curr : prev));
|
||||||
|
return hues.indexOf(closest);
|
||||||
|
}
|
||||||
|
|
||||||
|
const assinged = []; // store assigned heights
|
||||||
unassigned.forEach(el => {
|
unassigned.forEach(el => {
|
||||||
const colorFrom = el.dataset.color;
|
const clr = el.dataset.color;
|
||||||
const lab = d3.lab(colorFrom);
|
const height = type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr);
|
||||||
const normalized = type === "hue" ? rn(normalize(lab.b + lab.a / 2, -50, 200), 2) : rn(normalize(lab.l, -15, 100), 2);
|
const colorTo = color(1 - (height < 20 ? (height-5) / 100 : height / 100));
|
||||||
let heightTo = rn(normalized * 100);
|
viewbox.select("#heights").selectAll("polygon[fill='" + clr + "']").attr("fill", colorTo).attr("data-height", height);
|
||||||
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increased one
|
|
||||||
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increased one
|
|
||||||
if (assinged[heightTo] && heightTo > 3) heightTo -= 3; // if increased one is also added, try decreased one
|
|
||||||
if (assinged[heightTo] && heightTo > 1) heightTo -= 1; // if increased one is also added, try decreased one
|
|
||||||
|
|
||||||
const colorTo = color(1 - (heightTo < 20 ? (heightTo-5)/100 : heightTo/100));
|
if (assinged[height]) {el.remove(); return;} // if color is already added, remove it
|
||||||
viewbox.select("#heights").selectAll("polygon[fill='" + colorFrom + "']").attr("fill", colorTo).attr("data-height", heightTo);
|
|
||||||
|
|
||||||
if (assinged[heightTo]) {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 = heightTo;
|
el.dataset.height = height;
|
||||||
colorsAssigned.appendChild(el);
|
colorsAssigned.appendChild(el);
|
||||||
assinged[heightTo] = true;
|
assinged[height] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// sort assigned colors by height
|
// sort assigned colors by height
|
||||||
|
|
@ -1160,10 +1180,11 @@ function editHeightmap() {
|
||||||
|
|
||||||
colorsAssigned.style.display = "block";
|
colorsAssigned.style.display = "block";
|
||||||
colorsUnassigned.style.display = "none";
|
colorsUnassigned.style.display = "none";
|
||||||
|
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConvertColorsNumber() {
|
function setConvertColorsNumber() {
|
||||||
prompt(`Please set maximum number of colors. <br>An actual number is 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);
|
||||||
|
|
@ -1176,6 +1197,11 @@ function editHeightmap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyConversion() {
|
function applyConversion() {
|
||||||
|
if (colorsAssigned.childElementCount < 3) {
|
||||||
|
tip("Please do the assignment first", false, "error");
|
||||||
|
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);
|
||||||
|
|
@ -1195,9 +1221,7 @@ function editHeightmap() {
|
||||||
|
|
||||||
function restoreImageConverterState() {
|
function restoreImageConverterState() {
|
||||||
const canvas = document.getElementById("canvas");
|
const canvas = document.getElementById("canvas");
|
||||||
if (canvas) canvas.remove(); else return;
|
if (canvas) canvas.remove();
|
||||||
const img = document.getElementById("image");
|
|
||||||
if (img) img.remove(); else return;
|
|
||||||
|
|
||||||
d3.select("#imageConverter").selectAll("div.color-div").remove();
|
d3.select("#imageConverter").selectAll("div.color-div").remove();
|
||||||
colorsAssigned.style.display = "none";
|
colorsAssigned.style.display = "none";
|
||||||
|
|
@ -1206,12 +1230,18 @@ function editHeightmap() {
|
||||||
viewbox.style("cursor", "default").on(".drag", null);
|
viewbox.style("cursor", "default").on(".drag", null);
|
||||||
tip('Heightmap edit mode is active. Click on "Exit Customization" to finalize the heightmap', true);
|
tip('Heightmap edit mode is active. Click on "Exit Customization" to finalize the heightmap', true);
|
||||||
$("#imageConverter").dialog("destroy");
|
$("#imageConverter").dialog("destroy");
|
||||||
|
openBrushesPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeImageConverter(event) {
|
function closeImageConverter(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
alertMessage.innerHTML = 'Are you sure you want to close the Image Converter? Click "Cancel" to geck back to convertion. Click "Complete" to apply the conversion. Click "Close" to exit conversion mode and restore previous heightmap';
|
alertMessage.innerHTML = `
|
||||||
|
Are you sure you want to close the Image Converter?
|
||||||
|
Click "Cancel" to geck back to convertion.
|
||||||
|
Click "Complete" to apply the conversion.
|
||||||
|
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() {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ function editRiver(id) {
|
||||||
|
|
||||||
function drawControlPoints(node) {
|
function drawControlPoints(node) {
|
||||||
const l = node.getTotalLength() / 2;
|
const l = node.getTotalLength() / 2;
|
||||||
const segments = Math.ceil(l / 8);
|
const segments = Math.ceil(l / 4);
|
||||||
const increment = rn(l / segments * 1e5);
|
const increment = rn(l / segments * 1e5);
|
||||||
for (let i=increment*segments, c=i; i >= 0; i -= increment, c += increment) {
|
for (let i=increment*segments, c=i; i >= 0; i -= increment, c += increment) {
|
||||||
const p1 = node.getPointAtLength(i / 1e5);
|
const p1 = node.getPointAtLength(i / 1e5);
|
||||||
|
|
@ -183,7 +183,7 @@ function editRiver(id) {
|
||||||
|
|
||||||
function showElevationProfile() {
|
function showElevationProfile() {
|
||||||
modules.elevation = true;
|
modules.elevation = true;
|
||||||
showEPForRiver(node);
|
showEPForRiver(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRiverWidth() {
|
function showRiverWidth() {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ function editRoute(onClick) {
|
||||||
|
|
||||||
function drawControlPoints(node) {
|
function drawControlPoints(node) {
|
||||||
const l = node.getTotalLength();
|
const l = node.getTotalLength();
|
||||||
const increment = l / Math.ceil(l / 8);
|
const increment = l / Math.ceil(l / 4);
|
||||||
for (let i=0; i <= l; i += increment) {addControlPoint(node.getPointAtLength(i));}
|
for (let i=0; i <= l; i += increment) {addControlPoint(node.getPointAtLength(i));}
|
||||||
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||||
}
|
}
|
||||||
|
|
@ -101,16 +101,14 @@ function editRoute(onClick) {
|
||||||
|
|
||||||
elSelected.attr("d", round(lineGen(points)));
|
elSelected.attr("d", round(lineGen(points)));
|
||||||
const l = elSelected.node().getTotalLength();
|
const l = elSelected.node().getTotalLength();
|
||||||
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||||
|
|
||||||
if (modules.elevation) {
|
if (modules.elevation) showEPForRoute(elSelected.node());
|
||||||
showEPForRoute(elSelected.node());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showElevationProfile() {
|
function showElevationProfile() {
|
||||||
modules.elevation = true;
|
modules.elevation = true;
|
||||||
showEPForRoute(node);
|
showEPForRoute(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
function showGroupSection() {
|
function showGroupSection() {
|
||||||
|
|
|
||||||
|
|
@ -215,8 +215,7 @@ function editStates() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function editStateName(state) {
|
function editStateName(state) {
|
||||||
|
// reset input value and close add mode
|
||||||
//Reset input value and close add mode
|
|
||||||
stateNameEditorCustomForm.value = "";
|
stateNameEditorCustomForm.value = "";
|
||||||
const addModeActive = stateNameEditorCustomForm.style.display === "inline-block";
|
const addModeActive = stateNameEditorCustomForm.style.display === "inline-block";
|
||||||
if (addModeActive) {
|
if (addModeActive) {
|
||||||
|
|
@ -244,6 +243,7 @@ function editStates() {
|
||||||
document.getElementById("stateNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture);
|
document.getElementById("stateNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture);
|
||||||
document.getElementById("stateNameEditorShortRandom").addEventListener("click", regenerateShortNameRandom);
|
document.getElementById("stateNameEditorShortRandom").addEventListener("click", regenerateShortNameRandom);
|
||||||
document.getElementById("stateNameEditorAddForm").addEventListener("click", addCustomForm);
|
document.getElementById("stateNameEditorAddForm").addEventListener("click", addCustomForm);
|
||||||
|
document.getElementById("stateNameEditorCustomForm").addEventListener("change", addCustomForm);
|
||||||
document.getElementById("stateNameEditorFullRegenerate").addEventListener("click", regenerateFullName);
|
document.getElementById("stateNameEditorFullRegenerate").addEventListener("click", regenerateFullName);
|
||||||
|
|
||||||
function regenerateShortNameCuture() {
|
function regenerateShortNameCuture() {
|
||||||
|
|
@ -264,7 +264,7 @@ function editStates() {
|
||||||
const addModeActive = stateNameEditorCustomForm.style.display === "inline-block";
|
const addModeActive = stateNameEditorCustomForm.style.display === "inline-block";
|
||||||
stateNameEditorCustomForm.style.display = addModeActive ? "none" : "inline-block";
|
stateNameEditorCustomForm.style.display = addModeActive ? "none" : "inline-block";
|
||||||
stateNameEditorSelectForm.style.display = addModeActive ? "inline-block" : "none";
|
stateNameEditorSelectForm.style.display = addModeActive ? "inline-block" : "none";
|
||||||
if (addModeActive) applyOption(stateNameEditorSelectForm, value);
|
if (value && addModeActive) applyOption(stateNameEditorSelectForm, value);
|
||||||
stateNameEditorCustomForm.value = "";
|
stateNameEditorCustomForm.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,8 @@ function regenerateStates() {
|
||||||
tip(`Not enought burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn");
|
tip(`Not enought burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn");
|
||||||
}
|
}
|
||||||
|
|
||||||
// burg ids sorted by a bit randomized population:
|
// burg local ids sorted by a bit randomized population:
|
||||||
const sorted = burgs.map(b => [b.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
|
||||||
|
|
@ -194,8 +194,8 @@ function regenerateStates() {
|
||||||
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 (let i=0; i < sorted.length; i++) {
|
for (const i of sorted) {
|
||||||
capital = burgs[sorted[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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue