mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor: voronoi
This commit is contained in:
parent
e59b536e83
commit
00d8d28d76
16 changed files with 1912 additions and 171 deletions
|
|
@ -13,6 +13,7 @@
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
"@types/d3": "^5.9.0",
|
"@types/d3": "^5.9.0",
|
||||||
|
"@types/delaunator": "^5.0.0",
|
||||||
"@types/jquery": "^3.5.14",
|
"@types/jquery": "^3.5.14",
|
||||||
"@types/jqueryui": "^1.12.16",
|
"@types/jqueryui": "^1.12.16",
|
||||||
"rollup": "^2.75.7",
|
"rollup": "^2.75.7",
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,7 @@ export function open(options) {
|
||||||
calculateTemperatures(grid);
|
calculateTemperatures(grid);
|
||||||
generatePrecipitation(grid);
|
generatePrecipitation(grid);
|
||||||
reGraph(grid);
|
reGraph(grid);
|
||||||
|
reMarkFeatures();
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
Rivers.generate(erosionAllowed);
|
Rivers.generate(erosionAllowed);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
|
||||||
import {ERROR, TIME} from "config/logging";
|
import {ERROR, TIME} from "config/logging";
|
||||||
import {reMarkFeatures} from "modules/markup";
|
|
||||||
import {clipPoly} from "utils/lineUtils";
|
import {clipPoly} from "utils/lineUtils";
|
||||||
import {round} from "utils/stringUtils";
|
import {round} from "utils/stringUtils";
|
||||||
import {Ruler} from "modules/measurers";
|
import {Ruler} from "modules/measurers";
|
||||||
|
|
@ -9,7 +8,6 @@ import {Ruler} from "modules/measurers";
|
||||||
// Detect and draw the coastline
|
// Detect and draw the coastline
|
||||||
export function drawCoastline() {
|
export function drawCoastline() {
|
||||||
TIME && console.time("drawCoastline");
|
TIME && console.time("drawCoastline");
|
||||||
reMarkFeatures();
|
|
||||||
|
|
||||||
const {cells, vertices, features} = pack;
|
const {cells, vertices, features} = pack;
|
||||||
const n = cells.i.length;
|
const n = cells.i.length;
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,8 @@ export function resolveVersionConflicts(version) {
|
||||||
defs.select("#water").selectAll("path").remove();
|
defs.select("#water").selectAll("path").remove();
|
||||||
coastline.selectAll("path").remove();
|
coastline.selectAll("path").remove();
|
||||||
lakes.selectAll("path").remove();
|
lakes.selectAll("path").remove();
|
||||||
|
|
||||||
|
reMarkFeatures();
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,7 @@ async function parseLoadedData(data) {
|
||||||
grid = JSON.parse(data[6]);
|
grid = JSON.parse(data[6]);
|
||||||
|
|
||||||
const {cells, vertices} = calculateVoronoi(grid.points, grid.boundary);
|
const {cells, vertices} = calculateVoronoi(grid.points, grid.boundary);
|
||||||
|
|
||||||
grid.cells = cells;
|
grid.cells = cells;
|
||||||
grid.vertices = vertices;
|
grid.vertices = vertices;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ function markup({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-mark features (ocean, lakes, islands)
|
// Re-mark features (ocean, lakes, islands)
|
||||||
export function reMarkFeatures() {
|
export function reMarkFeatures(pack: IPackBase, grid: IGrid) {
|
||||||
TIME && console.time("reMarkFeatures");
|
TIME && console.time("reMarkFeatures");
|
||||||
const {cells} = pack;
|
const {cells} = pack;
|
||||||
const features: TPackFeatures = [0];
|
const features: TPackFeatures = [0];
|
||||||
|
|
@ -165,7 +165,7 @@ export function reMarkFeatures() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// markupPackLand
|
// markupPackLand
|
||||||
markup({graph: pack, distanceField: pack.cells.t, start: 3, increment: 1, limit: INT8_MAX});
|
markup({graph: pack, distanceField: cells.t, start: 3, increment: 1, limit: INT8_MAX});
|
||||||
|
|
||||||
function defineOceanGroup(number: number) {
|
function defineOceanGroup(number: number) {
|
||||||
if (number > grid.cells.i.length / 25) return "ocean";
|
if (number > grid.cells.i.length / 25) return "ocean";
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ window.Submap = (function () {
|
||||||
|
|
||||||
// remove misclassified cells
|
// remove misclassified cells
|
||||||
stage("Define coastline.");
|
stage("Define coastline.");
|
||||||
|
reMarkFeatures();
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
/****************************************************/
|
/****************************************************/
|
||||||
|
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
export class Voronoi {
|
|
||||||
/**
|
|
||||||
* Creates a Voronoi diagram from the given Delaunator, a list of points, and the number of points. The Voronoi diagram is constructed using (I think) the {@link https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm |Bowyer-Watson Algorithm}
|
|
||||||
* The {@link https://github.com/mapbox/delaunator/ |Delaunator} library uses {@link https://en.wikipedia.org/wiki/Doubly_connected_edge_list |half-edges} to represent the relationship between points and triangles.
|
|
||||||
* @param {{triangles: Uint32Array, halfedges: Int32Array}} delaunay A {@link https://github.com/mapbox/delaunator/blob/master/index.js |Delaunator} instance.
|
|
||||||
* @param {[number, number][]} points A list of coordinates.
|
|
||||||
* @param {number} pointsN The number of points.
|
|
||||||
*/
|
|
||||||
constructor(delaunay, points, pointsN) {
|
|
||||||
this.delaunay = delaunay;
|
|
||||||
this.points = points;
|
|
||||||
this.pointsN = pointsN;
|
|
||||||
this.cells = {v: [], c: [], b: []}; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
|
|
||||||
this.vertices = {p: [], v: [], c: []}; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
|
|
||||||
|
|
||||||
// Half-edges are the indices into the delaunator outputs:
|
|
||||||
// delaunay.triangles[e] gives the point ID where the half-edge starts
|
|
||||||
// delaunay.halfedges[e] returns either the opposite half-edge in the adjacent triangle, or -1 if there's not an adjacent triangle.
|
|
||||||
for (let e = 0; e < this.delaunay.triangles.length; e++) {
|
|
||||||
const p = this.delaunay.triangles[this.nextHalfedge(e)];
|
|
||||||
if (p < this.pointsN && !this.cells.c[p]) {
|
|
||||||
const edges = this.edgesAroundPoint(e);
|
|
||||||
this.cells.v[p] = edges.map(e => this.triangleOfEdge(e)); // cell: adjacent vertex
|
|
||||||
this.cells.c[p] = edges.map(e => this.delaunay.triangles[e]).filter(c => c < this.pointsN); // cell: adjacent valid cells
|
|
||||||
this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border
|
|
||||||
}
|
|
||||||
|
|
||||||
const t = this.triangleOfEdge(e);
|
|
||||||
if (!this.vertices.p[t]) {
|
|
||||||
this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates
|
|
||||||
this.vertices.v[t] = this.trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
|
|
||||||
this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the IDs of the points comprising the given triangle. Taken from {@link https://mapbox.github.io/delaunator/#triangle-to-points| the Delaunator docs.}
|
|
||||||
* @param {number} t The index of the triangle
|
|
||||||
* @returns {[number, number, number]} The IDs of the points comprising the given triangle.
|
|
||||||
*/
|
|
||||||
pointsOfTriangle(t) {
|
|
||||||
return this.edgesOfTriangle(t).map(edge => this.delaunay.triangles[edge]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifies what triangles are adjacent to the given triangle. Taken from {@link https://mapbox.github.io/delaunator/#triangle-to-triangles| the Delaunator docs.}
|
|
||||||
* @param {number} t The index of the triangle
|
|
||||||
* @returns {number[]} The indices of the triangles that share half-edges with this triangle.
|
|
||||||
*/
|
|
||||||
trianglesAdjacentToTriangle(t) {
|
|
||||||
let triangles = [];
|
|
||||||
for (let edge of this.edgesOfTriangle(t)) {
|
|
||||||
let opposite = this.delaunay.halfedges[edge];
|
|
||||||
triangles.push(this.triangleOfEdge(opposite));
|
|
||||||
}
|
|
||||||
return triangles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the indices of all the incoming and outgoing half-edges that touch the given point. Taken from {@link https://mapbox.github.io/delaunator/#point-to-edges| the Delaunator docs.}
|
|
||||||
* @param {number} start The index of an incoming half-edge that leads to the desired point
|
|
||||||
* @returns {number[]} The indices of all half-edges (incoming or outgoing) that touch the point.
|
|
||||||
*/
|
|
||||||
edgesAroundPoint(start) {
|
|
||||||
const result = [];
|
|
||||||
let incoming = start;
|
|
||||||
do {
|
|
||||||
result.push(incoming);
|
|
||||||
const outgoing = this.nextHalfedge(incoming);
|
|
||||||
incoming = this.delaunay.halfedges[outgoing];
|
|
||||||
} while (incoming !== -1 && incoming !== start && result.length < 20);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the center of the triangle located at the given index.
|
|
||||||
* @param {number} t The index of the triangle
|
|
||||||
* @returns {[number, number]}
|
|
||||||
*/
|
|
||||||
triangleCenter(t) {
|
|
||||||
let vertices = this.pointsOfTriangle(t).map(p => this.points[p]);
|
|
||||||
return this.circumcenter(vertices[0], vertices[1], vertices[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all of the half-edges for a specific triangle `t`. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.}
|
|
||||||
* @param {number} t The index of the triangle
|
|
||||||
* @returns {[number, number, number]} The edges of the triangle.
|
|
||||||
*/
|
|
||||||
edgesOfTriangle(t) {
|
|
||||||
return [3 * t, 3 * t + 1, 3 * t + 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables lookup of a triangle, given one of the half-edges of that triangle. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.}
|
|
||||||
* @param {number} e The index of the edge
|
|
||||||
* @returns {number} The index of the triangle
|
|
||||||
*/
|
|
||||||
triangleOfEdge(e) {
|
|
||||||
return Math.floor(e / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves to the next half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.}
|
|
||||||
* @param {number} e The index of the current half edge
|
|
||||||
* @returns {number} The index of the next half edge
|
|
||||||
*/
|
|
||||||
nextHalfedge(e) {
|
|
||||||
return e % 3 === 2 ? e - 2 : e + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves to the previous half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.}
|
|
||||||
* @param {number} e The index of the current half edge
|
|
||||||
* @returns {number} The index of the previous half edge
|
|
||||||
*/
|
|
||||||
prevHalfedge(e) {
|
|
||||||
return e % 3 === 0 ? e + 2 : e - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the circumcenter of the triangle identified by points a, b, and c. Taken from {@link https://en.wikipedia.org/wiki/Circumscribed_circle#Circumcenter_coordinates| Wikipedia}
|
|
||||||
* @param {[number, number]} a The coordinates of the first point of the triangle
|
|
||||||
* @param {[number, number]} b The coordinates of the second point of the triangle
|
|
||||||
* @param {[number, number]} c The coordinates of the third point of the triangle
|
|
||||||
* @return {[number, number]} The coordinates of the circumcenter of the triangle.
|
|
||||||
*/
|
|
||||||
circumcenter(a, b, c) {
|
|
||||||
const [ax, ay] = a;
|
|
||||||
const [bx, by] = b;
|
|
||||||
const [cx, cy] = c;
|
|
||||||
const ad = ax * ax + ay * ay;
|
|
||||||
const bd = bx * bx + by * by;
|
|
||||||
const cd = cx * cx + cy * cy;
|
|
||||||
const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
|
||||||
return [
|
|
||||||
Math.floor((1 / D) * (ad * (by - cy) + bd * (cy - ay) + cd * (ay - by))),
|
|
||||||
Math.floor((1 / D) * (ad * (cx - bx) + bd * (ax - cx) + cd * (bx - ax)))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
114
src/modules/voronoi.ts
Normal file
114
src/modules/voronoi.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
// creates a Voronoi diagram from the given Delaunator, a list of points, and the number of points
|
||||||
|
// based on https://mapbox.github.io/delaunator
|
||||||
|
|
||||||
|
interface Delaunay {
|
||||||
|
triangles: Uint32Array;
|
||||||
|
halfedges: Int32Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Number3 = [number, number, number];
|
||||||
|
|
||||||
|
export class Voronoi {
|
||||||
|
private readonly delaunay: Delaunay;
|
||||||
|
private readonly points: TPoints;
|
||||||
|
private readonly pointsN: number;
|
||||||
|
|
||||||
|
// voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
|
||||||
|
public readonly cells: {v: number[][]; c: number[][]; b: (0 | 1)[]};
|
||||||
|
|
||||||
|
// cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
|
||||||
|
public readonly vertices: {p: TPoints; v: Number3[]; c: Number3[]};
|
||||||
|
|
||||||
|
constructor(delaunay: Delaunay, points: TPoints, pointsN: number) {
|
||||||
|
this.delaunay = delaunay;
|
||||||
|
this.points = points;
|
||||||
|
this.pointsN = pointsN;
|
||||||
|
this.cells = {v: [], c: [], b: []};
|
||||||
|
this.vertices = {p: [], v: [], c: []};
|
||||||
|
|
||||||
|
// Half-edges are the indices into the delaunator outputs:
|
||||||
|
// delaunay.triangles[e] gives the point ID where the half-edge starts
|
||||||
|
// delaunay.halfedges[e] returns either the opposite half-edge in the adjacent triangle, or -1 if there's not an adjacent triangle
|
||||||
|
for (let e = 0; e < this.delaunay.triangles.length; e++) {
|
||||||
|
const p = this.delaunay.triangles[this.nextHalfedge(e)];
|
||||||
|
if (p < this.pointsN && !this.cells.c[p]) {
|
||||||
|
const edges = this.edgesAroundPoint(e);
|
||||||
|
this.cells.v[p] = edges.map(e => this.triangleOfEdge(e)); // cell: adjacent vertex
|
||||||
|
this.cells.c[p] = edges.map(e => this.delaunay.triangles[e]).filter(c => c < this.pointsN); // cell: adjacent valid cells
|
||||||
|
this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = this.triangleOfEdge(e);
|
||||||
|
if (!this.vertices.p[t]) {
|
||||||
|
this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates
|
||||||
|
this.vertices.v[t] = this.trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
|
||||||
|
this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the indices of all the incoming and outgoing half-edges that touch the given point
|
||||||
|
edgesAroundPoint(start: number): number[] {
|
||||||
|
const result = [];
|
||||||
|
let incoming = start;
|
||||||
|
do {
|
||||||
|
result.push(incoming);
|
||||||
|
const outgoing = this.nextHalfedge(incoming);
|
||||||
|
incoming = this.delaunay.halfedges[outgoing];
|
||||||
|
} while (incoming !== -1 && incoming !== start && result.length < 20);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves all of the half-edges for a specific triangle
|
||||||
|
edgesOfTriangle(t: number): Number3 {
|
||||||
|
return [3 * t, 3 * t + 1, 3 * t + 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the IDs of the points comprising the given triangle
|
||||||
|
pointsOfTriangle(t: number) {
|
||||||
|
return this.edgesOfTriangle(t).map(edge => this.delaunay.triangles[edge]) as Number3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifies what triangles are adjacent to the given triangle
|
||||||
|
trianglesAdjacentToTriangle(t: number) {
|
||||||
|
let triangles = [];
|
||||||
|
for (let edge of this.edgesOfTriangle(t)) {
|
||||||
|
let opposite = this.delaunay.halfedges[edge];
|
||||||
|
triangles.push(this.triangleOfEdge(opposite));
|
||||||
|
}
|
||||||
|
return triangles as Number3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the center of the triangle located at the given index.
|
||||||
|
triangleCenter(t: number): TPoint {
|
||||||
|
let vertices = this.pointsOfTriangle(t).map(p => this.points[p]);
|
||||||
|
return this.circumcenter(vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enables lookup of a triangle, given one of the half-edges of that triangle
|
||||||
|
triangleOfEdge(e: number): number {
|
||||||
|
return Math.floor(e / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves to the next half-edge of a triangle, given the current half-edge's index
|
||||||
|
nextHalfedge(e: number): number {
|
||||||
|
return e % 3 === 2 ? e - 2 : e + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves to the previous half-edge of a triangle, given the current half-edge's index
|
||||||
|
prevHalfedge(e: number): number {
|
||||||
|
return e % 3 === 0 ? e + 2 : e - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds the circumcenter of the triangle identified by points a, b, and c
|
||||||
|
circumcenter([[ax, ay], [bx, by], [cx, cy]]: TPoint[]): TPoint {
|
||||||
|
const ad = ax * ax + ay * ay;
|
||||||
|
const bd = bx * bx + by * by;
|
||||||
|
const cd = cx * cx + cy * cy;
|
||||||
|
const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
||||||
|
return [
|
||||||
|
Math.floor((1 / D) * (ad * (by - cy) + bd * (cy - ay) + cd * (ay - by))),
|
||||||
|
Math.floor((1 / D) * (ad * (cx - bx) + bd * (ax - cx) + cd * (bx - ax)))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import {initLayers, renderLayer, restoreLayers} from "layers";
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
import {drawCoastline} from "modules/coastline";
|
import {drawCoastline} from "modules/coastline";
|
||||||
import {calculateMapCoordinates, defineMapSize} from "modules/coordinates";
|
import {calculateMapCoordinates, defineMapSize} from "modules/coordinates";
|
||||||
import {markupGridFeatures} from "modules/markup";
|
import {markupGridFeatures, reMarkFeatures} from "modules/markup";
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
import {drawScaleBar, Rulers} from "modules/measurers";
|
import {drawScaleBar, Rulers} from "modules/measurers";
|
||||||
// @ts-expect-error js module
|
// @ts-expect-error js module
|
||||||
|
|
@ -55,7 +55,8 @@ export async function generate(options?: IGenerationOptions) {
|
||||||
|
|
||||||
const updatedGrid = await updateGrid(grid, precreatedGraph);
|
const updatedGrid = await updateGrid(grid, precreatedGraph);
|
||||||
|
|
||||||
reGraph(updatedGrid);
|
const pack = reGraph(updatedGrid);
|
||||||
|
reMarkFeatures(pack, grid);
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
Rivers.generate();
|
Rivers.generate();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
|
||||||
const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD;
|
const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD;
|
||||||
|
|
||||||
// recalculate Voronoi Graph to pack cells
|
// recalculate Voronoi Graph to pack cells
|
||||||
export function reGraph(grid: IGrid) {
|
export function reGraph(grid: IGrid): IPackBase {
|
||||||
TIME && console.time("reGraph");
|
TIME && console.time("reGraph");
|
||||||
const {cells: gridCells, points, features} = grid;
|
const {cells: gridCells, points, features} = grid;
|
||||||
const newCells: {p: TPoints; g: number[]; h: number[]} = {p: [], g: [], h: []}; // store new data
|
const newCells: {p: TPoints; g: number[]; h: number[]} = {p: [], g: [], h: []}; // store new data
|
||||||
|
|
@ -56,14 +56,20 @@ export function reGraph(grid: IGrid) {
|
||||||
return Math.min(area, UINT16_MAX);
|
return Math.min(area, UINT16_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {cells: packCells, vertices} = calculateVoronoi(newCells.p, grid.boundary);
|
const {cells, vertices} = calculateVoronoi(newCells.p, grid.boundary);
|
||||||
pack.vertices = vertices;
|
|
||||||
pack.cells = packCells;
|
const pack: IPackBase = {
|
||||||
pack.cells.p = newCells.p;
|
vertices,
|
||||||
pack.cells.g = createTypedArray({maxValue: grid.points.length, from: newCells.g});
|
cells: {
|
||||||
pack.cells.q = d3.quadtree(newCells.p.map(([x, y], i) => [x, y, i]));
|
...cells,
|
||||||
pack.cells.h = new Uint8Array(newCells.h);
|
p: newCells.p,
|
||||||
pack.cells.area = createTypedArray({maxValue: UINT16_MAX, from: pack.cells.i}).map(getCellArea);
|
g: createTypedArray({maxValue: grid.points.length, from: newCells.g}),
|
||||||
|
q: d3.quadtree(newCells.p.map(([x, y], i) => [x, y, i])),
|
||||||
|
h: new Uint8Array(newCells.h),
|
||||||
|
area: createTypedArray({maxValue: UINT16_MAX, from: cells.i}).map(getCellArea)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TIME && console.timeEnd("reGraph");
|
TIME && console.timeEnd("reGraph");
|
||||||
|
return pack;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
src/types/graph.d.ts
vendored
2
src/types/graph.d.ts
vendored
|
|
@ -12,7 +12,7 @@ interface IGraphVertices {
|
||||||
|
|
||||||
interface IGraphCells {
|
interface IGraphCells {
|
||||||
i: UintArray;
|
i: UintArray;
|
||||||
b: UintArray;
|
b: (0 | 1)[];
|
||||||
c: number[][];
|
c: number[][];
|
||||||
v: number[][];
|
v: number[][];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
src/types/pack.d.ts
vendored
9
src/types/pack.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
interface IPack extends IGraph {
|
interface IPack extends IGraph {
|
||||||
cells: IPackCells;
|
cells: IGraphCells & IPackCells;
|
||||||
features: TPackFeatures;
|
features: TPackFeatures;
|
||||||
states: IState[];
|
states: IState[];
|
||||||
cultures: ICulture[];
|
cultures: ICulture[];
|
||||||
|
|
@ -9,7 +9,7 @@ interface IPack extends IGraph {
|
||||||
religions: IReligion[];
|
religions: IReligion[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPackCells extends IGraphCells {
|
interface IPackCells {
|
||||||
p: TPoints; // cell center points
|
p: TPoints; // cell center points
|
||||||
h: Uint8Array; // heights, [0, 100], see MIN_LAND_HEIGHT constant
|
h: Uint8Array; // heights, [0, 100], see MIN_LAND_HEIGHT constant
|
||||||
t: Int8Array; // see DISTANCE_FIELD enum
|
t: Int8Array; // see DISTANCE_FIELD enum
|
||||||
|
|
@ -32,6 +32,11 @@ interface IPackCells extends IGraphCells {
|
||||||
q: d3.Quadtree<number[]>;
|
q: d3.Quadtree<number[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IPackBase extends IGraph {
|
||||||
|
cells: IGraphCells & Partial<IPackCells>;
|
||||||
|
features?: TPackFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
interface IPackFeatureBase {
|
interface IPackFeatureBase {
|
||||||
i: number; // feature id starting from 1
|
i: number; // feature id starting from 1
|
||||||
border: boolean; // if touches map border
|
border: boolean; // if touches map border
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
// @ts-nocheck
|
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import Delaunator from "delaunator";
|
import Delaunator from "delaunator";
|
||||||
|
|
||||||
|
import {DISTANCE_FIELD, MIN_LAND_HEIGHT} from "config/generation";
|
||||||
|
import {Voronoi} from "modules/voronoi";
|
||||||
import {aleaPRNG} from "scripts/aleaPRNG";
|
import {aleaPRNG} from "scripts/aleaPRNG";
|
||||||
import {TIME} from "../config/logging";
|
import {TIME} from "../config/logging";
|
||||||
import {createTypedArray} from "./arrayUtils";
|
import {createTypedArray} from "./arrayUtils";
|
||||||
import {rn} from "./numberUtils";
|
import {rn} from "./numberUtils";
|
||||||
import {byId} from "./shorthands";
|
import {byId} from "./shorthands";
|
||||||
import {Voronoi} from "/src/modules/voronoi";
|
|
||||||
import {MIN_LAND_HEIGHT, DISTANCE_FIELD} from "config/generation";
|
|
||||||
|
|
||||||
// check if new grid graph should be generated or we can use the existing one
|
// check if new grid graph should be generated or we can use the existing one
|
||||||
export function shouldRegenerateGridPoints(grid: IGrid) {
|
export function shouldRegenerateGridPoints(grid: IGrid) {
|
||||||
|
|
@ -45,21 +44,18 @@ function placePoints() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate Delaunay and then Voronoi diagram
|
// calculate Delaunay and then Voronoi diagram
|
||||||
export function calculateVoronoi(points: number[][], boundary: number[][]) {
|
export function calculateVoronoi(points: TPoints, boundary: TPoints): IGraph {
|
||||||
TIME && console.time("calculateDelaunay");
|
TIME && console.time("calculateDelaunay");
|
||||||
const allPoints = points.concat(boundary);
|
const allPoints: TPoints = points.concat(boundary);
|
||||||
const delaunay = Delaunator.from(allPoints);
|
const delaunay = Delaunator.from(allPoints);
|
||||||
TIME && console.timeEnd("calculateDelaunay");
|
TIME && console.timeEnd("calculateDelaunay");
|
||||||
|
|
||||||
TIME && console.time("calculateVoronoi");
|
TIME && console.time("calculateVoronoi");
|
||||||
const voronoi = new Voronoi(delaunay, allPoints, points.length);
|
const {cells, vertices} = new Voronoi(delaunay, allPoints, points.length);
|
||||||
|
const i = createTypedArray({maxValue: points.length, length: points.length}).map((_, i) => i); // array of indexes
|
||||||
|
|
||||||
const cells = voronoi.cells;
|
|
||||||
cells.i = createTypedArray({maxValue: points.length, length: points.length}).map((_, i) => i); // array of indexes
|
|
||||||
const vertices = voronoi.vertices;
|
|
||||||
TIME && console.timeEnd("calculateVoronoi");
|
TIME && console.timeEnd("calculateVoronoi");
|
||||||
|
return {cells: {...cells, i}, vertices};
|
||||||
return {cells, vertices};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add points along map edge to pseudo-clip voronoi cells
|
// add points along map edge to pseudo-clip voronoi cells
|
||||||
|
|
|
||||||
1752
yarn-error.log
Normal file
1752
yarn-error.log
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -337,6 +337,11 @@
|
||||||
"@types/d3-voronoi" "*"
|
"@types/d3-voronoi" "*"
|
||||||
"@types/d3-zoom" "^1"
|
"@types/d3-zoom" "^1"
|
||||||
|
|
||||||
|
"@types/delaunator@^5.0.0":
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/delaunator/-/delaunator-5.0.0.tgz#c6be322f0f3eff9fa9f6a5dcaa2ded54913b2d17"
|
||||||
|
integrity sha512-00nrXdKbhF9FQsq6MskmdUgEqd5MWzA+x8CoDt8OJqbS4QB528SgkOfEcnQMa2sIg2XTKowwwVQx8iGsAX6CAQ==
|
||||||
|
|
||||||
"@types/estree@0.0.39":
|
"@types/estree@0.0.39":
|
||||||
version "0.0.39"
|
version "0.0.39"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue