Nexus Vision: Update app.js

This commit is contained in:
2026-03-24 02:35:18 +00:00
parent d0edfe8725
commit eb081bf784

120
app.js
View File

@@ -29,8 +29,11 @@ let keys = {};
let mouseDown = false;
let batcaveTerminals = [];
let portals = []; // Registry of active portals
let visionPoints = []; // Registry of vision points
let activePortal = null; // Portal currently in proximity
let activeVisionPoint = null; // Vision point currently in proximity
let portalOverlayActive = false;
let visionOverlayActive = false;
let particles, dustParticles;
let debugOverlay;
let frameCount = 0, lastFPSTime = 0, fps = 0;
@@ -98,6 +101,15 @@ async function init() {
console.error('Failed to load portals.json:', e);
addChatMessage('error', 'Portal registry offline. Check logs.');
}
// Load Vision Points
try {
const response = await fetch('./vision.json');
const visionData = await response.json();
createVisionPoints(visionData);
} catch (e) {
console.error('Failed to load vision.json:', e);
}
updateLoad(80);
createParticles();
@@ -418,6 +430,53 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) {
batcaveTerminals.push({ group, scanMat, borderMat });
}
// ═══ VISION SYSTEM ═══
function createVisionPoints(data) {
data.forEach(config => {
const vp = createVisionPoint(config);
visionPoints.push(vp);
});
}
function createVisionPoint(config) {
const group = new THREE.Group();
group.position.set(config.position.x, config.position.y, config.position.z);
const color = new THREE.Color(config.color);
// Floating Crystal
const crystalGeo = new THREE.OctahedronGeometry(0.6, 0);
const crystalMat = new THREE.MeshPhysicalMaterial({
color: color,
emissive: color,
emissiveIntensity: 1,
roughness: 0,
metalness: 1,
transmission: 0.5,
thickness: 1,
});
const crystal = new THREE.Mesh(crystalGeo, crystalMat);
crystal.position.y = 2.5;
group.add(crystal);
// Glow Ring
const ringGeo = new THREE.TorusGeometry(0.8, 0.02, 16, 64);
const ringMat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.5 });
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.position.y = 2.5;
ring.rotation.x = Math.PI / 2;
group.add(ring);
// Light
const light = new THREE.PointLight(color, 1, 10);
light.position.set(0, 2.5, 0);
group.add(light);
scene.add(group);
return { config, group, crystal, ring, light };
}
// ═══ PORTAL SYSTEM ═══
function createPortals(data) {
data.forEach(config => {
@@ -762,6 +821,7 @@ function setupControls() {
if (e.key === 'Escape') {
document.getElementById('chat-input').blur();
if (portalOverlayActive) closePortalOverlay();
if (visionOverlayActive) closeVisionOverlay();
}
if (e.key.toLowerCase() === 'v' && document.activeElement !== document.getElementById('chat-input')) {
cycleNavMode();
@@ -769,6 +829,9 @@ function setupControls() {
if (e.key.toLowerCase() === 'f' && activePortal && !portalOverlayActive) {
activatePortal(activePortal);
}
if (e.key.toLowerCase() === 'e' && activeVisionPoint && !visionOverlayActive) {
activateVisionPoint(activeVisionPoint);
}
});
document.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
@@ -830,6 +893,7 @@ function setupControls() {
document.getElementById('chat-send').addEventListener('click', sendChatMessage);
document.getElementById('portal-close-btn').addEventListener('click', closePortalOverlay);
document.getElementById('vision-close-btn').addEventListener('click', closeVisionOverlay);
}
function sendChatMessage() {
@@ -932,6 +996,51 @@ function closePortalOverlay() {
document.getElementById('portal-overlay').style.display = 'none';
}
// ═══ VISION INTERACTION ═══
function checkVisionProximity() {
if (visionOverlayActive) return;
let closest = null;
let minDist = Infinity;
visionPoints.forEach(vp => {
const dist = playerPos.distanceTo(vp.group.position);
if (dist < 3.5 && dist < minDist) {
minDist = dist;
closest = vp;
}
});
activeVisionPoint = closest;
const hint = document.getElementById('vision-hint');
if (activeVisionPoint) {
document.getElementById('vision-hint-title').textContent = activeVisionPoint.config.title;
hint.style.display = 'flex';
} else {
hint.style.display = 'none';
}
}
function activateVisionPoint(vp) {
visionOverlayActive = true;
const overlay = document.getElementById('vision-overlay');
const titleDisplay = document.getElementById('vision-title-display');
const contentDisplay = document.getElementById('vision-content-display');
const statusDot = document.getElementById('vision-status-dot');
titleDisplay.textContent = vp.config.title.toUpperCase();
contentDisplay.textContent = vp.config.content;
statusDot.style.background = vp.config.color;
statusDot.style.boxShadow = `0 0 10px ${vp.config.color}`;
overlay.style.display = 'flex';
}
function closeVisionOverlay() {
visionOverlayActive = false;
document.getElementById('vision-overlay').style.display = 'none';
}
// ═══ GAME LOOP ═══
function gameLoop() {
requestAnimationFrame(gameLoop);
@@ -1007,6 +1116,7 @@ function gameLoop() {
// Proximity check
checkPortalProximity();
checkVisionProximity();
const sky = scene.getObjectByName('skybox');
if (sky) sky.material.uniforms.uTime.value = elapsed;
@@ -1032,6 +1142,16 @@ function gameLoop() {
portal.pSystem.geometry.attributes.position.needsUpdate = true;
});
// Animate Vision Points
visionPoints.forEach(vp => {
vp.crystal.rotation.y = elapsed * 0.8;
vp.crystal.rotation.x = Math.sin(elapsed * 0.5) * 0.2;
vp.crystal.position.y = 2.5 + Math.sin(elapsed * 1.5) * 0.2;
vp.ring.rotation.z = elapsed * 0.5;
vp.ring.scale.setScalar(1 + Math.sin(elapsed * 2) * 0.05);
vp.light.intensity = 1 + Math.sin(elapsed * 3) * 0.3;
});
if (particles?.material?.uniforms) {
particles.material.uniforms.uTime.value = elapsed;
}