mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
refactor: render covering layers
This commit is contained in:
parent
538ad3512e
commit
8abf443f70
25 changed files with 171 additions and 366 deletions
|
|
@ -18,6 +18,7 @@
|
|||
"@types/delaunator": "^5.0.0",
|
||||
"@types/jquery": "^3.5.14",
|
||||
"@types/jqueryui": "^1.12.16",
|
||||
"@types/polylabel": "^1.0.5",
|
||||
"c8": "^7.12.0",
|
||||
"happy-dom": "^6.0.4",
|
||||
"rollup": "^2.75.7",
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#404040",
|
||||
"stroke-width": 0.7,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -79,13 +79,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#404040",
|
||||
"stroke-width": 0.7,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.5,
|
||||
"stroke": "#404040",
|
||||
"stroke-width": 2,
|
||||
"stroke-width": 3,
|
||||
"filter": "url(#splotch)"
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.35,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 2,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": "url(#splotch)"
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -79,13 +79,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#404040",
|
||||
"stroke-width": 1,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 1.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.5,
|
||||
"stroke": null,
|
||||
"stroke-width": 0,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.5,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -79,13 +79,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#404040",
|
||||
"stroke-width": 0.7,
|
||||
"stroke-width": 3,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@
|
|||
"#relig": {
|
||||
"opacity": 0.7,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0,
|
||||
"stroke-width": 3,
|
||||
"filter": "url(#bluredSplotch)"
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-width": 3,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": "url(#splotch)"
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ a {
|
|||
}
|
||||
|
||||
#biomes {
|
||||
stroke-width: 0.7;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
#landmass {
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
import {clipPoly} from "utils/lineUtils";
|
||||
import {TIME} from "config/logging";
|
||||
|
||||
export function drawBiomes() {
|
||||
TIME && console.time("drawBiomes");
|
||||
biomes.selectAll("path").remove();
|
||||
|
||||
const {cells, vertices} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const paths = new Array(biomesData.i.length).fill("");
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.biome[i]) continue; // no need to mark marine biome (liquid water)
|
||||
if (used[i]) continue; // already marked
|
||||
const b = cells.biome[i];
|
||||
const onborder = cells.c[i].some(n => cells.biome[n] !== b);
|
||||
if (!onborder) continue;
|
||||
const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b));
|
||||
const chain = connectVertices(edgeVerticle, b);
|
||||
if (chain.length < 3) continue;
|
||||
const points = clipPoly(chain.map(v => vertices.p[v]));
|
||||
paths[b] += "M" + points.join("L") + "Z";
|
||||
}
|
||||
|
||||
paths.forEach(function (d, i) {
|
||||
if (d.length < 10) return;
|
||||
biomes
|
||||
.append("path")
|
||||
.attr("d", d)
|
||||
.attr("fill", biomesData.color[i])
|
||||
.attr("stroke", biomesData.color[i])
|
||||
.attr("id", "biome" + i);
|
||||
});
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start, b) {
|
||||
const chain = []; // vertices chain to form a path
|
||||
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||
chain.push(current); // add current vertex to sequence
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
c.filter(c => cells.biome[c] === b).forEach(c => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.biome[c[0]] !== b;
|
||||
const c1 = c[1] >= n || cells.biome[c[1]] !== b;
|
||||
const c2 = c[2] >= n || cells.biome[c[2]] !== b;
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
if (current === chain[chain.length - 1]) {
|
||||
ERROR && console.error("Next vertex is not found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("drawBiomes");
|
||||
}
|
||||
29
src/layers/renderers/drawBiomes.ts
Normal file
29
src/layers/renderers/drawBiomes.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import {pick} from "utils/functionUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {getPaths} from "./utils/getVertexPaths";
|
||||
|
||||
export function drawBiomes() {
|
||||
/* global */ const {cells, vertices, features} = pack;
|
||||
/* global */ const colors = biomesData.color;
|
||||
|
||||
const paths = getPaths({
|
||||
getType: (cellId: number) => cells.biome[cellId],
|
||||
cells: pick(cells, "c", "v", "b", "h", "f"),
|
||||
vertices,
|
||||
features,
|
||||
options: {fill: true, waterGap: true, halo: false}
|
||||
});
|
||||
|
||||
console.log(paths);
|
||||
|
||||
const htmlPaths = paths.map(([index, {fill, waterGap}]) => {
|
||||
const color = colors[Number(index)];
|
||||
|
||||
return /* html */ `
|
||||
<path d="${waterGap}" fill="none" stroke="${color}" id="biome-gap${index}" />
|
||||
<path d="${fill}" fill="${color}" stroke="none" id="biome${index}" />
|
||||
`;
|
||||
});
|
||||
|
||||
byId("biomes")!.innerHTML = htmlPaths.join("");
|
||||
}
|
||||
|
|
@ -1,44 +1,28 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {getPaths} from "./utilts";
|
||||
import {pick} from "utils/functionUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {getPaths} from "./utils/getVertexPaths";
|
||||
|
||||
export function drawCultures() {
|
||||
d3.select("#cults").selectAll("g").remove();
|
||||
|
||||
/* uses */ const {cells, vertices, features, cultures} = pack;
|
||||
/* global */ const {cells, vertices, features, cultures} = pack;
|
||||
|
||||
const paths = getPaths({
|
||||
getType: (cellId: number) => cells.culture[cellId],
|
||||
cells: pick(cells, "c", "v", "b", "h", "f"),
|
||||
vertices,
|
||||
features
|
||||
features,
|
||||
options: {fill: true, waterGap: true, halo: false}
|
||||
});
|
||||
|
||||
const getColor = (i: number) => (cultures[i] as ICulture).color;
|
||||
const getColor = (i: string) => (cultures[Number(i)] as ICulture).color;
|
||||
|
||||
d3.select("#cults")
|
||||
.append("g")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke-width", 3)
|
||||
.selectAll("path")
|
||||
.remove()
|
||||
.data(paths)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", ([, path]) => path.waterGap)
|
||||
.attr("stroke", ([i]) => getColor(Number(i)))
|
||||
.attr("id", ([i]) => "culture-gap" + i);
|
||||
const htmlPaths = paths.map(([index, {fill, waterGap}]) => {
|
||||
const color = getColor(index);
|
||||
|
||||
d3.select("#cults")
|
||||
.append("g")
|
||||
.attr("stroke", "none")
|
||||
.selectAll("path")
|
||||
.remove()
|
||||
.data(paths)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", ([, path]) => path.fill)
|
||||
.attr("fill", ([i]) => getColor(Number(i)))
|
||||
.attr("id", ([i]) => "culture" + i);
|
||||
return /* html */ `
|
||||
<path d="${waterGap}" fill="none" stroke="${color}" id="culture-gap${index}" />
|
||||
<path d="${fill}" fill="${color}" stroke="none" id="culture${index}" />
|
||||
`;
|
||||
});
|
||||
|
||||
byId("cults")!.innerHTML = htmlPaths.join("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {filterOutOfCanvasPoints} from "utils/lineUtils";
|
|||
import {round} from "utils/stringUtils";
|
||||
|
||||
export function drawFeatures() {
|
||||
/* uses */ const {vertices, features} = pack;
|
||||
/* global */ const {vertices, features} = pack;
|
||||
|
||||
const landMask = defs.select("#land");
|
||||
const waterMask = defs.select("#water");
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
export function drawReligions() {
|
||||
relig.selectAll("path").remove();
|
||||
const {cells, vertices, religions} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const body = new Array(religions.length).fill(""); // store path around each religion
|
||||
const gap = new Array(religions.length).fill(""); // store path along water for each religion to fill the gaps
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.religion[i]) continue;
|
||||
if (used[i]) continue;
|
||||
used[i] = 1;
|
||||
const r = cells.religion[i];
|
||||
const onborder = cells.c[i].filter(n => cells.religion[n] !== r);
|
||||
if (!onborder.length) continue;
|
||||
const borderWith = cells.c[i].map(c => cells.religion[c]).find(n => n !== r);
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.religion[i] === borderWith));
|
||||
const chain = connectVertices(vertex, r, borderWith);
|
||||
if (chain.length < 3) continue;
|
||||
const points = chain.map(v => vertices.p[v[0]]);
|
||||
|
||||
body[r] += "M" + points.join("L") + "Z";
|
||||
gap[r] +=
|
||||
"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 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("id", d => "religion" + d[1]);
|
||||
|
||||
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");
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start, t, religion) {
|
||||
const chain = []; // vertices chain to form a path
|
||||
let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.religion[c] !== t);
|
||||
function check(i) {
|
||||
religion = cells.religion[i];
|
||||
land = cells.h[i] >= 20;
|
||||
}
|
||||
|
||||
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||
chain.push([current, religion, land]); // add current vertex to sequence
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
c.filter(c => cells.religion[c] === t).forEach(c => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.religion[c[0]] !== t;
|
||||
const c1 = c[1] >= n || cells.religion[c[1]] !== t;
|
||||
const c2 = c[2] >= n || cells.religion[c[2]] !== t;
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
if (v[0] !== prev && c0 !== c1) {
|
||||
current = v[0];
|
||||
check(c0 ? c[0] : c[1]);
|
||||
} else if (v[1] !== prev && c1 !== c2) {
|
||||
current = v[1];
|
||||
check(c1 ? c[1] : c[2]);
|
||||
} else if (v[2] !== prev && c0 !== c2) {
|
||||
current = v[2];
|
||||
check(c2 ? c[2] : c[0]);
|
||||
}
|
||||
if (current === chain[chain.length - 1][0]) {
|
||||
ERROR && console.error("Next vertex is not found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
28
src/layers/renderers/drawReligions.ts
Normal file
28
src/layers/renderers/drawReligions.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import {pick} from "utils/functionUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {getPaths} from "./utils/getVertexPaths";
|
||||
|
||||
export function drawReligions() {
|
||||
/* global */ const {cells, vertices, features, religions} = pack;
|
||||
|
||||
const paths = getPaths({
|
||||
getType: (cellId: number) => cells.religion[cellId],
|
||||
cells: pick(cells, "c", "v", "b", "h", "f"),
|
||||
vertices,
|
||||
features,
|
||||
options: {fill: true, waterGap: true, halo: false}
|
||||
});
|
||||
|
||||
const getColor = (i: string) => (religions[Number(i)] as IReligion).color;
|
||||
|
||||
const htmlPaths = paths.map(([index, {fill, waterGap}]) => {
|
||||
const color = getColor(index);
|
||||
|
||||
return /* html */ `
|
||||
<path d="${waterGap}" fill="none" stroke="${color}" id="religion-gap${index}" />
|
||||
<path d="${fill}" fill="${color}" stroke="none" id="religion${index}" />
|
||||
`;
|
||||
});
|
||||
|
||||
byId("relig")!.innerHTML = htmlPaths.join("");
|
||||
}
|
||||
|
|
@ -10,9 +10,7 @@ const lineGenTypeMap: {[key in IRoute["type"]]: d3.CurveFactory | d3.CurveFactor
|
|||
};
|
||||
|
||||
export function drawRoutes() {
|
||||
routes.selectAll("path").remove();
|
||||
|
||||
/* uses */ const {cells, burgs} = pack;
|
||||
/* global */ const {cells, burgs} = pack;
|
||||
const lineGen = d3.line();
|
||||
|
||||
const SHARP_ANGLE = 135;
|
||||
|
|
@ -32,6 +30,7 @@ export function drawRoutes() {
|
|||
routePaths[type].push(`<path id="${type}${i}" d="${path}"/>`);
|
||||
}
|
||||
|
||||
routes.selectAll("path").remove();
|
||||
for (const type in routePaths) {
|
||||
routes.select(`[data-type=${type}]`).html(routePaths[type].join(""));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import polylabel from "polylabel";
|
||||
|
||||
export function drawStates() {
|
||||
regions.selectAll("path").remove();
|
||||
|
||||
const {cells, vertices, features} = pack;
|
||||
const states = pack.states;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const vArray = new Array(states.length); // store vertices array
|
||||
const body = new Array(states.length).fill(""); // path around each state
|
||||
const gap = new Array(states.length).fill(""); // path along water for each state to fill the gaps
|
||||
const halo = new Array(states.length).fill(""); // path around states, but not lakes
|
||||
|
||||
const getStringPoint = v => vertices.p[v[0]].join(",");
|
||||
|
||||
// define inner-state lakes to omit on border render
|
||||
const innerLakes = features.map(feature => {
|
||||
if (feature.type !== "lake") return false;
|
||||
|
||||
const shoreline = feature.shoreline || [];
|
||||
const states = shoreline.map(i => cells.state[i]);
|
||||
return new Set(states).size > 1 ? false : true;
|
||||
});
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.state[i] || used[i]) continue;
|
||||
const state = cells.state[i];
|
||||
|
||||
const onborder = cells.c[i].some(n => cells.state[n] !== state);
|
||||
if (!onborder) continue;
|
||||
|
||||
const borderWith = cells.c[i].map(c => cells.state[c]).find(n => n !== state);
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.state[i] === borderWith));
|
||||
const chain = connectVertices(vertex, state);
|
||||
|
||||
const noInnerLakes = chain.filter(v => v[1] !== "innerLake");
|
||||
if (noInnerLakes.length < 3) continue;
|
||||
|
||||
// get path around the state
|
||||
if (!vArray[state]) vArray[state] = [];
|
||||
const points = noInnerLakes.map(v => vertices.p[v[0]]);
|
||||
vArray[state].push(points);
|
||||
body[state] += "M" + points.join("L");
|
||||
|
||||
// connect path for halo
|
||||
let discontinued = true;
|
||||
halo[state] += noInnerLakes
|
||||
.map(v => {
|
||||
if (v[1] === "border") {
|
||||
discontinued = true;
|
||||
return "";
|
||||
}
|
||||
|
||||
const operation = discontinued ? "M" : "L";
|
||||
discontinued = false;
|
||||
return `${operation}${getStringPoint(v)}`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
// connect gaps between state and water into a single path
|
||||
discontinued = true;
|
||||
gap[state] += chain
|
||||
.map(v => {
|
||||
if (v[1] === "land") {
|
||||
discontinued = true;
|
||||
return "";
|
||||
}
|
||||
|
||||
const operation = discontinued ? "M" : "L";
|
||||
discontinued = false;
|
||||
return `${operation}${getStringPoint(v)}`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
// find state visual center
|
||||
vArray.forEach((ar, i) => {
|
||||
const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number
|
||||
states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility
|
||||
});
|
||||
|
||||
const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
|
||||
const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
|
||||
const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
|
||||
|
||||
const bodyString = bodyData.map(d => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join("");
|
||||
const gapString = gapData.map(d => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join("");
|
||||
const clipString = bodyData
|
||||
.map(d => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`)
|
||||
.join("");
|
||||
const haloString = haloData
|
||||
.map(
|
||||
d =>
|
||||
`<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${
|
||||
d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"
|
||||
}"/>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
statesBody.html(bodyString + gapString);
|
||||
defs.select("#statePaths").html(clipString);
|
||||
statesHalo.html(haloString);
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start, state) {
|
||||
const chain = []; // vertices chain to form a path
|
||||
const getType = c => {
|
||||
const borderCell = c.find(i => cells.b[i]);
|
||||
if (borderCell) return "border";
|
||||
|
||||
const waterCell = c.find(i => cells.h[i] < 20);
|
||||
if (!waterCell) return "land";
|
||||
if (innerLakes[cells.f[waterCell]]) return "innerLake";
|
||||
return features[cells.f[waterCell]].type;
|
||||
};
|
||||
|
||||
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||
const prev = chain.length ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
chain.push([current, getType(c)]); // add current vertex to sequence
|
||||
|
||||
c.filter(c => cells.state[c] === state).forEach(c => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.state[c[0]] !== state;
|
||||
const c1 = c[1] >= n || cells.state[c[1]] !== state;
|
||||
const c2 = c[2] >= n || cells.state[c[2]] !== state;
|
||||
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
|
||||
if (current === prev) {
|
||||
ERROR && console.error("Next vertex is not found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chain.length) chain.push(chain[0]);
|
||||
return chain;
|
||||
}
|
||||
|
||||
Zoom.invoke();
|
||||
}
|
||||
48
src/layers/renderers/drawStates.ts
Normal file
48
src/layers/renderers/drawStates.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {pick} from "utils/functionUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {getPaths} from "./utils/getVertexPaths";
|
||||
|
||||
export function drawStates() {
|
||||
/* global */ const {cells, vertices, features, states} = pack;
|
||||
|
||||
const paths = getPaths({
|
||||
getType: (cellId: number) => cells.state[cellId],
|
||||
cells: pick(cells, "c", "v", "b", "h", "f"),
|
||||
vertices,
|
||||
features,
|
||||
options: {fill: true, waterGap: true, halo: true}
|
||||
});
|
||||
|
||||
const getColor = (i: number) => (states[i] as IState).color;
|
||||
|
||||
const maxLength = states.length - 1;
|
||||
const bodyPaths = new Array(maxLength);
|
||||
const clipPaths = new Array(maxLength);
|
||||
const haloPaths = new Array(maxLength);
|
||||
|
||||
for (const [index, {fill, waterGap, halo}] of paths) {
|
||||
const color = getColor(Number(index));
|
||||
const haloColor = d3.color(color)?.darker().formatHex() || "#666666";
|
||||
|
||||
bodyPaths.push(/* html */ `
|
||||
<path d="${waterGap}" fill="none" stroke="${color}" id="state-gap${index}" />
|
||||
<path d="${fill}" fill="${color}" stroke="none" id="state${index}" />
|
||||
`);
|
||||
|
||||
clipPaths.push(/* html */ `
|
||||
<clipPath id="state-clip${index}"><use href="#state${index}"/></clipPath>
|
||||
`);
|
||||
|
||||
haloPaths.push(/* html */ `
|
||||
<path id="state-border${index}" d="${halo}" clip-path="url(#state-clip${index})" stroke="${haloColor}"/>
|
||||
`);
|
||||
}
|
||||
|
||||
byId("statesBody")!.innerHTML = bodyPaths.join("");
|
||||
byId("statePaths")!.innerHTML = clipPaths.join("");
|
||||
byId("statesHalo")!.innerHTML = haloPaths.join("");
|
||||
|
||||
/* global */ window.Zoom.invoke();
|
||||
}
|
||||
|
|
@ -8,12 +8,14 @@ export function getPaths({
|
|||
vertices,
|
||||
getType,
|
||||
features,
|
||||
cells
|
||||
cells,
|
||||
options
|
||||
}: {
|
||||
vertices: IGraphVertices;
|
||||
getType: (cellId: number) => number;
|
||||
features: TPackFeatures;
|
||||
cells: Pick<IPack["cells"], "c" | "v" | "b" | "h" | "f">;
|
||||
options: {[key in keyof TPath]: boolean};
|
||||
}) {
|
||||
const paths: Dict<TPath> = {};
|
||||
|
||||
|
|
@ -55,11 +57,12 @@ export function getPaths({
|
|||
function getFillPath(vertexChain: number[]) {
|
||||
const points: TPoints = vertexChain.map(getVertexPoint);
|
||||
const firstPoint = points.shift();
|
||||
return `M${firstPoint} L${points.join(" ")} Z`;
|
||||
return `M${firstPoint} L${points.join(" ")}`;
|
||||
}
|
||||
|
||||
function getBorderPath(vertexChain: number[], discontinue: (vertex: number) => boolean) {
|
||||
let discontinued = true;
|
||||
let lastOperation = "";
|
||||
const path = vertexChain.map(vertex => {
|
||||
if (discontinue(vertex)) {
|
||||
discontinued = true;
|
||||
|
|
@ -67,8 +70,12 @@ export function getPaths({
|
|||
}
|
||||
|
||||
const operation = discontinued ? "M" : "L";
|
||||
const command = operation === lastOperation ? "" : operation;
|
||||
|
||||
discontinued = false;
|
||||
return ` ${operation}${getVertexPoint(vertex)}`;
|
||||
lastOperation = operation;
|
||||
|
||||
return ` ${command}${getVertexPoint(vertex)}`;
|
||||
});
|
||||
|
||||
return path.join("").trim();
|
||||
|
|
@ -87,9 +94,9 @@ export function getPaths({
|
|||
function addPath(index: number, vertexChain: number[]) {
|
||||
if (!paths[index]) paths[index] = {fill: "", waterGap: "", halo: ""};
|
||||
|
||||
paths[index].fill += getFillPath(vertexChain);
|
||||
paths[index].halo += getBorderPath(vertexChain, isBorderVertex);
|
||||
paths[index].waterGap += getBorderPath(vertexChain, isLandVertex);
|
||||
if (options.fill) paths[index].fill += getFillPath(vertexChain);
|
||||
if (options.halo) paths[index].halo += getBorderPath(vertexChain, isBorderVertex);
|
||||
if (options.waterGap) paths[index].waterGap += getBorderPath(vertexChain, isLandVertex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +26,8 @@ import {createGrid} from "./grid/grid";
|
|||
import {createPack} from "./pack/pack";
|
||||
import {getInputValue, setInputValue} from "utils/nodeUtils";
|
||||
import {calculateMapCoordinates} from "modules/coordinates";
|
||||
import {drawPolygons} from "utils/debugUtils";
|
||||
import {drawPoint, drawPolygons} from "utils/debugUtils";
|
||||
import {isReligion} from "utils/typeUtils";
|
||||
|
||||
const {Zoom, ThreeD} = window;
|
||||
|
||||
|
|
@ -70,10 +71,15 @@ async function generate(options?: IGenerationOptions) {
|
|||
// renderLayer("biomes");
|
||||
renderLayer("burgs");
|
||||
renderLayer("routes");
|
||||
renderLayer("cultures");
|
||||
//renderLayer("religions");
|
||||
// renderLayer("states");
|
||||
renderLayer("religions");
|
||||
|
||||
// drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true});
|
||||
pack.religions.filter(isReligion).forEach(({center}) =>
|
||||
drawPoint(pack.cells.p[center], {
|
||||
radius: 5
|
||||
})
|
||||
);
|
||||
|
||||
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
|
||||
// showStatistics();
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ export function generateReligions({
|
|||
pick(cells, "i", "c", "biome", "culture", "burg", "state", "route")
|
||||
);
|
||||
|
||||
console.log(religions);
|
||||
|
||||
TIME && console.timeEnd("generateReligions");
|
||||
return {religionIds, religions};
|
||||
}
|
||||
|
|
|
|||
1
src/types/pack/states.d.ts
vendored
1
src/types/pack/states.d.ts
vendored
|
|
@ -9,6 +9,7 @@ interface IState {
|
|||
fullName: string;
|
||||
capital: Logical;
|
||||
coa: ICoa | string;
|
||||
// pole: TPoint ?
|
||||
removed?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -422,6 +422,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3"
|
||||
integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==
|
||||
|
||||
"@types/polylabel@^1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/polylabel/-/polylabel-1.0.5.tgz#9262f269de36f1e9248aeb9dee0ee9d10065e043"
|
||||
integrity sha512-gnaNmo1OJiYNBFAZMZdqLZ3hKx2ee4ksAzqhKWBxuQ61PmhINHMcvIqsGmyCD1WFKCkwRt9NFhMSmKE6AgYY+w==
|
||||
|
||||
"@types/qs@^6.2.31":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue