[claude] Lightning arcs between floating crystals during high activity (#257) (#338)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
This commit was merged in pull request #338.
This commit is contained in:
145
app.js
145
app.js
@@ -1073,6 +1073,134 @@ function startWarp() {
|
||||
warpPass.uniforms['distortionStrength'].value = 0.0;
|
||||
}
|
||||
|
||||
// === FLOATING CRYSTALS & LIGHTNING ARCS ===
|
||||
// Crystals float above the platform. When zone activity is high, lightning arcs jump between them.
|
||||
|
||||
const CRYSTAL_COUNT = 5;
|
||||
const CRYSTAL_BASE_POSITIONS = [
|
||||
new THREE.Vector3(-4.5, 3.2, -3.8),
|
||||
new THREE.Vector3( 4.8, 2.8, -4.0),
|
||||
new THREE.Vector3(-5.5, 4.0, 1.5),
|
||||
new THREE.Vector3( 5.2, 3.5, 2.0),
|
||||
new THREE.Vector3( 0.0, 5.0, -5.5),
|
||||
];
|
||||
// Colors aligned to agent zones: Claude, Timmy, Kimi, Perplexity, center
|
||||
const CRYSTAL_COLORS = [0xff6440, 0x40a0ff, 0x40ff8c, 0xc840ff, 0xffd700];
|
||||
|
||||
const crystalGroup = new THREE.Group();
|
||||
scene.add(crystalGroup);
|
||||
|
||||
/**
|
||||
* @type {Array<{mesh: THREE.Mesh, light: THREE.PointLight, basePos: THREE.Vector3, floatPhase: number}>}
|
||||
*/
|
||||
const crystals = [];
|
||||
|
||||
for (let i = 0; i < CRYSTAL_COUNT; i++) {
|
||||
const geo = new THREE.OctahedronGeometry(0.35, 0);
|
||||
const color = CRYSTAL_COLORS[i];
|
||||
const mat = new THREE.MeshStandardMaterial({
|
||||
color,
|
||||
emissive: new THREE.Color(color).multiplyScalar(0.6),
|
||||
roughness: 0.05,
|
||||
metalness: 0.3,
|
||||
transparent: true,
|
||||
opacity: 0.88,
|
||||
});
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
const basePos = CRYSTAL_BASE_POSITIONS[i].clone();
|
||||
mesh.position.copy(basePos);
|
||||
mesh.userData.zoomLabel = 'Crystal';
|
||||
crystalGroup.add(mesh);
|
||||
|
||||
const light = new THREE.PointLight(color, 0.3, 6);
|
||||
light.position.copy(basePos);
|
||||
crystalGroup.add(light);
|
||||
|
||||
crystals.push({ mesh, light, basePos, floatPhase: (i / CRYSTAL_COUNT) * Math.PI * 2 });
|
||||
}
|
||||
|
||||
// ---- Lightning arc pool ----
|
||||
|
||||
const LIGHTNING_POOL_SIZE = 6;
|
||||
const LIGHTNING_SEGMENTS = 8;
|
||||
const LIGHTNING_REFRESH_MS = 130;
|
||||
let lastLightningRefreshTime = 0;
|
||||
|
||||
/** @type {Array<THREE.Line>} */
|
||||
const lightningArcs = [];
|
||||
|
||||
for (let i = 0; i < LIGHTNING_POOL_SIZE; i++) {
|
||||
const positions = new Float32Array((LIGHTNING_SEGMENTS + 1) * 3);
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
const mat = new THREE.LineBasicMaterial({
|
||||
color: 0x88ccff,
|
||||
transparent: true,
|
||||
opacity: 0.0,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
});
|
||||
const arc = new THREE.Line(geo, mat);
|
||||
scene.add(arc);
|
||||
lightningArcs.push(arc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a jagged lightning path between two points.
|
||||
* @param {THREE.Vector3} start
|
||||
* @param {THREE.Vector3} end
|
||||
* @param {number} jagAmount
|
||||
* @returns {Float32Array}
|
||||
*/
|
||||
function buildLightningPath(start, end, jagAmount) {
|
||||
const out = new Float32Array((LIGHTNING_SEGMENTS + 1) * 3);
|
||||
for (let s = 0; s <= LIGHTNING_SEGMENTS; s++) {
|
||||
const t = s / LIGHTNING_SEGMENTS;
|
||||
const x = start.x + (end.x - start.x) * t + (s > 0 && s < LIGHTNING_SEGMENTS ? (Math.random() - 0.5) * jagAmount : 0);
|
||||
const y = start.y + (end.y - start.y) * t + (s > 0 && s < LIGHTNING_SEGMENTS ? (Math.random() - 0.5) * jagAmount : 0);
|
||||
const z = start.z + (end.z - start.z) * t + (s > 0 && s < LIGHTNING_SEGMENTS ? (Math.random() - 0.5) * jagAmount : 0);
|
||||
out[s * 3] = x; out[s * 3 + 1] = y; out[s * 3 + 2] = z;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mean activity [0..1] across all agent zones.
|
||||
*/
|
||||
function totalActivity() {
|
||||
const vals = Object.values(zoneIntensity);
|
||||
return vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes lightning arcs based on current activity level.
|
||||
*/
|
||||
function updateLightningArcs() {
|
||||
const activity = totalActivity();
|
||||
const activeCount = Math.round(activity * LIGHTNING_POOL_SIZE);
|
||||
|
||||
for (let i = 0; i < LIGHTNING_POOL_SIZE; i++) {
|
||||
const arc = lightningArcs[i];
|
||||
if (i >= activeCount) {
|
||||
arc.material.opacity = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
const a = Math.floor(Math.random() * CRYSTAL_COUNT);
|
||||
let b = Math.floor(Math.random() * (CRYSTAL_COUNT - 1));
|
||||
if (b >= a) b++;
|
||||
|
||||
const jagAmount = 0.45 + activity * 0.85;
|
||||
const path = buildLightningPath(crystals[a].mesh.position, crystals[b].mesh.position, jagAmount);
|
||||
const attr = arc.geometry.attributes.position;
|
||||
attr.array.set(path);
|
||||
attr.needsUpdate = true;
|
||||
|
||||
arc.material.opacity = (0.35 + Math.random() * 0.55) * Math.min(activity * 1.5, 1.0);
|
||||
arc.material.color.setHex(CRYSTAL_COLORS[Math.floor(Math.random() * CRYSTAL_COLORS.length)]);
|
||||
}
|
||||
}
|
||||
|
||||
// === ANIMATION LOOP ===
|
||||
const clock = new THREE.Clock();
|
||||
|
||||
@@ -1301,6 +1429,23 @@ function animate() {
|
||||
}
|
||||
}
|
||||
|
||||
// Animate crystals — gentle float and slow spin
|
||||
const activity = totalActivity();
|
||||
for (const crystal of crystals) {
|
||||
crystal.mesh.position.x = crystal.basePos.x;
|
||||
crystal.mesh.position.y = crystal.basePos.y + Math.sin(elapsed * 0.65 + crystal.floatPhase) * 0.35;
|
||||
crystal.mesh.position.z = crystal.basePos.z;
|
||||
crystal.mesh.rotation.y = elapsed * 0.4 + crystal.floatPhase;
|
||||
crystal.light.position.copy(crystal.mesh.position);
|
||||
crystal.light.intensity = 0.2 + activity * 0.8 + Math.sin(elapsed * 2.0 + crystal.floatPhase) * 0.1;
|
||||
}
|
||||
|
||||
// Refresh lightning arcs periodically
|
||||
if (elapsed * 1000 - lastLightningRefreshTime > LIGHTNING_REFRESH_MS) {
|
||||
lastLightningRefreshTime = elapsed * 1000;
|
||||
updateLightningArcs();
|
||||
}
|
||||
|
||||
composer.render();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user