mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor: draw state labels start
This commit is contained in:
parent
e35cc2e9cb
commit
e07bf91cd7
4 changed files with 131 additions and 18 deletions
|
|
@ -1,13 +1,17 @@
|
||||||
import {MIN_LAND_HEIGHT} from "config/generation";
|
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import Delaunator from "delaunator";
|
import Delaunator from "delaunator";
|
||||||
import {Voronoi} from "modules/voronoi";
|
import FlatQueue from "flatqueue";
|
||||||
|
|
||||||
|
import {simplify} from "scripts/simplify";
|
||||||
|
import {Voronoi} from "modules/voronoi";
|
||||||
|
import {MIN_LAND_HEIGHT} from "config/generation";
|
||||||
import {findCell} from "utils/graphUtils";
|
import {findCell} from "utils/graphUtils";
|
||||||
import {isState} from "utils/typeUtils";
|
import {isState} from "utils/typeUtils";
|
||||||
|
import {drawPath, drawPoint, drawPolyline} from "utils/debugUtils";
|
||||||
|
|
||||||
export function drawLabels() {
|
export function drawLabels() {
|
||||||
/* global */ const {cells, vertices, features, states, burgs} = pack;
|
/* global */ const {cells, vertices, features, states, burgs} = pack;
|
||||||
|
/* global: findCell, graphWidth, graphHeight */
|
||||||
|
|
||||||
drawStateLabels(cells, features, states, vertices);
|
drawStateLabels(cells, features, states, vertices);
|
||||||
drawBurgLabels(burgs);
|
drawBurgLabels(burgs);
|
||||||
|
|
@ -62,42 +66,64 @@ function drawStateLabels(cells: IPack["cells"], features: TPackFeatures, states:
|
||||||
const mode = options.stateLabelsMode || "auto";
|
const mode = options.stateLabelsMode || "auto";
|
||||||
|
|
||||||
const labelPaths = getLabelPaths();
|
const labelPaths = getLabelPaths();
|
||||||
|
console.log(labelPaths);
|
||||||
|
|
||||||
function getLabelPaths() {
|
function getLabelPaths() {
|
||||||
const labelPaths: number[][] = [];
|
const labelPaths: [number, TPoints][] = [];
|
||||||
const MIN_HULL_SIZE = 20;
|
const MIN_HULL_SIZE = 20;
|
||||||
|
const lineGen = d3.line().curve(d3.curveBundle.beta(1));
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
if (!isState(state)) continue;
|
if (!isState(state)) continue;
|
||||||
const used: Dict<boolean> = {};
|
const used: Dict<boolean> = {}; // mutable
|
||||||
|
|
||||||
const visualCenter = findCell(...state.pole);
|
const visualCenter = findCell(...state.pole);
|
||||||
const start = cells.state[visualCenter] === state.i ? visualCenter : state.center;
|
const startingCell = cells.state[visualCenter] === state.i ? visualCenter : state.center;
|
||||||
const hull = getHull(start, state.i, state.cells, used);
|
const hull = getHull(startingCell, state.i, state.cells, used);
|
||||||
const points = [...hull].map(vertex => vertices.p[vertex]);
|
const points = [...hull].map(vertex => vertices.p[vertex]);
|
||||||
|
|
||||||
const delaunay = Delaunator.from(points);
|
const delaunay = Delaunator.from(points);
|
||||||
const voronoi = new Voronoi(delaunay, points, points.length);
|
const voronoi = new Voronoi(delaunay, points, points.length);
|
||||||
const chain = connectCenters(voronoi.vertices, state.pole[1]);
|
const chain = connectVertices(voronoi.vertices, state.pole, used);
|
||||||
const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i % 15 === 0 || i + 1 === chain.length);
|
|
||||||
labelPaths.push([state.i, relaxed]);
|
drawPoint(state.pole, {color: "blue", radius: 1});
|
||||||
|
|
||||||
|
if (state.i === 1) {
|
||||||
|
points.forEach(point => {
|
||||||
|
drawPoint(point, {color: "red", radius: 0.5});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathPoints = simplify(
|
||||||
|
chain.map(i => voronoi.vertices.p[i]),
|
||||||
|
30
|
||||||
|
);
|
||||||
|
|
||||||
|
drawPath(lineGen(pathPoints)!, {stroke: "red", strokeWidth: 0.5});
|
||||||
|
|
||||||
|
labelPaths.push([state.i, pathPoints]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return labelPaths;
|
return labelPaths;
|
||||||
|
|
||||||
function getHull(start: number, stateId: number, stateCells: number, used: Dict<boolean>) {
|
function getHull(start: number, stateId: number, stateCells: number, used: Dict<boolean>) {
|
||||||
const queue = [start];
|
|
||||||
const hull = new Set<number>();
|
|
||||||
const addHull = (cellId: number, neibCellIndex: number) => hull.add(cells.v[cellId][neibCellIndex]);
|
|
||||||
const maxPassableLakeSize = stateCells / 10;
|
const maxPassableLakeSize = stateCells / 10;
|
||||||
|
const queue = [start];
|
||||||
|
|
||||||
|
const hull = new Set<number>();
|
||||||
|
const addToHull = (cellId: number, index: number) => {
|
||||||
|
const vertex = cells.v[cellId][index];
|
||||||
|
if (vertex) hull.add(vertex);
|
||||||
|
};
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const cellId = queue.pop()!;
|
const cellId = queue.pop()!;
|
||||||
|
|
||||||
cells.c[cellId].forEach((neibCellId, neibCellIndex) => {
|
cells.c[cellId].forEach((neibCellId, index) => {
|
||||||
if (used[neibCellId]) return;
|
if (used[neibCellId]) return;
|
||||||
if (isHullEdge(neibCellId)) return addHull(neibCellId, neibCellIndex);
|
|
||||||
|
|
||||||
used[neibCellId] = true;
|
used[neibCellId] = true;
|
||||||
|
|
||||||
|
if (isHullEdge(neibCellId)) return addToHull(cellId, index);
|
||||||
return queue.push(neibCellId);
|
return queue.push(neibCellId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -125,5 +151,77 @@ function drawStateLabels(cells: IPack["cells"], features: TPackFeatures, states:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectVertices(vertices: Voronoi["vertices"], pole: TPoint, used: Dict<boolean>) {
|
||||||
|
// check if vertex is inside the area
|
||||||
|
const inside = vertices.p.map(([x, y]) => {
|
||||||
|
if (x <= 0 || y <= 0 || x >= graphWidth || y >= graphHeight) return false; // out of the screen
|
||||||
|
return used[findCell(x, y)];
|
||||||
|
});
|
||||||
|
|
||||||
|
const innerVertices = d3.range(vertices.p.length).filter(i => inside[i]);
|
||||||
|
if (innerVertices.length < 2) return [0];
|
||||||
|
|
||||||
|
const horyzontalShift = getHoryzontalShift(vertices.p.length);
|
||||||
|
const {right: start, left: end} = getEdgeVertices(innerVertices, vertices.p, pole, horyzontalShift);
|
||||||
|
|
||||||
|
// connect leftmost and rightmost vertices with shortest path
|
||||||
|
const cost: number[] = [];
|
||||||
|
const from: number[] = [];
|
||||||
|
const queue = new FlatQueue<number>();
|
||||||
|
queue.push(start, 0);
|
||||||
|
|
||||||
|
while (queue.length) {
|
||||||
|
const priority = queue.peekValue()!;
|
||||||
|
const next = queue.pop()!;
|
||||||
|
|
||||||
|
if (next === end) break;
|
||||||
|
|
||||||
|
for (const neibVertex of vertices.v[next]) {
|
||||||
|
if (neibVertex === -1) continue;
|
||||||
|
|
||||||
|
const totalCost = priority + (inside[neibVertex] ? 1 : 100);
|
||||||
|
if (from[neibVertex] || totalCost >= cost[neibVertex]) continue;
|
||||||
|
|
||||||
|
cost[neibVertex] = totalCost;
|
||||||
|
from[neibVertex] = next;
|
||||||
|
queue.push(neibVertex, totalCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore path
|
||||||
|
const chain = [end];
|
||||||
|
let cur = end;
|
||||||
|
while (cur !== start) {
|
||||||
|
cur = from[cur];
|
||||||
|
if (inside[cur]) chain.push(cur);
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHoryzontalShift(verticesNumber: number) {
|
||||||
|
console.log({verticesNumber});
|
||||||
|
return 0;
|
||||||
|
if (verticesNumber < 100) return 1;
|
||||||
|
if (verticesNumber < 200) return 0.3;
|
||||||
|
if (verticesNumber < 300) return 0.1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEdgeVertices(innerVertices: number[], points: TPoints, pole: TPoint, horyzontalShift: number) {
|
||||||
|
let leftmost = {value: Infinity, vertex: innerVertices.at(0)!};
|
||||||
|
let rightmost = {value: -Infinity, vertex: innerVertices.at(-1)!};
|
||||||
|
|
||||||
|
for (const vertex of innerVertices) {
|
||||||
|
const [x, y] = points[vertex];
|
||||||
|
const valueX = x - pole[0];
|
||||||
|
const valueY = Math.abs(y - pole[1]) * horyzontalShift;
|
||||||
|
|
||||||
|
if (valueX + valueY < leftmost.value) leftmost = {value: valueX + valueY, vertex};
|
||||||
|
if (valueX - valueY > rightmost.value) rightmost = {value: valueX - valueY, vertex};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {left: leftmost.vertex, right: rightmost.vertex};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ async function generate(options?: IGenerationOptions) {
|
||||||
// renderLayer("biomes");
|
// renderLayer("biomes");
|
||||||
renderLayer("burgs");
|
renderLayer("burgs");
|
||||||
renderLayer("routes");
|
renderLayer("routes");
|
||||||
// renderLayer("states");
|
renderLayer("states");
|
||||||
renderLayer("provinces");
|
renderLayer("labels");
|
||||||
|
|
||||||
// pack.cells.route.forEach((route, index) => {
|
// pack.cells.route.forEach((route, index) => {
|
||||||
// if (route === 2) drawPoint(pack.cells.p[index], {color: "black"});
|
// if (route === 2) drawPoint(pack.cells.p[index], {color: "black"});
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,12 @@ interface IGetPolesProps {
|
||||||
export function getPolesOfInaccessibility(props: IGetPolesProps) {
|
export function getPolesOfInaccessibility(props: IGetPolesProps) {
|
||||||
TIME && console.time("getPolesOfInaccessibility");
|
TIME && console.time("getPolesOfInaccessibility");
|
||||||
const multiPolygons = getMultiPolygons(props);
|
const multiPolygons = getMultiPolygons(props);
|
||||||
|
const sortByLength = (a: unknown[], b: unknown[]) => b.length - a.length;
|
||||||
|
console.log(multiPolygons);
|
||||||
|
|
||||||
const poles: Dict<TPoint> = Object.fromEntries(
|
const poles: Dict<TPoint> = Object.fromEntries(
|
||||||
Object.entries(multiPolygons).map(([id, multiPolygon]) => {
|
Object.entries(multiPolygons).map(([id, multiPolygon]) => {
|
||||||
const [x, y] = polylabel(multiPolygon, 20);
|
const [x, y] = polylabel(multiPolygon.sort(sortByLength), 20);
|
||||||
return [id, [rn(x), rn(y)]];
|
return [id, [rn(x), rn(y)]];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,19 @@ export function drawLine([x1, y1]: TPoint, [x2, y2]: TPoint, {stroke = "#444", s
|
||||||
.attr("stroke-width", strokeWidth);
|
.attr("stroke-width", strokeWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function drawPolyline(points: TPoints, {fill = "none", stroke = "#444", strokeWidth = 0.2} = {}) {
|
||||||
|
debug
|
||||||
|
.append("polyline")
|
||||||
|
.attr("points", points.join(" "))
|
||||||
|
.attr("fill", fill)
|
||||||
|
.attr("stroke", stroke)
|
||||||
|
.attr("stroke-width", strokeWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function drawPath(d: string, {fill = "none", stroke = "#444", strokeWidth = 0.2} = {}) {
|
||||||
|
debug.append("path").attr("d", d).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", strokeWidth);
|
||||||
|
}
|
||||||
|
|
||||||
export function drawArrow([x1, y1]: TPoint, [x2, y2]: TPoint, {width = 1, color = "#444"} = {}) {
|
export function drawArrow([x1, y1]: TPoint, [x2, y2]: TPoint, {width = 1, color = "#444"} = {}) {
|
||||||
const normal = getNormal([x1, y1], [x2, y2]);
|
const normal = getNormal([x1, y1], [x2, y2]);
|
||||||
const [xMid, yMid] = [(x1 + x2) / 2, (y1 + y2) / 2];
|
const [xMid, yMid] = [(x1 + x2) / 2, (y1 + y2) / 2];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue