mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor(religions): render religions
This commit is contained in:
parent
707cdd77ac
commit
7a3a87e935
10 changed files with 137 additions and 55 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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" +
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
4
src/types/pack/features.d.ts
vendored
4
src/types/pack/features.d.ts
vendored
|
|
@ -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[]];
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue