const MAX_RINGS = 40; const MAX_GLOWS = 60; const MAX_STREAKS = 90; const MAX_ARCS = 28; const easeOut = (t) => 1 - (1 - t) * (1 - t); export class Effects { constructor() { this.rings = []; this.glows = []; this.streaks = []; this.arcs = []; } ring(x, y, { endRadius = 140, width = 5, life = 0.5, color = [1, 1, 1, 1], additive = true, } = {}) { if (this.rings.length >= MAX_RINGS) this.rings.shift(); this.rings.push({ x, y, radius: endRadius * 0.12, endRadius, width, life, maxLife: life, color, additive, }); } glow(x, y, { radius = 90, life = 0.35, color = [1, 1, 1, 1], additive = true, } = {}) { if (this.glows.length >= MAX_GLOWS) this.glows.shift(); this.glows.push({ x, y, radius: radius * 0.2, endRadius: radius, life, maxLife: life, color, additive, }); } streak(x, y, angle, length, { width = 3, life = 0.25, fade = true, color = [1, 1, 1, 1], additive = true, tail = 0.7, } = {}) { if (this.streaks.length >= MAX_STREAKS) this.streaks.shift(); this.streaks.push({ x: x - Math.cos(angle) * length * tail, y: y - Math.sin(angle) * length * tail, angle, length, width, life, maxLife: life, fade, color, additive, tail, }); } arc(x1, y1, x2, y2, { segments = 8, width = 3, life = 0.3, color = [1, 1, 1, 1], jitter = 26, additive = true, } = {}) { if (this.arcs.length >= MAX_ARCS) this.arcs.shift(); this.arcs.push({ x1, y1, x2, y2, segments, width, life, maxLife: life, color, jitter, additive, seed: Math.random() * 100, }); } update(dt) { const tick = (list) => { for (let i = list.length - 1; i >= 0; i -= 1) { list[i].life -= dt; if (list[i].life <= 0) list.splice(i, 1); } }; tick(this.rings); tick(this.glows); tick(this.streaks); tick(this.arcs); } draw(renderer) { for (const list of [this.glows, this.rings, this.streaks, this.arcs]) { const additive = list.filter((effect) => effect.additive); const normal = list.filter((effect) => !effect.additive); if (additive.length > 0) renderer.setBlendMode('additive'); for (const effect of additive) this._drawEffect(renderer, effect); if (additive.length > 0) renderer.setBlendMode('normal'); for (const effect of normal) this._drawEffect(renderer, effect); } } _drawEffect(renderer, effect) { const t = 1 - effect.life / effect.maxLife; const alpha = effect.life / effect.maxLife; if (effect.x1 !== undefined) { this._drawArc(renderer, effect, t, alpha); } else if (effect.angle !== undefined) { this._drawStreak(renderer, effect, t, alpha); } else if (effect.width !== undefined) { this._drawRing(renderer, effect, t, alpha); } else { this._drawGlow(renderer, effect, t, alpha); } } _drawGlow(renderer, effect, t, alpha) { const radius = effect.radius + (effect.endRadius - effect.radius) * easeOut(t); renderer.drawTexture( renderer.glowTexture, effect.x - radius, effect.y - radius, radius * 2, radius * 2, [ effect.color[0], effect.color[1], effect.color[2], effect.color[3] * alpha, ], ); } _drawRing(renderer, effect, t, alpha) { const radius = effect.radius + (effect.endRadius - effect.radius) * easeOut(t); const fade = Math.min(1, alpha * 1.3); renderer.drawTexture( renderer.ringTexture, effect.x - radius, effect.y - radius, radius * 2, radius * 2, [ effect.color[0], effect.color[1], effect.color[2], Math.max(0, effect.color[3] * fade), ], ); } _drawStreak(renderer, effect, t, alpha) { const progress = easeOut(t); const len = effect.length * (1 - progress * (1 - effect.tail)); const fade = effect.fade ? alpha : 1; renderer.drawTexture( renderer.glowTexture, effect.x, effect.y, len, effect.width, [ effect.color[0], effect.color[1], effect.color[2], effect.color[3] * fade, ], effect.angle, ); } _drawArc(renderer, effect, t, alpha) { const { x1, y1, x2, y2, segments, width, jitter, seed, color } = effect; const wobble = 0.45 + 0.55 * Math.abs(Math.sin(seed + t * 34)); const amp = jitter * alpha * wobble; let prevX = x1; let prevY = y1; for (let i = 1; i <= segments; i += 1) { const u = i / segments; const edge = (i === segments) ? 0 : 1; const px = x1 + (x2 - x1) * u + (Math.random() - 0.5) * 2 * amp * edge; const py = y1 + (y2 - y1) * u + (Math.random() - 0.5) * 2 * amp * edge; const len = Math.hypot(px - prevX, py - prevY); if (len > 0) { renderer.drawTexture( renderer.whiteTexture, prevX, prevY, len, width, [color[0], color[1], color[2], color[3] * alpha], Math.atan2(py - prevY, px - prevX), ); } prevX = px; prevY = py; } } clear() { this.rings.length = 0; this.glows.length = 0; this.streaks.length = 0; this.arcs.length = 0; } }