[claude] Glass floor sections showing void below (#483) #497
155
app.js
155
app.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user