mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
Fix for Firefox mask/use bug for cultures/biomes/religions
This commit is contained in:
parent
228b4f2932
commit
1f598ae736
4 changed files with 82 additions and 53 deletions
|
|
@ -105,7 +105,7 @@ button, select, a {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#statesBody, #provincesBody, #relig, #biomes, #cults {
|
#statesBody, #provincesBody, #religionsBody, #biomesBody, #culturesBody {
|
||||||
stroke-width: .6;
|
stroke-width: .6;
|
||||||
fill-rule: evenodd;
|
fill-rule: evenodd;
|
||||||
mask: url(#land);
|
mask: url(#land);
|
||||||
|
|
|
||||||
11
main.js
11
main.js
|
|
@ -7,7 +7,7 @@
|
||||||
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
|
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
const version = "1.22"; // generator version
|
const version = "1.23"; // generator version
|
||||||
document.title += " v" + version;
|
document.title += " v" + version;
|
||||||
|
|
||||||
// if map version is not stored, clear localStorage and show a message
|
// if map version is not stored, clear localStorage and show a message
|
||||||
|
|
@ -30,6 +30,7 @@ let landmass = viewbox.append("g").attr("id", "landmass");
|
||||||
let texture = viewbox.append("g").attr("id", "texture");
|
let texture = viewbox.append("g").attr("id", "texture");
|
||||||
let terrs = viewbox.append("g").attr("id", "terrs");
|
let terrs = viewbox.append("g").attr("id", "terrs");
|
||||||
let biomes = viewbox.append("g").attr("id", "biomes");
|
let biomes = viewbox.append("g").attr("id", "biomes");
|
||||||
|
let biomesBody = biomes.append("g").attr("id", "biomesBody");
|
||||||
let cells = viewbox.append("g").attr("id", "cells");
|
let cells = viewbox.append("g").attr("id", "cells");
|
||||||
let gridOverlay = viewbox.append("g").attr("id", "gridOverlay");
|
let gridOverlay = viewbox.append("g").attr("id", "gridOverlay");
|
||||||
let coordinates = viewbox.append("g").attr("id", "coordinates");
|
let coordinates = viewbox.append("g").attr("id", "coordinates");
|
||||||
|
|
@ -37,7 +38,9 @@ let compass = viewbox.append("g").attr("id", "compass");
|
||||||
let rivers = viewbox.append("g").attr("id", "rivers");
|
let rivers = viewbox.append("g").attr("id", "rivers");
|
||||||
let terrain = viewbox.append("g").attr("id", "terrain");
|
let terrain = viewbox.append("g").attr("id", "terrain");
|
||||||
let relig = viewbox.append("g").attr("id", "relig");
|
let relig = viewbox.append("g").attr("id", "relig");
|
||||||
|
let religionsBody = relig.append("g").attr("id", "religionsBody");
|
||||||
let cults = viewbox.append("g").attr("id", "cults");
|
let cults = viewbox.append("g").attr("id", "cults");
|
||||||
|
let culturesBody = cults.append("g").attr("id", "culturesBody");
|
||||||
let regions = viewbox.append("g").attr("id", "regions");
|
let regions = viewbox.append("g").attr("id", "regions");
|
||||||
let statesBody = regions.append("g").attr("id", "statesBody");
|
let statesBody = regions.append("g").attr("id", "statesBody");
|
||||||
let statesHalo = regions.append("g").attr("id", "statesHalo");
|
let statesHalo = regions.append("g").attr("id", "statesHalo");
|
||||||
|
|
@ -328,14 +331,12 @@ function showWelcomeMessage() {
|
||||||
<li>Ability to save map as JPEG image</li>
|
<li>Ability to save map as JPEG image</li>
|
||||||
<li>Diplomacy Editor enhancements</li>
|
<li>Diplomacy Editor enhancements</li>
|
||||||
<li>Rivers Overview screen [v 1.21] <b>*</b></li>
|
<li>Rivers Overview screen [v 1.21] <b>*</b></li>
|
||||||
|
<li>Fix for religions overflowing into lakes.</li>
|
||||||
|
<li>Religions, cultures, and biomes follow water boundaries.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p style="color:#990000; font-style: italic"><b>*</b> It's recommended to regenerate rivers to get clean data for Rivers Overview.<p>
|
<p style="color:#990000; font-style: italic"><b>*</b> It's recommended to regenerate rivers to get clean data for Rivers Overview.<p>
|
||||||
|
|
||||||
<p class="announcement">We are happy to invite you to participate in our first map making contest!
|
|
||||||
Valuable prizes for winners and our respect for all participants.
|
|
||||||
See ${link("https://www.reddit.com/r/FantasyMapGenerator/comments/dn2sqv/azgaars_fantasy_map_generator_mapmaking_contest/", "Reddit post")} for the details.</p>
|
|
||||||
|
|
||||||
<p>Join our ${reddit} and ${discord} to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
<p>Join our ${reddit} and ${discord} to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
||||||
<p>Thanks for all supporters on ${patreon}!</i></p>`;
|
<p>Thanks for all supporters on ${patreon}!</i></p>`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -928,6 +928,12 @@ function parseLoadedData(data) {
|
||||||
BurgsAndStates.collectStatistics();
|
BurgsAndStates.collectStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version < 1.23) {
|
||||||
|
culturesBody = cults.append("g").attr("id", "culturesBody");
|
||||||
|
biomesBody = biomes.append("g").attr("id", "biomesBody");
|
||||||
|
religionsBody = relig.append("g").attr("id", "religionsBody");
|
||||||
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
changeMapSize();
|
changeMapSize();
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ function drawTemp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleBiomes(event) {
|
function toggleBiomes(event) {
|
||||||
if (!biomes.selectAll("path").size()) {
|
if (!layerIsOn("toggleBiomes")) {
|
||||||
turnButtonOn("toggleBiomes");
|
turnButtonOn("toggleBiomes");
|
||||||
drawBiomes();
|
drawBiomes();
|
||||||
if (event && event.ctrlKey) editStyle("biomes");
|
if (event && event.ctrlKey) editStyle("biomes");
|
||||||
|
|
@ -331,45 +331,56 @@ function toggleBiomes(event) {
|
||||||
|
|
||||||
function drawBiomes() {
|
function drawBiomes() {
|
||||||
biomes.selectAll("path").remove();
|
biomes.selectAll("path").remove();
|
||||||
|
|
||||||
const cells = pack.cells, vertices = pack.vertices, n = cells.i.length;
|
const cells = pack.cells, vertices = pack.vertices, n = cells.i.length;
|
||||||
const used = new Uint8Array(cells.i.length);
|
const used = new Uint8Array(cells.i.length);
|
||||||
const paths = new Array(biomesData.i.length).fill("");
|
const vArray = new Array(biomesData.i.length); // store vertices array
|
||||||
|
const body = new Array(biomesData.i.length).fill(""); // store path around each biome
|
||||||
|
const gap = new Array(biomesData.i.length).fill(""); // store path along water for each biome to fill the gaps
|
||||||
|
|
||||||
for (const i of cells.i) {
|
for (const i of cells.i) {
|
||||||
if (!cells.biome[i]) continue; // no need to mark water
|
if (!cells.biome[i] || used[i]) continue;
|
||||||
if (used[i]) continue; // already marked
|
|
||||||
const b = cells.biome[i];
|
const b = cells.biome[i];
|
||||||
const onborder = cells.c[i].some(n => cells.biome[n] !== b);
|
const onborder = cells.c[i].some(n => cells.biome[n] !== b);
|
||||||
if (!onborder) continue;
|
if (!onborder) continue;
|
||||||
const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b));
|
|
||||||
const chain = connectVertices(edgeVerticle, b);
|
const borderWith = cells.c[i].map(c => cells.biome[c]).find(n => n !== b);
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] === borderWith));
|
||||||
|
const chain = connectVertices(vertex, b, borderWith);
|
||||||
if (chain.length < 3) continue;
|
if (chain.length < 3) continue;
|
||||||
const points = chain.map(v => vertices.p[v]);
|
const points = chain.map(v => vertices.p[v[0]]);
|
||||||
paths[b] += "M" + points.join("L") + "Z";
|
if (!vArray[b]) vArray[b] = [];
|
||||||
|
vArray[b].push(points);
|
||||||
|
body[b] += "M" + points.join("L");
|
||||||
|
gap[b] += "M" + vertices.p[chain[0][0]] + chain.reduce((r,v,i,d) => !i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i+1] && !d[i+1][2] ? r + "M" + vertices.p[v[0]] : r, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
paths.forEach(function(d, i) {
|
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, biomesData.color[i]]).filter(d => d[0]);
|
||||||
if (d.length < 10) return;
|
biomesBody.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "biome"+d[1]);
|
||||||
biomes.append("path").attr("d", d).attr("fill", biomesData.color[i]).attr("stroke", biomesData.color[i]).attr("id", "biome"+i);
|
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, biomesData.color[i]]).filter(d => d[0]);
|
||||||
});
|
biomesBody.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "biome-gap"+d[1]).attr("stroke-width", "10px");
|
||||||
|
|
||||||
// connect vertices to chain
|
// connect vertices to chain
|
||||||
function connectVertices(start, b) {
|
function connectVertices(start, t, biome) {
|
||||||
const chain = []; // vertices chain to form a path
|
const chain = []; // vertices chain to form a path
|
||||||
|
let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.biome[c] !== t);
|
||||||
|
function check(i) {biome = cells.biome[i]; land = cells.h[i] >= 20;}
|
||||||
|
|
||||||
for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
|
for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
|
||||||
const prev = chain[chain.length - 1]; // previous vertex in chain
|
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||||
chain.push(current); // add current vertex to sequence
|
chain.push([current, biome, land]); // add current vertex to sequence
|
||||||
const c = vertices.c[current]; // cells adjacent to vertex
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
c.filter(c => cells.biome[c] === b).forEach(c => used[c] = 1);
|
c.filter(c => cells.biome[c] === t).forEach(c => used[c] = 1);
|
||||||
const c0 = c[0] >= n || cells.biome[c[0]] !== b;
|
const c0 = c[0] >= n || cells.biome[c[0]] !== t;
|
||||||
const c1 = c[1] >= n || cells.biome[c[1]] !== b;
|
const c1 = c[1] >= n || cells.biome[c[1]] !== t;
|
||||||
const c2 = c[2] >= n || cells.biome[c[2]] !== b;
|
const c2 = c[2] >= n || cells.biome[c[2]] !== t;
|
||||||
const v = vertices.v[current]; // neighboring vertices
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else
|
||||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else
|
||||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);}
|
||||||
if (current === chain[chain.length - 1]) {console.error("Next vertex is not found"); break;}
|
if (current === chain[chain.length - 1][0]) {console.error("Next vertex is not found"); break;}
|
||||||
}
|
}
|
||||||
|
chain.push([start, biome, land]); // add starting vertex to sequence to close the path
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -456,9 +467,7 @@ function drawCells() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCultures(event) {
|
function toggleCultures(event) {
|
||||||
const cultures = pack.cultures.filter(c => c.i && !c.removed);
|
if (!layerIsOn("toggleCultures")) {
|
||||||
const empty = !cults.selectAll("path").size();
|
|
||||||
if (empty && cultures.length) {
|
|
||||||
turnButtonOn("toggleCultures");
|
turnButtonOn("toggleCultures");
|
||||||
drawCultures();
|
drawCultures();
|
||||||
if (event && event.ctrlKey) editStyle("cults");
|
if (event && event.ctrlKey) editStyle("cults");
|
||||||
|
|
@ -471,54 +480,67 @@ function toggleCultures(event) {
|
||||||
|
|
||||||
function drawCultures() {
|
function drawCultures() {
|
||||||
console.time("drawCultures");
|
console.time("drawCultures");
|
||||||
|
|
||||||
cults.selectAll("path").remove();
|
cults.selectAll("path").remove();
|
||||||
const cells = pack.cells, vertices = pack.vertices, cultures = pack.cultures, n = cells.i.length;
|
const cells = pack.cells, vertices = pack.vertices, cultures = pack.cultures, n = cells.i.length;
|
||||||
const used = new Uint8Array(cells.i.length);
|
const used = new Uint8Array(cells.i.length);
|
||||||
const paths = new Array(cultures.length).fill("");
|
const vArray = new Array(cultures.length); // store vertices array
|
||||||
|
const body = new Array(cultures.length).fill(""); // store path around each culture
|
||||||
|
const gap = new Array(cultures.length).fill(""); // store path along water for each culture to fill the gaps
|
||||||
|
|
||||||
for (const i of cells.i) {
|
for (const i of cells.i) {
|
||||||
if (!cells.culture[i]) continue;
|
if (!cells.culture[i]) continue;
|
||||||
if (used[i]) continue;
|
if (used[i]) continue;
|
||||||
used[i] = 1;
|
used[i] = 1;
|
||||||
const c = cells.culture[i];
|
const c = cells.culture[i];
|
||||||
const onborder = cells.c[i].some(n => cells.culture[n] !== c);
|
const onborder = cells.c[i].filter(n => cells.culture[n] !== c);
|
||||||
if (!onborder) continue;
|
if (!onborder.length) continue;
|
||||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.culture[i] !== c));
|
const borderWith = cells.c[i].map(c => cells.culture[c]).find(n => n !== c);
|
||||||
const chain = connectVertices(vertex, c);
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.culture[i] === borderWith));
|
||||||
|
const chain = connectVertices(vertex, c, borderWith);
|
||||||
if (chain.length < 3) continue;
|
if (chain.length < 3) continue;
|
||||||
const points = chain.map(v => vertices.p[v]);
|
const points = chain.map(v => vertices.p[v[0]]);
|
||||||
paths[c] += "M" + points.join("L") + "Z";
|
if (!vArray[c]) vArray[c] = [];
|
||||||
|
vArray[c].push(points);
|
||||||
|
body[c] += "M" + points.join("L");
|
||||||
|
gap[c] += "M" + vertices.p[chain[0][0]] + chain.reduce((r2,v,i,d) => !i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i+1] && !d[i+1][2] ? r2 + "M" + vertices.p[v[0]] : r2, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = paths.map((p, i) => [p, i]).filter(d => d[0].length > 10);
|
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, cultures[i].color]).filter(d => d[0]);
|
||||||
cults.selectAll("path").data(data).enter().append("path").attr("d", d => d[0]).attr("fill", d => cultures[d[1]].color).attr("id", d => "culture"+d[1]);
|
culturesBody.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "culture"+d[1]);
|
||||||
|
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, cultures[i].color]).filter(d => d[0]);
|
||||||
|
culturesBody.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "culture-gap"+d[1]).attr("stroke-width", "10px");
|
||||||
|
|
||||||
// connect vertices to chain
|
// connect vertices to chain
|
||||||
function connectVertices(start, t) {
|
function connectVertices(start, t, culture) {
|
||||||
const chain = []; // vertices chain to form a path
|
const chain = []; // vertices chain to form a path
|
||||||
|
let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.culture[c] !== t);
|
||||||
|
function check(i) {culture = cells.culture[i]; land = cells.h[i] >= 20;}
|
||||||
|
|
||||||
for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
|
for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
|
||||||
const prev = chain[chain.length - 1]; // previous vertex in chain
|
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||||
chain.push(current); // add current vertex to sequence
|
chain.push([current, culture, land]); // add current vertex to sequence
|
||||||
const c = vertices.c[current]; // cells adjacent to vertex
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
c.filter(c => cells.culture[c] === t).forEach(c => used[c] = 1);
|
c.filter(c => cells.culture[c] === t).forEach(c => used[c] = 1);
|
||||||
const c0 = c[0] >= n || cells.culture[c[0]] !== t;
|
const c0 = c[0] >= n || cells.culture[c[0]] !== t;
|
||||||
const c1 = c[1] >= n || cells.culture[c[1]] !== t;
|
const c1 = c[1] >= n || cells.culture[c[1]] !== t;
|
||||||
const c2 = c[2] >= n || cells.culture[c[2]] !== t;
|
const c2 = c[2] >= n || cells.culture[c[2]] !== t;
|
||||||
const v = vertices.v[current]; // neighboring vertices
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else
|
||||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else
|
||||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);}
|
||||||
if (current === chain[chain.length - 1]) {console.error("Next vertex is not found"); break;}
|
if (current === chain[chain.length - 1][0]) {console.error("Next vertex is not found"); break;}
|
||||||
|
|
||||||
}
|
}
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.timeEnd("drawCultures");
|
console.timeEnd("drawCultures");
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleReligions(event) {
|
function toggleReligions(event) {
|
||||||
const religions = pack.religions.filter(r => r.i && !r.removed);
|
const religions = pack.religions.filter(r => r.i && !r.removed);
|
||||||
if (!relig.selectAll("path").size() && religions.length) {
|
if (!layerIsOn("toggleReligions")) {
|
||||||
turnButtonOn("toggleReligions");
|
turnButtonOn("toggleReligions");
|
||||||
drawReligions();
|
drawReligions();
|
||||||
if (event && event.ctrlKey) editStyle("relig");
|
if (event && event.ctrlKey) editStyle("relig");
|
||||||
|
|
@ -558,9 +580,9 @@ function drawReligions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
||||||
relig.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "religion"+d[1]);
|
religionsBody.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "religion"+d[1]);
|
||||||
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
||||||
relig.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "religion-gap"+d[1]).attr("stroke-width", "10px");
|
religionsBody.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "religion-gap"+d[1]).attr("stroke-width", "10px");
|
||||||
|
|
||||||
// connect vertices to chain
|
// connect vertices to chain
|
||||||
function connectVertices(start, t, religion) {
|
function connectVertices(start, t, religion) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue