forked from Rockachopa/the-matrix
Compare commits
1 Commits
main
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21caa2ad5e |
107
js/agents.js
107
js/agents.js
@@ -1,5 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { AGENT_DEFS, colorToCss } from './agent-defs.js';
|
import { AGENT_DEFS, colorToCss } from './agent-defs.js';
|
||||||
|
import { getQualityTier } from './quality.js';
|
||||||
|
|
||||||
const agents = new Map();
|
const agents = new Map();
|
||||||
let scene;
|
let scene;
|
||||||
@@ -66,6 +67,105 @@ class Agent {
|
|||||||
const light = new THREE.PointLight(this.color, 1.5, 10);
|
const light = new THREE.PointLight(this.color, 1.5, 10);
|
||||||
this.group.add(light);
|
this.group.add(light);
|
||||||
this.light = light;
|
this.light = light;
|
||||||
|
|
||||||
|
this._initParticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
_initParticles() {
|
||||||
|
const tier = getQualityTier();
|
||||||
|
// Particle counts scaled by quality tier — fewer on mobile for performance
|
||||||
|
const count = tier === 'low' ? 20 : tier === 'medium' ? 40 : 64;
|
||||||
|
this._pCount = count;
|
||||||
|
this._pPos = new Float32Array(count * 3); // local-space positions
|
||||||
|
this._pVel = new Float32Array(count * 3); // velocity in units/ms
|
||||||
|
this._pLife = new Float32Array(count); // remaining lifetime in ms (0 = dead)
|
||||||
|
this._pEmit = 0; // fractional emit accumulator
|
||||||
|
// Particles emitted per ms when active
|
||||||
|
this._pRate = tier === 'low' ? 0.005 : tier === 'medium' ? 0.01 : 0.016;
|
||||||
|
this._pLastT = null;
|
||||||
|
|
||||||
|
// Start all particles hidden
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
this._pPos[i * 3 + 1] = -1000;
|
||||||
|
this._pLife[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const geo = new THREE.BufferGeometry();
|
||||||
|
geo.setAttribute('position', new THREE.BufferAttribute(this._pPos, 3));
|
||||||
|
|
||||||
|
const mat = new THREE.PointsMaterial({
|
||||||
|
color: this.color,
|
||||||
|
size: tier === 'low' ? 0.2 : 0.14,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.9,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
sizeAttenuation: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._pMesh = new THREE.Points(geo, mat);
|
||||||
|
this._pMesh.frustumCulled = false;
|
||||||
|
this.group.add(this._pMesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
_spawnParticle() {
|
||||||
|
for (let i = 0; i < this._pCount; i++) {
|
||||||
|
if (this._pLife[i] > 0) continue;
|
||||||
|
// Spawn near agent core
|
||||||
|
const spread = 0.5;
|
||||||
|
this._pPos[i * 3] = (Math.random() - 0.5) * spread;
|
||||||
|
this._pPos[i * 3 + 1] = (Math.random() - 0.5) * spread;
|
||||||
|
this._pPos[i * 3 + 2] = (Math.random() - 0.5) * spread;
|
||||||
|
// Outward + upward drift (units per ms)
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
const radial = 0.0008 + Math.random() * 0.0018;
|
||||||
|
const rise = 0.001 + Math.random() * 0.003;
|
||||||
|
this._pVel[i * 3] = Math.cos(angle) * radial;
|
||||||
|
this._pVel[i * 3 + 1] = rise;
|
||||||
|
this._pVel[i * 3 + 2] = Math.sin(angle) * radial;
|
||||||
|
// Lifetime 600–1400 ms
|
||||||
|
this._pLife[i] = 600 + Math.random() * 800;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateParticles(time) {
|
||||||
|
if (!this._pMesh) return;
|
||||||
|
|
||||||
|
const active = this.state === 'active';
|
||||||
|
const dt = this._pLastT === null ? 16 : Math.min(time - this._pLastT, 100);
|
||||||
|
this._pLastT = time;
|
||||||
|
|
||||||
|
// Emit sparks while active
|
||||||
|
if (active) {
|
||||||
|
this._pEmit += dt * this._pRate;
|
||||||
|
while (this._pEmit >= 1) {
|
||||||
|
this._pEmit -= 1;
|
||||||
|
this._spawnParticle();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._pEmit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integrate existing particles
|
||||||
|
let anyAlive = false;
|
||||||
|
for (let i = 0; i < this._pCount; i++) {
|
||||||
|
if (this._pLife[i] <= 0) continue;
|
||||||
|
this._pLife[i] -= dt;
|
||||||
|
if (this._pLife[i] <= 0) {
|
||||||
|
this._pLife[i] = 0;
|
||||||
|
this._pPos[i * 3 + 1] = -1000; // move off-screen
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._pPos[i * 3] += this._pVel[i * 3] * dt;
|
||||||
|
this._pPos[i * 3 + 1] += this._pVel[i * 3 + 1] * dt;
|
||||||
|
this._pPos[i * 3 + 2] += this._pVel[i * 3 + 2] * dt;
|
||||||
|
anyAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pMesh.geometry.attributes.position.needsUpdate = true;
|
||||||
|
// Fade opacity: visible when active or particles still coasting
|
||||||
|
this._pMesh.material.opacity = (active || anyAlive) ? 0.9 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildLabel() {
|
_buildLabel() {
|
||||||
@@ -104,6 +204,8 @@ class Agent {
|
|||||||
this.ring.material.opacity = 0.3 + pulse * 0.2;
|
this.ring.material.opacity = 0.3 + pulse * 0.2;
|
||||||
|
|
||||||
this.group.position.y = this.position.y + Math.sin(time * 0.001 + this.pulsePhase) * 0.15;
|
this.group.position.y = this.position.y + Math.sin(time * 0.001 + this.pulsePhase) * 0.15;
|
||||||
|
|
||||||
|
this._updateParticles(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state) {
|
setState(state) {
|
||||||
@@ -120,6 +222,11 @@ class Agent {
|
|||||||
this.glow.material.dispose();
|
this.glow.material.dispose();
|
||||||
this.sprite.material.map.dispose();
|
this.sprite.material.map.dispose();
|
||||||
this.sprite.material.dispose();
|
this.sprite.material.dispose();
|
||||||
|
if (this._pMesh) {
|
||||||
|
this._pMesh.geometry.dispose();
|
||||||
|
this._pMesh.material.dispose();
|
||||||
|
this._pMesh = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user