Merge branch 'master' of https://github.com/mosuzi/Fantasy-Map-Generator into localization-dev

This commit is contained in:
mosuzi 2023-10-09 10:03:54 +08:00
commit b3dc77578c
303 changed files with 11387 additions and 2609 deletions

View file

@ -12,7 +12,11 @@ window.ThreeD = (function () {
waterColor: "#466eab",
extendedWater: 0,
labels3d: 0,
resolution: 2
wireframe: 0,
resolution: 2,
resolutionScale: 2048,
sunColor: "#cccccc",
subdivide: 0
};
// set variables
@ -92,7 +96,11 @@ window.ThreeD = (function () {
const setScale = function (scale) {
options.scale = scale;
geometry.vertices.forEach((v, i) => (v.z = getMeshHeight(i)));
let vertices = geometry.getAttribute("position");
for (let i = 0; i < vertices.count; i++) {
vertices.setZ(i, getMeshHeight(i));
}
geometry.setAttribute("position", vertices);
geometry.verticesNeedUpdate = true;
geometry.computeVertexNormals();
geometry.verticesNeedUpdate = false;
@ -100,6 +108,17 @@ window.ThreeD = (function () {
redraw();
};
const setSunColor = function (color) {
options.sunColor = color;
spotLight.color = new THREE.Color(color);
render();
};
const setResolutionScale = function (scale) {
options.resolutionScale = scale;
redraw();
};
const setLightness = function (intensity) {
options.lightness = intensity;
ambientLight.intensity = intensity;
@ -148,6 +167,16 @@ window.ThreeD = (function () {
}
};
const toggle3dSubdivision = function () {
options.subdivide = !options.subdivide;
redraw();
};
const toggleWireframe = function () {
options.wireframe = !options.wireframe;
redraw();
};
const setColors = function (sky, water) {
options.skyColor = sky;
scene.background = scene.fog.color = new THREE.Color(sky);
@ -189,16 +218,20 @@ window.ThreeD = (function () {
// light
ambientLight = new THREE.AmbientLight(0xcccccc, options.lightness);
scene.add(ambientLight);
spotLight = new THREE.SpotLight(0xcccccc, 0.8, 2000, 0.8, 0, 0);
spotLight = new THREE.SpotLight(options.sunColor, 0.8, 2000, 0.8, 0, 0);
spotLight.position.set(options.sun.x, options.sun.y, options.sun.z);
spotLight.castShadow = true;
//maybe add a option for this. But changing the option will require to reinstance the spotLight.
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
scene.add(spotLight);
//scene.add(new THREE.SpotLightHelper(spotLight));
// Rendered
// Renderer
Renderer = new THREE.WebGLRenderer({canvas, antialias: true, preserveDrawingBuffer: true});
Renderer.setSize(canvas.width, canvas.height);
Renderer.shadowMap.enabled = true;
// Renderer.shadowMap.type = THREE.PCFSoftShadowMap;
if (options.extendedWater) extendWater(graphWidth, graphHeight);
createMesh(graphWidth, graphHeight, grid.cellsX, grid.cellsY);
@ -223,7 +256,7 @@ window.ThreeD = (function () {
function textureToSprite(texture, width, height) {
const map = new THREE.TextureLoader().load(texture);
map.anisotropy = Renderer.getMaxAnisotropy();
map.anisotropy = Renderer.capabilities.getMaxAnisotropy();
const material = new THREE.SpriteMaterial({map});
const sprite = new THREE.Sprite(material);
@ -242,7 +275,11 @@ window.ThreeD = (function () {
context2d.fillStyle = color;
context2d.fillText(text, 0, size * quality);
return textureToSprite(context2d.canvas.toDataURL(), context2d.canvas.width / quality, context2d.canvas.height / quality);
return textureToSprite(
context2d.canvas.toDataURL(),
context2d.canvas.width / quality,
context2d.canvas.height / quality
);
}
function get3dCoords(baseX, baseY) {
@ -296,9 +333,23 @@ window.ThreeD = (function () {
};
const city_icon_material = new THREE.MeshPhongMaterial({color: cityOptions.iconColor});
city_icon_material.wireframe = options.wireframe;
const town_icon_material = new THREE.MeshPhongMaterial({color: townOptions.iconColor});
const city_icon_geometry = new THREE.CylinderGeometry(cityOptions.iconSize * 2, cityOptions.iconSize * 2, cityOptions.iconSize, 16, 1);
const town_icon_geometry = new THREE.CylinderGeometry(townOptions.iconSize * 2, townOptions.iconSize * 2, townOptions.iconSize, 16, 1);
town_icon_material.wireframe = options.wireframe;
const city_icon_geometry = new THREE.CylinderGeometry(
cityOptions.iconSize * 2,
cityOptions.iconSize * 2,
cityOptions.iconSize,
16,
1
);
const town_icon_geometry = new THREE.CylinderGeometry(
townOptions.iconSize * 2,
townOptions.iconSize * 2,
townOptions.iconSize,
16,
1
);
const line_material = new THREE.LineBasicMaterial({color: cityOptions.iconColor});
// burg labels
@ -387,32 +438,78 @@ window.ThreeD = (function () {
lines = [];
}
async function createMeshTextureUrl() {
return new Promise(async (resolve, reject) => {
const mapOptions = {
noLabels: options.labels3d,
noWater: options.extendedWater,
fullMap: true
};
const url = await getMapURL("mesh", mapOptions);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = options.resolutionScale;
canvas.height = options.resolutionScale;
const img = new Image();
img.src = url;
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob(blob => {
const blobObj = window.URL.createObjectURL(blob);
window.setTimeout(() => {
canvas.remove();
window.URL.revokeObjectURL(blobObj);
}, 100);
resolve(blobObj);
});
};
});
}
// create a mesh from pixel data
async function createMesh(width, height, segmentsX, segmentsY) {
const mapOptions = {
noLabels: options.labels3d,
noWater: options.extendedWater,
fullMap: true
};
const url = await getMapURL("mesh", mapOptions);
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
if (texture) texture.dispose();
texture = new THREE.TextureLoader().load(url, render);
texture.needsUpdate = true;
if (!options.wireframe) {
texture = new THREE.TextureLoader().load(await createMeshTextureUrl(), render);
texture.needsUpdate = true;
texture.anisotropy = Renderer.capabilities.getMaxAnisotropy();
}
if (material) material.dispose();
material = new THREE.MeshLambertMaterial();
material.map = texture;
material.transparent = true;
if (options.wireframe) {
material.wireframe = true;
} else {
material.map = texture;
material.transparent = true;
}
if (geometry) geometry.dispose();
geometry = new THREE.PlaneGeometry(width, height, segmentsX - 1, segmentsY - 1);
geometry.vertices.forEach((v, i) => (v.z = getMeshHeight(i)));
geometry.computeVertexNormals();
let vertices = geometry.getAttribute("position");
for (let i = 0; i < vertices.count; i++) {
vertices.setZ(i, getMeshHeight(i));
}
geometry.setAttribute("position", vertices);
geometry.computeVertexNormals();
if (mesh) scene.remove(mesh);
mesh = new THREE.Mesh(geometry, material);
if (options.subdivide) {
await loadLoopSubdivision();
const subdivideParams = {
split: true,
uvSmooth: false,
preserveEdges: true,
flatOnly: false,
maxTriangles: Infinity
};
const smoothGeometry = loopSubdivision.modify(geometry, 1, subdivideParams);
mesh = new THREE.Mesh(smoothGeometry, material);
} else {
mesh = new THREE.Mesh(geometry, material);
}
mesh.rotation.x = -Math.PI / 2;
mesh.castShadow = true;
mesh.receiveShadow = true;
@ -449,7 +546,7 @@ window.ThreeD = (function () {
noWater: options.extendedWater,
fullMap: true
};
const url = await getMapURL("mesh", mapOptions);
const url = await createMeshTextureUrl();
window.setTimeout(() => window.URL.revokeObjectURL(url), 4000);
texture = new THREE.TextureLoader().load(url, render);
material.map = texture;
@ -464,7 +561,10 @@ window.ThreeD = (function () {
// scene
scene = new THREE.Scene();
scene.background = new THREE.TextureLoader().load("https://i0.wp.com/azgaar.files.wordpress.com/2019/10/stars-1.png", render);
scene.background = new THREE.TextureLoader().load(
"https://i0.wp.com/azgaar.files.wordpress.com/2019/10/stars-1.png",
render
);
// Renderer
Renderer = new THREE.WebGLRenderer({canvas, antialias: true, preserveDrawingBuffer: true});
@ -579,6 +679,17 @@ window.ThreeD = (function () {
});
}
function loadLoopSubdivision() {
if (window.loopSubdivision) return Promise.resolve(true);
return new Promise(resolve => {
const script = document.createElement("script");
script.src = "libs/loopsubdivison.min.js";
document.head.append(script);
script.onload = () => resolve(true);
script.onerror = () => resolve(false);
});
}
function OrbitControls(camera, domElement) {
if (THREE.OrbitControls) return new THREE.OrbitControls(camera, domElement);
@ -596,7 +707,7 @@ window.ThreeD = (function () {
return new Promise(resolve => {
const script = document.createElement("script");
script.src = "libs/objexporter.min.js";
script.src = "libs/objexporter.min.js?v=1.89.35";
document.head.append(script);
script.onload = () => resolve(new THREE.OBJExporter());
script.onerror = () => resolve(false);
@ -609,11 +720,15 @@ window.ThreeD = (function () {
update,
stop,
options,
setSunColor,
setScale,
setResolutionScale,
setLightness,
setSun,
setRotation,
toggleLabels,
toggle3dSubdivision,
toggleWireframe,
toggleSky,
setResolution,
setColors,

View file

@ -88,7 +88,9 @@ function editBiomes() {
const rural = b.rural[i] * populationRate;
const urban = b.urban[i] * populationRate * urbanization;
const population = rn(rural + urban);
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`;
const populationTip = `Total population: ${si(population)}; Rural population: ${si(
rural
)}; Urban population: ${si(urban)}`;
totalArea += area;
totalPopulation += population;
@ -104,7 +106,9 @@ function editBiomes() {
data-color=${b.color[i]}
>
<fill-box fill="${b.color[i]}"></fill-box>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false" />
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${
b.name[i]
}" autocorrect="off" spellcheck="false" />
<span data-tip="Biome habitability percent" class="hide">%</span>
<input
data-tip="Biome habitability percent. Click and set new value to change"
@ -121,7 +125,11 @@ function editBiomes() {
<span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""}
${
i > 12 && !b.cells[i]
? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>'
: ""
}
</div>
`;
}
@ -403,7 +411,14 @@ function editBiomes() {
// change of append new element
if (exists.size()) exists.attr("data-biome", biomeNew).attr("fill", color).attr("stroke", color);
else temp.append("polygon").attr("data-cell", i).attr("data-biome", biomeNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color);
else
temp
.append("polygon")
.attr("data-cell", i)
.attr("data-biome", biomeNew)
.attr("points", getPackPolygon(i))
.attr("fill", color)
.attr("stroke", color);
});
}
@ -449,8 +464,8 @@ function editBiomes() {
}
function restoreInitialBiomes() {
biomesData = applyDefaultBiomesSystem();
defineBiomes();
biomesData = Biomes.getDefault();
Biomes.define();
drawBiomes();
recalculatePopulation();
refreshBiomesEditor();

View file

@ -1176,13 +1176,13 @@ function refreshAllEditors() {
// dynamically loaded editors
async function editStates() {
if (customization) return;
const Editor = await import("../dynamic/editors/states-editor.js?v=1.89.35");
const Editor = await import("../dynamic/editors/states-editor.js?v=1.92.00");
Editor.open();
}
async function editCultures() {
if (customization) return;
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.89.09");
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.91.00");
Editor.open();
}

View file

@ -112,13 +112,13 @@ function editEmblem(type, id, el) {
if (type === "burg") name = "Burg of " + name;
document.getElementById("emblemArmiger").innerText = name;
if (el.coa === "custom") emblemShapeSelector.disabled = true;
if (el.coa.custom) emblemShapeSelector.disabled = true;
else {
emblemShapeSelector.disabled = false;
emblemShapeSelector.value = el.coa.shield;
}
const size = el.coaSize || 1;
const size = el.coa.size || 1;
document.getElementById("emblemSizeSlider").value = size;
document.getElementById("emblemSizeNumber").value = size;
}
@ -178,7 +178,9 @@ function editEmblem(type, id, el) {
}
function changeSize() {
const size = (el.coaSize = +this.value);
const size = +this.value;
el.coa.size = size;
document.getElementById("emblemSizeSlider").value = size;
document.getElementById("emblemSizeNumber").value = size;
@ -189,8 +191,9 @@ function editEmblem(type, id, el) {
// re-append use element
const categotySize = +g.attr("font-size");
const shift = (categotySize * size) / 2;
const x = el.x || el.pole[0];
const y = el.y || el.pole[1];
const x = el.coa.x || el.x || el.pole[0];
const y = el.coa.y || el.y || el.pole[1];
g.append("use")
.attr("data-i", el.i)
.attr("x", rn(x - shift), 2)
@ -220,7 +223,7 @@ function editEmblem(type, id, el) {
}
function openInArmoria() {
const coa = el.coa && el.coa !== "custom" ? el.coa : {t1: "sable"};
const coa = el.coa && !el.coa.custom ? el.coa : {t1: "sable"};
const json = JSON.stringify(coa).replaceAll("#", "%23");
const url = `https://azgaar.github.io/Armoria/?coa=${json}&from=FMG`;
openURL(url);
@ -281,7 +284,13 @@ function editEmblem(type, id, el) {
defs.insertAdjacentHTML("beforeend", svg);
if (oldEmblem) oldEmblem.remove();
el.coa = "custom";
const customCoa = {custom: true};
if (el.coa.size) customCoa.size = el.coa.size;
if (el.coa.x) customCoa.x = el.coa.x;
if (el.coa.y) customCoa.y = el.coa.y;
el.coa = customCoa;
emblemShapeSelector.disabled = true;
};
@ -509,13 +518,21 @@ function editEmblem(type, id, el) {
}
function dragEmblem() {
const tr = parseTransform(this.getAttribute("transform"));
const x = +tr[0] - d3.event.x,
y = +tr[1] - d3.event.y;
const x = Number(this.getAttribute("x")) - d3.event.x;
const y = Number(this.getAttribute("y")) - d3.event.y;
d3.event.on("drag", function () {
const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
this.setAttribute("transform", transform);
this.setAttribute("x", x + d3.event.x);
this.setAttribute("y", y + d3.event.y);
});
d3.event.on("end", function () {
const categotySize = Number(this.parentNode.getAttribute("font-size"));
const size = el.coa.size || 1;
const shift = (categotySize * size) / 2;
el.coa.x = rn(x + d3.event.x + shift, 2);
el.coa.y = rn(y + d3.event.y + shift, 2);
});
}

View file

@ -87,6 +87,8 @@ function handleMouseMove() {
if (cellInfo?.offsetParent) updateCellInfo(point, i, gridCell);
}
let currentNoteId = null; // store currently displayed node to not rerender to often
// show note box on hover (if any)
function showNotes(e) {
if (notesEditor?.offsetParent) return;
@ -96,13 +98,17 @@ function showNotes(e) {
const note = notes.find(note => note.id === id);
if (note !== undefined && note.legend !== "") {
if (currentNoteId === id) return;
currentNoteId = id;
document.getElementById("notes").style.display = "block";
document.getElementById("notesHeader").innerHTML = note.name;
document.getElementById("notesBody").innerHTML = note.legend;
} else if (!options.pinNotes && !markerEditor?.offsetParent) {
} else if (!options.pinNotes && !markerEditor?.offsetParent && !e.shiftKey) {
document.getElementById("notes").style.display = "none";
document.getElementById("notesHeader").innerHTML = "";
document.getElementById("notesBody").innerHTML = "";
currentNoteId = null;
}
}
@ -160,7 +166,7 @@ function showMapTooltip(point, e, i, g) {
}
if (group === "labels") return tip("Click to edit the Label");
if (group === "markers") return tip("Click to edit the Marker and pin the marker note");
if (group === "markers") return tip("Click to edit the Marker. Hold Shift to not close the assosiated note");
if (group === "ruler") {
const tag = e.target.tagName;
@ -494,6 +500,7 @@ function showInfo() {
const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit");
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
const Armoria = link("https://azgaar.github.io/Armoria", "Armoria");
const Deorum = link("https://deorum.vercel.app", "Deorum");
const QuickStart = link(
"https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial",
@ -515,8 +522,6 @@ function showInfo() {
and ${VideoTutorial}.
</p>
<p>Check out our another project: ${Armoria} heraldry generator and editor.</p>
<ul style="columns:2">
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li>
@ -524,7 +529,14 @@ function showInfo() {
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li>
<li>${link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Devboard")}</li>
<li><a href="mailto:azgaar.fmg@yandex.by" target="_blank">Contact Azgaar</a></li>
</ul>`;
</ul>
<p>Check out our other projects:
<ul>
<li>${Armoria}: a tool for creating heraldic coats of arms</li>
<li>${Deorum}: a vast gallery of customizable fantasy characters</li>
</ul>
</p>`;
$("#alert").dialog({
resizable: false,

View file

@ -28,7 +28,7 @@ function editHeightmap(options) {
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</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>Please <span class="pseudoLink" onclick="dowloadMap();">save the map</span> before editing the heightmap!</p>
<p>Please <span class="pseudoLink" onclick="saveMap('machine')">save the map</span> before editing the heightmap!</p>
<p style="margin-bottom: 0">Check out ${link(
"https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization",
"wiki"
@ -192,11 +192,14 @@ function editHeightmap(options) {
document
.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
.forEach(e => {
const wasOn = editHeightmap.layers.includes(e.id);
if ((wasOn && !layerIsOn(e.id)) || (!wasOn && layerIsOn(e.id))) e.click();
});
if (!layerIsOn("toggleBorders")) borders.selectAll("path").remove();
if (!layerIsOn("toggleStates")) regions.selectAll("path").remove();
if (!layerIsOn("toggleRivers")) rivers.selectAll("*").remove();
getCurrentPreset();
}
@ -236,7 +239,7 @@ function editHeightmap(options) {
drawRivers();
Lakes.defineGroup();
defineBiomes();
Biomes.define();
rankCells();
Cultures.generate();
@ -250,7 +253,7 @@ function editHeightmap(options) {
drawStates();
drawBorders();
BurgsAndStates.drawStateLabels();
drawStateLabels();
Rivers.specify();
Lakes.generateName();
@ -370,10 +373,6 @@ function editHeightmap(options) {
const g = pack.cells.g[i];
const isLand = pack.cells.h[i] >= 20;
// check biome
pack.cells.biome[i] =
isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
// rivers data
if (!erosionAllowed) {
pack.cells.r[i] = r[g];
@ -381,6 +380,12 @@ function editHeightmap(options) {
pack.cells.fl[i] = fl[g];
}
// check biome
pack.cells.biome[i] =
isLand && biome[g]
? biome[g]
: Biomes.getId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i], Boolean(pack.cells.r[i]));
if (!isLand) continue;
pack.cells.culture[i] = culture[g];
pack.cells.pop[i] = pop[g];
@ -437,7 +442,7 @@ function editHeightmap(options) {
c.center = findCell(c.x, c.y);
}
BurgsAndStates.drawStateLabels();
drawStateLabels();
drawStates();
drawBorders();

View file

@ -25,15 +25,15 @@ function handleKeyup(event) {
if (code === "F1") showInfo();
else if (code === "F2") regeneratePrompt();
else if (code === "F6") quickSave();
else if (code === "F6") saveMap("storage");
else if (code === "F9") quickLoad();
else if (code === "Tab") toggleOptions(event);
else if (code === "Escape") closeAllDialogs();
else if (code === "Delete") removeElementOnKey();
else if (code === "KeyO" && document.getElementById("canvas3d")) toggle3dOptions();
else if (ctrl && code === "KeyQ") toggleSaveReminder();
else if (ctrl && code === "KeyS") dowloadMap();
else if (ctrl && code === "KeyC") saveToDropbox();
else if (ctrl && code === "KeyS") saveMap("machine");
else if (ctrl && code === "KeyC") saveMap("dropbox");
else if (ctrl && code === "KeyZ" && undo?.offsetParent) undo.click();
else if (ctrl && code === "KeyY" && redo?.offsetParent) redo.click();
else if (shift && code === "KeyH") editHeightmap();

View file

@ -78,7 +78,9 @@ function editLabel() {
}
function updateValues(textPath) {
document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")].map(tspan => tspan.textContent).join("|");
document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")]
.map(tspan => tspan.textContent)
.join("|");
document.getElementById("labelStartOffset").value = parseFloat(textPath.getAttribute("startOffset"));
document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size"));
}
@ -298,22 +300,15 @@ function editLabel() {
function changeText() {
const input = document.getElementById("labelText").value;
const el = elSelected.select("textPath").node();
const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node();
const lines = input.split("|");
const top = (lines.length - 1) / -2; // y offset
const inner = lines
.map((l, d) => {
example.innerHTML = l;
const left = example.getBBox().width / -2; // x offset
return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`;
})
.join("");
if (lines.length > 1) {
const top = (lines.length - 1) / -2; // y offset
el.innerHTML = lines.map((line, index) => `<tspan x="0" dy="${index ? 1 : top}em">${line}</tspan>`).join("");
} else el.innerHTML = `<tspan x="0">${lines}</tspan>`;
el.innerHTML = inner;
example.remove();
if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning");
if (elSelected.attr("id").slice(0, 10) === "stateLabel")
tip("Use States Editor to change an actual state name, not just a label", false, "warning");
}
function generateRandomName() {

View file

@ -1517,27 +1517,15 @@ function toggleRelief(event) {
function toggleTexture(event) {
if (!layerIsOn("toggleTexture")) {
turnButtonOn("toggleTexture");
// append default texture image selected by default. Don't append on load to not harm performance
if (!texture.selectAll("*").size()) {
const x = +styleTextureShiftX.value;
const y = +styleTextureShiftY.value;
const image = texture
.append("image")
.attr("id", "textureImage")
.attr("x", x)
.attr("y", y)
.attr("width", graphWidth - x)
.attr("height", graphHeight - y)
.attr("preserveAspectRatio", "xMidYMid slice");
getBase64(styleTextureInput.value, base64 => image.attr("xlink:href", base64));
}
$("#texture").fadeIn();
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
// href is not set directly to avoid image loading when layer is off
const textureImage = byId("textureImage");
if (textureImage) textureImage.setAttribute("href", textureImage.getAttribute("src"));
if (event && isCtrlClick(event)) editStyle("texture");
} else {
if (event && isCtrlClick(event)) return editStyle("texture");
$("#texture").fadeOut();
turnButtonOff("toggleTexture");
texture.select("image").attr("href", null);
}
}
@ -1762,9 +1750,9 @@ function drawEmblems() {
TIME && console.time("drawEmblems");
const {states, provinces, burgs} = pack;
const validStates = states.filter(s => s.i && !s.removed && s.coa && s.coaSize != 0);
const validProvinces = provinces.filter(p => p.i && !p.removed && p.coa && p.coaSize != 0);
const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa && b.coaSize != 0);
const validStates = states.filter(s => s.i && !s.removed && s.coa && s.coa.size !== 0);
const validProvinces = provinces.filter(p => p.i && !p.removed && p.coa && p.coa.size !== 0);
const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa && b.coa.size !== 0);
const getStateEmblemsSize = () => {
const startSize = minmax((graphHeight + graphWidth) / 40, 10, 100);
@ -1790,26 +1778,26 @@ function drawEmblems() {
const sizeBurgs = getBurgEmblemSize();
const burgCOAs = validBurgs.map(burg => {
const {x, y} = burg;
const size = burg.coaSize || 1;
const size = burg.coa.size || 1;
const shift = (sizeBurgs * size) / 2;
return {type: "burg", i: burg.i, x, y, size, shift};
return {type: "burg", i: burg.i, x: burg.coa.x || x, y: burg.coa.y || y, size, shift};
});
const sizeProvinces = getProvinceEmblemsSize();
const provinceCOAs = validProvinces.map(province => {
if (!province.pole) getProvincesVertices();
const [x, y] = province.pole || pack.cells.p[province.center];
const size = province.coaSize || 1;
const size = province.coa.size || 1;
const shift = (sizeProvinces * size) / 2;
return {type: "province", i: province.i, x, y, size, shift};
return {type: "province", i: province.i, x: province.coa.x || x, y: province.coa.y || y, size, shift};
});
const sizeStates = getStateEmblemsSize();
const stateCOAs = validStates.map(state => {
const [x, y] = state.pole || pack.cells.p[state.center];
const size = state.coaSize || 1;
const size = state.coa.size || 1;
const shift = (sizeStates * size) / 2;
return {type: "state", i: state.i, x, y, size, shift};
return {type: "state", i: state.i, x: state.coa.x || x, y: state.coa.y || y, size, shift};
});
const nodes = burgCOAs.concat(provinceCOAs).concat(stateCOAs);

View file

@ -14,6 +14,8 @@ function overviewMarkers() {
const markersGenerationConfig = document.getElementById("markersGenerationConfig");
const markersRemoveAll = document.getElementById("markersRemoveAll");
const markersExport = document.getElementById("markersExport");
const markerTypeInput = document.getElementById("addedMarkerType");
const markerTypeSelector = document.getElementById("markerTypeSelector");
addLines();
@ -33,9 +35,26 @@ function overviewMarkers() {
listen(markersAddFromOverview, "click", toggleAddMarker),
listen(markersGenerationConfig, "click", configMarkersGeneration),
listen(markersRemoveAll, "click", triggerRemoveAll),
listen(markersExport, "click", exportMarkers)
listen(markersExport, "click", exportMarkers),
listen(markerTypeSelector, "click", toggleMarkerTypeMenu)
];
const types = [{type: "empty", icon: "❓"}, ...Markers.getConfig()];
types.forEach(({icon, type}) => {
const option = document.createElement("button");
option.textContent = `${icon} ${type}`;
markerTypeSelectMenu.appendChild(option);
listeners.push(
listen(option, "click", () => {
markerTypeSelector.textContent = icon;
markerTypeInput.value = type;
changeMarkerType();
toggleMarkerTypeMenu();
})
);
});
function handleLineClick(ev) {
const el = ev.target;
const i = +el.parentNode.dataset.i;
@ -139,11 +158,21 @@ function overviewMarkers() {
});
}
function toggleMarkerTypeMenu() {
document.getElementById("markerTypeSelectMenu").classList.toggle("visible");
}
function toggleAddMarker() {
markersAddFromOverview.classList.toggle("pressed");
addMarker.click();
}
function changeMarkerType() {
if (!markersAddFromOverview.classList.contains("pressed")) {
toggleAddMarker();
}
}
function removeMarker(i) {
notes = notes.filter(note => note.id !== `marker${i}`);
pack.markers = pack.markers.filter(marker => marker.i !== i);

View file

@ -76,7 +76,7 @@ document
// show popup with a list of Patreon supportes (updated manually)
async function showSupporters() {
const {supporters} = await import("../dynamic/supporters.js?v=1.89.15");
const {supporters} = await import("../dynamic/supporters.js?v=1.93.03");
const list = supporters.split("\n").sort();
const columns = window.innerWidth < 800 ? 2 : 5;
@ -381,7 +381,7 @@ function changeEmblemShape(emblemShape) {
};
pack.states.forEach(state => {
if (!state.i || state.removed || !state.coa || state.coa === "custom") return;
if (!state.i || state.removed || !state.coa || state.coa.custom) return;
const newShield = specificShape || COA.getShield(state.culture, null);
if (newShield === state.coa.shield) return;
state.coa.shield = newShield;
@ -389,7 +389,7 @@ function changeEmblemShape(emblemShape) {
});
pack.provinces.forEach(province => {
if (!province.i || province.removed || !province.coa || province.coa === "custom") return;
if (!province.i || province.removed || !province.coa || province.coa.custom) return;
const culture = pack.cells.culture[province.center];
const newShield = specificShape || COA.getShield(culture, province.state);
if (newShield === province.coa.shield) return;
@ -398,7 +398,7 @@ function changeEmblemShape(emblemShape) {
});
pack.burgs.forEach(burg => {
if (!burg.i || burg.removed || !burg.coa || burg.coa === "custom") return;
if (!burg.i || burg.removed || !burg.coa || burg.coa.custom) return;
const newShield = specificShape || COA.getShield(burg.culture, burg.state);
if (newShield === burg.coa.shield) return;
burg.coa.shield = newShield;
@ -559,11 +559,10 @@ function applyStoredOptions() {
if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5));
}
if (stored("winds"))
options.winds = localStorage
.getItem("winds")
.split(",")
.map(w => +w);
if (stored("winds")) options.winds = localStorage.getItem("winds").split(",").map(Number);
if (stored("temperatureEquator")) options.temperatureEquator = +localStorage.getItem("temperatureEquator");
if (stored("temperatureNorthPole")) options.temperatureNorthPole = +localStorage.getItem("temperatureNorthPole");
if (stored("temperatureSouthPole")) options.temperatureSouthPole = +localStorage.getItem("temperatureSouthPole");
if (stored("military")) options.military = JSON.parse(stored("military"));
if (stored("tooltipSize")) changeTooltipSize(stored("tooltipSize"));
@ -607,13 +606,10 @@ function randomizeOptions() {
if (randomize || !locked("culturesSet")) randomizeCultureSet();
// 'Configure World' settings
if (randomize || !locked("temperatureEquator")) options.temperatureEquator = gauss(25, 7, 20, 35, 0);
if (randomize || !locked("temperatureNorthPole")) options.temperatureNorthPole = gauss(-25, 7, -40, 10, 0);
if (randomize || !locked("temperatureSouthPole")) options.temperatureSouthPole = gauss(-15, 7, -40, 10, 0);
if (randomize || !locked("prec")) precInput.value = precOutput.value = gauss(100, 40, 5, 500);
const tMax = 30,
tMin = -30; // temperature extremes
if (randomize || !locked("temperatureEquator"))
temperatureEquatorOutput.value = temperatureEquatorInput.value = rand(tMax - 10, tMax);
if (randomize || !locked("temperaturePole"))
temperaturePoleOutput.value = temperaturePoleInput.value = rand(tMin, tMin + 30);
// 'Units Editor' settings
const US = navigator.language === "en-US";
@ -789,7 +785,7 @@ function showExportPane() {
}
async function exportToJson(type) {
const {exportToJson} = await import("../dynamic/export-json.js");
const {exportToJson} = await import("../dynamic/export-json.js?v=1.93.03");
exportToJson(type);
}
@ -797,7 +793,7 @@ async function showLoadPane() {
$("#loadMapData").dialog({
title: "Load map",
resizable: false,
width: "24em",
width: "auto",
position: {my: "center", at: "center", of: "svg"},
buttons: {
Close: function () {
@ -848,7 +844,7 @@ async function connectToDropbox() {
function loadURL() {
const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
const inner = `Provide URL to a .map file:
const inner = `Provide URL to map file:
<input id="mapURL" type="url" style="width: 24em" placeholder="https://e-cloud.com/test.map">
<br><i>Please note server should allow CORS for file to be loaded. If CORS is not allowed, save file to Dropbox and provide a direct link</i>`;
alertMessage.innerHTML = inner;
@ -1060,6 +1056,7 @@ function toggle3dOptions() {
document.getElementById("options3dSunX").addEventListener("change", changeSunPosition);
document.getElementById("options3dSunY").addEventListener("change", changeSunPosition);
document.getElementById("options3dSunZ").addEventListener("change", changeSunPosition);
document.getElementById("options3dMeshSkinResolution").addEventListener("change", changeResolutionScale);
document.getElementById("options3dMeshRotationRange").addEventListener("input", changeRotation);
document.getElementById("options3dMeshRotationNumber").addEventListener("change", changeRotation);
document.getElementById("options3dGlobeRotationRange").addEventListener("input", changeRotation);
@ -1069,6 +1066,9 @@ function toggle3dOptions() {
document.getElementById("options3dMeshSky").addEventListener("input", changeColors);
document.getElementById("options3dMeshWater").addEventListener("input", changeColors);
document.getElementById("options3dGlobeResolution").addEventListener("change", changeResolution);
// document.getElementById("options3dMeshWireframeMode").addEventListener("change",toggleWireframe3d);
document.getElementById("options3dSunColor").addEventListener("input", changeSunColor);
document.getElementById("options3dSubdivide").addEventListener("change", toggle3dSubdivision);
function updateValues() {
const globe = document.getElementById("canvas3d").dataset.type === "viewGlobe";
@ -1081,6 +1081,7 @@ function toggle3dOptions() {
options3dSunY.value = ThreeD.options.sun.y;
options3dSunZ.value = ThreeD.options.sun.z;
options3dMeshRotationRange.value = options3dMeshRotationNumber.value = ThreeD.options.rotateMesh;
options3dMeshSkinResolution.value = ThreeD.options.resolutionScale;
options3dGlobeRotationRange.value = options3dGlobeRotationNumber.value = ThreeD.options.rotateGlobe;
options3dMeshLabels3d.value = ThreeD.options.labels3d;
options3dMeshSkyMode.value = ThreeD.options.extendedWater;
@ -1088,6 +1089,8 @@ function toggle3dOptions() {
options3dMeshSky.value = ThreeD.options.skyColor;
options3dMeshWater.value = ThreeD.options.waterColor;
options3dGlobeResolution.value = ThreeD.options.resolution;
options3dSunColor.value = ThreeD.options.sunColor;
options3dSubdivide.value = ThreeD.options.subdivide;
}
function changeHeightScale() {
@ -1095,11 +1098,20 @@ function toggle3dOptions() {
ThreeD.setScale(+this.value);
}
function changeResolutionScale() {
options3dMeshSkinResolution.value = this.value;
ThreeD.setResolutionScale(+this.value);
}
function changeLightness() {
options3dLightnessRange.value = options3dLightnessNumber.value = this.value;
ThreeD.setLightness(this.value / 100);
}
function changeSunColor() {
ThreeD.setSunColor(options3dSunColor.value);
}
function changeSunPosition() {
const x = +options3dSunX.value;
const y = +options3dSunY.value;
@ -1117,6 +1129,14 @@ function toggle3dOptions() {
ThreeD.toggleLabels();
}
function toggle3dSubdivision() {
ThreeD.toggle3dSubdivision();
}
// function toggleWireframe3d() {
// ThreeD.toggleWireframe();
// }
function toggleSkyMode() {
const hide = ThreeD.options.extendedWater;
options3dColorSection.style.display = hide ? "none" : "block";

View file

@ -124,7 +124,9 @@ function editProvinces() {
const rural = p.rural * populationRate;
const urban = p.urban * populationRate * urbanization;
const population = rn(rural + urban);
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`;
const populationTip = `Total population: ${si(population)}; Rural population: ${si(
rural
)}; Urban population: ${si(urban)}`;
totalPopulation += population;
const stateName = pack.states[p.state].name;
@ -145,9 +147,15 @@ function editProvinces() {
>
<fill-box fill="${p.color}"></fill-box>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly />
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span>
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${
p.i
}"></use></svg>
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${
p.formName
}" readonly />
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${
p.burg ? "" : "placeholder"
}"></span>
<select
data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital"
class="cultureBase hide ${p.burgs.length ? "" : "placeholder"}"
@ -164,7 +172,7 @@ function editProvinces() {
class="icon-flag-empty ${separable ? "" : "placeholder"} hide"
></span>
<span data-tip="Toggle province focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
<span data-tip="Lock the province" class="icon-lock${p.lock ? '' : '-open'} hide"></span>
<span data-tip="Lock the province" class="icon-lock${p.lock ? "" : "-open"} hide"></span>
<span data-tip="Remove the province" class="icon-trash-empty hide"></span>
</div>`;
}
@ -193,7 +201,9 @@ function editProvinces() {
function getCapitalOptions(burgs, capital) {
let options = "";
burgs.forEach(b => (options += `<option ${b === capital ? "selected" : ""} value="${b}">${pack.burgs[b].name}</option>`));
burgs.forEach(
b => (options += `<option ${b === capital ? "selected" : ""} value="${b}">${pack.burgs[b].name}</option>`)
);
return options;
}
@ -267,7 +277,11 @@ function editProvinces() {
const {name, burg: burgId, burgs: provinceBurgs} = province;
if (provinceBurgs.some(b => burgs[b].capital))
return tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error");
return tip(
"Cannot declare independence of a province having capital burg. Please change capital first",
false,
"error"
);
if (!burgId) return tip("Cannot declare independence of a province without burg", false, "error");
const oldStateId = province.state;
@ -313,7 +327,10 @@ function editProvinces() {
return relations;
});
diplomacy.push("x");
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldStateId].name}`]);
states[0].diplomacy.push([
`Independance declaration`,
`${name} declared its independance from ${states[oldStateId].name}`
]);
// create new state
states.push({
@ -348,7 +365,7 @@ function editProvinces() {
BurgsAndStates.collectStatistics();
BurgsAndStates.defineStateForms(newStates);
BurgsAndStates.drawStateLabels(allStates);
drawStateLabels(allStates);
// redraw emblems
allStates.forEach(stateId => {
@ -375,8 +392,12 @@ function editProvinces() {
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = /* html */ ` Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${p.burgs.length ? "" : "disabled"} />
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${
p.burgs.length ? "" : "disabled"
} />
<p>Total population: ${l(total)} <span id="totalPop">${l(
total
)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@ -694,7 +715,13 @@ function editProvinces() {
function updateChart() {
const value =
this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : d => d.rural + d.urban;
this.value === "area"
? d => d.area
: this.value === "rural"
? d => d.rural
: this.value === "urban"
? d => d.urban
: d => d.rural + d.urban;
root.sum(value);
node.data(treeLayout(root).leaves());
@ -776,7 +803,13 @@ function editProvinces() {
customization = 11;
provs.select("g#provincesBody").append("g").attr("id", "temp");
provs.select("g#provincesBody").append("g").attr("id", "centers").attr("fill", "none").attr("stroke", "#ff0000").attr("stroke-width", 1);
provs
.select("g#provincesBody")
.append("g")
.attr("id", "centers")
.attr("fill", "none")
.attr("stroke", "#ff0000")
.attr("stroke-width", 1);
document.querySelectorAll("#provincesBottom > *").forEach(el => (el.style.display = "none"));
document.getElementById("provincesManuallyButtons").style.display = "inline-block";
@ -788,7 +821,11 @@ function editProvinces() {
$("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
tip("Click on a province to select, drag the circle to change province", true);
viewbox.style("cursor", "crosshair").on("click", selectProvinceOnMapClick).call(d3.drag().on("start", dragBrush)).on("touchmove mousemove", moveBrush);
viewbox
.style("cursor", "crosshair")
.on("click", selectProvinceOnMapClick)
.call(d3.drag().on("start", dragBrush))
.on("touchmove mousemove", moveBrush);
body.querySelector("div").classList.add("selected");
selectProvince(+body.querySelector("div").dataset.id);
@ -859,7 +896,11 @@ function editProvinces() {
if (i === pack.provinces[provinceOld].center) {
const center = centers.select("polygon[data-center='" + i + "']");
if (!center.size()) centers.append("polygon").attr("data-center", i).attr("points", getPackPolygon(i));
tip("Province center cannot be assigned to a different region. Please remove the province first", false, "error");
tip(
"Province center cannot be assigned to a different region. Please remove the province first",
false,
"error"
);
return;
}
@ -921,7 +962,8 @@ function editProvinces() {
provincesHeader.querySelector("div[data-sortby='state']").style.left = "22em";
provincesFooter.style.display = "block";
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all"));
if (!close) $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
if (!close)
$("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
restoreDefaultEvents();
clearMainTip();
@ -943,14 +985,20 @@ function editProvinces() {
const {cells, provinces} = pack;
const point = d3.mouse(this);
const center = findCell(point[0], point[1]);
if (cells.h[center] < 20) return tip("You cannot place province into the water. Please click on a land cell", false, "error");
if (cells.h[center] < 20)
return tip("You cannot place province into the water. Please click on a land cell", false, "error");
const oldProvince = cells.province[center];
if (oldProvince && provinces[oldProvince].center === center)
return tip("The cell is already a center of a different province. Select other cell", false, "error");
const state = cells.state[center];
if (!state) return tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error");
if (!state)
return tip(
"You cannot create a province in neutral lands. Please assign this land to a state first",
false,
"error"
);
if (d3.event.shiftKey === false) exitAddProvinceMode();
@ -1016,7 +1064,10 @@ function editProvinces() {
function downloadProvincesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = "Id,Province,Full Name,Form,State,Color,Capital,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
let data =
"Id,Province,Full Name,Form,State,Color,Capital,Area " +
unit +
",Total Population,Rural Population,Urban Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) {
const key = parseInt(el.dataset.id);

View file

@ -76,9 +76,22 @@ function selectStyleElement() {
// stroke color and width
if (
["armies", "routes", "lakes", "borders", "cults", "relig", "cells", "coastline", "prec", "ice", "icons", "coordinates", "zones", "gridOverlay"].includes(
sel
)
[
"armies",
"routes",
"lakes",
"borders",
"cults",
"relig",
"cells",
"coastline",
"prec",
"ice",
"icons",
"coordinates",
"zones",
"gridOverlay"
].includes(sel)
) {
styleStroke.style.display = "block";
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
@ -87,14 +100,29 @@ function selectStyleElement() {
}
// stroke dash
if (["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(sel)) {
if (
["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(sel)
) {
styleStrokeDash.style.display = "block";
styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || "";
styleStrokeLinecapInput.value = el.attr("stroke-linecap") || "inherit";
}
// clipping
if (["cells", "gridOverlay", "coordinates", "compass", "terrain", "temperature", "routes", "texture", "biomes", "zones"].includes(sel)) {
if (
[
"cells",
"gridOverlay",
"coordinates",
"compass",
"terrain",
"temperature",
"routes",
"texture",
"biomes",
"zones"
].includes(sel)
) {
styleClipping.style.display = "block";
styleClippingInput.value = el.attr("mask") || "";
}
@ -142,8 +170,12 @@ function selectStyleElement() {
if (sel === "population") {
stylePopulation.style.display = "block";
stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population.select("#rural").attr("stroke");
stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population.select("#urban").attr("stroke");
stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population
.select("#rural")
.attr("stroke");
stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population
.select("#urban")
.attr("stroke");
styleStrokeWidth.style.display = "block";
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || "";
}
@ -233,7 +265,8 @@ function selectStyleElement() {
styleOcean.style.display = "block";
styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill");
styleOceanPattern.value = document.getElementById("oceanicPattern")?.getAttribute("href");
styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value = document.getElementById("oceanicPattern").getAttribute("opacity") || 1;
styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value =
document.getElementById("oceanicPattern").getAttribute("opacity") || 1;
outlineLayers.value = oceanLayers.attr("layers");
}
@ -334,8 +367,9 @@ styleFilterInput.addEventListener("change", function () {
});
styleTextureInput.addEventListener("change", function () {
if (this.value === "none") texture.select("image").attr("xlink:href", "");
else getBase64(this.value, base64 => texture.select("image").attr("xlink:href", base64));
texture.select("image").attr("src", this.value);
if (layerIsOn("toggleTexture")) texture.select("image").attr("href", this.value);
zoom.scaleBy(svg, 1.00001);
});
styleTextureShiftX.addEventListener("input", function () {
@ -551,7 +585,10 @@ styleFontAdd.addEventListener("click", function () {
if (!family) return tip("Please provide a font name", false, "error");
const existingFont = method === "fontURL" ? fonts.find(font => font.family === family && font.src === src) : fonts.find(font => font.family === family);
const existingFont =
method === "fontURL"
? fonts.find(font => font.family === family && font.src === src)
: fonts.find(font => font.family === family);
if (existingFont) return tip("The font is already added", false, "error");
if (method === "fontURL") addWebFont(family, src);
@ -726,17 +763,17 @@ function textureProvideURL() {
buttons: {
Apply: function () {
const name = textureURL.value.split("/").pop();
if (!name || name === "") {
tip("Please provide a valid URL", false, "error");
return;
}
if (!name || name === "") return tip("Please provide a valid URL", false, "error");
const opt = document.createElement("option");
opt.value = textureURL.value;
opt.text = name.slice(0, 20);
styleTextureInput.add(opt);
styleTextureInput.value = textureURL.value;
getBase64(textureURL.value, base64 => texture.select("image").attr("xlink:href", base64));
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
const image = texture.select("image");
image.attr("src", textureURL.value);
if (layerIsOn("toggleTexture")) image.attr("href", textureURL.value);
$(this).dialog("close");
},
Cancel: function () {

View file

@ -89,6 +89,10 @@ function applyStyle(style) {
} else {
el.setAttribute(attribute, value);
}
if (layerIsOn("toggleTexture") && selector === "#textureImage" && attribute === "src") {
el.setAttribute("href", value);
}
}
}
}
@ -225,7 +229,7 @@ function addStylePreset() {
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#emblems": ["opacity", "stroke-width", "filter"],
"#texture": ["opacity", "filter", "mask"],
"#textureImage": ["x", "y"],
"#textureImage": ["x", "y", "src"],
"#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#oceanLayers": ["filter", "layers"],
"#oceanBase": ["fill"],

View file

@ -48,7 +48,10 @@ function showBurgTemperatureGraph(id) {
// Standard deviation for average temperature for the year from [0, 1] to [min, max]
const yearSig = lstOut[0] * 62.9466411977018 + 0.28613807855649165;
// Standard deviation for the difference between the minimum and maximum temperatures for the year
const yearDelTmpSig = lstOut[1] * 13.541688670361175 + 0.1414213562373084 > yearSig ? yearSig : lstOut[1] * 13.541688670361175 + 0.1414213562373084;
const yearDelTmpSig =
lstOut[1] * 13.541688670361175 + 0.1414213562373084 > yearSig
? yearSig
: lstOut[1] * 13.541688670361175 + 0.1414213562373084;
// Expected value for the difference between the minimum and maximum temperatures for the year
const yearDelTmpMu = lstOut[2] * 15.266666666666667 + 0.6416666666666663;
@ -67,7 +70,20 @@ function showBurgTemperatureGraph(id) {
const year = new Date().getFullYear(); // use current year
const startDate = new Date(year, 0, 1);
const endDate = new Date(year, 11, 31);
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
const xscale = d3.scaleTime().domain([startDate, endDate]).range([0, chartWidth]);
const yscale = d3.scaleLinear().domain([minT, maxT]).range([chartHeight, 0]);
@ -91,7 +107,11 @@ function showBurgTemperatureGraph(id) {
});
drawGraph();
$("#alert").dialog({title: "Annual temperature in " + b.name, width: "auto", position: {my: "center", at: "center", of: "svg"}});
$("#alert").dialog({
title: "Annual temperature in " + b.name,
width: "auto",
position: {my: "center", at: "center", of: "svg"}
});
function drawGraph() {
alertMessage.innerHTML = "";
@ -109,11 +129,26 @@ function showBurgTemperatureGraph(id) {
const legendX = n => (chartWidth * n) / 4;
const legendTextX = n => legendX(n) + 10;
legend.append("circle").attr("cx", legendX(1)).attr("cy", legendY).attr("r", 4).style("fill", "red");
legend.append("text").attr("x", legendTextX(1)).attr("y", legendY).attr("alignment-baseline", "central").text("Day temperature");
legend
.append("text")
.attr("x", legendTextX(1))
.attr("y", legendY)
.attr("alignment-baseline", "central")
.text("Day temperature");
legend.append("circle").attr("cx", legendX(2)).attr("cy", legendY).attr("r", 4).style("fill", "orange");
legend.append("text").attr("x", legendTextX(2)).attr("y", legendY).attr("alignment-baseline", "central").text("Mean temperature");
legend
.append("text")
.attr("x", legendTextX(2))
.attr("y", legendY)
.attr("alignment-baseline", "central")
.text("Mean temperature");
legend.append("circle").attr("cx", legendX(3)).attr("cy", legendY).attr("r", 4).style("fill", "blue");
legend.append("text").attr("x", legendTextX(3)).attr("y", legendY).attr("alignment-baseline", "central").text("Night temperature");
legend
.append("text")
.attr("x", legendTextX(3))
.attr("y", legendY)
.attr("alignment-baseline", "central")
.text("Night temperature");
const xGrid = d3.axisBottom(xscale).ticks().tickSize(-chartHeight);
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth);
@ -135,7 +170,10 @@ function showBurgTemperatureGraph(id) {
}
const xAxis = d3.axisBottom(xscale).ticks().tickFormat(d3.timeFormat("%B"));
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(convertTemperature);
const yAxis = d3
.axisLeft(yscale)
.ticks(5)
.tickFormat(v => convertTemperature(v));
const axis = chart.append("g");
axis
@ -146,9 +184,24 @@ function showBurgTemperatureGraph(id) {
axis.select("path.domain").attr("d", `M0.5,0.5 H${chartWidth + 0.5}`);
const curves = chart.append("g").attr("fill", "none").style("stroke-width", 2.5);
curves.append("path").attr("d", getCurve(tempMean)).attr("data-type", "daily").attr("stroke", "orange").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMin)).attr("data-type", "night").attr("stroke", "blue").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMax)).attr("data-type", "day").attr("stroke", "red").on("mousemove", printVal);
curves
.append("path")
.attr("d", getCurve(tempMean))
.attr("data-type", "daily")
.attr("stroke", "orange")
.on("mousemove", printVal);
curves
.append("path")
.attr("d", getCurve(tempMin))
.attr("data-type", "night")
.attr("stroke", "blue")
.on("mousemove", printVal);
curves
.append("path")
.attr("d", getCurve(tempMax))
.attr("data-type", "day")
.attr("stroke", "red")
.on("mousemove", printVal);
function printVal() {
const [x, y] = d3.mouse(this);

View file

@ -74,7 +74,7 @@ toolsContent.addEventListener("click", function (event) {
});
function processFeatureRegeneration(event, button) {
if (button === "regenerateStateLabels") BurgsAndStates.drawStateLabels();
if (button === "regenerateStateLabels") drawStateLabels();
else if (button === "regenerateReliefIcons") {
ReliefIcons();
if (!layerIsOn("toggleRelief")) toggleRelief();
@ -154,7 +154,7 @@ function regenerateStates() {
layerIsOn("toggleBorders") ? drawBorders() : toggleBorders();
if (layerIsOn("toggleProvinces")) drawProvinces();
BurgsAndStates.drawStateLabels();
drawStateLabels();
Military.generate();
if (layerIsOn("toggleEmblems")) drawEmblems();
@ -570,9 +570,8 @@ function addLabelOnClick() {
.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("y", 0).text(name);
const width = example.node().getBBox().width;
const x = width / -2; // x offset;
example.remove();
group.classed("hidden", false);
@ -584,7 +583,7 @@ function addLabelOnClick() {
.attr("startOffset", "50%")
.attr("font-size", "100%")
.append("tspan")
.attr("x", x)
.attr("x", 0)
.text(name);
defs
@ -828,9 +827,17 @@ function addMarkerOnClick() {
// Find the currently selected marker to use as a base
const isMarkerSelected = markers.length && elSelected?.node()?.parentElement?.id === "markers";
const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;
const baseMarker = selectedMarker || {icon: "❓"};
const selectedType = document.getElementById("addedMarkerType").value;
const selectedConfig = Markers.getConfig().find(({type}) => type === selectedType);
const baseMarker = selectedMarker || selectedConfig || {icon: "❓"};
const marker = Markers.add({...baseMarker, x, y, cell});
if (selectedConfig && selectedConfig.add) {
selectedConfig.add("marker" + marker.i, cell);
}
const markersElement = document.getElementById("markers");
const rescale = +markersElement.getAttribute("rescale");
markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));

View file

@ -1,5 +1,6 @@
function editWorld() {
if (customization) return;
$("#worldConfigurator").dialog({
title: "Configure World",
resizable: false,
@ -8,8 +9,7 @@ function editWorld() {
"Whole World": () => applyWorldPreset(100, 50),
Northern: () => applyWorldPreset(33, 25),
Tropical: () => applyWorldPreset(33, 50),
Southern: () => applyWorldPreset(33, 75),
"Restore Winds": restoreDefaultWinds
Southern: () => applyWorldPreset(33, 75)
},
open: function () {
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
@ -17,7 +17,6 @@ function editWorld() {
buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
},
close: function () {
$(this).dialog("destroy");
@ -25,28 +24,56 @@ function editWorld() {
});
const globe = d3.select("#globe");
const clr = d3.scaleSequential(d3.interpolateSpectral);
const tMax = 30,
tMin = -25; // temperature extremes
const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
const path = d3.geoPath(projection);
updateInputValues();
updateGlobeTemperature();
updateGlobePosition();
if (modules.editWorld) return;
modules.editWorld = true;
document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target));
byId("worldControls").addEventListener("input", e => updateWorld(e.target));
globe.select("#globeWindArrows").on("click", changeWind);
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
updateWindDirections();
byId("restoreWinds").addEventListener("click", restoreDefaultWinds);
function updateInputValues() {
byId("temperatureEquatorInput").value = options.temperatureEquator;
byId("temperatureEquatorOutput").value = options.temperatureEquator;
byId("temperatureEquatorF").innerText = convertTemperature(options.temperatureEquator, "°F");
byId("temperatureNorthPoleInput").value = options.temperatureNorthPole;
byId("temperatureNorthPoleOutput").value = options.temperatureNorthPole;
byId("temperatureNorthPoleF").innerText = convertTemperature(options.temperatureNorthPole, "°F");
byId("temperatureSouthPoleInput").value = options.temperatureSouthPole;
byId("temperatureSouthPoleOutput").value = options.temperatureSouthPole;
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
function updateWorld(el) {
if (el) {
document.getElementById(el.dataset.stored + "Input").value = el.value;
document.getElementById(el.dataset.stored + "Output").value = el.value;
if (el.dataset.stored) lock(el.dataset.stored);
if (el?.dataset.stored) {
const stored = el.dataset.stored;
byId(stored + "Input").value = el.value;
byId(stored + "Output").value = el.value;
lock(el.dataset.stored);
if (stored === "temperatureEquator") {
options.temperatureEquator = Number(el.value);
byId("temperatureEquatorF").innerText = convertTemperature(options.temperatureEquator, "°F");
}
if (stored === "temperatureNorthPole") {
options.temperatureNorthPole = Number(el.value);
byId("temperatureNorthPoleF").innerText = convertTemperature(options.temperatureNorthPole, "°F");
}
if (stored === "temperatureSouthPole") {
options.temperatureSouthPole = Number(el.value);
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
}
updateGlobeTemperature();
@ -58,18 +85,18 @@ function editWorld() {
Lakes.defineGroup();
Rivers.specify();
pack.cells.h = new Float32Array(heights);
defineBiomes();
Biomes.define();
if (layerIsOn("toggleTemp")) drawTemp();
if (layerIsOn("togglePrec")) drawPrec();
if (layerIsOn("toggleBiomes")) drawBiomes();
if (layerIsOn("toggleCoordinates")) drawCoordinates();
if (layerIsOn("toggleRivers")) drawRivers();
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
if (byId("canvas3d")) setTimeout(ThreeD.update(), 500);
}
function updateGlobePosition() {
const size = +document.getElementById("mapSizeOutput").value;
const size = +byId("mapSizeOutput").value;
const eqD = ((graphHeight / 2) * 100) / size;
calculateMapCoordinates();
@ -77,12 +104,12 @@ function editWorld() {
const scale = +distanceScaleInput.value;
const unit = distanceUnitInput.value;
const meridian = toKilometer(eqD * 2 * scale);
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
document.getElementById("meridianLength").innerHTML = rn(eqD * 2);
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
byId("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
byId("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
byId("meridianLength").innerHTML = rn(eqD * 2);
byId("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
byId("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
byId("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
function toKilometer(v) {
if (unit === "km") return v;
@ -104,15 +131,24 @@ function editWorld() {
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
}
// update temperatures on globe (visual-only)
function updateGlobeTemperature() {
const tEq = +document.getElementById("temperatureEquatorOutput").value;
document.getElementById("temperatureEquatorF").innerHTML = rn((tEq * 9) / 5 + 32);
const tPole = +document.getElementById("temperaturePoleOutput").value;
document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32);
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin)));
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
const tEq = options.temperatureEquator;
const tNP = options.temperatureNorthPole;
const tSP = options.temperatureSouthPole;
const scale = d3.scaleSequential(d3.interpolateSpectral);
const getColor = value => scale(1 - value);
const [tMin, tMax] = [-25, 30]; // temperature extremes
const tDelta = tMax - tMin;
globe.select("#grad90").attr("stop-color", getColor((tNP - tMin) / tDelta));
globe.select("#grad60").attr("stop-color", getColor((tEq - ((tEq - tNP) * 2) / 3 - tMin) / tDelta));
globe.select("#grad30").attr("stop-color", getColor((tEq - ((tEq - tNP) * 1) / 4 - tMin) / tDelta));
globe.select("#grad0").attr("stop-color", getColor((tEq - tMin) / tDelta));
globe.select("#grad-30").attr("stop-color", getColor((tEq - ((tEq - tSP) * 1) / 4 - tMin) / tDelta));
globe.select("#grad-60").attr("stop-color", getColor((tEq - ((tEq - tSP) * 2) / 3 - tMin) / tDelta));
globe.select("#grad-90").attr("stop-color", getColor((tSP - tMin) / tDelta));
}
function updateWindDirections() {
@ -146,8 +182,8 @@ function editWorld() {
}
function applyWorldPreset(size, lat) {
document.getElementById("mapSizeInput").value = document.getElementById("mapSizeOutput").value = size;
document.getElementById("latitudeInput").value = document.getElementById("latitudeOutput").value = lat;
byId("mapSizeInput").value = byId("mapSizeOutput").value = size;
byId("latitudeInput").value = byId("latitudeOutput").value = lat;
lock("mapSize");
lock("latitude");
updateWorld();