99 lines
3.3 KiB
JavaScript
99 lines
3.3 KiB
JavaScript
import { SIZE, clamp, indexOf, inside } from "./mapUtils.js";
|
|
|
|
export function pathSetSignature(paths) {
|
|
let cells = 0;
|
|
let endpoints = 0;
|
|
for (const path of paths || []) {
|
|
cells += path?.length || 0;
|
|
const a = path?.[0];
|
|
const b = path?.[path.length - 1];
|
|
if (a) endpoints = (endpoints + a[0] * 17 + a[1] * 31) | 0;
|
|
if (b) endpoints = (endpoints + b[0] * 47 + b[1] * 73) | 0;
|
|
}
|
|
return `${paths?.length || 0}:${cells}:${endpoints >>> 0}`;
|
|
}
|
|
|
|
export function createPathInfluenceCache(influenceFromPaths) {
|
|
const cache = new Map();
|
|
return (paths, radius, label = "paths") => {
|
|
const key = `${label}:${radius}:${pathSetSignature(paths)}`;
|
|
let grid = cache.get(key);
|
|
if (!grid) {
|
|
grid = influenceFromPaths(paths, radius);
|
|
cache.set(key, grid);
|
|
}
|
|
return grid;
|
|
};
|
|
}
|
|
|
|
export function packDebugField(field) {
|
|
const out = new Uint8Array(SIZE);
|
|
for (let i = 0; i < SIZE; i++) out[i] = Math.round(clamp(field?.[i] || 0) * 255);
|
|
return out;
|
|
}
|
|
|
|
export function pathLengthCells(path) {
|
|
let total = 0;
|
|
for (let i = 1; i < (path?.length || 0); i++) {
|
|
total += Math.hypot(path[i][0] - path[i - 1][0], path[i][1] - path[i - 1][1]);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
export function pathAverageField(path, field) {
|
|
if (!path?.length || !field) return 0;
|
|
let sum = 0;
|
|
let n = 0;
|
|
for (const [x, y] of path) {
|
|
if (!inside(x, y)) continue;
|
|
sum += field[indexOf(x, y)] || 0;
|
|
n++;
|
|
}
|
|
return n ? sum / n : 0;
|
|
}
|
|
|
|
export function routeQualityStats(path, fields = {}) {
|
|
if (!path?.length) return { length: 0, compactness: Infinity, highElevationShare: 1, steepShare: 1, waterShare: 1, avgPotential: 0, avgPenalty: 0 };
|
|
const length = pathLengthCells(path);
|
|
const first = path[0];
|
|
const last = path[path.length - 1];
|
|
const direct = first && last ? Math.hypot(first[0] - last[0], first[1] - last[1]) : 0;
|
|
let high = 0;
|
|
let steep = 0;
|
|
let water = 0;
|
|
let potential = 0;
|
|
let penalty = 0;
|
|
let n = 0;
|
|
for (const [x, y] of path) {
|
|
if (!inside(x, y)) continue;
|
|
const i = indexOf(x, y);
|
|
if (fields.sea?.[i]) water++;
|
|
if ((fields.elevation?.[i] || 0) > (fields.highElevationThreshold ?? 0.72)) high++;
|
|
if ((fields.slope?.[i] || 0) > (fields.steepThreshold ?? 0.44)) steep++;
|
|
potential += fields.potential?.[i] || 0;
|
|
penalty += fields.penalty?.[i] || 0;
|
|
n++;
|
|
}
|
|
return {
|
|
length,
|
|
compactness: direct > 0.001 ? length / direct : Infinity,
|
|
highElevationShare: high / Math.max(1, n),
|
|
steepShare: steep / Math.max(1, n),
|
|
waterShare: water / Math.max(1, n),
|
|
avgPotential: potential / Math.max(1, n),
|
|
avgPenalty: penalty / Math.max(1, n),
|
|
};
|
|
}
|
|
|
|
export function routeQualityAcceptable(path, fields = {}, limits = {}) {
|
|
const q = routeQualityStats(path, fields);
|
|
if (q.length < (limits.minLength ?? 2)) return false;
|
|
if (q.length > (limits.maxLength ?? Infinity)) return false;
|
|
if (q.compactness > (limits.maxCompactness ?? 3.2)) return false;
|
|
if (q.highElevationShare > (limits.maxHighElevationShare ?? 0.0)) return false;
|
|
if (q.steepShare > (limits.maxSteepShare ?? 0.38)) return false;
|
|
if (q.waterShare > (limits.maxWaterShare ?? 0)) return false;
|
|
if (q.avgPenalty > (limits.maxAvgPenalty ?? Infinity) && q.avgPotential < (limits.minPotentialIfPenalty ?? 0.35)) return false;
|
|
if (q.avgPotential < (limits.minAvgPotential ?? 0)) return false;
|
|
return true;
|
|
}
|