human tweaks
This commit is contained in:
parent
1ea8ba1701
commit
f5e7a1df1d
9 changed files with 1201 additions and 45 deletions
|
|
@ -409,25 +409,31 @@ function localBoundaryEnergy(labels, i, candidateId, targetScore, centerDist, po
|
|||
if (same4 === 1) energy += 1.7;
|
||||
if (diff4 >= 3 && targetScore[i] < 0.42) energy += 1.25;
|
||||
if (diagDiff >= 3 && diff4 >= 2 && targetScore[i] < 0.50) energy += 0.42;
|
||||
if (candidateId !== oldId && centerDist[candidateId] && centerDist[oldId]) {
|
||||
const drift = centerDist[candidateId][i] - centerDist[oldId][i];
|
||||
if (drift > 0) energy += Math.min(0.9, drift * 0.012);
|
||||
if (candidateId !== oldId) {
|
||||
const candidateDistance = centerDistanceAt(centerDist, candidateId, i);
|
||||
const oldDistance = centerDistanceAt(centerDist, oldId, i);
|
||||
if (Number.isFinite(candidateDistance) && Number.isFinite(oldDistance)) {
|
||||
const drift = candidateDistance - oldDistance;
|
||||
if (drift > 0) energy += Math.min(0.9, drift * 0.012);
|
||||
}
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
function centerDistanceAt(centerDist, id, i) {
|
||||
const field = centerDist?.fields?.[id] || centerDist?.[id];
|
||||
if (field) return field[i];
|
||||
const center = centerDist?.centers?.[id];
|
||||
if (!center || !inside(center.x, center.y)) return 24;
|
||||
const [x, y] = xyOf(i);
|
||||
return Math.hypot(x - center.x, y - center.y);
|
||||
}
|
||||
|
||||
function buildCenterDistanceFields(adminIds, adminCenters, prefectureMask, sea) {
|
||||
const fields = [];
|
||||
for (const id of adminIds) {
|
||||
const center = adminCenters[id];
|
||||
const field = new Float32Array(SIZE);
|
||||
if (!center || !inside(center.x, center.y) || sea[indexOf(center.x, center.y)] || !prefectureMask[indexOf(center.x, center.y)]) field.fill(24);
|
||||
else {
|
||||
for (let y = 0; y < MAP_H; y++) for (let x = 0; x < MAP_W; x++) field[indexOf(x, y)] = Math.hypot(x - center.x, y - center.y);
|
||||
}
|
||||
fields[id] = field;
|
||||
}
|
||||
return fields;
|
||||
// Older versions materialized one full SIZE Float32Array per municipality.
|
||||
// In multi-prefecture generation this can create heavy transient memory use.
|
||||
// Keep the same interface conceptually, but compute distances on demand.
|
||||
return { ids: adminIds, centers: adminCenters, prefectureMask, sea };
|
||||
}
|
||||
|
||||
function repairAdminTopology(adminId, prefectureMask, sea, adminCenters = [], targetScore = null, populationDensity = null, landuse = null) {
|
||||
|
|
@ -981,6 +987,7 @@ function splitNaturalCompartmentCompact(unit, newId, compartmentId, fields, seed
|
|||
}
|
||||
|
||||
function buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, crestCrossingScore = null, plain = null, agriculture = null, populationDensity = null, landuse = null, options = {}) {
|
||||
const progress = typeof options.progress === "function" ? options.progress : null;
|
||||
const naturalBarrierScore = buildNaturalBarrierScore(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, crestCrossingScore, plain, agriculture, populationDensity, landuse);
|
||||
const cellClass = new Int16Array(SIZE);
|
||||
cellClass.fill(-1);
|
||||
|
|
@ -991,6 +998,7 @@ function buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, r
|
|||
const requestedTarget = options.targetCompartmentCount || clamp(Math.round(landArea / 34), 40, 360);
|
||||
const targetCount = clamp(Math.round(requestedTarget), Math.min(1, landArea), Math.max(1, Math.floor(landArea / 8)));
|
||||
const { seeds } = chooseNaturalCompartmentSeeds(landComponents, targetCount, fields, options.seed || 0);
|
||||
progress?.(`natural seeds chosen: ${seeds.length}/${targetCount}`);
|
||||
const compartmentId = new Int32Array(SIZE);
|
||||
compartmentId.fill(-1);
|
||||
const dist = new Float32Array(SIZE);
|
||||
|
|
@ -1017,22 +1025,25 @@ function buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, r
|
|||
}
|
||||
}
|
||||
for (let i = 0; i < SIZE; i++) if (prefectureMask[i] && !sea[i] && compartmentId[i] < 0) compartmentId[i] = 0;
|
||||
progress?.("natural seeded growth complete");
|
||||
|
||||
let compartments = buildUnitsFromAssignment(compartmentId, cellClass, prefectureMask, sea, fields);
|
||||
rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
mergeTinyLandscapeUnits(compartmentId, compartments, 9);
|
||||
progress?.(`natural post-split units: ${compartments.filter((unit) => unit && unit.area > 0).length}`);
|
||||
splitDisconnectedCompartments(compartmentId, compartments, prefectureMask, sea);
|
||||
compartments = renumberCompartments(compartmentId, compartments, prefectureMask, sea);
|
||||
refreshAllCompartmentStats(compartments, fields);
|
||||
rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
progress?.(`natural pre-split units: ${compartments.filter((unit) => unit && unit.area > 0).length}`);
|
||||
|
||||
const maxNaturalCompartmentArea = options.maxNaturalCompartmentArea || Math.max(28, Math.round(landArea / Math.max(1, targetCount) * 1.55));
|
||||
let guard = Math.max(80, targetCount * 3);
|
||||
let guard = Math.max(60, targetCount * 2);
|
||||
while (guard-- > 0) {
|
||||
let active = compartments.filter((unit) => unit && unit.area > 0);
|
||||
const needMore = active.length < targetCount;
|
||||
const worst = active
|
||||
.filter((unit) => unit.area >= 20 && (needMore || unit.area > maxNaturalCompartmentArea * 1.18 || (unit.elongation || 1) > 4.2))
|
||||
.filter((unit) => unit.area >= 20 && (unit._splitRejected || 0) < 3 && (needMore || unit.area > maxNaturalCompartmentArea * 1.18 || (unit.elongation || 1) > 4.2))
|
||||
.sort((a, b) => {
|
||||
const sa = (a.area / maxNaturalCompartmentArea) * 1.8 + Math.max(0, (a.elongation || 1) - 3.0) * 1.2;
|
||||
const sb = (b.area / maxNaturalCompartmentArea) * 1.8 + Math.max(0, (b.elongation || 1) - 3.0) * 1.2;
|
||||
|
|
@ -1490,10 +1501,13 @@ export function extractCompartmentBorders(compartmentId, prefectureMask, sea) {
|
|||
}
|
||||
|
||||
export function assignAdminRegionsFromNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCenters = [], options = {}) {
|
||||
const progress = typeof options.progress === "function" ? options.progress : null;
|
||||
const { compartmentId, compartments, naturalBarrierScore } = buildNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, null, plain, agriculture, populationDensity, landuse, options);
|
||||
progress?.("natural compartments built");
|
||||
const adminId = new Int16Array(SIZE);
|
||||
adminId.fill(-1);
|
||||
const owner = graphVoronoiCompartmentOwners(compartments, compartmentId, adminCenters, options);
|
||||
progress?.("natural compartments assigned");
|
||||
for (const unit of compartments) {
|
||||
const assigned = owner[unit.id];
|
||||
if (assigned < 0) continue;
|
||||
|
|
@ -1505,6 +1519,7 @@ export function assignAdminRegionsFromNaturalCompartments(prefectureMask, sea, e
|
|||
adminId[i] = comp && owner[comp.id] >= 0 ? owner[comp.id] : 0;
|
||||
}
|
||||
repairAdminTopology(adminId, prefectureMask, sea, adminCenters, naturalBarrierScore, populationDensity, landuse);
|
||||
progress?.("natural topology repaired");
|
||||
const activeCompartments = compartments.filter((unit) => unit.area > 0);
|
||||
const relationMetrics = compartmentMunicipalityMetrics(compartments, owner, options.targetMunicipalityCount || adminCenters.length, options.targetCompartmentCount || 0);
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue