refactor: start files migration nightmare

This commit is contained in:
Azgaar 2022-06-25 00:47:48 +03:00
parent c4736cc640
commit bc65e0e207
64 changed files with 1990 additions and 816 deletions

View file

@ -7,54 +7,3 @@ function last(array) {
function unique(array) {
return [...new Set(array)];
}
// deep copy for Arrays (and other objects)
function deepCopy(obj) {
const id = x => x;
const dcTArray = a => a.map(id);
const dcObject = x => Object.fromEntries(Object.entries(x).map(([k, d]) => [k, dcAny(d)]));
const dcAny = x => (x instanceof Object ? (cf.get(x.constructor) || id)(x) : x);
// don't map keys, probably this is what we would expect
const dcMapCore = m => [...m.entries()].map(([k, v]) => [k, dcAny(v)]);
const cf = new Map([
[Int8Array, dcTArray],
[Uint8Array, dcTArray],
[Uint8ClampedArray, dcTArray],
[Int16Array, dcTArray],
[Uint16Array, dcTArray],
[Int32Array, dcTArray],
[Uint32Array, dcTArray],
[Float32Array, dcTArray],
[Float64Array, dcTArray],
[BigInt64Array, dcTArray],
[BigUint64Array, dcTArray],
[Map, m => new Map(dcMapCore(m))],
[WeakMap, m => new WeakMap(dcMapCore(m))],
[Array, a => a.map(dcAny)],
[Set, s => [...s.values()].map(dcAny)],
[Date, d => new Date(d.getTime())],
[Object, dcObject]
// ... extend here to implement their custom deep copy
]);
return dcAny(obj);
}
function getTypedArray(maxValue) {
console.assert(
Number.isInteger(maxValue) && maxValue >= 0 && maxValue <= UINT32_MAX,
`Array maxValue must be an integer between 0 and ${UINT32_MAX}, got ${maxValue}`
);
if (maxValue <= UINT8_MAX) return Uint8Array;
if (maxValue <= UINT16_MAX) return Uint16Array;
if (maxValue <= UINT32_MAX) return Uint32Array;
return Uint32Array;
}
function createTypedArray({maxValue, length, from}) {
const typedArray = getTypedArray(maxValue);
if (!from) return new typedArray(length);
return typedArray.from(from);
}

View file

@ -1,331 +0,0 @@
"use strict";
// FMG utils related to graph
// 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 voronoi = new Voronoi(delaunay, allPoints, points.length);
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");
return {cells, vertices};
}
// add points along map edge to pseudo-clip voronoi cells
function getBoundaryPoints(width, height, spacing) {
const offset = rn(-1 * spacing);
const bSpacing = spacing * 2;
const w = width - offset * 2;
const h = height - offset * 2;
const numberX = Math.ceil(w / bSpacing) - 1;
const numberY = Math.ceil(h / bSpacing) - 1;
const points = [];
for (let i = 0.5; i < numberX; i++) {
let x = Math.ceil((w * i) / numberX + offset);
points.push([x, offset], [x, h + offset]);
}
for (let i = 0.5; i < numberY; i++) {
let y = Math.ceil((h * i) / numberY + offset);
points.push([offset, y], [w + offset, y]);
}
return points;
}
// get points on a regular square grid and jitter them a bit
function getJitteredGrid(width, height, spacing) {
const radius = spacing / 2; // square radius
const jittering = radius * 0.9; // max deviation
const doubleJittering = jittering * 2;
const jitter = () => Math.random() * doubleJittering - jittering;
let points = [];
for (let y = radius; y < height; y += spacing) {
for (let x = radius; x < width; x += spacing) {
const xj = Math.min(rn(x + jitter(), 2), width);
const yj = Math.min(rn(y + jitter(), 2), height);
points.push([xj, yj]);
}
}
return points;
}
// return cell index on a regular square grid
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 array of cell indexes in radius on a regular square grid
function findGridAll(x, y, radius) {
const c = grid.cells.c;
let r = Math.floor(radius / grid.spacing);
let found = [findGridCell(x, y, grid)];
if (!r || radius === 1) return found;
if (r > 0) found = found.concat(c[found[0]]);
if (r > 1) {
let frontier = c[found[0]];
while (r > 1) {
let cycle = frontier.slice();
frontier = [];
cycle.forEach(function (s) {
c[s].forEach(function (e) {
if (found.indexOf(e) !== -1) return;
found.push(e);
frontier.push(e);
});
});
r--;
}
}
return found;
}
// return closest pack points quadtree datum
function find(x, y, radius = Infinity) {
return pack.cells.q.find(x, y, radius);
}
// return closest cell index
function findCell(x, y, radius = Infinity) {
const found = pack.cells.q.find(x, y, radius);
return found ? found[2] : undefined;
}
// return array of cell indexes in radius
function findAll(x, y, radius) {
const found = pack.cells.q.findAll(x, y, radius);
return found.map(r => r[2]);
}
// get polygon points for packed cells knowing cell id
function getPackPolygon(i) {
return pack.cells.v[i].map(v => pack.vertices.p[v]);
}
// get polygon points for initial cells knowing cell id
function getGridPolygon(i) {
return grid.cells.v[i].map(v => grid.vertices.p[v]);
}
// mbostock's poissonDiscSampler
function* poissonDiscSampler(x0, y0, x1, y1, r, k = 3) {
if (!(x1 >= x0) || !(y1 >= y0) || !(r > 0)) throw new Error();
const width = x1 - x0;
const height = y1 - y0;
const r2 = r * r;
const r2_3 = 3 * r2;
const cellSize = r * Math.SQRT1_2;
const gridWidth = Math.ceil(width / cellSize);
const gridHeight = Math.ceil(height / cellSize);
const grid = new Array(gridWidth * gridHeight);
const queue = [];
function far(x, y) {
const i = (x / cellSize) | 0;
const j = (y / cellSize) | 0;
const i0 = Math.max(i - 2, 0);
const j0 = Math.max(j - 2, 0);
const i1 = Math.min(i + 3, gridWidth);
const j1 = Math.min(j + 3, gridHeight);
for (let j = j0; j < j1; ++j) {
const o = j * gridWidth;
for (let i = i0; i < i1; ++i) {
const s = grid[o + i];
if (s) {
const dx = s[0] - x;
const dy = s[1] - y;
if (dx * dx + dy * dy < r2) return false;
}
}
}
return true;
}
function sample(x, y) {
queue.push((grid[gridWidth * ((y / cellSize) | 0) + ((x / cellSize) | 0)] = [x, y]));
return [x + x0, y + y0];
}
yield sample(width / 2, height / 2);
pick: while (queue.length) {
const i = (Math.random() * queue.length) | 0;
const parent = queue[i];
for (let j = 0; j < k; ++j) {
const a = 2 * Math.PI * Math.random();
const r = Math.sqrt(Math.random() * r2_3 + r2);
const x = parent[0] + r * Math.cos(a);
const y = parent[1] + r * Math.sin(a);
if (0 <= x && x < width && 0 <= y && y < height && far(x, y)) {
yield sample(x, y);
continue pick;
}
}
const r = queue.pop();
if (i < queue.length) queue[i] = r;
}
}
// filter land cells
function isLand(i) {
return pack.cells.h[i] >= 20;
}
// filter water cells
function isWater(i) {
return pack.cells.h[i] < 20;
}
// findAll d3.quandtree search from https://bl.ocks.org/lwthatcher/b41479725e0ff2277c7ac90df2de2b5e
void (function addFindAll() {
const Quad = function (node, x0, y0, x1, y1) {
this.node = node;
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
};
const tree_filter = function (x, y, radius) {
var t = {x, y, x0: this._x0, y0: this._y0, x3: this._x1, y3: this._y1, quads: [], node: this._root};
if (t.node) {
t.quads.push(new Quad(t.node, t.x0, t.y0, t.x3, t.y3));
}
radiusSearchInit(t, radius);
var i = 0;
while ((t.q = t.quads.pop())) {
i++;
// Stop searching if this quadrant cant contain a closer node.
if (!(t.node = t.q.node) || (t.x1 = t.q.x0) > t.x3 || (t.y1 = t.q.y0) > t.y3 || (t.x2 = t.q.x1) < t.x0 || (t.y2 = t.q.y1) < t.y0) continue;
// Bisect the current quadrant.
if (t.node.length) {
t.node.explored = true;
var xm = (t.x1 + t.x2) / 2,
ym = (t.y1 + t.y2) / 2;
t.quads.push(
new Quad(t.node[3], xm, ym, t.x2, t.y2),
new Quad(t.node[2], t.x1, ym, xm, t.y2),
new Quad(t.node[1], xm, t.y1, t.x2, ym),
new Quad(t.node[0], t.x1, t.y1, xm, ym)
);
// Visit the closest quadrant first.
if ((t.i = ((y >= ym) << 1) | (x >= xm))) {
t.q = t.quads[t.quads.length - 1];
t.quads[t.quads.length - 1] = t.quads[t.quads.length - 1 - t.i];
t.quads[t.quads.length - 1 - t.i] = t.q;
}
}
// Visit this point. (Visiting coincident points isnt necessary!)
else {
var dx = x - +this._x.call(null, t.node.data),
dy = y - +this._y.call(null, t.node.data),
d2 = dx * dx + dy * dy;
radiusSearchVisit(t, d2);
}
}
return t.result;
};
d3.quadtree.prototype.findAll = tree_filter;
var radiusSearchInit = function (t, radius) {
t.result = [];
(t.x0 = t.x - radius), (t.y0 = t.y - radius);
(t.x3 = t.x + radius), (t.y3 = t.y + radius);
t.radius = radius * radius;
};
var radiusSearchVisit = function (t, d2) {
t.node.data.scanned = true;
if (d2 < t.radius) {
do {
t.result.push(t.node.data);
t.node.data.selected = true;
} while ((t.node = t.node.next));
}
};
})();
// helper function non-used for the generation
function drawCellsValue(data) {
debug.selectAll("text").remove();
debug
.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", (d, i) => pack.cells.p[i][0])
.attr("y", (d, i) => pack.cells.p[i][1])
.text(d => d);
}
// helper function non-used for the generation
function drawPolygons(data) {
const max = d3.max(data),
min = d3.min(data),
scheme = getColorScheme(terrs.attr("scheme"));
data = data.map(d => 1 - normalize(d, min, max));
debug.selectAll("polygon").remove();
debug
.selectAll("polygon")
.data(data)
.enter()
.append("polygon")
.attr("points", (d, i) => getPackPolygon(i))
.attr("fill", d => scheme(d))
.attr("stroke", d => scheme(d));
}