ice generation and draw now split

This commit is contained in:
kruschen 2024-09-08 13:12:29 +00:00
parent 1684cbece7
commit 8f78e460fe
7 changed files with 132 additions and 74 deletions

View file

@ -39,11 +39,11 @@ export function open() {
isLoaded = true;
// add listeners
byId("iceEditStyle")?.on("click", () => editStyle("ice"));
byId("iceRandomize")?.on("click", randomizeShape);
byId("iceSize")?.on("input", changeSize);
byId("iceNew")?.on("click", toggleAdd);
byId("iceRemove")?.on("click", removeIce);
byId("iceEditStyle").on("click", () => editStyle("ice"));
byId("iceRandomize").on("click", randomizeShape);
byId("iceSize").on("input", changeSize);
byId("iceNew").on("click", toggleAdd);
byId("iceRemove").on("click", removeIce);
function randomizeShape() {
const c = grid.points[+elSelected.attr("cell")];

View file

@ -1,78 +1,22 @@
import {getGridPolygon} from "utils/graphUtils";
import {P, normalize, rn, last} from "utils";
import {aleaPRNG} from "scripts/aleaPRNG";
import {clipPoly} from "utils/lineUtils";
import { getGridPolygon } from "utils/graphUtils";
import { P, normalize, rn, last } from "utils";
import { clipPoly } from "utils/lineUtils";
import { ERROR } from "config/logging";
import * as d3 from "d3";
export function drawIce() {
const {cells, vertices} = grid;
const {temp, h} = cells;
const n = cells.i.length;
const ice = d3.select("#ice");
const { ice: icePack } = pack;
const used = new Uint8Array(cells.i.length);
Math.random = aleaPRNG(seed);
const shieldMin = -8; // max temp to form ice shield (glacier)
const icebergMax = 1; // max temp to form an iceberg
for (const i of grid.cells.i) {
const t = temp[i];
if (t > icebergMax) continue; // too warm: no ice
if (t > shieldMin && h[i] >= 20) continue; // non-glacier land: no ice
if (t <= shieldMin) {
// very cold: ice shield
if (used[i]) continue; // already rendered
const onborder = cells.c[i].some(n => temp[n] > shieldMin);
if (!onborder) continue; // need to start from onborder cell
const vertex = cells.v[i].find(v => vertices.c[v].some(i => temp[i] > shieldMin));
if (vertex === undefined) continue; // no suitable vertex found
const chain = connectVertices(vertex);
if (chain.length < 3) continue;
const points = clipPoly(chain.map(v => vertices.p[v]));
ice.append("polygon").attr("points", points.toString()).attr("type", "iceShield");
continue;
}
// mildly cold: iceberd
if (P(normalize(t, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size
if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
size = Math.min(size * (0.4 + Math.random() * 1.2), 0.95); // randomize iceberg size
resizePolygon(i, size);
}
function resizePolygon(i: number, size: number) {
const c = grid.points[i];
const points = getGridPolygon(i).map(p => [(p[0] + (c[0] - p[0]) * size) | 0, (p[1] + (c[1] - p[1]) * size) | 0]);
for (const shield of icePack.iceShields) {
ice
.append("polygon")
.attr("points", points.toString())
.attr("cell", i)
.attr("size", rn(1 - size, 2));
.attr("points", shield.points.toString())
}
// connect vertices to chain
function connectVertices(start: number) {
const chain = []; // vertices chain to form a path
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
const prev = last(chain); // previous vertex in chain
chain.push(current); // add current vertex to sequence
const c = vertices.c[current]; // cells adjacent to vertex
c.filter(c => temp[c] <= shieldMin).forEach(c => (used[c] = 1));
const c0 = c[0] >= n || temp[c[0]] > shieldMin;
const c1 = c[1] >= n || temp[c[1]] > shieldMin;
const c2 = c[2] >= n || temp[c[2]] > shieldMin;
const v = vertices.v[current]; // neighboring vertices
if (v[0] !== prev && c0 !== c1) current = v[0];
else if (v[1] !== prev && c1 !== c2) current = v[1];
else if (v[2] !== prev && c0 !== c2) current = v[2];
if (current === chain[chain.length - 1]) {
ERROR && console.error("Next vertex is not found");
break;
}
}
return chain;
for (const iceberg of icePack.icebergs) {
ice
.append("polygon")
.attr("points", iceberg.points.toString())
}
}

View file

@ -0,0 +1,91 @@
import { ERROR } from "config/logging";
import { aleaPRNG } from "scripts/aleaPRNG";
import { IIce, Iiceberg } from "types/pack/ice";
import { clipPoly, last, normalize, P, rn } from "utils";
export function generateIce(
cells: Pick<IPack["cells"], "i" | "h" | "c" | "v" | "p">,
vertices: IGraphVertices,
temp: Int8Array,
features: TGridFeatures,
gridCells: Pick<IGrid["cells"], "f" | "t">,
): IIce {
const shieldMin = -8; // max temp to form ice shield (glacier)
const icebergMax = 1; // max temp to form an iceberg
const nOfCells = cells.i.length;
const used = new Uint8Array(cells.i.length);
Math.random = aleaPRNG(seed);
const icePack: IIce = { icebergs: [], iceShields: [] };
for (const i of cells.i) {
const temperature = temp[i];
if (temperature > icebergMax) continue; // too warm: no ice
if (temperature > shieldMin && cells.h[i] >= 20) continue; // non-glacier land: no ice
if (temperature <= shieldMin) {
// very cold: ice shield
if (used[i]) continue; // already rendered
const onborder = cells.c[i].some((n) => temp[n] > shieldMin);
if (!onborder) continue; // need to start from onborder cell
const vertex = cells.v[i].find((v) =>
vertices.c[v]?.some((i) => temp[i] > shieldMin)
);
if (vertex === undefined) continue; // no suitable vertex found
const chain = connectVertices(vertex);
if (chain.length < 3) continue;
const points = clipPoly(chain.map((v) => vertices.p[v]));
icePack.iceShields.push({ points, type: "iceShield" });
continue;
}
// mildly cold: iceberd
if (P(normalize(temperature, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
if (gridCells.f[i] !== 0 && (features[gridCells.f[i]] as IGridFeature).type === "lake") continue; // lake: no icebers // MARKER as IGridFeature
let size = (6.5 + temperature) / 10; // iceberg size: 0 = full size, 1 = zero size
if (gridCells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
size = Math.min(size * (0.4 + Math.random() * 1.2), 0.95); // randomize iceberg size
icePack.icebergs.push(generateIceberg(i, size));
}
return icePack;
// Helper functions
function generateIceberg(i: number, size: number): Iiceberg {
const cellMidPoint = cells.p[i];
const points = cells.v[i].map(v => vertices.p[v]).map((point) => [
(point[0] + (cellMidPoint[0] - point[0]) * size) | 0,
(point[1] + (cellMidPoint[1] - point[1]) * size) | 0,
]);
return { points, cell: i, size: rn(1 - size, 2) };
}
// connect vertices to chain
function connectVertices(start: number) {
const chain = []; // vertices chain to form a path
for (
let i = 0, current = start;
i === 0 || (current !== start && i < 20000);
i++
) {
const prev = last(chain); // previous vertex in chain
chain.push(current); // add current vertex to sequence
const currentVertex = vertices.c[current]; // cells adjacent to vertex
currentVertex
.filter((cellIndicie) => temp[cellIndicie] <= shieldMin)
.forEach((cellIndice) => (used[cellIndice] = 1));
const c0 = currentVertex[0] >= nOfCells || temp[currentVertex[0]] > shieldMin;
const c1 = currentVertex[1] >= nOfCells || temp[currentVertex[1]] > shieldMin;
const c2 = currentVertex[2] >= nOfCells || temp[currentVertex[2]] > shieldMin;
const vertexNeighbors = vertices.v[current]; // neighboring vertices
if (vertexNeighbors[0] !== prev && c0 !== c1)
current = vertexNeighbors[0];
else if (vertexNeighbors[1] !== prev && c1 !== c2)
current = vertexNeighbors[1];
else if (vertexNeighbors[2] !== prev && c0 !== c2)
current = vertexNeighbors[2];
if (current === chain[chain.length - 1]) {
ERROR && console.error("Next vertex is not found");
break;
}
}
return chain;
}
}

View file

@ -11,6 +11,7 @@ import {repackGrid} from "./repackGrid";
import {generateRivers} from "./rivers/generateRivers";
import {specifyRivers} from "./rivers/specifyRivers";
import {generateRoutes} from "./routes/generateRoutes";
import { generateIce } from "./generateIce";
const {Biomes} = window;
@ -161,6 +162,7 @@ export function createPack(grid: IGrid): IPack {
const rivers = specifyRivers(rawRivers, cultureIds, cultures);
const features = generateLakeNames(mergedFeatures, cultureIds, cultures);
const ice = generateIce(cells, vertices, temp, features, grid.cells);
// Military.generate();
// Markers.generate();
@ -198,7 +200,8 @@ export function createPack(grid: IGrid): IPack {
routes,
religions,
provinces,
events
events,
ice
};
return pack;

View file

@ -9,6 +9,7 @@ import {calculateVoronoi} from "../graph";
const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD;
// repack grid cells: discart deep water cells, add land cells along the coast
export function repackGrid(grid: IGrid) {
TIME && console.time("repackGrid");

18
src/types/pack/ice.d.ts vendored Normal file
View file

@ -0,0 +1,18 @@
export interface IIceBase {
points: number[][];
}
export interface Iiceberg extends IIceBase {
cell: number;
size: number;
}
export interface IiceShield extends IIceBase {
type: string;
}
export interface IIce{
icebergs: Iiceberg[];
iceShields: IiceShield[];
}

View file

@ -9,6 +9,7 @@ interface IPack extends IGraph {
religions: TReligions;
routes: TRoutes;
events: IEvents;
ice: IIce;
}
interface IPackCells {