diff --git a/app.js b/app.js index 085ed8f..3f0f411 100644 --- a/app.js +++ b/app.js @@ -2933,6 +2933,168 @@ function updateAshStorm(delta, elapsed) { } } + + +// ═══════════════════════════════════════════ +// PROJECT MNEMOSYNE — HOLOGRAPHIC MEMORY ORBS +// ═══════════════════════════════════════════ + +/** + * Spawn a glowing memory orb at the given position. + * Used to visualize RAG retrievals and memory recalls in the Nexus. + * + * @param {THREE.Vector3} position - World position for the orb + * @param {number} color - Hex color (default: 0x4af0c0 - cyan) + * @param {number} size - Radius of the orb (default: 0.5) + * @param {object} metadata - Optional metadata for the memory (source, timestamp, etc.) + * @returns {THREE.Mesh} The created orb mesh + */ +function spawnMemoryOrb(position, color = 0x4af0c0, size = 0.5, metadata = {}) { + // Create geometry with higher detail for better glow effect + const geometry = new THREE.SphereGeometry(size, 32, 32); + + // Use MeshStandardMaterial for PBR rendering with transmission + const material = new THREE.MeshStandardMaterial({ + color: color, + emissive: color, + emissiveIntensity: 2.5, // High emissive for glow + metalness: 0.3, + roughness: 0.2, + transparent: true, + opacity: 0.85, + envMapIntensity: 1.5 + }); + + const orb = new THREE.Mesh(geometry, material); + orb.position.copy(position); + orb.castShadow = true; + orb.receiveShadow = true; + + // Store metadata for interaction + orb.userData = { + type: 'memory_orb', + pulse: 0, + pulseSpeed: 0.002 + Math.random() * 0.001, // Slight variation + originalScale: size, + metadata: metadata, + createdAt: Date.now() + }; + + // Add point light for local illumination + const light = new THREE.PointLight(color, 1.5, 8); + light.position.set(0, 0, 0); + orb.add(light); + + // Add to scene if available + if (typeof scene !== 'undefined' && scene) { + scene.add(orb); + } + + // Add to memory orbs registry for animation + if (typeof memoryOrbs !== 'undefined') { + memoryOrbs.push(orb); + } + + console.info('[Mnemosyne] Memory orb spawned:', metadata.source || 'unknown'); + return orb; +} + +/** + * Remove a memory orb from the scene. + * @param {THREE.Mesh} orb - The orb to remove + */ +function removeMemoryOrb(orb) { + if (!orb) return; + + // Remove from scene + if (orb.parent) { + orb.parent.remove(orb); + } + + // Dispose geometry and material + if (orb.geometry) orb.geometry.dispose(); + if (orb.material) orb.material.dispose(); + + // Remove from registry + if (typeof memoryOrbs !== 'undefined') { + const idx = memoryOrbs.indexOf(orb); + if (idx > -1) memoryOrbs.splice(idx, 1); + } +} + +/** + * Animate all memory orbs (call from render loop). + * @param {number} delta - Time since last frame + */ +function animateMemoryOrbs(delta) { + if (typeof memoryOrbs === 'undefined') return; + + for (const orb of memoryOrbs) { + if (!orb.userData) continue; + + // Pulse animation + orb.userData.pulse += orb.userData.pulseSpeed * delta * 1000; + const pulseFactor = 1 + Math.sin(orb.userData.pulse) * 0.1; + orb.scale.setScalar(pulseFactor * orb.userData.originalScale); + + // Gentle rotation + orb.rotation.y += delta * 0.5; + + // Fade based on age (optional: orbs fade after 30 seconds) + const age = (Date.now() - orb.userData.createdAt) / 1000; + if (age > 30) { + const fadeStart = 30; + const fadeDuration = 10; + const fadeProgress = Math.min(1, (age - fadeStart) / fadeDuration); + orb.material.opacity = 0.85 * (1 - fadeProgress); + + if (fadeProgress >= 1) { + removeMemoryOrb(orb); + } + } + } +} + +/** + * Spawn memory orbs for RAG retrieval results. + * @param {Array} results - Array of retrieval results with {content, score, source} + * @param {THREE.Vector3} center - Center position to spawn orbs around + */ +function spawnRetrievalOrbs(results, center = new THREE.Vector3(0, 2, 0)) { + if (!results || !Array.isArray(results)) return; + + const colors = [0x4af0c0, 0x7b5cff, 0xffd700, 0xff4466, 0x00ff88]; + const radius = 3; + + results.forEach((result, i) => { + // Arrange in a spiral pattern + const angle = (i / results.length) * Math.PI * 2; + const height = (i / results.length) * 2 - 1; + + const position = new THREE.Vector3( + center.x + Math.cos(angle) * radius, + center.y + height, + center.z + Math.sin(angle) * radius + ); + + // Color based on relevance score + const colorIdx = Math.min(colors.length - 1, Math.floor(result.score * colors.length)); + const color = colors[colorIdx]; + + // Size based on score + const size = 0.3 + result.score * 0.4; + + spawnMemoryOrb(position, color, size, { + source: result.source, + score: result.score, + contentPreview: result.content?.substring(0, 100) || '' + }); + }); +} + +// Initialize memory orbs registry +const memoryOrbs = []; + init().then(() => { createAshStorm(); createPortalTunnel();