Files
the-nexus/modules/terrain/island.js
Perplexity Computer 675b61d65e
All checks were successful
CI / validate (pull_request) Successful in 14s
CI / auto-merge (pull_request) Successful in 0s
refactor: modularize app.js into ES module architecture
Split the monolithic 5393-line app.js into 32 focused ES modules under
modules/ with a thin ~330-line orchestrator. No bundler required — runs
in-browser via import maps.

Module structure:
  core/     — scene, ticker, state, theme, audio
  data/     — gitea, weather, bitcoin, loaders
  terrain/  — stars, clouds, island
  effects/  — matrix-rain, energy-beam, lightning, shockwave, rune-ring, gravity-zones
  panels/   — heatmap, sigil, sovereignty, dual-brain, batcave, earth, agent-board, lora-panel
  portals/  — portal-system, commit-banners
  narrative/ — bookshelves, oath, chat
  utils/    — perlin

All files pass node --check. No new dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 18:12:53 +00:00

222 lines
8.8 KiB
JavaScript

// modules/terrain/island.js — Floating island + glass platform + crystals
import * as THREE from 'three';
import { THEME } from '../core/theme.js';
import { perlin } from '../utils/perlin.js';
const GLASS_RADIUS = 4.55;
const GLASS_TILE_SIZE = 0.85;
const GLASS_TILE_GAP = 0.14;
const GLASS_TILE_STEP = GLASS_TILE_SIZE + GLASS_TILE_GAP;
export { GLASS_RADIUS };
const glassEdgeMaterials = [];
let voidLight;
export function init(scene) {
// --- Glass Platform ---
const glassPlatformGroup = new THREE.Group();
const platformFrameMat = new THREE.MeshStandardMaterial({
color: 0x0a1828, metalness: 0.9, roughness: 0.1,
emissive: new THREE.Color(THEME.colors.accent).multiplyScalar(0.06),
});
const platformRim = new THREE.Mesh(new THREE.RingGeometry(4.7, 5.3, 64), platformFrameMat);
platformRim.rotation.x = -Math.PI / 2;
platformRim.castShadow = true;
platformRim.receiveShadow = true;
glassPlatformGroup.add(platformRim);
const borderTorus = new THREE.Mesh(new THREE.TorusGeometry(5.0, 0.1, 6, 64), platformFrameMat);
borderTorus.rotation.x = Math.PI / 2;
borderTorus.castShadow = true;
borderTorus.receiveShadow = true;
glassPlatformGroup.add(borderTorus);
const glassTileMat = new THREE.MeshPhysicalMaterial({
color: new THREE.Color(THEME.colors.accent), transparent: true, opacity: 0.09,
roughness: 0.0, metalness: 0.0, transmission: 0.92, thickness: 0.06,
side: THREE.DoubleSide, depthWrite: false,
});
const glassEdgeBaseMat = new THREE.LineBasicMaterial({
color: THEME.colors.accent, transparent: true, opacity: 0.55,
});
const tileGeo = new THREE.PlaneGeometry(GLASS_TILE_SIZE, GLASS_TILE_SIZE);
const tileEdgeGeo = new THREE.EdgesGeometry(tileGeo);
for (let row = -5; row <= 5; row++) {
for (let col = -5; col <= 5; col++) {
const x = col * GLASS_TILE_STEP;
const z = row * GLASS_TILE_STEP;
const distFromCenter = Math.sqrt(x * x + z * z);
if (distFromCenter > GLASS_RADIUS) continue;
const tile = new THREE.Mesh(tileGeo, glassTileMat.clone());
tile.rotation.x = -Math.PI / 2;
tile.position.set(x, 0, z);
glassPlatformGroup.add(tile);
const mat = glassEdgeBaseMat.clone();
const edges = new THREE.LineSegments(tileEdgeGeo, mat);
edges.rotation.x = -Math.PI / 2;
edges.position.set(x, 0.002, z);
glassPlatformGroup.add(edges);
glassEdgeMaterials.push({ mat, distFromCenter });
}
}
voidLight = new THREE.PointLight(THEME.colors.accent, 0.5, 14);
voidLight.position.set(0, -3.5, 0);
glassPlatformGroup.add(voidLight);
scene.add(glassPlatformGroup);
glassPlatformGroup.traverse(obj => {
if (obj.isMesh) obj.userData.zoomLabel = 'Glass Platform';
});
// --- Floating Island Terrain ---
const ISLAND_RADIUS = 9.5;
const SEGMENTS = 96;
const SIZE = ISLAND_RADIUS * 2;
function islandFBm(nx, nz) {
const wx = perlin(nx * 0.5 + 3.7, nz * 0.5 + 1.2) * 0.55;
const wz = perlin(nx * 0.5 + 8.3, nz * 0.5 + 5.9) * 0.55;
const px = nx + wx, pz = nz + wz;
let h = 0;
h += perlin(px, pz) * 1.000;
h += perlin(px * 2, pz * 2) * 0.500;
h += perlin(px * 4, pz * 4) * 0.250;
h += perlin(px * 8, pz * 8) * 0.125;
h += perlin(px * 16, pz * 16) * 0.063;
h /= 1.938;
const ridge = 1.0 - Math.abs(perlin(px * 3.1 + 5.0, pz * 3.1 + 7.0));
return h * 0.78 + ridge * 0.22;
}
const geo = new THREE.PlaneGeometry(SIZE, SIZE, SEGMENTS, SEGMENTS);
geo.rotateX(-Math.PI / 2);
const pos = geo.attributes.position;
const vCount = pos.count;
const rawHeights = new Float32Array(vCount);
for (let i = 0; i < vCount; i++) {
const x = pos.getX(i);
const z = pos.getZ(i);
const dist = Math.sqrt(x * x + z * z) / ISLAND_RADIUS;
const rimNoise = perlin(x * 0.38 + 10, z * 0.38 + 10) * 0.10;
const edgeFactor = Math.max(0, 1 - Math.pow(Math.max(0, dist - rimNoise), 2.4));
const h = islandFBm(x * 0.15, z * 0.15);
const height = ((h + 1) * 0.5) * edgeFactor * 3.2;
pos.setY(i, height);
rawHeights[i] = height;
}
geo.computeVertexNormals();
const colBuf = new Float32Array(vCount * 3);
for (let i = 0; i < vCount; i++) {
const h = rawHeights[i];
let r, g, b;
if (h < 0.25) { r = 0.11; g = 0.09; b = 0.07; }
else if (h < 0.75) { const t = (h - 0.25) / 0.50; r = 0.11 + t * 0.13; g = 0.09 + t * 0.09; b = 0.07 + t * 0.06; }
else if (h < 1.4) { const t = (h - 0.75) / 0.65; r = 0.24 + t * 0.12; g = 0.18 + t * 0.10; b = 0.13 + t * 0.10; }
else if (h < 2.2) { const t = (h - 1.4) / 0.80; r = 0.36 + t * 0.14; g = 0.28 + t * 0.11; b = 0.23 + t * 0.13; }
else { const t = Math.min(1, (h - 2.2) / 0.9); r = 0.50 + t * 0.05; g = 0.39 + t * 0.10; b = 0.36 + t * 0.28; }
colBuf[i * 3] = r; colBuf[i * 3 + 1] = g; colBuf[i * 3 + 2] = b;
}
geo.setAttribute('color', new THREE.BufferAttribute(colBuf, 3));
const topMat = new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.86, metalness: 0.05 });
const topMesh = new THREE.Mesh(geo, topMat);
topMesh.castShadow = true;
topMesh.receiveShadow = true;
// Crystal spires
const crystalMat = new THREE.MeshStandardMaterial({
color: new THREE.Color(THEME.colors.accent).multiplyScalar(0.55),
emissive: new THREE.Color(THEME.colors.accent), emissiveIntensity: 0.5,
roughness: 0.08, metalness: 0.25, transparent: true, opacity: 0.80,
});
const CRYSTAL_MIN_H = 2.05;
const crystalGroup = new THREE.Group();
for (let row = -5; row <= 5; row++) {
for (let col = -5; col <= 5; col++) {
const bx = col * 1.75, bz = row * 1.75;
if (Math.sqrt(bx * bx + bz * bz) > ISLAND_RADIUS * 0.72) continue;
const edF = Math.max(0, 1 - Math.pow(Math.sqrt(bx * bx + bz * bz) / ISLAND_RADIUS, 2.4));
const candidateH = ((islandFBm(bx * 0.15, bz * 0.15) + 1) * 0.5) * edF * 3.2;
if (candidateH < CRYSTAL_MIN_H) continue;
const jx = bx + perlin(bx * 0.7 + 20, bz * 0.7 + 20) * 0.55;
const jz = bz + perlin(bx * 0.7 + 30, bz * 0.7 + 30) * 0.55;
if (Math.sqrt(jx * jx + jz * jz) > ISLAND_RADIUS * 0.68) continue;
const clusterSize = 2 + Math.floor(Math.abs(perlin(bx * 0.5 + 40, bz * 0.5 + 40)) * 3);
for (let c = 0; c < clusterSize; c++) {
const angle = (c / clusterSize) * Math.PI * 2 + perlin(bx + c, bz + c) * 1.4;
const spread = 0.08 + Math.abs(perlin(bx + c * 5, bz + c * 5)) * 0.22;
const sx = jx + Math.cos(angle) * spread;
const sz = jz + Math.sin(angle) * spread;
const spireScale = 0.14 + (candidateH - CRYSTAL_MIN_H) * 0.11;
const spireH = spireScale * (0.8 + Math.abs(perlin(sx, sz)) * 0.45);
const spireR = spireH * 0.17;
const spireGeo = new THREE.ConeGeometry(spireR, spireH * 2.8, 5);
const spire = new THREE.Mesh(spireGeo, crystalMat);
spire.position.set(sx, candidateH + spireH * 0.5, sz);
spire.rotation.z = perlin(sx * 2, sz * 2) * 0.28;
spire.rotation.x = perlin(sx * 3 + 1, sz * 3 + 1) * 0.18;
spire.castShadow = true;
crystalGroup.add(spire);
}
}
}
// Rocky underside
const BOTTOM_SEGS_R = 52, BOTTOM_SEGS_V = 10, BOTTOM_HEIGHT = 2.6;
const bottomGeo = new THREE.CylinderGeometry(
ISLAND_RADIUS * 0.80, ISLAND_RADIUS * 0.28, BOTTOM_HEIGHT, BOTTOM_SEGS_R, BOTTOM_SEGS_V, true
);
const bPos = bottomGeo.attributes.position;
for (let i = 0; i < bPos.count; i++) {
const bx = bPos.getX(i), bz = bPos.getZ(i), by = bPos.getY(i);
const bAngle = Math.atan2(bz, bx);
const r = Math.sqrt(bx * bx + bz * bz);
const radDisp = perlin(Math.cos(bAngle) * 1.6 + 50, Math.sin(bAngle) * 1.6 + 50) * 0.65;
const vNorm = (by + BOTTOM_HEIGHT * 0.5) / BOTTOM_HEIGHT;
const stalDisp = (1 - vNorm) * Math.abs(perlin(bx * 0.35 + 70, by * 0.7 + bz * 0.35)) * 0.9;
const newR = r + radDisp;
bPos.setX(i, (bx / r) * newR);
bPos.setZ(i, (bz / r) * newR);
bPos.setY(i, by - stalDisp);
}
bottomGeo.computeVertexNormals();
const bottomMat = new THREE.MeshStandardMaterial({ color: 0x0c0a08, roughness: 0.93, metalness: 0.02 });
const bottomMesh = new THREE.Mesh(bottomGeo, bottomMat);
bottomMesh.position.y = -BOTTOM_HEIGHT * 0.5;
bottomMesh.castShadow = true;
const capGeo = new THREE.CircleGeometry(ISLAND_RADIUS * 0.28, 48);
capGeo.rotateX(Math.PI / 2);
const capMesh = new THREE.Mesh(capGeo, bottomMat);
capMesh.position.y = -(BOTTOM_HEIGHT + 0.1);
const islandGroup = new THREE.Group();
islandGroup.add(topMesh);
islandGroup.add(crystalGroup);
islandGroup.add(bottomMesh);
islandGroup.add(capMesh);
islandGroup.position.y = -2.8;
scene.add(islandGroup);
}
export function update(elapsed) {
for (const { mat, distFromCenter } of glassEdgeMaterials) {
const phase = elapsed * 1.1 - distFromCenter * 0.18;
mat.opacity = 0.25 + Math.sin(phase) * 0.22;
}
if (voidLight) voidLight.intensity = 0.35 + Math.sin(elapsed * 1.4) * 0.2;
}