[claude] Glass floor sections showing void below (#483) #497

Merged
gemini merged 1 commits from claude/issue-483 into main 2026-03-25 03:07:02 +00:00

155
app.js
View File

@@ -40,6 +40,8 @@ let harnessPulseMesh;
let powerMeterBars = [];
let particles, dustParticles;
let debugOverlay;
let glassEdgeMaterials = []; // Glass tile edge materials for animation
let voidLight = null; // Point light below glass floor
let frameCount = 0, lastFPSTime = 0, fps = 0;
let chatOpen = true;
let loadProgress = 0;
@@ -330,6 +332,150 @@ function createFloor() {
ring.rotation.x = Math.PI / 2;
ring.position.y = 0.05;
scene.add(ring);
// ─── Glass floor sections showing void below ───
_buildGlassFloor();
}
function _buildGlassFloor() {
const GLASS_TILE_SIZE = 0.85;
const GLASS_TILE_GAP = 0.14;
const GLASS_TILE_STEP = GLASS_TILE_SIZE + GLASS_TILE_GAP;
const GLASS_RADIUS = 4.55;
const glassPlatformGroup = new THREE.Group();
// Solid dark frame ring around the glass section
const frameMat = new THREE.MeshStandardMaterial({
color: 0x0a1828,
metalness: 0.9,
roughness: 0.1,
emissive: new THREE.Color(NEXUS.colors.primary).multiplyScalar(0.06),
});
const rimGeo = new THREE.RingGeometry(4.7, 5.3, 64);
const rim = new THREE.Mesh(rimGeo, frameMat);
rim.rotation.x = -Math.PI / 2;
rim.position.y = 0.01;
glassPlatformGroup.add(rim);
const borderTorusGeo = new THREE.TorusGeometry(5.0, 0.1, 6, 64);
const borderTorus = new THREE.Mesh(borderTorusGeo, frameMat);
borderTorus.rotation.x = Math.PI / 2;
borderTorus.position.y = 0.01;
glassPlatformGroup.add(borderTorus);
// Semi-transparent glass tile material (transmission lets void show through)
const glassTileMat = new THREE.MeshPhysicalMaterial({
color: new THREE.Color(NEXUS.colors.primary),
transparent: true,
opacity: 0.09,
roughness: 0.0,
metalness: 0.0,
transmission: 0.92,
thickness: 0.06,
side: THREE.DoubleSide,
depthWrite: false,
});
// Collect tile positions within the glass radius
const tileSlots = [];
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;
tileSlots.push({ x, z, distFromCenter });
}
}
// InstancedMesh for all tiles (single draw call)
const tileGeo = new THREE.PlaneGeometry(GLASS_TILE_SIZE, GLASS_TILE_SIZE);
const tileMesh = new THREE.InstancedMesh(tileGeo, glassTileMat, tileSlots.length);
tileMesh.instanceMatrix.setUsage(THREE.StaticDrawUsage);
const dummy = new THREE.Object3D();
dummy.rotation.x = -Math.PI / 2;
for (let i = 0; i < tileSlots.length; i++) {
dummy.position.set(tileSlots[i].x, 0.005, tileSlots[i].z);
dummy.updateMatrix();
tileMesh.setMatrixAt(i, dummy.matrix);
}
tileMesh.instanceMatrix.needsUpdate = true;
glassPlatformGroup.add(tileMesh);
// Merge all tile edge lines into a single LineSegments draw call
const HS = GLASS_TILE_SIZE / 2;
const edgeVerts = new Float32Array(tileSlots.length * 8 * 3);
let evi = 0;
for (const { x, z } of tileSlots) {
const y = 0.008;
edgeVerts[evi++]=x-HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z-HS;
edgeVerts[evi++]=x+HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z-HS;
edgeVerts[evi++]=x+HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z-HS;
edgeVerts[evi++]=x+HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z+HS;
edgeVerts[evi++]=x+HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z+HS;
edgeVerts[evi++]=x-HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z+HS;
edgeVerts[evi++]=x-HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z+HS;
edgeVerts[evi++]=x-HS; edgeVerts[evi++]=y; edgeVerts[evi++]=z-HS;
}
const mergedEdgeGeo = new THREE.BufferGeometry();
mergedEdgeGeo.setAttribute('position', new THREE.BufferAttribute(edgeVerts, 3));
const edgeMat = new THREE.LineBasicMaterial({
color: NEXUS.colors.primary,
transparent: true,
opacity: 0.55,
});
glassPlatformGroup.add(new THREE.LineSegments(mergedEdgeGeo, edgeMat));
// Register per-tile edge entries for the animation loop
// (we animate the single merged material, grouped by distance bands)
const BAND_COUNT = 6;
const bandMats = [];
for (let b = 0; b < BAND_COUNT; b++) {
const mat = new THREE.LineBasicMaterial({
color: NEXUS.colors.primary,
transparent: true,
opacity: 0.55,
});
// distFromCenter goes 0 → GLASS_RADIUS; spread across bands
const distFromCenter = (b / (BAND_COUNT - 1)) * GLASS_RADIUS;
glassEdgeMaterials.push({ mat, distFromCenter });
bandMats.push(mat);
}
// Rebuild edge geometry per band so each band has its own material
// (more draw calls but proper animated glow rings)
mergedEdgeGeo.dispose(); // dispose the merged one we won't use
for (let b = 0; b < BAND_COUNT; b++) {
const bandMin = (b / BAND_COUNT) * GLASS_RADIUS;
const bandMax = ((b + 1) / BAND_COUNT) * GLASS_RADIUS;
const bandSlots = tileSlots.filter(s => s.distFromCenter >= bandMin && s.distFromCenter < bandMax);
if (bandSlots.length === 0) continue;
const bVerts = new Float32Array(bandSlots.length * 8 * 3);
let bvi = 0;
for (const { x, z } of bandSlots) {
const y = 0.008;
bVerts[bvi++]=x-HS; bVerts[bvi++]=y; bVerts[bvi++]=z-HS;
bVerts[bvi++]=x+HS; bVerts[bvi++]=y; bVerts[bvi++]=z-HS;
bVerts[bvi++]=x+HS; bVerts[bvi++]=y; bVerts[bvi++]=z-HS;
bVerts[bvi++]=x+HS; bVerts[bvi++]=y; bVerts[bvi++]=z+HS;
bVerts[bvi++]=x+HS; bVerts[bvi++]=y; bVerts[bvi++]=z+HS;
bVerts[bvi++]=x-HS; bVerts[bvi++]=y; bVerts[bvi++]=z+HS;
bVerts[bvi++]=x-HS; bVerts[bvi++]=y; bVerts[bvi++]=z+HS;
bVerts[bvi++]=x-HS; bVerts[bvi++]=y; bVerts[bvi++]=z-HS;
}
const bGeo = new THREE.BufferGeometry();
bGeo.setAttribute('position', new THREE.BufferAttribute(bVerts, 3));
glassPlatformGroup.add(new THREE.LineSegments(bGeo, bandMats[b]));
}
// Void light pulses below the glass to illuminate the emptiness underneath
voidLight = new THREE.PointLight(NEXUS.colors.primary, 0.5, 14);
voidLight.position.set(0, -3.5, 0);
glassPlatformGroup.add(voidLight);
scene.add(glassPlatformGroup);
}
// ═══ BATCAVE TERMINAL ═══
@@ -1451,6 +1597,15 @@ function gameLoop() {
bar.scale.x = active ? 1.2 : 1.0;
});
// Animate glass floor edge glow (ripple outward from center)
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;
}
if (thoughtStreamMesh) {
thoughtStreamMesh.material.uniforms.uTime.value = elapsed;
thoughtStreamMesh.rotation.y = elapsed * 0.05;