Echo / src /scene /ParticleField.js
mlmihjaz's picture
Initial commit: ECHO 3D vocab game with Node+SQLite backend, admin dashboard, Docker deploy
173e683
Raw
History Blame Contribute Delete
1.87 kB
/**
* ParticleField — twinkling stars overhead, fade in as difficulty rises
*/
import {
AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture,
Color, Points, PointsMaterial,
} from 'three';
function starTexture() {
const c = document.createElement('canvas');
c.width = c.height = 64;
const ctx = c.getContext('2d');
const g = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
g.addColorStop(0, 'rgba(255,255,255,1)');
g.addColorStop(0.35, 'rgba(255,255,255,0.85)');
g.addColorStop(0.7, 'rgba(255,255,255,0.15)');
g.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = g;
ctx.beginPath(); ctx.arc(32, 32, 32, 0, Math.PI * 2); ctx.fill();
const t = new CanvasTexture(c);
t.needsUpdate = true;
return t;
}
export class ParticleField {
constructor(scene) {
this.scene = scene;
const N = 700;
const geom = new BufferGeometry();
const pos = new Float32Array(N * 3);
for (let i = 0; i < N; i++) {
pos[i * 3] = (Math.random() * 2 - 1) * 90;
pos[i * 3 + 1] = 12 + Math.random() * 45;
pos[i * 3 + 2] = -20 - Math.random() * 55;
}
geom.setAttribute('position', new BufferAttribute(pos, 3));
this.material = new PointsMaterial({
size: 0.6,
color: 0xffffff,
map: starTexture(),
transparent: true,
opacity: 0,
blending: AdditiveBlending,
depthWrite: false,
});
this.points = new Points(geom, this.material);
this.scene.add(this.points);
this.t = 0;
}
applyPalette(p) {
this.targetOpacity = p.starOpacity;
}
update(dt) {
this.t += dt;
this.points.rotation.y += 0.005 * dt;
if (this.targetOpacity !== undefined) {
this.material.opacity += (this.targetOpacity - this.material.opacity) * 0.04;
}
// gentle twinkle
this.material.size = 0.55 + Math.sin(this.t * 2) * 0.07;
}
}