Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Failing after 3m48s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
99 lines
2.7 KiB
TypeScript
99 lines
2.7 KiB
TypeScript
import { createNoise2D } from 'simplex-noise';
|
|
|
|
// Seeded random number generator
|
|
export function LCG(seed: number) {
|
|
return function () {
|
|
seed = Math.imul(48271, seed) | 0 % 2147483647;
|
|
return (seed & 2147483647) / 2147483648;
|
|
};
|
|
}
|
|
|
|
export type Vec2 = [number, number];
|
|
export type Vec3 = [number, number, number];
|
|
|
|
export function distance2D(a: Vec2, b: Vec2) {
|
|
const dx = a[0] - b[0];
|
|
const dy = a[1] - b[1];
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
export function distance3D(a: Vec3, b: Vec3) {
|
|
const dx = a[0] - b[0];
|
|
const dy = a[1] - b[1];
|
|
const dz = a[2] - b[2];
|
|
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
}
|
|
|
|
// Generate points for a hanging cable between two points (catenary/sag)
|
|
export function generateCatenaryCurve(
|
|
p1: Vec3,
|
|
p2: Vec3,
|
|
sag: number,
|
|
segments: number = 10
|
|
): Vec3[] {
|
|
const points: Vec3[] = [];
|
|
for (let i = 0; i <= segments; i++) {
|
|
const t = i / segments;
|
|
const x = p1[0] + (p2[0] - p1[0]) * t;
|
|
const z = p1[2] + (p2[2] - p1[2]) * t;
|
|
|
|
// Linear interpolation of height
|
|
const baseHeight = p1[1] + (p2[1] - p1[1]) * t;
|
|
|
|
// Add parabolic sag
|
|
// t ranges from 0 to 1. Parabola that is 0 at ends and 1 in middle is 4 * t * (1 - t)
|
|
const sagOffset = 4 * t * (1 - t) * sag;
|
|
|
|
const y = baseHeight - sagOffset;
|
|
points.push([x, y, z]);
|
|
}
|
|
return points;
|
|
}
|
|
|
|
// Simple terrain height function combining multiple frequencies
|
|
export function getTerrainHeight(noise2D: ReturnType<typeof createNoise2D>, x: number, z: number): number {
|
|
// We want a gigantic, hilly planet. We'll use low frequency noise.
|
|
const n1 = noise2D(x * 0.002, z * 0.002) * 40; // Main hills
|
|
const n2 = noise2D(x * 0.01, z * 0.01) * 10; // Smaller details
|
|
return n1 + n2;
|
|
}
|
|
|
|
// Helper: Place points within a radius using rejection sampling to avoid overlap
|
|
export function generateClusteredPoints(
|
|
rng: () => number,
|
|
center: Vec2,
|
|
count: number,
|
|
radius: number,
|
|
minDist: number
|
|
): Vec2[] {
|
|
const points: Vec2[] = [];
|
|
let attempts = 0;
|
|
const maxAttempts = count * 50;
|
|
|
|
while (points.length < count && attempts < maxAttempts) {
|
|
attempts++;
|
|
const angle = rng() * Math.PI * 2;
|
|
// Square root for uniform distribution in a circle
|
|
const r = Math.sqrt(rng()) * radius;
|
|
|
|
const p: Vec2 = [
|
|
center[0] + Math.cos(angle) * r,
|
|
center[1] + Math.sin(angle) * r
|
|
];
|
|
|
|
let valid = true;
|
|
for (const other of points) {
|
|
if (distance2D(p, other) < minDist) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
points.push(p);
|
|
}
|
|
}
|
|
|
|
return points;
|
|
}
|