diff --git a/app.js b/app.js index b396094..e834ee8 100644 --- a/app.js +++ b/app.js @@ -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.