mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
heightmap selection - refactor, make generation immutable to get predictable result
This commit is contained in:
parent
4f372c7a46
commit
662163176b
12 changed files with 197 additions and 158 deletions
85
main.js
85
main.js
|
|
@ -629,7 +629,7 @@ void (function addDragToUpload() {
|
||||||
async function generate(options) {
|
async function generate(options) {
|
||||||
try {
|
try {
|
||||||
const timeStart = performance.now();
|
const timeStart = performance.now();
|
||||||
const {seed: precreatedSeed} = options || {};
|
const {seed: precreatedSeed, graph: precreatedGraph} = options || {};
|
||||||
|
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
setSeed(precreatedSeed);
|
setSeed(precreatedSeed);
|
||||||
|
|
@ -638,11 +638,9 @@ async function generate(options) {
|
||||||
applyMapSize();
|
applyMapSize();
|
||||||
randomizeOptions();
|
randomizeOptions();
|
||||||
|
|
||||||
if (shouldRegenerateGrid()) {
|
if (shouldRegenerateGrid(grid)) grid = precreatedGraph || generateGrid();
|
||||||
placePoints();
|
else delete grid.cells.h;
|
||||||
calculateVoronoi(grid, grid.points);
|
grid.cells.h = await HeightmapGenerator.generate(grid);
|
||||||
}
|
|
||||||
await HeightmapGenerator.generate();
|
|
||||||
|
|
||||||
markFeatures();
|
markFeatures();
|
||||||
markupGridOcean();
|
markupGridOcean();
|
||||||
|
|
@ -654,6 +652,7 @@ async function generate(options) {
|
||||||
calculateMapCoordinates();
|
calculateMapCoordinates();
|
||||||
calculateTemperatures();
|
calculateTemperatures();
|
||||||
generatePrecipitation();
|
generatePrecipitation();
|
||||||
|
|
||||||
reGraph();
|
reGraph();
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
|
|
@ -736,52 +735,13 @@ function setSeed(precreatedSeed) {
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if new grid graph should be generated or we can use the existing one
|
|
||||||
function shouldRegenerateGrid() {
|
|
||||||
if (!grid.spacing) return true;
|
|
||||||
const cellsDesired = +byId("pointsInput").dataset.cells;
|
|
||||||
const newSpacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2);
|
|
||||||
return grid.spacing !== newSpacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place points to calculate Voronoi diagram
|
|
||||||
function placePoints() {
|
|
||||||
TIME && console.time("placePoints");
|
|
||||||
Math.random = aleaPRNG(seed); // reset PRNG
|
|
||||||
|
|
||||||
const cellsDesired = +byId("pointsInput").dataset.cells;
|
|
||||||
const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jirrering
|
|
||||||
grid.spacing = spacing;
|
|
||||||
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
|
|
||||||
grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid
|
|
||||||
grid.cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing);
|
|
||||||
grid.cellsY = Math.floor((graphHeight + 0.5 * spacing - 1e-10) / spacing);
|
|
||||||
TIME && console.timeEnd("placePoints");
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate Delaunay and then Voronoi diagram
|
|
||||||
function calculateVoronoi(graph, points) {
|
|
||||||
TIME && console.time("calculateDelaunay");
|
|
||||||
const n = points.length;
|
|
||||||
const allPoints = points.concat(grid.boundary);
|
|
||||||
const delaunay = Delaunator.from(allPoints);
|
|
||||||
TIME && console.timeEnd("calculateDelaunay");
|
|
||||||
|
|
||||||
TIME && console.time("calculateVoronoi");
|
|
||||||
const voronoi = new Voronoi(delaunay, allPoints, n);
|
|
||||||
graph.cells = voronoi.cells;
|
|
||||||
graph.cells.i = n < 65535 ? Uint16Array.from(d3.range(n)) : Uint32Array.from(d3.range(n)); // array of indexes
|
|
||||||
graph.vertices = voronoi.vertices;
|
|
||||||
TIME && console.timeEnd("calculateVoronoi");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark features (ocean, lakes, islands) and calculate distance field
|
// Mark features (ocean, lakes, islands) and calculate distance field
|
||||||
function markFeatures() {
|
function markFeatures() {
|
||||||
TIME && console.time("markFeatures");
|
TIME && console.time("markFeatures");
|
||||||
Math.random = aleaPRNG(seed); // get the same result on heightmap edit in Erase mode
|
Math.random = aleaPRNG(seed); // get the same result on heightmap edit in Erase mode
|
||||||
|
|
||||||
const cells = grid.cells,
|
const cells = grid.cells;
|
||||||
heights = grid.cells.h;
|
const heights = grid.cells.h;
|
||||||
cells.f = new Uint16Array(cells.i.length); // cell feature number
|
cells.f = new Uint16Array(cells.i.length); // cell feature number
|
||||||
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land coast; -1 = water near coast
|
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land coast; -1 = water near coast
|
||||||
grid.features = [0];
|
grid.features = [0];
|
||||||
|
|
@ -1201,8 +1161,8 @@ function generatePrecipitation() {
|
||||||
// recalculate Voronoi Graph to pack cells
|
// recalculate Voronoi Graph to pack cells
|
||||||
function reGraph() {
|
function reGraph() {
|
||||||
TIME && console.time("reGraph");
|
TIME && console.time("reGraph");
|
||||||
let {cells, points, features} = grid;
|
const {cells, points, features} = grid;
|
||||||
const newCells = {p: [], g: [], h: []}; // to store new data
|
const newCells = {p: [], g: [], h: []}; // store new data
|
||||||
const spacing2 = grid.spacing ** 2;
|
const spacing2 = grid.spacing ** 2;
|
||||||
|
|
||||||
for (const i of cells.i) {
|
for (const i of cells.i) {
|
||||||
|
|
@ -1236,14 +1196,17 @@ function reGraph() {
|
||||||
newCells.h.push(height);
|
newCells.h.push(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateVoronoi(pack, newCells.p);
|
function getCellArea(i) {
|
||||||
cells = pack.cells;
|
const area = Math.abs(d3.polygonArea(getPackPolygon(i)));
|
||||||
cells.p = newCells.p; // points coordinates [x, y]
|
return Math.min(area, 65535);
|
||||||
cells.g = grid.cells.i.length < 65535 ? Uint16Array.from(newCells.g) : Uint32Array.from(newCells.g); // reference to initial grid cell
|
}
|
||||||
cells.q = d3.quadtree(cells.p.map((p, d) => [p[0], p[1], d])); // points quadtree for fast search
|
|
||||||
cells.h = new Uint8Array(newCells.h); // heights
|
pack = calculateVoronoi(newCells.p, grid.boundary);
|
||||||
cells.area = new Uint16Array(cells.i.length); // cell area
|
pack.cells.p = newCells.p;
|
||||||
cells.i.forEach(i => (cells.area[i] = Math.abs(d3.polygonArea(getPackPolygon(i)))));
|
pack.cells.g = getTypedArray(grid.points.length).from(newCells.g);
|
||||||
|
pack.cells.q = d3.quadtree(newCells.p.map(([x, y], i) => [x, y, i]));
|
||||||
|
pack.cells.h = getTypedArray(100).from(newCells.h);
|
||||||
|
pack.cells.area = getTypedArray(65535).from(pack.cells.i).map(getCellArea);
|
||||||
|
|
||||||
TIME && console.timeEnd("reGraph");
|
TIME && console.timeEnd("reGraph");
|
||||||
}
|
}
|
||||||
|
|
@ -1946,7 +1909,11 @@ function showStatistics() {
|
||||||
|
|
||||||
const regenerateMap = debounce(async function (options) {
|
const regenerateMap = debounce(async function (options) {
|
||||||
WARN && console.warn("Generate new random map");
|
WARN && console.warn("Generate new random map");
|
||||||
showLoading();
|
|
||||||
|
const cellsDesired = +byId("pointsInput").dataset.cells;
|
||||||
|
const shouldShowLoading = cellsDesired > 10000;
|
||||||
|
|
||||||
|
shouldShowLoading && showLoading();
|
||||||
closeDialogs("#worldConfigurator, #options3d");
|
closeDialogs("#worldConfigurator, #options3d");
|
||||||
customization = 0;
|
customization = 0;
|
||||||
resetZoom(1000);
|
resetZoom(1000);
|
||||||
|
|
@ -1955,7 +1922,7 @@ const regenerateMap = debounce(async function (options) {
|
||||||
restoreLayers();
|
restoreLayers();
|
||||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||||
hideLoading();
|
shouldShowLoading && hideLoading();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// clear the map
|
// clear the map
|
||||||
|
|
|
||||||
|
|
@ -520,4 +520,9 @@ export function resolveVersionConflicts(version) {
|
||||||
if (!zone.dataset.type) zone.dataset.type = "Unknown";
|
if (!zone.dataset.type) zone.dataset.type = "Unknown";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version < 1.84) {
|
||||||
|
// v1.84.0 added grid.cellsDesired to stored data
|
||||||
|
if (!grid.cellsDesired) grid.cellsDesired = rn((graphWidth * graphHeight) / grid.spacing ** 2, -3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
const initialSeed = generateSeed();
|
const initialSeed = generateSeed();
|
||||||
|
let graph = getGraph(grid);
|
||||||
|
|
||||||
appendStyleSheet();
|
appendStyleSheet();
|
||||||
insertEditorHtml();
|
insertEditorHtml();
|
||||||
addListeners();
|
addListeners();
|
||||||
|
|
@ -8,6 +10,7 @@ export function open() {
|
||||||
|
|
||||||
const $templateInput = byId("templateInput");
|
const $templateInput = byId("templateInput");
|
||||||
setSelected($templateInput.value);
|
setSelected($templateInput.value);
|
||||||
|
graph = getGraph(graph);
|
||||||
|
|
||||||
$("#heightmapSelection").dialog({
|
$("#heightmapSelection").dialog({
|
||||||
title: "Select Heightmap",
|
title: "Select Heightmap",
|
||||||
|
|
@ -30,8 +33,7 @@ export function open() {
|
||||||
lock("template");
|
lock("template");
|
||||||
|
|
||||||
const seed = getSeed();
|
const seed = getSeed();
|
||||||
Math.random = aleaPRNG(seed);
|
regeneratePrompt({seed, graph});
|
||||||
regeneratePrompt({seed});
|
|
||||||
|
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
}
|
}
|
||||||
|
|
@ -182,13 +184,14 @@ function insertEditorHtml() {
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
byId("dialogs").insertAdjacentHTML("beforeend", heightmapSelectionHtml);
|
byId("dialogs").insertAdjacentHTML("beforeend", heightmapSelectionHtml);
|
||||||
|
|
||||||
const sections = document.getElementsByClassName("heightmap-selection_container");
|
const sections = document.getElementsByClassName("heightmap-selection_container");
|
||||||
|
|
||||||
sections[0].innerHTML = Object.keys(heightmapTemplates)
|
sections[0].innerHTML = Object.keys(heightmapTemplates)
|
||||||
.map(key => {
|
.map(key => {
|
||||||
const name = heightmapTemplates[key].name;
|
const name = heightmapTemplates[key].name;
|
||||||
Math.random = aleaPRNG(initialSeed);
|
Math.random = aleaPRNG(initialSeed);
|
||||||
const heights = generateHeightmap(key);
|
const heights = HeightmapGenerator.fromTemplate(graph, key);
|
||||||
const dataUrl = drawHeights(heights);
|
const dataUrl = drawHeights(heights);
|
||||||
|
|
||||||
return /* html */ `<article data-id="${key}" data-seed="${initialSeed}">
|
return /* html */ `<article data-id="${key}" data-seed="${initialSeed}">
|
||||||
|
|
@ -220,12 +223,8 @@ function addListeners() {
|
||||||
if (!article) return;
|
if (!article) return;
|
||||||
|
|
||||||
const id = article.dataset.id;
|
const id = article.dataset.id;
|
||||||
if (event.target.matches("span.icon-cw")) {
|
if (event.target.matches("span.icon-cw")) regeneratePreview(article, id);
|
||||||
const seed = generateSeed();
|
setSelected(id);
|
||||||
article.dataset.seed = seed;
|
|
||||||
Math.random = aleaPRNG(seed);
|
|
||||||
drawTemplatePreview(id);
|
|
||||||
} else setSelected(id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
byId("heightmapSelectionRenderOcean").on("change", redrawAll);
|
byId("heightmapSelectionRenderOcean").on("change", redrawAll);
|
||||||
|
|
@ -254,12 +253,18 @@ function getName(id) {
|
||||||
return isTemplate ? heightmapTemplates[id].name : precreatedHeightmaps[id].name;
|
return isTemplate ? heightmapTemplates[id].name : precreatedHeightmaps[id].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGraph(currentGraph) {
|
||||||
|
const newGraph = shouldRegenerateGrid(currentGraph) ? generateGrid() : deepCopy(currentGraph);
|
||||||
|
delete newGraph.cells.h;
|
||||||
|
return newGraph;
|
||||||
|
}
|
||||||
|
|
||||||
function drawHeights(heights) {
|
function drawHeights(heights) {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = grid.cellsX;
|
canvas.width = graph.cellsX;
|
||||||
canvas.height = grid.cellsY;
|
canvas.height = graph.cellsY;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const imageData = ctx.createImageData(grid.cellsX, grid.cellsY);
|
const imageData = ctx.createImageData(graph.cellsX, graph.cellsY);
|
||||||
|
|
||||||
const schemeId = byId("heightmapSelectionColorScheme").value;
|
const schemeId = byId("heightmapSelectionColorScheme").value;
|
||||||
const scheme = getColorScheme(schemeId);
|
const scheme = getColorScheme(schemeId);
|
||||||
|
|
@ -281,32 +286,30 @@ function drawHeights(heights) {
|
||||||
return canvas.toDataURL("image/png");
|
return canvas.toDataURL("image/png");
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateHeightmap(id) {
|
|
||||||
const heights = new Uint8Array(grid.points.length);
|
|
||||||
// use cells number of the current graph, no matter what UI input value is
|
|
||||||
const cellsDesired = rn((graphWidth * graphHeight) / grid.spacing ** 2, -3);
|
|
||||||
|
|
||||||
HeightmapGenerator.setHeights(heights, cellsDesired);
|
|
||||||
const newHeights = HeightmapGenerator.fromTemplate(id);
|
|
||||||
HeightmapGenerator.cleanup();
|
|
||||||
return newHeights;
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawTemplatePreview(id) {
|
function drawTemplatePreview(id) {
|
||||||
const heights = generateHeightmap(id);
|
const heights = HeightmapGenerator.fromTemplate(graph, id);
|
||||||
const dataUrl = drawHeights(heights);
|
const dataUrl = drawHeights(heights);
|
||||||
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
|
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
|
||||||
article.querySelector("img").src = dataUrl;
|
article.querySelector("img").src = dataUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function drawPrecreatedHeightmap(id) {
|
async function drawPrecreatedHeightmap(id) {
|
||||||
const heights = await HeightmapGenerator.fromPrecreated(id);
|
const heights = await HeightmapGenerator.fromPrecreated(graph, id);
|
||||||
const dataUrl = drawHeights(heights);
|
const dataUrl = drawHeights(heights);
|
||||||
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
|
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
|
||||||
article.querySelector("img").src = dataUrl;
|
article.querySelector("img").src = dataUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function regeneratePreview(article, id) {
|
||||||
|
graph = getGraph(graph);
|
||||||
|
const seed = generateSeed();
|
||||||
|
article.dataset.seed = seed;
|
||||||
|
Math.random = aleaPRNG(seed);
|
||||||
|
drawTemplatePreview(id);
|
||||||
|
}
|
||||||
|
|
||||||
function redrawAll() {
|
function redrawAll() {
|
||||||
|
graph = getGraph(graph);
|
||||||
const articles = byId("heightmapSelection").querySelectorAll(`article`);
|
const articles = byId("heightmapSelection").querySelectorAll(`article`);
|
||||||
for (const article of articles) {
|
for (const article of articles) {
|
||||||
const {id, seed} = article.dataset;
|
const {id, seed} = article.dataset;
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,48 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
window.HeightmapGenerator = (function () {
|
window.HeightmapGenerator = (function () {
|
||||||
|
let grid = null;
|
||||||
let heights = null;
|
let heights = null;
|
||||||
let blobPower;
|
let blobPower;
|
||||||
let linePower;
|
let linePower;
|
||||||
|
|
||||||
const setHeights = (savedHeights, cellsNumber) => {
|
const setGraph = graph => {
|
||||||
heights = savedHeights;
|
const {cellsDesired, cells, points} = graph;
|
||||||
blobPower = getBlobPower(cellsNumber);
|
heights = cells.h || createTypedArray({maxValue: 100, length: points.length});
|
||||||
linePower = getLinePower(cellsNumber);
|
blobPower = getBlobPower(cellsDesired);
|
||||||
|
linePower = getLinePower(cellsDesired);
|
||||||
|
grid = graph;
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetHeights = () => {
|
|
||||||
heights = new Uint8Array(grid.points.length);
|
|
||||||
const cellsNumber = +byId("pointsInput").dataset.cells;
|
|
||||||
blobPower = getBlobPower(cellsNumber);
|
|
||||||
linePower = getLinePower(cellsNumber);
|
|
||||||
};
|
|
||||||
const getHeights = () => heights;
|
const getHeights = () => heights;
|
||||||
|
|
||||||
const cleanup = () => (heights = null);
|
const clearData = () => {
|
||||||
|
heights = null;
|
||||||
|
grid = null;
|
||||||
|
};
|
||||||
|
|
||||||
const fromTemplate = template => {
|
const fromTemplate = (graph, id) => {
|
||||||
const templateString = heightmapTemplates[template]?.template || "";
|
const templateString = heightmapTemplates[id]?.template || "";
|
||||||
const steps = templateString.split("\n");
|
const steps = templateString.split("\n");
|
||||||
|
|
||||||
if (!steps.length) throw new Error(`Heightmap template: no steps. Template: ${template}. Steps: ${steps}`);
|
if (!steps.length) throw new Error(`Heightmap template: no steps. Template: ${id}. Steps: ${steps}`);
|
||||||
|
setGraph(graph);
|
||||||
|
|
||||||
for (const step of steps) {
|
for (const step of steps) {
|
||||||
const elements = step.trim().split(" ");
|
const elements = step.trim().split(" ");
|
||||||
if (elements.length < 2) throw new Error(`Heightmap template: steps < 2. Template: ${template}. Step: ${elements}`);
|
if (elements.length < 2) throw new Error(`Heightmap template: steps < 2. Template: ${id}. Step: ${elements}`);
|
||||||
addStep(...elements);
|
addStep(...elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
return heights;
|
return heights;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromPrecreated = id => {
|
const fromPrecreated = (graph, id) => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// create canvas where 1px corresponts to a cell
|
// create canvas where 1px corresponts to a cell
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const {cellsX, cellsY} = grid;
|
const {cellsX, cellsY} = graph;
|
||||||
canvas.width = cellsX;
|
canvas.width = cellsX;
|
||||||
canvas.height = cellsY;
|
canvas.height = cellsY;
|
||||||
|
|
||||||
|
|
@ -51,7 +52,8 @@ window.HeightmapGenerator = (function () {
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
ctx.drawImage(img, 0, 0, cellsX, cellsY);
|
ctx.drawImage(img, 0, 0, cellsX, cellsY);
|
||||||
const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
|
const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
|
||||||
const heights = getHeightsFromImageData(imageData.data);
|
setGraph(graph);
|
||||||
|
getHeightsFromImageData(imageData.data);
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
img.remove();
|
img.remove();
|
||||||
resolve(heights);
|
resolve(heights);
|
||||||
|
|
@ -59,18 +61,17 @@ window.HeightmapGenerator = (function () {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const generate = async function () {
|
const generate = async function (graph) {
|
||||||
Math.random = aleaPRNG(seed);
|
|
||||||
|
|
||||||
TIME && console.time("defineHeightmap");
|
TIME && console.time("defineHeightmap");
|
||||||
const id = byId("templateInput").value;
|
const id = byId("templateInput").value;
|
||||||
resetHeights();
|
|
||||||
|
|
||||||
|
Math.random = aleaPRNG(seed);
|
||||||
const isTemplate = id in heightmapTemplates;
|
const isTemplate = id in heightmapTemplates;
|
||||||
grid.cells.h = isTemplate ? fromTemplate(id) : await fromPrecreated(id);
|
const heights = isTemplate ? fromTemplate(graph, id) : await fromPrecreated(graph, id);
|
||||||
|
|
||||||
cleanup();
|
|
||||||
TIME && console.timeEnd("defineHeightmap");
|
TIME && console.timeEnd("defineHeightmap");
|
||||||
|
|
||||||
|
clearData();
|
||||||
|
return heights;
|
||||||
};
|
};
|
||||||
|
|
||||||
function addStep(tool, a2, a3, a4, a5) {
|
function addStep(tool, a2, a3, a4, a5) {
|
||||||
|
|
@ -141,7 +142,7 @@ window.HeightmapGenerator = (function () {
|
||||||
do {
|
do {
|
||||||
const x = getPointInRange(rangeX, graphWidth);
|
const x = getPointInRange(rangeX, graphWidth);
|
||||||
const y = getPointInRange(rangeY, graphHeight);
|
const y = getPointInRange(rangeY, graphHeight);
|
||||||
start = findGridCell(x, y);
|
start = findGridCell(x, y, grid);
|
||||||
limit++;
|
limit++;
|
||||||
} while (heights[start] + h > 90 && limit < 50);
|
} while (heights[start] + h > 90 && limit < 50);
|
||||||
|
|
||||||
|
|
@ -177,7 +178,7 @@ window.HeightmapGenerator = (function () {
|
||||||
do {
|
do {
|
||||||
const x = getPointInRange(rangeX, graphWidth);
|
const x = getPointInRange(rangeX, graphWidth);
|
||||||
const y = getPointInRange(rangeY, graphHeight);
|
const y = getPointInRange(rangeY, graphHeight);
|
||||||
start = findGridCell(x, y);
|
start = findGridCell(x, y, grid);
|
||||||
limit++;
|
limit++;
|
||||||
} while (heights[start] < 20 && limit < 50);
|
} while (heights[start] < 20 && limit < 50);
|
||||||
|
|
||||||
|
|
@ -223,7 +224,9 @@ window.HeightmapGenerator = (function () {
|
||||||
limit++;
|
limit++;
|
||||||
} while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50);
|
} while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50);
|
||||||
|
|
||||||
let range = getRange(findGridCell(startX, startY), findGridCell(endX, endY));
|
const startCell = findGridCell(startX, startY, grid);
|
||||||
|
const endCell = findGridCell(endX, endY, grid);
|
||||||
|
let range = getRange(startCell, endCell);
|
||||||
|
|
||||||
// get main ridge
|
// get main ridge
|
||||||
function getRange(cur, end) {
|
function getRange(cur, end) {
|
||||||
|
|
@ -305,7 +308,7 @@ window.HeightmapGenerator = (function () {
|
||||||
do {
|
do {
|
||||||
startX = getPointInRange(rangeX, graphWidth);
|
startX = getPointInRange(rangeX, graphWidth);
|
||||||
startY = getPointInRange(rangeY, graphHeight);
|
startY = getPointInRange(rangeY, graphHeight);
|
||||||
start = findGridCell(startX, startY);
|
start = findGridCell(startX, startY, grid);
|
||||||
limit++;
|
limit++;
|
||||||
} while (heights[start] < 20 && limit < 50);
|
} while (heights[start] < 20 && limit < 50);
|
||||||
|
|
||||||
|
|
@ -317,7 +320,7 @@ window.HeightmapGenerator = (function () {
|
||||||
limit++;
|
limit++;
|
||||||
} while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50);
|
} while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50);
|
||||||
|
|
||||||
let range = getRange(start, findGridCell(endX, endY));
|
let range = getRange(start, findGridCell(endX, endY, grid));
|
||||||
|
|
||||||
// get main ridge
|
// get main ridge
|
||||||
function getRange(cur, end) {
|
function getRange(cur, end) {
|
||||||
|
|
@ -388,8 +391,8 @@ window.HeightmapGenerator = (function () {
|
||||||
const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5;
|
const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5;
|
||||||
const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
|
const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
|
||||||
|
|
||||||
const start = findGridCell(startX, startY);
|
const start = findGridCell(startX, startY, grid);
|
||||||
const end = findGridCell(endX, endY);
|
const end = findGridCell(endX, endY, grid);
|
||||||
let range = getRange(start, end);
|
let range = getRange(start, end);
|
||||||
const query = [];
|
const query = [];
|
||||||
|
|
||||||
|
|
@ -502,20 +505,16 @@ window.HeightmapGenerator = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHeightsFromImageData(imageData) {
|
function getHeightsFromImageData(imageData) {
|
||||||
const heights = new Uint8Array(grid.points.length);
|
|
||||||
for (let i = 0; i < heights.length; i++) {
|
for (let i = 0; i < heights.length; i++) {
|
||||||
const lightness = imageData[i * 4] / 255;
|
const lightness = imageData[i * 4] / 255;
|
||||||
const powered = lightness < 0.2 ? lightness : 0.2 + (lightness - 0.2) ** 0.8;
|
const powered = lightness < 0.2 ? lightness : 0.2 + (lightness - 0.2) ** 0.8;
|
||||||
heights[i] = minmax(Math.floor(powered * 100), 0, 100);
|
heights[i] = minmax(Math.floor(powered * 100), 0, 100);
|
||||||
}
|
}
|
||||||
return heights;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setHeights,
|
setGraph,
|
||||||
resetHeights,
|
|
||||||
getHeights,
|
getHeights,
|
||||||
cleanup,
|
|
||||||
generate,
|
generate,
|
||||||
fromTemplate,
|
fromTemplate,
|
||||||
fromPrecreated,
|
fromPrecreated,
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,11 @@ async function parseLoadedData(data) {
|
||||||
|
|
||||||
void (function parseGridData() {
|
void (function parseGridData() {
|
||||||
grid = JSON.parse(data[6]);
|
grid = JSON.parse(data[6]);
|
||||||
calculateVoronoi(grid, grid.points);
|
|
||||||
|
const {cells, vertices} = calculateVoronoi(grid.points, grid.boundary);
|
||||||
|
grid.cells = cells;
|
||||||
|
grid.vertices = vertices;
|
||||||
|
|
||||||
grid.cells.h = Uint8Array.from(data[7].split(","));
|
grid.cells.h = Uint8Array.from(data[7].split(","));
|
||||||
grid.cells.prec = Uint8Array.from(data[8].split(","));
|
grid.cells.prec = Uint8Array.from(data[8].split(","));
|
||||||
grid.cells.f = Uint16Array.from(data[9].split(","));
|
grid.cells.f = Uint16Array.from(data[9].split(","));
|
||||||
|
|
@ -333,7 +337,6 @@ async function parseLoadedData(data) {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
void (function parsePackData() {
|
void (function parsePackData() {
|
||||||
pack = {};
|
|
||||||
reGraph();
|
reGraph();
|
||||||
reMarkFeatures();
|
reMarkFeatures();
|
||||||
pack.features = JSON.parse(data[12]);
|
pack.features = JSON.parse(data[12]);
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ function getMapData() {
|
||||||
|
|
||||||
const serializedSVG = new XMLSerializer().serializeToString(cloneEl);
|
const serializedSVG = new XMLSerializer().serializeToString(cloneEl);
|
||||||
|
|
||||||
const {spacing, cellsX, cellsY, boundary, points, features} = grid;
|
const {spacing, cellsX, cellsY, boundary, points, features, cellsDesired} = grid;
|
||||||
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features});
|
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features, cellsDesired});
|
||||||
const packFeatures = JSON.stringify(pack.features);
|
const packFeatures = JSON.stringify(pack.features);
|
||||||
const cultures = JSON.stringify(pack.cultures);
|
const cultures = JSON.stringify(pack.cultures);
|
||||||
const states = JSON.stringify(pack.states);
|
const states = JSON.stringify(pack.states);
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,7 @@ window.Submap = (function () {
|
||||||
|
|
||||||
// create new grid
|
// create new grid
|
||||||
applyMapSize();
|
applyMapSize();
|
||||||
placePoints();
|
grid = generateGrid();
|
||||||
calculateVoronoi(grid, grid.points);
|
|
||||||
drawScaleBar(scale);
|
drawScaleBar(scale);
|
||||||
|
|
||||||
const resampler = (points, qtree, f) => {
|
const resampler = (points, qtree, f) => {
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,10 @@ function handleMouseMove() {
|
||||||
if (i === undefined) return;
|
if (i === undefined) return;
|
||||||
|
|
||||||
showNotes(d3.event);
|
showNotes(d3.event);
|
||||||
const g = findGridCell(point[0], point[1]); // grid cell id
|
const gridCell = findGridCell(point[0], point[1], grid);
|
||||||
if (tooltip.dataset.main) showMainTip();
|
if (tooltip.dataset.main) showMainTip();
|
||||||
else showMapTooltip(point, d3.event, i, g);
|
else showMapTooltip(point, d3.event, i, gridCell);
|
||||||
if (cellInfo?.offsetParent) updateCellInfo(point, i, g);
|
if (cellInfo?.offsetParent) updateCellInfo(point, i, gridCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
// show note box on hover (if any)
|
// show note box on hover (if any)
|
||||||
|
|
@ -244,7 +244,7 @@ function updateCellInfo(point, i, g) {
|
||||||
infoCell.innerHTML = i;
|
infoCell.innerHTML = i;
|
||||||
infoArea.innerHTML = cells.area[i] ? si(getArea(cells.area[i])) + " " + getAreaUnit() : "n/a";
|
infoArea.innerHTML = cells.area[i] ? si(getArea(cells.area[i])) + " " + getAreaUnit() : "n/a";
|
||||||
infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]);
|
infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]);
|
||||||
infoDepth.innerHTML = getDepth(pack.features[f], pack.cells.h[i], point);
|
infoDepth.innerHTML = getDepth(pack.features[f], point);
|
||||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
||||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
||||||
|
|
@ -276,11 +276,11 @@ function getElevation(f, h) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get water depth
|
// get water depth
|
||||||
function getDepth(f, h, p) {
|
function getDepth(f, p) {
|
||||||
if (f.land) return "0 " + heightUnit.value; // land: 0
|
if (f.land) return "0 " + heightUnit.value; // land: 0
|
||||||
|
|
||||||
// lake: difference between surface and bottom
|
// lake: difference between surface and bottom
|
||||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
const gridH = grid.cells.h[findGridCell(p[0], p[1], grid)];
|
||||||
if (f.type === "lake") {
|
if (f.type === "lake") {
|
||||||
const depth = gridH === 19 ? f.height / 2 : gridH;
|
const depth = gridH === 19 ? f.height / 2 : gridH;
|
||||||
return getHeight(depth, "abs");
|
return getHeight(depth, "abs");
|
||||||
|
|
@ -290,9 +290,9 @@ function getDepth(f, h, p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get user-friendly (real-world) height value from map data
|
// get user-friendly (real-world) height value from map data
|
||||||
function getFriendlyHeight(p) {
|
function getFriendlyHeight([x, y]) {
|
||||||
const packH = pack.cells.h[findCell(p[0], p[1])];
|
const packH = pack.cells.h[findCell(x, y, grid)];
|
||||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
const gridH = grid.cells.h[findGridCell(x, y, grid)];
|
||||||
const h = packH < 20 ? gridH : packH;
|
const h = packH < 20 ? gridH : packH;
|
||||||
return getHeight(h);
|
return getHeight(h);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ function editHeightmap(options) {
|
||||||
|
|
||||||
function moveCursor() {
|
function moveCursor() {
|
||||||
const [x, y] = d3.mouse(this);
|
const [x, y] = d3.mouse(this);
|
||||||
const cell = findGridCell(x, y);
|
const cell = findGridCell(x, y, grid);
|
||||||
heightmapInfoX.innerHTML = rn(x);
|
heightmapInfoX.innerHTML = rn(x);
|
||||||
heightmapInfoY.innerHTML = rn(y);
|
heightmapInfoY.innerHTML = rn(y);
|
||||||
heightmapInfoCell.innerHTML = cell;
|
heightmapInfoCell.innerHTML = cell;
|
||||||
|
|
@ -605,8 +605,8 @@ function editHeightmap(options) {
|
||||||
|
|
||||||
function dragBrush() {
|
function dragBrush() {
|
||||||
const r = brushRadius.valueAsNumber;
|
const r = brushRadius.valueAsNumber;
|
||||||
const point = d3.mouse(this);
|
const [x, y] = d3.mouse(this);
|
||||||
const start = findGridCell(point[0], point[1]);
|
const start = findGridCell(x, y, grid);
|
||||||
|
|
||||||
d3.event.on("drag", () => {
|
d3.event.on("drag", () => {
|
||||||
const p = d3.mouse(this);
|
const p = d3.mouse(this);
|
||||||
|
|
@ -664,7 +664,7 @@ function editHeightmap(options) {
|
||||||
if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error");
|
if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error");
|
||||||
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error");
|
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error");
|
||||||
|
|
||||||
HeightmapGenerator.setHeights(grid.cells.h);
|
HeightmapGenerator.setGraph(grid);
|
||||||
|
|
||||||
if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0);
|
if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0);
|
||||||
else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0);
|
else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0);
|
||||||
|
|
@ -673,15 +673,13 @@ function editHeightmap(options) {
|
||||||
else if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand);
|
else if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand);
|
||||||
|
|
||||||
grid.cells.h = HeightmapGenerator.getHeights();
|
grid.cells.h = HeightmapGenerator.getHeights();
|
||||||
HeightmapGenerator.cleanup();
|
|
||||||
updateHeightmap();
|
updateHeightmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
function smoothAllHeights() {
|
function smoothAllHeights() {
|
||||||
HeightmapGenerator.setHeights(grid.cells.h);
|
HeightmapGenerator.setGraph(grid);
|
||||||
HeightmapGenerator.smooth(4, 1.5);
|
HeightmapGenerator.smooth(4, 1.5);
|
||||||
grid.cells.h = HeightmapGenerator.getHeights();
|
grid.cells.h = HeightmapGenerator.getHeights();
|
||||||
HeightmapGenerator.cleanup();
|
|
||||||
updateHeightmap();
|
updateHeightmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -940,11 +938,8 @@ function editHeightmap(options) {
|
||||||
const seed = byId("templateSeed").value;
|
const seed = byId("templateSeed").value;
|
||||||
if (seed) Math.random = aleaPRNG(seed);
|
if (seed) Math.random = aleaPRNG(seed);
|
||||||
|
|
||||||
const heights = new Uint8Array(grid.points.length);
|
grid.cells.h = createTypedArray({maxValue: 100, length: grid.points.length});
|
||||||
// use cells number of the current graph, no matter what UI input value is
|
HeightmapGenerator.setGraph(grid);
|
||||||
const cellsDesired = rn((graphWidth * graphHeight) / grid.spacing ** 2, -3);
|
|
||||||
HeightmapGenerator.setHeights(heights, cellsDesired);
|
|
||||||
|
|
||||||
restartHistory();
|
restartHistory();
|
||||||
|
|
||||||
for (const step of steps) {
|
for (const step of steps) {
|
||||||
|
|
@ -973,7 +968,6 @@ function editHeightmap(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.cells.h = HeightmapGenerator.getHeights();
|
grid.cells.h = HeightmapGenerator.getHeights();
|
||||||
HeightmapGenerator.cleanup();
|
|
||||||
updateStatistics();
|
updateStatistics();
|
||||||
mockHeightmap();
|
mockHeightmap();
|
||||||
if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened
|
if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@ function editIce() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addIcebergOnClick() {
|
function addIcebergOnClick() {
|
||||||
const point = d3.mouse(this);
|
const [x, y] = d3.mouse(this);
|
||||||
const i = findGridCell(point[0], point[1]);
|
const i = findGridCell(x, y, grid);
|
||||||
const c = grid.points[i];
|
const c = grid.points[i];
|
||||||
const s = +document.getElementById("iceSize").value;
|
const s = +document.getElementById("iceSize").value;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
// FMG utils related to arrays
|
|
||||||
|
|
||||||
// return the last element of array
|
|
||||||
function last(array) {
|
function last(array) {
|
||||||
return array[array.length - 1];
|
return array[array.length - 1];
|
||||||
}
|
}
|
||||||
|
|
@ -37,9 +35,24 @@ function deepCopy(obj) {
|
||||||
[Set, s => [...s.values()].map(dcAny)],
|
[Set, s => [...s.values()].map(dcAny)],
|
||||||
[Date, d => new Date(d.getTime())],
|
[Date, d => new Date(d.getTime())],
|
||||||
[Object, dcObject]
|
[Object, dcObject]
|
||||||
// other types will be referenced
|
|
||||||
// ... extend here to implement their custom deep copy
|
// ... extend here to implement their custom deep copy
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return dcAny(obj);
|
return dcAny(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTypedArray(maxValue) {
|
||||||
|
console.assert(
|
||||||
|
Number.isInteger(maxValue) && maxValue >= 0 && maxValue <= 4294967295,
|
||||||
|
`Array maxValue must be an integer between 0 and 4294967295, got ${maxValue}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (maxValue <= 255) return Uint8Array;
|
||||||
|
if (maxValue <= 65535) return Uint16Array;
|
||||||
|
if (maxValue <= 4294967295) return Uint32Array;
|
||||||
|
return Uint32Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTypedArray({maxValue, length}) {
|
||||||
|
return new (getTypedArray(maxValue))(length);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,60 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
// FMG utils related to graph
|
// FMG utils related to graph
|
||||||
|
|
||||||
// add boundary points to pseudo-clip voronoi cells
|
// check if new grid graph should be generated or we can use the existing one
|
||||||
|
function shouldRegenerateGrid(grid) {
|
||||||
|
const cellsDesired = +byId("pointsInput").dataset.cells;
|
||||||
|
if (cellsDesired !== grid.cellsDesired) return true;
|
||||||
|
|
||||||
|
const newSpacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2);
|
||||||
|
const newCellsX = Math.floor((graphWidth + 0.5 * newSpacing - 1e-10) / newSpacing);
|
||||||
|
const newCellsY = Math.floor((graphHeight + 0.5 * newSpacing - 1e-10) / newSpacing);
|
||||||
|
|
||||||
|
return grid.spacing !== newSpacing || grid.cellsX !== newCellsX || grid.cellsY !== newCellsY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateGrid() {
|
||||||
|
Math.random = aleaPRNG(seed); // reset PRNG
|
||||||
|
const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
|
||||||
|
const {cells, vertices} = calculateVoronoi(points, boundary);
|
||||||
|
return {spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices};
|
||||||
|
}
|
||||||
|
|
||||||
|
// place random points to calculate Voronoi diagram
|
||||||
|
function placePoints() {
|
||||||
|
TIME && console.time("placePoints");
|
||||||
|
const cellsDesired = +byId("pointsInput").dataset.cells;
|
||||||
|
const spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2); // spacing between points before jirrering
|
||||||
|
|
||||||
|
const boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
|
||||||
|
const points = getJitteredGrid(graphWidth, graphHeight, spacing); // points of jittered square grid
|
||||||
|
const cellsX = Math.floor((graphWidth + 0.5 * spacing - 1e-10) / spacing);
|
||||||
|
const cellsY = Math.floor((graphHeight + 0.5 * spacing - 1e-10) / spacing);
|
||||||
|
TIME && console.timeEnd("placePoints");
|
||||||
|
|
||||||
|
return {spacing, cellsDesired, boundary, points, cellsX, cellsY};
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate Delaunay and then Voronoi diagram
|
||||||
|
function calculateVoronoi(points, boundary) {
|
||||||
|
TIME && console.time("calculateDelaunay");
|
||||||
|
const allPoints = points.concat(boundary);
|
||||||
|
const delaunay = Delaunator.from(allPoints);
|
||||||
|
TIME && console.timeEnd("calculateDelaunay");
|
||||||
|
|
||||||
|
TIME && console.time("calculateVoronoi");
|
||||||
|
const n = points.length;
|
||||||
|
const voronoi = new Voronoi(delaunay, allPoints, n);
|
||||||
|
|
||||||
|
const cells = voronoi.cells;
|
||||||
|
cells.i = getTypedArray(n).from(d3.range(n)); // array of indexes
|
||||||
|
const vertices = voronoi.vertices;
|
||||||
|
TIME && console.timeEnd("calculateVoronoi");
|
||||||
|
|
||||||
|
return {cells, vertices};
|
||||||
|
}
|
||||||
|
|
||||||
|
// add points along map edge to pseudo-clip voronoi cells
|
||||||
function getBoundaryPoints(width, height, spacing) {
|
function getBoundaryPoints(width, height, spacing) {
|
||||||
const offset = rn(-1 * spacing);
|
const offset = rn(-1 * spacing);
|
||||||
const bSpacing = spacing * 2;
|
const bSpacing = spacing * 2;
|
||||||
|
|
@ -9,15 +62,18 @@ function getBoundaryPoints(width, height, spacing) {
|
||||||
const h = height - offset * 2;
|
const h = height - offset * 2;
|
||||||
const numberX = Math.ceil(w / bSpacing) - 1;
|
const numberX = Math.ceil(w / bSpacing) - 1;
|
||||||
const numberY = Math.ceil(h / bSpacing) - 1;
|
const numberY = Math.ceil(h / bSpacing) - 1;
|
||||||
let points = [];
|
const points = [];
|
||||||
|
|
||||||
for (let i = 0.5; i < numberX; i++) {
|
for (let i = 0.5; i < numberX; i++) {
|
||||||
let x = Math.ceil((w * i) / numberX + offset);
|
let x = Math.ceil((w * i) / numberX + offset);
|
||||||
points.push([x, offset], [x, h + offset]);
|
points.push([x, offset], [x, h + offset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0.5; i < numberY; i++) {
|
for (let i = 0.5; i < numberY; i++) {
|
||||||
let y = Math.ceil((h * i) / numberY + offset);
|
let y = Math.ceil((h * i) / numberY + offset);
|
||||||
points.push([offset, y], [w + offset, y]);
|
points.push([offset, y], [w + offset, y]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +96,7 @@ function getJitteredGrid(width, height, spacing) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// return cell index on a regular square grid
|
// return cell index on a regular square grid
|
||||||
function findGridCell(x, y) {
|
function findGridCell(x, y, grid) {
|
||||||
return Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1));
|
return Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +104,7 @@ function findGridCell(x, y) {
|
||||||
function findGridAll(x, y, radius) {
|
function findGridAll(x, y, radius) {
|
||||||
const c = grid.cells.c;
|
const c = grid.cells.c;
|
||||||
let r = Math.floor(radius / grid.spacing);
|
let r = Math.floor(radius / grid.spacing);
|
||||||
let found = [findGridCell(x, y)];
|
let found = [findGridCell(x, y, grid)];
|
||||||
if (!r || radius === 1) return found;
|
if (!r || radius === 1) return found;
|
||||||
if (r > 0) found = found.concat(c[found[0]]);
|
if (r > 0) found = found.concat(c[found[0]]);
|
||||||
if (r > 1) {
|
if (r > 1) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue