[manus] Three.js scene foundation (#4) #38
188
app.js
188
app.js
@@ -332,173 +332,71 @@ function createFloor() {
|
|||||||
const ringMat = new THREE.MeshBasicMaterial({
|
const ringMat = new THREE.MeshBasicMaterial({
|
||||||
color: NEXUS.colors.primary,
|
color: NEXUS.colors.primary,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.15,
|
opacity: 0.4,
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
});
|
});
|
||||||
const ring = new THREE.Mesh(ringGeo, ringMat);
|
const ring = new THREE.Mesh(ringGeo, ringMat);
|
||||||
ring.rotation.x = -Math.PI / 2;
|
ring.rotation.x = Math.PI / 2;
|
||||||
ring.position.y = 0.05;
|
ring.position.y = 0.05;
|
||||||
scene.add(ring);
|
scene.add(ring);
|
||||||
|
|
||||||
// Inner ring
|
|
||||||
const innerRingGeo = new THREE.RingGeometry(14.5, 15, 32);
|
|
||||||
const innerRingMat = new THREE.MeshBasicMaterial({
|
|
||||||
color: NEXUS.colors.secondary,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.08,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
});
|
|
||||||
const innerRing = new THREE.Mesh(innerRingGeo, innerRingMat);
|
|
||||||
innerRing.rotation.x = -Math.PI / 2;
|
|
||||||
innerRing.position.y = 0.03;
|
|
||||||
scene.add(innerRing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ BATCAVE TERMINAL ═══
|
// ═══ BATCAVE TERMINAL ═══
|
||||||
function createBatcaveTerminal() {
|
function createBatcaveTerminal() {
|
||||||
const termGroup = new THREE.Group();
|
const terminalGroup = new THREE.Group();
|
||||||
termGroup.position.set(0, 0, -8);
|
terminalGroup.position.set(0, 0, -8);
|
||||||
|
|
||||||
// Main large screen
|
const panelData = [
|
||||||
createHoloPanel(termGroup, {
|
{ title: 'NEXUS COMMAND', color: NEXUS.colors.primary, rot: -0.4, x: -6, y: 3, lines: ['> STATUS: NOMINAL', '> UPTIME: 142.4h', '> HARNESS: STABLE', '> MODE: SOVEREIGN'] },
|
||||||
x: 0, y: 4, z: 0, w: 8, h: 5,
|
{ title: 'DEV QUEUE', color: NEXUS.colors.gold, rot: -0.2, x: -3, y: 3, lines: ['> ISSUE #4: CORE', '> ISSUE #5: PORTAL', '> ISSUE #6: TERMINAL', '> ISSUE #7: TIMMY'] },
|
||||||
title: '◈ NEXUS COMMAND',
|
{ title: 'METRICS', color: NEXUS.colors.secondary, rot: 0, x: 0, y: 3, lines: ['> CPU: 12% [||....]', '> MEM: 4.2GB', '> COMMITS: 842', '> ACTIVE LOOPS: 5'] },
|
||||||
lines: [
|
{ title: 'THOUGHTS', color: NEXUS.colors.primary, rot: 0.2, x: 3, y: 3, lines: ['> ANALYZING WORLD...', '> SYNCING MEMORY...', '> WAITING FOR INPUT', '> SOUL ON BITCOIN'] },
|
||||||
'┌─────────────────────────────────┐',
|
{ title: 'AGENT STATUS', color: NEXUS.colors.gold, rot: 0.4, x: 6, y: 3, lines: ['> TIMMY: ● RUNNING', '> KIMI: ○ STANDBY', '> CLAUDE: ● ACTIVE', '> PERPLEXITY: ○'] },
|
||||||
'│ SYSTEM STATUS NOMINAL │',
|
];
|
||||||
'│ HERMES HARNESS ACTIVE │',
|
|
||||||
'│ AGENT LOOPS 3/3 RUN │',
|
panelData.forEach(data => {
|
||||||
'│ MEMORY BANKS 2.4 GB │',
|
createTerminalPanel(terminalGroup, data.x, data.y, data.rot, data.title, data.color, data.lines);
|
||||||
'│ THOUGHT CYCLES 14,892 │',
|
|
||||||
'├─────────────────────────────────┤',
|
|
||||||
'│ ACTIVE PROCESSES │',
|
|
||||||
'│ ▸ triage-daemon ● RUNNING │',
|
|
||||||
'│ ▸ code-review-loop ● RUNNING │',
|
|
||||||
'│ ▸ world-builder ○ STANDBY │',
|
|
||||||
'│ ▸ matrix-renderer ● RUNNING │',
|
|
||||||
'└─────────────────────────────────┘',
|
|
||||||
],
|
|
||||||
color: NEXUS.colors.primary,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Left panel — Dev Items
|
scene.add(terminalGroup);
|
||||||
createHoloPanel(termGroup, {
|
|
||||||
x: -6, y: 3.5, z: 1, w: 4, h: 4, rotY: 0.3,
|
|
||||||
title: '⚡ DEV QUEUE',
|
|
||||||
lines: [
|
|
||||||
'#1090 Nexus v1 Build',
|
|
||||||
'#1079 Code Hygiene Epic',
|
|
||||||
'#1080 Showcase Epic',
|
|
||||||
'#864 PR Pending Merge',
|
|
||||||
'#1076 Deep Triage Gov.',
|
|
||||||
'',
|
|
||||||
'Open Issues: 293',
|
|
||||||
'Closed Today: 19',
|
|
||||||
],
|
|
||||||
color: NEXUS.colors.secondary,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Right panel — Metrics
|
|
||||||
createHoloPanel(termGroup, {
|
|
||||||
x: 6, y: 3.5, z: 1, w: 4, h: 4, rotY: -0.3,
|
|
||||||
title: '📊 METRICS',
|
|
||||||
lines: [
|
|
||||||
'Uptime: 23d 14h 22m',
|
|
||||||
'Commits: 1,847',
|
|
||||||
'Agents: 5 active',
|
|
||||||
'Worlds: 1 (Nexus)',
|
|
||||||
'Portals: 1 staging',
|
|
||||||
'',
|
|
||||||
'CPU: ████████░░ 78%',
|
|
||||||
'MEM: ██████░░░░ 62%',
|
|
||||||
],
|
|
||||||
color: 0x44aaff,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Far left — Thought Stream
|
|
||||||
createHoloPanel(termGroup, {
|
|
||||||
x: -10, y: 2.5, z: 3, w: 3.5, h: 3, rotY: 0.5,
|
|
||||||
title: '💭 THOUGHTS',
|
|
||||||
lines: [
|
|
||||||
'Considering portal arch.',
|
|
||||||
'Morrowind integration is',
|
|
||||||
'next priority after the',
|
|
||||||
'Nexus shell is stable.',
|
|
||||||
'',
|
|
||||||
'The harness is the core',
|
|
||||||
'product. Focus there.',
|
|
||||||
],
|
|
||||||
color: NEXUS.colors.gold,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Far right — Agents
|
|
||||||
createHoloPanel(termGroup, {
|
|
||||||
x: 10, y: 2.5, z: 3, w: 3.5, h: 3, rotY: -0.5,
|
|
||||||
title: '🤖 AGENTS',
|
|
||||||
lines: [
|
|
||||||
'Claude Code ● ACTIVE',
|
|
||||||
'Kimi ● ACTIVE',
|
|
||||||
'Gemini ○ STANDBY',
|
|
||||||
'Hermes ● ACTIVE',
|
|
||||||
'Perplexity ● ACTIVE',
|
|
||||||
],
|
|
||||||
color: 0xff8844,
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.add(termGroup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHoloPanel(parent, opts) {
|
function createTerminalPanel(parent, x, y, rot, title, color, lines) {
|
||||||
const { x, y, z, w, h, title, lines, color, rotY } = opts;
|
const w = 2.8, h = 3.5;
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
group.position.set(x, y, z);
|
group.position.set(x, y, 0);
|
||||||
if (rotY) group.rotation.y = rotY;
|
group.rotation.y = rot;
|
||||||
|
|
||||||
// Background panel
|
// Panel background (glassy)
|
||||||
const panelGeo = new THREE.PlaneGeometry(w, h);
|
const bgGeo = new THREE.PlaneGeometry(w, h);
|
||||||
const panelMat = new THREE.MeshBasicMaterial({
|
const bgMat = new THREE.MeshPhysicalMaterial({
|
||||||
color: 0x000815,
|
color: NEXUS.colors.panelBg,
|
||||||
transparent: true,
|
|
||||||
opacity: 0.7,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
});
|
|
||||||
const panel = new THREE.Mesh(panelGeo, panelMat);
|
|
||||||
group.add(panel);
|
|
||||||
|
|
||||||
// Border frame
|
|
||||||
const borderGeo = new THREE.EdgesGeometry(panelGeo);
|
|
||||||
const borderMat = new THREE.LineBasicMaterial({
|
|
||||||
color: color,
|
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
|
roughness: 0.1,
|
||||||
|
metalness: 0.5,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
});
|
});
|
||||||
const border = new THREE.LineSegments(borderGeo, borderMat);
|
const bg = new THREE.Mesh(bgGeo, bgMat);
|
||||||
|
group.add(bg);
|
||||||
|
|
||||||
|
// Border
|
||||||
|
const borderMat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.3, side: THREE.DoubleSide });
|
||||||
|
const border = new THREE.Mesh(new THREE.PlaneGeometry(w + 0.05, h + 0.05), borderMat);
|
||||||
|
border.position.z = -0.01;
|
||||||
group.add(border);
|
group.add(border);
|
||||||
|
|
||||||
// Text content via CanvasTexture
|
// Canvas for text
|
||||||
const textCanvas = document.createElement('canvas');
|
const textCanvas = document.createElement('canvas');
|
||||||
|
textCanvas.width = 512;
|
||||||
|
textCanvas.height = 640;
|
||||||
const ctx = textCanvas.getContext('2d');
|
const ctx = textCanvas.getContext('2d');
|
||||||
const res = 512;
|
|
||||||
textCanvas.width = res * (w / h);
|
|
||||||
textCanvas.height = res;
|
|
||||||
|
|
||||||
ctx.fillStyle = 'transparent';
|
// Header
|
||||||
ctx.clearRect(0, 0, textCanvas.width, textCanvas.height);
|
ctx.fillStyle = '#' + new THREE.Color(color).getHexString();
|
||||||
|
ctx.font = 'bold 32px "Orbitron", sans-serif';
|
||||||
// Title
|
ctx.fillText(title, 20, 45);
|
||||||
const cHex = '#' + new THREE.Color(color).getHexString();
|
ctx.fillRect(20, 55, 472, 2);
|
||||||
ctx.font = 'bold 28px "JetBrains Mono", monospace';
|
|
||||||
ctx.fillStyle = cHex;
|
|
||||||
ctx.fillText(title, 20, 40);
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
ctx.strokeStyle = cHex;
|
|
||||||
ctx.globalAlpha = 0.3;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(20, 52);
|
|
||||||
ctx.lineTo(textCanvas.width - 20, 52);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
|
|
||||||
// Lines
|
// Lines
|
||||||
ctx.font = '20px "JetBrains Mono", monospace';
|
ctx.font = '20px "JetBrains Mono", monospace';
|
||||||
@@ -510,7 +408,7 @@ function createHoloPanel(parent, opts) {
|
|||||||
else if (line.includes('○ STANDBY')) fillColor = '#5a6a8a';
|
else if (line.includes('○ STANDBY')) fillColor = '#5a6a8a';
|
||||||
else if (line.includes('NOMINAL')) fillColor = '#4af0c0';
|
else if (line.includes('NOMINAL')) fillColor = '#4af0c0';
|
||||||
ctx.fillStyle = fillColor;
|
ctx.fillStyle = fillColor;
|
||||||
ctx.fillText(line, 20, 80 + i * 30);
|
ctx.fillText(line, 20, 100 + i * 40);
|
||||||
});
|
});
|
||||||
|
|
||||||
const textTexture = new THREE.CanvasTexture(textCanvas);
|
const textTexture = new THREE.CanvasTexture(textCanvas);
|
||||||
@@ -797,8 +695,6 @@ function createAmbientStructures() {
|
|||||||
color: NEXUS.colors.primary,
|
color: NEXUS.colors.primary,
|
||||||
emissive: NEXUS.colors.primary,
|
emissive: NEXUS.colors.primary,
|
||||||
emissiveIntensity: 0.5,
|
emissiveIntensity: 0.5,
|
||||||
roughness: 0.3,
|
|
||||||
metalness: 0.7,
|
|
||||||
});
|
});
|
||||||
const stone = new THREE.Mesh(geo, mat);
|
const stone = new THREE.Mesh(geo, mat);
|
||||||
stone.position.set(Math.cos(angle) * r, 5 + Math.sin(i * 1.3) * 1.5, Math.sin(angle) * r);
|
stone.position.set(Math.cos(angle) * r, 5 + Math.sin(i * 1.3) * 1.5, Math.sin(angle) * r);
|
||||||
|
|||||||
Reference in New Issue
Block a user