mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
feat: draw borders
This commit is contained in:
parent
a6d777aecb
commit
50edcb4e30
8 changed files with 182 additions and 111 deletions
|
|
@ -26,11 +26,11 @@
|
|||
"filter": null
|
||||
},
|
||||
"#provinceBorders": {
|
||||
"opacity": 0.8,
|
||||
"opacity": 0.6,
|
||||
"stroke": "#56566d",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-dasharray": "0 2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-width": 0.2,
|
||||
"stroke-dasharray": "1 1",
|
||||
"stroke-linecap": "butt",
|
||||
"filter": null
|
||||
},
|
||||
"#cells": {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ export function drawBiomes() {
|
|||
options: {fill: true, waterGap: true, halo: false}
|
||||
});
|
||||
|
||||
console.log(paths);
|
||||
|
||||
const htmlPaths = paths.map(([index, {fill, waterGap}]) => {
|
||||
const color = colors[Number(index)];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
export function drawBorders() {
|
||||
borders.selectAll("path").remove();
|
||||
|
||||
const {cells, vertices} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
const sPath = [];
|
||||
const pPath = [];
|
||||
|
||||
const sUsed = new Array(pack.states.length).fill("").map(_ => []);
|
||||
const pUsed = new Array(pack.provinces.length).fill("").map(_ => []);
|
||||
|
||||
for (let i = 0; i < cells.i.length; i++) {
|
||||
if (!cells.state[i]) continue;
|
||||
const p = cells.province[i];
|
||||
const s = cells.state[i];
|
||||
|
||||
// if cell is on province border
|
||||
const provToCell = cells.c[i].find(
|
||||
n => cells.state[n] === s && p > cells.province[n] && pUsed[p][n] !== cells.province[n]
|
||||
);
|
||||
if (provToCell) {
|
||||
const provTo = cells.province[provToCell];
|
||||
pUsed[p][provToCell] = provTo;
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.province[i] === provTo));
|
||||
const chain = connectVertices(vertex, p, cells.province, provTo, pUsed);
|
||||
|
||||
if (chain.length > 1) {
|
||||
pPath.push("M" + chain.map(c => vertices.p[c]).join(" "));
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// if cell is on state border
|
||||
const stateToCell = cells.c[i].find(n => cells.h[n] >= 20 && s > cells.state[n] && sUsed[s][n] !== cells.state[n]);
|
||||
if (stateToCell !== undefined) {
|
||||
const stateTo = cells.state[stateToCell];
|
||||
sUsed[s][stateToCell] = stateTo;
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] >= 20 && cells.state[i] === stateTo));
|
||||
const chain = connectVertices(vertex, s, cells.state, stateTo, sUsed);
|
||||
|
||||
if (chain.length > 1) {
|
||||
sPath.push("M" + chain.map(c => vertices.p[c]).join(" "));
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stateBorders.append("path").attr("d", sPath.join(" "));
|
||||
provinceBorders.append("path").attr("d", pPath.join(" "));
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(current, f, array, t, used) {
|
||||
let chain = [];
|
||||
const checkCell = c => c >= n || array[c] !== f;
|
||||
const checkVertex = v =>
|
||||
vertices.c[v].some(c => array[c] === f) && vertices.c[v].some(c => array[c] === t && cells.h[c] >= 20);
|
||||
|
||||
// find starting vertex
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
if (i === 999) ERROR && console.error("Find starting vertex: limit is reached", current, f, t);
|
||||
const p = chain[chain.length - 2] || -1; // previous vertex
|
||||
const v = vertices.v[current],
|
||||
c = vertices.c[current];
|
||||
|
||||
const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]);
|
||||
const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]);
|
||||
const v2 = checkCell(c[0]) !== checkCell(c[2]) && checkVertex(v[2]);
|
||||
if (v0 + v1 + v2 === 1) break;
|
||||
current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2];
|
||||
|
||||
if (current === chain[0]) break;
|
||||
if (current === p) return [];
|
||||
chain.push(current);
|
||||
}
|
||||
|
||||
chain = [current]; // vertices chain to form a path
|
||||
// find path
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
if (i === 999) ERROR && console.error("Find path: limit is reached", current, f, t);
|
||||
const p = chain[chain.length - 2] || -1; // previous vertex
|
||||
const v = vertices.v[current],
|
||||
c = vertices.c[current];
|
||||
c.filter(c => array[c] === t).forEach(c => (used[f][c] = t));
|
||||
|
||||
const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]);
|
||||
const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]);
|
||||
const v2 = checkCell(c[0]) !== checkCell(c[2]) && checkVertex(v[2]);
|
||||
current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2];
|
||||
|
||||
if (current === p) break;
|
||||
if (current === chain[chain.length - 1]) break;
|
||||
if (chain.length > 1 && v0 + v1 + v2 < 2) break;
|
||||
chain.push(current);
|
||||
if (current === chain[0]) break;
|
||||
}
|
||||
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
168
src/layers/renderers/drawBorders.ts
Normal file
168
src/layers/renderers/drawBorders.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import {MIN_LAND_HEIGHT} from "config/generation";
|
||||
|
||||
export function drawBorders() {
|
||||
/* global */ const {cells, vertices} = pack;
|
||||
const statePath: string[] = [];
|
||||
const provincePath: string[] = [];
|
||||
|
||||
const checkedStates: Dict<boolean> = {};
|
||||
const checkedProvinces: Dict<boolean> = {};
|
||||
|
||||
const isLand = (i: number) => cells.h[i] >= MIN_LAND_HEIGHT;
|
||||
|
||||
for (let cellId = 0; cellId < cells.i.length; cellId++) {
|
||||
if (!cells.state[cellId]) continue;
|
||||
const provinceId = cells.province[cellId];
|
||||
const stateId = cells.state[cellId];
|
||||
|
||||
// bordering cell of another province
|
||||
const provToCell =
|
||||
provinceId &&
|
||||
cells.c[cellId].find(neibId => {
|
||||
const neibProvinceId = cells.province[neibId];
|
||||
|
||||
return (
|
||||
neibProvinceId &&
|
||||
provinceId > neibProvinceId &&
|
||||
!checkedProvinces[`${provinceId}-${neibProvinceId}-${cellId}`] &&
|
||||
cells.state[neibId] === stateId
|
||||
);
|
||||
});
|
||||
|
||||
if (provToCell !== undefined) {
|
||||
const addToChecked = (cellId: number) =>
|
||||
(checkedProvinces[`${provinceId}-${cells.province[provToCell]}-${cellId}`] = true);
|
||||
|
||||
const border = getBorder({
|
||||
cells,
|
||||
vertices,
|
||||
type: "province",
|
||||
fromCell: cellId,
|
||||
toCell: provToCell,
|
||||
addToChecked
|
||||
});
|
||||
|
||||
if (border) {
|
||||
provincePath.push(border);
|
||||
cellId--; // check the same cell again
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// if cell is on state border
|
||||
const stateToCell = cells.c[cellId].find(neibId => {
|
||||
const neibStateId = cells.state[neibId];
|
||||
return isLand(neibId) && stateId > neibStateId && !checkedStates[`${stateId}-${neibStateId}-${cellId}`];
|
||||
});
|
||||
|
||||
if (stateToCell !== undefined) {
|
||||
const addToChecked = (cellId: number) =>
|
||||
(checkedStates[`${stateId}-${cells.state[stateToCell]}-${cellId}`] = true);
|
||||
|
||||
const border = getBorder({
|
||||
cells,
|
||||
vertices,
|
||||
type: "state",
|
||||
fromCell: cellId,
|
||||
toCell: stateToCell,
|
||||
addToChecked
|
||||
});
|
||||
|
||||
if (border) {
|
||||
statePath.push(border);
|
||||
cellId--; // check the same cell again
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg.select("#borders").selectAll("path").remove();
|
||||
svg.select("#stateBorders").append("path").attr("d", statePath.join(" "));
|
||||
svg.select("#provinceBorders").append("path").attr("d", provincePath.join(" "));
|
||||
}
|
||||
|
||||
function getBorder({
|
||||
cells,
|
||||
vertices,
|
||||
type,
|
||||
fromCell,
|
||||
toCell,
|
||||
addToChecked
|
||||
}: {
|
||||
cells: IGraphCells & IPackCells;
|
||||
vertices: IGraphVertices;
|
||||
type: "state" | "province";
|
||||
fromCell: number;
|
||||
toCell: number;
|
||||
addToChecked: (cellId: number) => void;
|
||||
}) {
|
||||
const getType = (cellId: number) => cells[type][cellId];
|
||||
const isLand = (i: number) => cells.h[i] >= MIN_LAND_HEIGHT;
|
||||
|
||||
const n = cells.i.length;
|
||||
const isTypeFrom = (cellId: number) => cellId < n && getType(cellId) === getType(fromCell);
|
||||
const istypeTo = (cellId: number) => cellId < n && getType(cellId) === getType(toCell);
|
||||
|
||||
addToChecked(fromCell);
|
||||
const startingVertex = cells.v[fromCell].find(v => vertices.c[v].some(i => isLand(i) && istypeTo(i)));
|
||||
if (startingVertex === undefined) return null;
|
||||
|
||||
const checkVertex = (vertex: number) =>
|
||||
vertices.c[vertex].some(isTypeFrom) && vertices.c[vertex].some(c => isLand(c) && istypeTo(c));
|
||||
|
||||
const chain = getVerticesLine({vertices, startingVertex, checkCell: isTypeFrom, checkVertex, addToChecked});
|
||||
if (chain.length > 1) return "M" + chain.map(cellId => vertices.p[cellId]).join(" ");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const MAX_ITERATIONS = 50000;
|
||||
|
||||
// connect vertices to chain to form a border
|
||||
function getVerticesLine({
|
||||
vertices,
|
||||
startingVertex,
|
||||
checkCell,
|
||||
checkVertex,
|
||||
addToChecked
|
||||
}: {
|
||||
vertices: IGraphVertices;
|
||||
startingVertex: number;
|
||||
checkCell: (cellId: number) => boolean;
|
||||
checkVertex: (vertex: number) => boolean;
|
||||
addToChecked: (cellId: number) => void;
|
||||
}) {
|
||||
let chain: number[] = []; // vertices chain to form a path
|
||||
let next = startingVertex;
|
||||
|
||||
for (let run = 0; run < 2; run++) {
|
||||
// first run: from any vertex to a border edge
|
||||
// second run: from found border edge to another edge
|
||||
chain = [];
|
||||
|
||||
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
||||
const previous = chain.at(-1);
|
||||
const current = next;
|
||||
chain.push(current);
|
||||
|
||||
const neibCells = vertices.c[current];
|
||||
neibCells.map(addToChecked);
|
||||
|
||||
const [c1, c2, c3] = neibCells.map(checkCell);
|
||||
const [v1, v2, v3] = vertices.v[current].map(checkVertex);
|
||||
const [vertex1, vertex2, vertex3] = vertices.v[current];
|
||||
|
||||
if (v1 && vertex1 !== previous && c1 !== c2) next = vertex1;
|
||||
else if (v2 && vertex2 !== previous && c2 !== c3) next = vertex2;
|
||||
else if (v3 && vertex3 !== previous && c1 !== c3) next = vertex3;
|
||||
|
||||
if (next === current || next === startingVertex) {
|
||||
if (next === startingVertex) chain.push(startingVertex);
|
||||
startingVertex = next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import * as d3 from "d3";
|
|||
|
||||
export function defineSvg(width, height) {
|
||||
// append svg layers (in default order)
|
||||
svg = d3.select("#map");
|
||||
svg = d3.select("#map"); // to be: the only global var
|
||||
defs = svg.select("#deftemp");
|
||||
viewbox = svg.select("#viewbox");
|
||||
scaleBar = svg.select("#scaleBar");
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ export function randomizeOptions() {
|
|||
// 'Options' settings
|
||||
if (randomize || !locked("template")) randomizeHeightmapTemplate();
|
||||
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(18, 5, 2, 30);
|
||||
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
|
||||
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(5, 15, 3, 100);
|
||||
if (randomize || !locked("manors")) {
|
||||
manorsInput.value = 1000;
|
||||
manorsOutput.value = "auto";
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ async function generate(options?: IGenerationOptions) {
|
|||
// renderLayer("burgs");
|
||||
// renderLayer("routes");
|
||||
renderLayer("states");
|
||||
renderLayer("labels");
|
||||
renderLayer("borders");
|
||||
// renderLayer("labels");
|
||||
|
||||
// pack.cells.route.forEach((route, index) => {
|
||||
// if (route === 2) drawPoint(pack.cells.p[index], {color: "black"});
|
||||
|
|
|
|||
|
|
@ -60,3 +60,9 @@ function getTypedArray(maxValue: number) {
|
|||
if (maxValue <= UINT32_MAX) return Uint32Array;
|
||||
return Uint32Array;
|
||||
}
|
||||
|
||||
type Nested<T> = (T | Nested<T>)[];
|
||||
export function createNestedArray<T>(length: number, depth: number, value: boolean | number | string): Nested<T> {
|
||||
if (depth === 0) return new Array(length).fill(value);
|
||||
return new Array(length).fill(value).map(_ => createNestedArray(length, depth - 1, value));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue