Files
the-matrix/js/interaction.js
Perplexity Computer fdfae19956 feat: The Matrix — Sovereign Agent World
3D visualization for AI agent swarms built with Three.js.
Matrix green/noir cyberpunk aesthetic.

- 4 agents: Timmy (orchestrator), Forge (builder), Seer (planner), Echo (comms)
- Central core pillar, animated green grid, digital rain
- Agent info panels, chat, task list, memory views
- WebSocket protocol for real-time state updates
- iPad-ready: touch controls, add-to-homescreen
- Post-processing: bloom, scanlines, vignette
- No build step — pure ES modules via esm.sh CDN

Created with Perplexity Computer
2026-03-18 18:32:47 -04:00

148 lines
3.7 KiB
JavaScript

// ===== Interaction: Raycasting, selection, camera control =====
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
export class InteractionManager {
constructor(camera, renderer, agents, ui) {
this.camera = camera;
this.renderer = renderer;
this.agents = agents;
this.ui = ui;
this.raycaster = new THREE.Raycaster();
this.pointer = new THREE.Vector2();
this.selectedAgent = null;
this.interactables = [];
this.coreObjects = [];
this._setupControls();
this._bindEvents();
}
_setupControls() {
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.08;
this.controls.rotateSpeed = 0.5;
this.controls.panSpeed = 0.5;
this.controls.zoomSpeed = 0.8;
// Limits
this.controls.minDistance = 10;
this.controls.maxDistance = 80;
this.controls.minPolarAngle = 0.2;
this.controls.maxPolarAngle = Math.PI / 2.1;
// Target center
this.controls.target.set(0, 3, 0);
// Touch config
this.controls.touches = {
ONE: THREE.TOUCH.ROTATE,
TWO: THREE.TOUCH.DOLLY_PAN,
};
}
setInteractables(interactables) {
this.interactables = interactables;
}
setCoreObjects(coreObjects) {
this.coreObjects = coreObjects;
}
_bindEvents() {
const canvas = this.renderer.domElement;
// Click / tap
let pointerDownTime = 0;
let pointerDownPos = { x: 0, y: 0 };
canvas.addEventListener('pointerdown', (e) => {
pointerDownTime = Date.now();
pointerDownPos = { x: e.clientX, y: e.clientY };
});
canvas.addEventListener('pointerup', (e) => {
const elapsed = Date.now() - pointerDownTime;
const dx = e.clientX - pointerDownPos.x;
const dy = e.clientY - pointerDownPos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
// Short tap with minimal movement = click
if (elapsed < 400 && dist < 15) {
this._handleTap(e);
}
});
}
_handleTap(event) {
const rect = this.renderer.domElement.getBoundingClientRect();
this.pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
this.pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
this.raycaster.setFromCamera(this.pointer, this.camera);
// Check core first
const coreHits = this.raycaster.intersectObjects(this.coreObjects, true);
if (coreHits.length > 0) {
this._deselectAgent();
this.ui.selectCore();
return;
}
// Check agents
const hits = this.raycaster.intersectObjects(this.interactables, true);
if (hits.length > 0) {
// Walk up to find agentId
let agentId = null;
let obj = hits[0].object;
while (obj) {
if (obj.userData && obj.userData.agentId) {
agentId = obj.userData.agentId;
break;
}
obj = obj.parent;
}
if (agentId) {
this._selectAgent(agentId);
return;
}
}
// Tap empty space — close panels
if (this.ui.isPanelOpen()) {
this._deselectAgent();
this.ui.closePanel();
this.ui.closeSystemPanel();
}
}
_selectAgent(agentId) {
if (this.selectedAgent === agentId) return;
// Deselect previous
this._deselectAgent();
this.selectedAgent = agentId;
this.agents.highlightAgent(agentId, true);
this.ui.selectAgent(agentId);
}
_deselectAgent() {
if (this.selectedAgent) {
this.agents.highlightAgent(this.selectedAgent, false);
this.selectedAgent = null;
}
}
update(delta) {
this.controls.update();
}
dispose() {
this.controls.dispose();
}
}