refactor(religions): render religions

This commit is contained in:
Azgaar 2022-08-29 00:42:37 +03:00
parent 707cdd77ac
commit 7a3a87e935
10 changed files with 137 additions and 55 deletions

View file

@ -137,7 +137,8 @@ a {
stroke-linejoin: round; stroke-linejoin: round;
} }
t, /* TODO: turn on after debugging */
/* t,
#regions, #regions,
#cults, #cults,
#relig, #relig,
@ -150,7 +151,7 @@ t,
#landmass, #landmass,
#fogging { #fogging {
pointer-events: none; pointer-events: none;
} } */
#armies text { #armies text {
pointer-events: none; pointer-events: none;

View file

@ -1,22 +1,44 @@
import * as d3 from "d3"; import * as d3 from "d3";
import {getPaths} from "./utilts"; import {getPaths} from "./utilts";
import {pick} from "utils/functionUtils";
export function drawCultures() { export function drawCultures() {
/* uses */ const {cells, vertices, cultures} = pack; d3.select("#cults").selectAll("g").remove();
const getType = (cellId: number) => cells.culture[cellId]; /* uses */ const {cells, vertices, features, cultures} = pack;
const paths = getPaths(cells.c, cells.v, vertices, getType);
const getColor = (i: number) => i && (cultures[i] as ICulture).color; const paths = getPaths({
getType: (cellId: number) => cells.culture[cellId],
cells: pick(cells, "c", "v", "b", "h", "f"),
vertices,
features
});
const getColor = (i: number) => (cultures[i] as ICulture).color;
d3.select("#cults") d3.select("#cults")
.append("g")
.attr("fill", "none")
.attr("stroke-width", 3)
.selectAll("path") .selectAll("path")
.remove() .remove()
.data(Object.entries(paths)) .data(paths)
.enter() .enter()
.append("path") .append("path")
.attr("d", ([, path]) => path) .attr("d", ([, path]) => path.waterGap)
.attr("stroke", ([i]) => getColor(Number(i)))
.attr("id", ([i]) => "culture-gap" + i);
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("fill", ([i]) => getColor(Number(i)))
.attr("id", ([i]) => "culture" + i); .attr("id", ([i]) => "culture" + i);
} }

View file

@ -4,7 +4,6 @@ export function drawReligions() {
const n = cells.i.length; const n = cells.i.length;
const used = new Uint8Array(cells.i.length); const used = new Uint8Array(cells.i.length);
const vArray = new Array(religions.length); // store vertices array
const body = new Array(religions.length).fill(""); // store path around each religion 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 const gap = new Array(religions.length).fill(""); // store path along water for each religion to fill the gaps
@ -20,8 +19,7 @@ export function drawReligions() {
const chain = connectVertices(vertex, r, borderWith); const chain = connectVertices(vertex, r, borderWith);
if (chain.length < 3) continue; if (chain.length < 3) continue;
const points = chain.map(v => vertices.p[v[0]]); const points = chain.map(v => vertices.p[v[0]]);
if (!vArray[r]) vArray[r] = [];
vArray[r].push(points);
body[r] += "M" + points.join("L") + "Z"; body[r] += "M" + points.join("L") + "Z";
gap[r] += gap[r] +=
"M" + "M" +

View file

@ -12,7 +12,7 @@ const lineGenTypeMap: {[key in IRoute["type"]]: d3.CurveFactory | d3.CurveFactor
export function drawRoutes() { export function drawRoutes() {
routes.selectAll("path").remove(); routes.selectAll("path").remove();
const {cells, burgs} = pack; /* uses */ const {cells, burgs} = pack;
const lineGen = d3.line(); const lineGen = d3.line();
const SHARP_ANGLE = 135; const SHARP_ANGLE = 135;

View file

@ -1,40 +1,99 @@
import {MIN_LAND_HEIGHT} from "config/generation";
import {connectVertices} from "scripts/connectVertices"; import {connectVertices} from "scripts/connectVertices";
import {isLake} from "utils/typeUtils";
export function getPaths( type TPath = {fill: string; waterGap: string; halo: string};
cellNeighbors: number[][],
cellVertices: number[][],
vertices: IGraphVertices,
getType: (cellId: number) => number
) {
const paths: Dict<string> = {};
function addPath(index: number, points: TPoints) { export function getPaths({
if (!paths[index]) paths[index] = ""; vertices,
paths[index] += "M" + points.join("L") + "Z"; getType,
} features,
cells
}: {
vertices: IGraphVertices;
getType: (cellId: number) => number;
features: TPackFeatures;
cells: Pick<IPack["cells"], "c" | "v" | "b" | "h" | "f">;
}) {
const paths: Dict<TPath> = {};
const checkedCells = new Uint8Array(cellNeighbors.length); const checkedCells = new Uint8Array(cells.c.length);
for (let cellId = 0; cellId < cellNeighbors.length; cellId++) { const addToChecked = (cellId: number) => {
if (checkedCells[cellId]) continue;
if (!getType(cellId)) continue;
checkedCells[cellId] = 1; checkedCells[cellId] = 1;
};
const isChecked = (cellId: number) => checkedCells[cellId] === 1;
for (let cellId = 0; cellId < cells.c.length; cellId++) {
if (isChecked(cellId) || getType(cellId) === 0) continue;
addToChecked(cellId);
const type = getType(cellId); const type = getType(cellId);
const ofSameType = (cellId: number) => getType(cellId) === type; const ofSameType = (cellId: number) => getType(cellId) === type;
const ofDifferentType = (cellId: number) => getType(cellId) !== type;
const isOnborder = cellNeighbors[cellId].some(cellId => !ofSameType(cellId)); const onborderCell = cells.c[cellId].find(ofDifferentType);
if (!isOnborder) continue; if (onborderCell === undefined) continue;
const startingVertex = cellVertices[cellId].find(v => vertices.c[v].some(cellId => !ofSameType(cellId))); const feature = features[cells.f[onborderCell]];
if (startingVertex === undefined) throw new Error(`getPath: starting vertex for cell ${cellId} is not found`); if (isInnerLake(feature, ofSameType)) continue;
const chain = connectVertices({vertices, startingVertex, ofSameType, checkedCellsMutable: checkedCells}); const startingVertex = cells.v[cellId].find(v => vertices.c[v].some(ofDifferentType));
if (startingVertex === undefined) throw new Error(`Starting vertex for cell ${cellId} is not found`);
if (chain.length < 3) continue; const vertexChain = connectVertices({vertices, startingVertex, ofSameType, addToChecked, closeRing: true});
const points = chain.map(v => vertices.p[v]); if (vertexChain.length < 3) continue;
addPath(type, points); addPath(type, vertexChain);
} }
return paths; return Object.entries(paths);
function getVertexPoint(vertex: number) {
return vertices.p[vertex];
}
function getFillPath(vertexChain: number[]) {
const points: TPoints = vertexChain.map(getVertexPoint);
const firstPoint = points.shift();
return `M${firstPoint} L${points.join(" ")} Z`;
}
function getBorderPath(vertexChain: number[], discontinue: (vertex: number) => boolean) {
let discontinued = true;
const path = vertexChain.map(vertex => {
if (discontinue(vertex)) {
discontinued = true;
return "";
}
const operation = discontinued ? "M" : "L";
discontinued = false;
return ` ${operation}${getVertexPoint(vertex)}`;
});
return path.join("").trim();
}
function isBorderVertex(vertex: number) {
const adjacentCells = vertices.c[vertex];
return adjacentCells.some(i => cells.b[i]);
}
function isLandVertex(vertex: number) {
const adjacentCells = vertices.c[vertex];
return adjacentCells.every(i => cells.h[i] >= MIN_LAND_HEIGHT);
}
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);
}
}
function isInnerLake(feature: 0 | TPackFeature, ofSameType: (cellId: number) => boolean) {
if (!isLake(feature)) return false;
return feature.shoreline.every(ofSameType);
} }

View file

@ -88,38 +88,32 @@ function findStartingVertex({
throw new Error(`Markup: firstCell of feature ${featureId} has no neighbors of other features or external vertices`); throw new Error(`Markup: firstCell of feature ${featureId} has no neighbors of other features or external vertices`);
} }
const CONNECT_VERTICES_MAX_ITERATIONS = 50000; const MAX_ITERATIONS = 50000;
// connect vertices around feature // connect vertices around feature
export function connectVertices({ export function connectVertices({
vertices, vertices,
startingVertex, startingVertex,
ofSameType, ofSameType,
checkedCellsMutable addToChecked,
closeRing
}: { }: {
vertices: IGraphVertices; vertices: IGraphVertices;
startingVertex: number; startingVertex: number;
ofSameType: (cellId: number) => boolean; ofSameType: (cellId: number) => boolean;
checkedCellsMutable?: Uint8Array; addToChecked?: (cellId: number) => void;
closeRing?: boolean;
}) { }) {
const chain: number[] = []; // vertices chain to form a path const chain: number[] = []; // vertices chain to form a path
const addToChecked = (cellIds: number[]) => {
if (checkedCellsMutable) {
cellIds.forEach(cellId => {
checkedCellsMutable[cellId] = 1;
});
}
};
let next = startingVertex; let next = startingVertex;
for (let i = 0; i === 0 || (next !== startingVertex && i < CONNECT_VERTICES_MAX_ITERATIONS); i++) { for (let i = 0; i === 0 || (next !== startingVertex && i < MAX_ITERATIONS); i++) {
const previous = chain.at(-1); const previous = chain.at(-1);
const current = next; const current = next;
chain.push(current); chain.push(current);
const neibCells = vertices.c[current]; const neibCells = vertices.c[current];
addToChecked(neibCells); if (addToChecked) neibCells.forEach(addToChecked);
const [c1, c2, c3] = neibCells.map(ofSameType); const [c1, c2, c3] = neibCells.map(ofSameType);
const [v1, v2, v3] = vertices.v[current]; const [v1, v2, v3] = vertices.v[current];
@ -134,5 +128,6 @@ export function connectVertices({
} }
} }
if (closeRing) chain.push(startingVertex);
return chain; return chain;
} }

View file

@ -70,8 +70,10 @@ async function generate(options?: IGenerationOptions) {
// renderLayer("biomes"); // renderLayer("biomes");
renderLayer("burgs"); renderLayer("burgs");
renderLayer("routes"); renderLayer("routes");
renderLayer("cultures");
//renderLayer("religions");
drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true}); // drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true});
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
// showStatistics(); // showStatistics();

View file

@ -35,6 +35,6 @@ interface IPackFeatureLake extends IPackFeatureBase {
type TPackFeature = IPackFeatureOcean | IPackFeatureIsland | IPackFeatureLake; type TPackFeature = IPackFeatureOcean | IPackFeatureIsland | IPackFeatureLake;
type FirstElement = 0; type TNoFeature = 0;
type TPackFeatures = [FirstElement, ...TPackFeature[]]; type TPackFeatures = [TNoFeature, ...TPackFeature[]];

View file

@ -63,12 +63,12 @@ function getPointOffCanvasSide([x, y]: TPoint) {
// remove intermediate out-of-canvas points from polyline // remove intermediate out-of-canvas points from polyline
export function filterOutOfCanvasPoints(points: TPoints) { export function filterOutOfCanvasPoints(points: TPoints) {
const pointsOutSide = points.map(getPointOffCanvasSide); const pointsOutside = points.map(getPointOffCanvasSide);
const SAFE_ZONE = 3; const SAFE_ZONE = 3;
const fragment = (i: number) => sliceFragment(pointsOutSide, i, SAFE_ZONE); const fragment = (i: number) => sliceFragment(pointsOutside, i, SAFE_ZONE);
const filterOutCanvasPoint = (i: number) => { const filterOutCanvasPoint = (i: number) => {
const pointSide = pointsOutSide[i]; const pointSide = pointsOutside[i];
return !pointSide || fragment(i).some(side => !side || side !== pointSide); return !pointSide || fragment(i).some(side => !side || side !== pointSide);
}; };

View file

@ -1,3 +1,8 @@
export const isFeature = (feature: TNoFeature | TPackFeature): feature is TPackFeature => feature !== 0;
export const isLake = (feature: TNoFeature | TPackFeature): feature is IPackFeatureLake =>
isFeature(feature) && feature.type === "lake";
export const isState = (state: TNeutrals | IState): state is IState => state.i !== 0 && !(state as IState).removed; export const isState = (state: TNeutrals | IState): state is IState => state.i !== 0 && !(state as IState).removed;
export const isNeutals = (neutrals: TNeutrals | IState): neutrals is TNeutrals => neutrals.i === 0; export const isNeutals = (neutrals: TNeutrals | IState): neutrals is TNeutrals => neutrals.i === 0;