This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
+120
View File
@@ -0,0 +1,120 @@
// Coarse reference grid for resolution-independent plate boundaries.
// Generates plates on a fixed ~20K-region mesh, then projects onto any
// high-res mesh with FBM noise perturbation for fractal boundaries.
import { makeRng } from './rng.js';
import { buildSphere } from './sphere-mesh.js';
import { SimplexNoise } from './simplex-noise.js';
import { generatePlates } from './plates.js';
import { assignOceanLand } from './ocean-land.js';
import {
N_COARSE, COARSE_JITTER, COARSE_PERTURB_BASE, COARSE_PERTURB_LOW_T,
COARSE_FBM_BASE_FREQ, COARSE_FBM_OCTAVES, COARSE_FBM_DECAY, COARSE_FBM_FREQ_MULT,
PLATE_LOW_PLATE_T_HIGH, PLATE_LOW_PLATE_T_RANGE,
} from './terrain-config.js';
/**
* Generate plates and ocean/land on a fixed coarse reference mesh.
* Uses isolated RNG so it doesn't affect the main mesh's random stream.
* Jitter is fixed so plate shapes don't change when the user adjusts irregularity.
*/
export function generateCoarsePlates(seed, numPlates, numContinents, continentSizeVariety = 0, landCoverage = 0.3) {
const coarseRng = makeRng(seed + 137);
const { mesh: coarseMesh, r_xyz: coarse_xyz } = buildSphere(N_COARSE, COARSE_JITTER, coarseRng);
const { r_plate: coarse_r_plate, plateSeeds: coarsePlateSeeds, plateVec: coarsePlateVec } =
generatePlates(coarseMesh, coarse_xyz, numPlates, seed);
const coarsePlateIsOcean = assignOceanLand(
coarseMesh, coarse_r_plate, coarsePlateSeeds, coarse_xyz, seed, numContinents, continentSizeVariety, landCoverage
);
return {
coarseMesh,
coarse_xyz,
coarse_r_plate,
coarsePlateSeeds,
coarsePlateVec,
coarsePlateIsOcean,
};
}
/**
* Project coarse plate assignments onto a high-res mesh via nearest-neighbor
* with FBM noise perturbation for fractal plate boundaries.
*
* Each hi-res point is shifted by multi-octave simplex noise before the
* nearest-neighbor lookup, which wobbles the plate boundary by ~2 coarse
* cell widths with fractal detail at multiple scales.
*
* Uses adjacency-walk on the coarse mesh with warm-starting for O(1)
* amortized cost per region.
*/
export function projectCoarsePlates(mesh, r_xyz, coarseMesh, coarse_xyz, coarse_r_plate, seed, numPlates) {
const N = mesh.numRegions;
const r_plate = new Int32Array(N);
const { adjOffset: cOff, adjList: cAdj } = coarseMesh;
// FBM noise for fractal boundary perturbation
const noise = new SimplexNoise(seed + 999);
const coarseEdgeRad = Math.PI / Math.sqrt(coarseMesh.numRegions);
const lowPlateT = numPlates != null ? Math.max(0, Math.min(1, (PLATE_LOW_PLATE_T_HIGH - numPlates) / PLATE_LOW_PLATE_T_RANGE)) : 0;
const perturbAmp = coarseEdgeRad * (COARSE_PERTURB_BASE + COARSE_PERTURB_LOW_T * lowPlateT); // 1.5 → 2.5 coarse cells
const BASE_FREQ = COARSE_FBM_BASE_FREQ; // ~8 features per sphere diameter → ~16 around equator
const NC = coarseMesh.numRegions;
const MAX_WALK = Math.ceil(Math.sqrt(NC)); // safety cap for greedy walk
let cur = 0; // current best coarse region — warm-started across iterations
for (let r = 0; r < N; r++) {
const ox = r_xyz[3 * r], oy = r_xyz[3 * r + 1], oz = r_xyz[3 * r + 2];
// FBM perturbation: shift lookup point for fractal boundaries
let dx = 0, dy = 0, dz = 0;
let amp = perturbAmp, freq = BASE_FREQ;
for (let oct = 0; oct < COARSE_FBM_OCTAVES; oct++) {
dx += noise.noise3D(ox * freq, oy * freq, oz * freq) * amp;
dy += noise.noise3D(ox * freq + 100, oy * freq + 100, oz * freq + 100) * amp;
dz += noise.noise3D(ox * freq + 200, oy * freq + 200, oz * freq + 200) * amp;
amp *= COARSE_FBM_DECAY;
freq *= COARSE_FBM_FREQ_MULT;
}
// Project perturbed point back onto unit sphere
let px = ox + dx, py = oy + dy, pz = oz + dz;
const len = Math.sqrt(px * px + py * py + pz * pz) || 1;
px /= len; py /= len; pz /= len;
// Greedy walk: find nearest coarse region to the perturbed point
let bestDot = px * coarse_xyz[3 * cur] + py * coarse_xyz[3 * cur + 1] + pz * coarse_xyz[3 * cur + 2];
let improved = true;
let steps = 0;
while (improved && steps < MAX_WALK) {
improved = false;
steps++;
for (let i = cOff[cur], iEnd = cOff[cur + 1]; i < iEnd; i++) {
const nb = cAdj[i];
const d = px * coarse_xyz[3 * nb] + py * coarse_xyz[3 * nb + 1] + pz * coarse_xyz[3 * nb + 2];
if (d > bestDot) {
bestDot = d;
cur = nb;
improved = true;
}
}
}
// Fallback: if greedy walk hit the step limit, brute-force search
if (steps >= MAX_WALK) {
for (let c = 0; c < NC; c++) {
const d = px * coarse_xyz[3 * c] + py * coarse_xyz[3 * c + 1] + pz * coarse_xyz[3 * c + 2];
if (d > bestDot) { bestDot = d; cur = c; }
}
}
r_plate[r] = coarse_r_plate[cur];
}
return r_plate;
}