[claude] Procedural terrain generation for floating island (#251) #296

Merged
claude merged 1 commits from claude/issue-251 into main 2026-03-24 04:46:18 +00:00

114
app.js
View File

@@ -232,6 +232,120 @@ glassPlatformGroup.add(voidLight);
scene.add(glassPlatformGroup);
// === PERLIN NOISE ===
// Classic Perlin noise used for procedural terrain generation.
function createPerlinNoise() {
const p = new Uint8Array(256);
for (let i = 0; i < 256; i++) p[i] = i;
// Fisher-Yates shuffle with a fixed seed sequence for reproducibility
let seed = 42;
function seededRand() {
seed = (seed * 1664525 + 1013904223) & 0xffffffff;
return (seed >>> 0) / 0xffffffff;
}
for (let i = 255; i > 0; i--) {
const j = Math.floor(seededRand() * (i + 1));
const tmp = p[i]; p[i] = p[j]; p[j] = tmp;
}
const perm = new Uint8Array(512);
for (let i = 0; i < 512; i++) perm[i] = p[i & 255];
function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
function lerp(a, b, t) { return a + t * (b - a); }
function grad(hash, x, y, z) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 4 ? y : (h === 12 || h === 14) ? x : z;
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
}
return function noise(x, y, z) {
z = z || 0;
const X = Math.floor(x) & 255, Y = Math.floor(y) & 255, Z = Math.floor(z) & 255;
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
const u = fade(x), v = fade(y), w = fade(z);
const A = perm[X] + Y, AA = perm[A] + Z, AB = perm[A + 1] + Z;
const B = perm[X + 1] + Y, BA = perm[B] + Z, BB = perm[B + 1] + Z;
return lerp(
lerp(lerp(grad(perm[AA], x, y, z ), grad(perm[BA], x-1, y, z ), u),
lerp(grad(perm[AB], x, y-1, z ), grad(perm[BB], x-1, y-1, z ), u), v),
lerp(lerp(grad(perm[AA + 1], x, y, z-1), grad(perm[BA + 1], x-1, y, z-1), u),
lerp(grad(perm[AB + 1], x, y-1, z-1), grad(perm[BB + 1], x-1, y-1, z-1), u), v),
w
);
};
}
const perlin = createPerlinNoise();
// === FLOATING ISLAND TERRAIN ===
// Procedural terrain below the glass platform, shaped like a floating rock island.
// Heights generated via fBm (fractional Brownian motion) layered Perlin noise.
(function buildFloatingIsland() {
const ISLAND_RADIUS = 9.5;
const SEGMENTS = 90;
const SIZE = ISLAND_RADIUS * 2;
const geo = new THREE.PlaneGeometry(SIZE, SIZE, SEGMENTS, SEGMENTS);
geo.rotateX(-Math.PI / 2);
const pos = geo.attributes.position;
const count = pos.count;
for (let i = 0; i < count; i++) {
const x = pos.getX(i);
const z = pos.getZ(i);
const dist = Math.sqrt(x * x + z * z) / ISLAND_RADIUS;
// Island edge taper — smooth falloff toward rim
const edgeFactor = Math.max(0, 1 - Math.pow(dist, 2.2));
// fBm: four octaves of Perlin noise
const nx = x * 0.17, nz = z * 0.17;
let h = 0;
h += perlin(nx, nz ) * 1.000;
h += perlin(nx * 2, nz * 2 ) * 0.500;
h += perlin(nx * 4, nz * 4 ) * 0.250;
h += perlin(nx * 8, nz * 8 ) * 0.125;
h /= 1.875; // normalise to ~[-1, 1]
const height = ((h + 1) * 0.5) * edgeFactor * 2.6;
pos.setY(i, height);
}
geo.computeVertexNormals();
// Vertex colours: low=dark earth, mid=dusty stone, high=pale rock
const colors = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const t = Math.min(1, pos.getY(i) / 2.0);
colors[i * 3] = 0.18 + t * 0.22; // R
colors[i * 3 + 1] = 0.14 + t * 0.16; // G
colors[i * 3 + 2] = 0.10 + t * 0.15; // B
}
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const topMat = new THREE.MeshStandardMaterial({
vertexColors: true,
roughness: 0.88,
metalness: 0.04,
});
const topMesh = new THREE.Mesh(geo, topMat);
// Underside — tapered cylinder giving the island its rocky underbelly
const bottomGeo = new THREE.CylinderGeometry(ISLAND_RADIUS * 0.82, ISLAND_RADIUS * 0.35, 2.0, 64, 1);
const bottomMat = new THREE.MeshStandardMaterial({ color: 0x0c0a08, roughness: 0.92, metalness: 0.03 });
const bottomMesh = new THREE.Mesh(bottomGeo, bottomMat);
bottomMesh.position.y = -1.0;
const islandGroup = new THREE.Group();
islandGroup.add(topMesh);
islandGroup.add(bottomMesh);
islandGroup.position.y = -2.8; // float below the glass platform
scene.add(islandGroup);
})();
// === COMMIT HEATMAP ===
// Canvas-texture overlay on the floor. Each agent occupies a polar sector;
// recent commits make that sector glow brighter. Activity decays over 24 h.