mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
refactor(es modules): modulize utils
This commit is contained in:
parent
11df349394
commit
12e1c9f334
45 changed files with 620 additions and 283 deletions
10
src/main.ts
10
src/main.ts
|
|
@ -21,8 +21,10 @@ import {
|
|||
isLand,
|
||||
shouldRegenerateGrid
|
||||
} from "./utils/graphUtils";
|
||||
import {parseError} from "@/utils/errorUtils";
|
||||
import {rn, minmax, normalize} from "./utils/numberUtils";
|
||||
import {createTypedArray} from "./utils/arrayUtils";
|
||||
import {clipPoly} from "@/utils/lineUtils";
|
||||
import {byId} from "./utils/shorthands";
|
||||
import "./components";
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ options = {
|
|||
winds: [225, 45, 225, 315, 135, 315],
|
||||
stateLabelsMode: "auto"
|
||||
};
|
||||
mapCoordinates = {}; // map coordinates on globe
|
||||
|
||||
populationRate = +byId("populationRateInput").value;
|
||||
distanceScale = +byId("distanceScaleInput").value;
|
||||
urbanization = +byId("urbanizationInput").value;
|
||||
|
|
@ -368,7 +370,7 @@ async function generate(options) {
|
|||
|
||||
OceanLayers();
|
||||
defineMapSize();
|
||||
calculateMapCoordinates();
|
||||
window.mapCoordinates = calculateMapCoordinates();
|
||||
calculateTemperatures();
|
||||
generatePrecipitation();
|
||||
|
||||
|
|
@ -674,7 +676,7 @@ function defineMapSize() {
|
|||
}
|
||||
|
||||
// calculate map position on globe
|
||||
function calculateMapCoordinates() {
|
||||
function calculateMapCoordinates(): IMapCoordinates {
|
||||
const size = +byId("mapSizeOutput").value;
|
||||
const latShift = +byId("latitudeOutput").value;
|
||||
|
||||
|
|
@ -683,7 +685,7 @@ function calculateMapCoordinates() {
|
|||
const latS = rn(latN - latT, 1);
|
||||
|
||||
const lon = rn(Math.min(((graphWidth / graphHeight) * latT) / 2, 180));
|
||||
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
|
||||
return {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
|
||||
}
|
||||
|
||||
// temperature model
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {findCell} from "/src/utils/graphUtils";
|
||||
import {last} from "/src/utils/arrayUtils";
|
||||
import {getSegmentId} from "@/utils/lineUtils";
|
||||
import {rn} from "/src/utils/numberUtils";
|
||||
|
||||
export class Rulers {
|
||||
|
|
|
|||
50
src/scripts/indexedDB.js
Normal file
50
src/scripts/indexedDB.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// indexedDB support: ldb object
|
||||
|
||||
// @ts-ignore unimplemented historical interfaces
|
||||
const indexedDBfactory = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
if (!indexedDBfactory) console.error("indexedDB not supported");
|
||||
|
||||
let database;
|
||||
const databaseRequest = indexedDBfactory.open("d2", 1);
|
||||
|
||||
databaseRequest.onsuccess = function () {
|
||||
database = this.result;
|
||||
};
|
||||
|
||||
databaseRequest.onerror = function (e) {
|
||||
console.error("indexedDB request error", e);
|
||||
};
|
||||
|
||||
databaseRequest.onupgradeneeded = function (event) {
|
||||
database = null;
|
||||
const store = databaseRequest.result.createObjectStore("s", {keyPath: "k"});
|
||||
store.transaction.oncomplete = function (e) {
|
||||
database = e.target.db;
|
||||
};
|
||||
};
|
||||
|
||||
function getValue(key, callback) {
|
||||
if (!database) {
|
||||
setTimeout(() => getValue(key, callback), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
database.transaction("s").objectStore("s").get(key).onsuccess = function (e) {
|
||||
const value = (e.target.result && e.target.result.v) || null;
|
||||
callback(value);
|
||||
};
|
||||
}
|
||||
|
||||
function setValue(key) {
|
||||
if (!database) {
|
||||
setTimeout(() => setValue(key, value), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
database
|
||||
.transaction("s", "readwrite")
|
||||
.objectStore("s")
|
||||
.put({[key]: value});
|
||||
}
|
||||
|
||||
export const ldb = {get: getValue, set: setValue};
|
||||
44
src/scripts/prompt.ts
Normal file
44
src/scripts/prompt.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import {ERROR} from "@/config/logging";
|
||||
|
||||
// prompt replacer (prompt does not work in Electron)
|
||||
const $prompt: HTMLElement = document.getElementById("prompt")!;
|
||||
const $form: HTMLFormElement = $prompt.querySelector("#promptForm")!;
|
||||
const $input: HTMLInputElement = $prompt.querySelector("#promptInput")!;
|
||||
const $text: HTMLDivElement = $prompt.querySelector("#promptText")!;
|
||||
const $cancel: HTMLButtonElement = $prompt.querySelector("#promptCancel")!;
|
||||
|
||||
const defaultText = "Please provide an input";
|
||||
const defaultOptions = {default: 1, step: 0.01, min: 0, max: 100, required: true};
|
||||
|
||||
export function prompt(promptText = defaultText, options = defaultOptions, callback: (value: number | string) => void) {
|
||||
if (options.default === undefined)
|
||||
return ERROR && console.error("Prompt: options object does not have default value defined");
|
||||
|
||||
$text.innerHTML = promptText;
|
||||
$input.type = typeof options.default === "number" ? "number" : "text";
|
||||
|
||||
if (options.step !== undefined) $input.step = String(options.step);
|
||||
if (options.min !== undefined) $input.min = String(options.min);
|
||||
if (options.max !== undefined) $input.max = String(options.max);
|
||||
|
||||
$input.required = options.required === false ? false : true;
|
||||
$input.placeholder = "type a " + $input.type;
|
||||
$input.value = String(options.default);
|
||||
$prompt.style.display = "block";
|
||||
|
||||
$form.addEventListener(
|
||||
"submit",
|
||||
event => {
|
||||
event.preventDefault();
|
||||
$prompt.style.display = "none";
|
||||
|
||||
const value = $input.type === "number" ? Number($input.value) : $input.value;
|
||||
if (callback) callback(value);
|
||||
},
|
||||
{once: true}
|
||||
);
|
||||
}
|
||||
|
||||
$cancel.addEventListener("click", () => {
|
||||
$prompt.style.display = "none";
|
||||
});
|
||||
1
src/types/common.d.ts
vendored
Normal file
1
src/types/common.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
type UnknownObject = {[key: string]: unknown};
|
||||
8
src/types/coordinates.d.ts
vendored
Normal file
8
src/types/coordinates.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
interface IMapCoordinates {
|
||||
latT: number;
|
||||
latN: number;
|
||||
latS: number;
|
||||
lonT: number;
|
||||
lonW: number;
|
||||
lonE: number;
|
||||
}
|
||||
4
src/types/modules.d.ts
vendored
Normal file
4
src/types/modules.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
declare module "lineclip" {
|
||||
export function polygon(points: number[][], bbox: number[], result?: number[][]): number[][];
|
||||
export function lineclip(points: number[][], bbox: number[]): number[][];
|
||||
}
|
||||
|
|
@ -13,6 +13,9 @@ interface Window {
|
|||
pack: IPack;
|
||||
grig: IGrid;
|
||||
d3: typeof d3;
|
||||
graphHeight: number;
|
||||
graphWidth: number;
|
||||
mapCoordinates: IMapCoordinates;
|
||||
}
|
||||
|
||||
interface Node {
|
||||
3
src/types/point.d.ts
vendored
Normal file
3
src/types/point.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
type TPoint = [number, number];
|
||||
|
||||
type TPoints = TPoint[];
|
||||
25
src/utils/coordinateUtils.ts
Normal file
25
src/utils/coordinateUtils.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import {rn} from "./numberUtils";
|
||||
|
||||
const {mapCoordinates, graphWidth, graphHeight} = window;
|
||||
|
||||
function getLongitude(x: number, decimals = 2) {
|
||||
return rn(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, decimals);
|
||||
}
|
||||
|
||||
function getLatitude(y: number, decimals = 2) {
|
||||
return rn(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, decimals);
|
||||
}
|
||||
|
||||
export function getCoordinates(x: number, y: number, decimals = 2) {
|
||||
return [getLongitude(x, decimals), getLatitude(y, decimals)];
|
||||
}
|
||||
|
||||
// convert coordinate to DMS format
|
||||
export function toDMS(coord: number, type: "lat" | "lon") {
|
||||
const degrees = Math.floor(Math.abs(coord));
|
||||
const minutesNotTruncated = (Math.abs(coord) - degrees) * 60;
|
||||
const minutes = Math.floor(minutesNotTruncated);
|
||||
const seconds = Math.floor((minutesNotTruncated - minutes) * 60);
|
||||
const cardinal = type === "lat" ? (coord >= 0 ? "N" : "S") : coord >= 0 ? "E" : "W";
|
||||
return `${degrees}° ${minutes}′ ${seconds}″ ${cardinal}`;
|
||||
}
|
||||
8
src/utils/errorUtils.ts
Normal file
8
src/utils/errorUtils.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// parse error to get the readable string
|
||||
export function parseError(error: Error) {
|
||||
const errorString = error.toString() + " " + error.stack;
|
||||
const regex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
|
||||
const errorNoURL = errorString.replace(regex, url => "<i>" + url.split("/").at(-1) + "</i>");
|
||||
const errorParsed = errorNoURL.replace(/at /gi, "<br> at ");
|
||||
return errorParsed;
|
||||
}
|
||||
3
src/utils/keyboardUtils.ts
Normal file
3
src/utils/keyboardUtils.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function isCtrlClick(event: MouseEvent) {
|
||||
return event.ctrlKey || event.metaKey;
|
||||
}
|
||||
56
src/utils/lineUtils.ts
Normal file
56
src/utils/lineUtils.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import {polygon} from "lineclip";
|
||||
|
||||
const {graphWidth, graphHeight, pack} = window;
|
||||
|
||||
// clip polygon by graph bbox
|
||||
export function clipPoly(points: TPoints) {
|
||||
return polygon(points, [0, 0, graphWidth, graphHeight]);
|
||||
}
|
||||
|
||||
// get segment of any point on polyline
|
||||
export function getSegmentId(points: TPoints, point: TPoint, step = 10) {
|
||||
if (points.length === 2) return 1;
|
||||
const d2 = (p1: TPoint, p2: TPoint) => (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2;
|
||||
|
||||
let minSegment = 1;
|
||||
let minDist = Infinity;
|
||||
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const p1 = points[i];
|
||||
const p2 = points[i + 1];
|
||||
|
||||
const length = Math.sqrt(d2(p1, p2));
|
||||
const segments = Math.ceil(length / step);
|
||||
const dx = (p2[0] - p1[0]) / segments;
|
||||
const dy = (p2[1] - p1[1]) / segments;
|
||||
|
||||
for (let s = 0; s < segments; s++) {
|
||||
const x = p1[0] + s * dx;
|
||||
const y = p1[1] + s * dy;
|
||||
const dist2 = d2(point, [x, y]);
|
||||
|
||||
if (dist2 >= minDist) continue;
|
||||
minDist = dist2;
|
||||
minSegment = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return minSegment;
|
||||
}
|
||||
|
||||
// return center point of common edge of 2 pack cells
|
||||
export function getMiddlePoint(cell1: number, cell2: number) {
|
||||
const {cells, vertices} = pack;
|
||||
|
||||
const commonVertices = cells.v[cell1].filter((vertex: number) =>
|
||||
vertices.c[vertex].some((cellId: number) => cellId === cell2)
|
||||
);
|
||||
|
||||
const [x1, y1] = vertices.p[commonVertices[0]];
|
||||
const [x2, y2] = vertices.p[commonVertices[1]];
|
||||
|
||||
const x = (x1 + x2) / 2;
|
||||
const y = (y1 + y2) / 2;
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
14
src/utils/linkUtils.ts
Normal file
14
src/utils/linkUtils.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// open URL in a new tab or window
|
||||
export function openURL(url: string) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
|
||||
// open project wiki-page
|
||||
export function wiki(page: string) {
|
||||
window.open("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/" + page, "_blank");
|
||||
}
|
||||
|
||||
// wrap URL into html anchor element
|
||||
export function link(url: string, text: string) {
|
||||
return `<a href="${url}" rel="noopener" target="_blank">${text}</a>`;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue