Add distinct theme core animations and fix retro redraw
This commit is contained in:
+64
-34
@@ -13,6 +13,9 @@ class StarshipVisualizer {
|
||||
this.warpParticles = [];
|
||||
this.mode = 'warp-core'; // 'warp-core' or 'time-rotor'
|
||||
this.rotorPhase = 0;
|
||||
this.coreTime = 0;
|
||||
this.coreLoad = 0.6;
|
||||
this.lastFrameTime = null;
|
||||
|
||||
// Hook into warp core pulse callback
|
||||
if (this.warpSynth) {
|
||||
@@ -72,6 +75,8 @@ class StarshipVisualizer {
|
||||
}
|
||||
|
||||
spawnWarpPulses() {
|
||||
// The new machines render their own flow; only legacy particle modes need a pool.
|
||||
if (this.mode !== 'warp-core' && this.mode !== 'time-rotor') return;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
this.warpParticles.push({
|
||||
y: 0.5, // Center matter/antimatter reaction plane
|
||||
@@ -83,32 +88,48 @@ class StarshipVisualizer {
|
||||
}
|
||||
|
||||
startRenderLoop() {
|
||||
const render = () => {
|
||||
this.lastFrameTime = null;
|
||||
const render = (now) => {
|
||||
const elapsed = this.lastFrameTime === null ? 0 : Math.min((now - this.lastFrameTime) / 1000, 0.05);
|
||||
this.lastFrameTime = now;
|
||||
const volume = this.warpSynth && !this.warpSynth.isMuted ? this.warpSynth.params.volume : 0;
|
||||
const targetLoad = Math.max(0, Math.min(1, volume * (0.65 + this.pulseEnergy * 0.35)));
|
||||
this.coreLoad += (targetLoad - this.coreLoad) * (1 - Math.exp(-elapsed * 6));
|
||||
this.coreTime += elapsed * (this.mode === 'tactical-flywheel' ? 0.6 + this.coreLoad * 0.8 : 1);
|
||||
this.renderSpectrum();
|
||||
switch (this.mode) {
|
||||
case 'time-rotor':
|
||||
this.renderTimeRotor();
|
||||
break;
|
||||
case 'industrial-reactor':
|
||||
this.renderIndustrialReactor();
|
||||
break;
|
||||
case 'bio-heart':
|
||||
this.renderBioHeart();
|
||||
break;
|
||||
case 'retro-oscilloscope':
|
||||
this.renderRetroOscilloscope();
|
||||
break;
|
||||
case 'singularity-core':
|
||||
this.renderSingularityCore();
|
||||
break;
|
||||
case 'warp-core':
|
||||
default:
|
||||
this.renderWarpCore();
|
||||
break;
|
||||
if (window.CoreAnimations && CoreAnimations.has(this.mode)) {
|
||||
if (this.warpCoreCanvas && this.warpCoreCtx) {
|
||||
CoreAnimations.render(this.warpCoreCtx, this.mode,
|
||||
this.warpCoreCanvas.width / window.devicePixelRatio,
|
||||
this.warpCoreCanvas.height / window.devicePixelRatio,
|
||||
this.coreTime, this.coreLoad);
|
||||
}
|
||||
} else {
|
||||
switch (this.mode) {
|
||||
case 'time-rotor':
|
||||
this.renderTimeRotor();
|
||||
break;
|
||||
case 'industrial-reactor':
|
||||
this.renderIndustrialReactor();
|
||||
break;
|
||||
case 'bio-heart':
|
||||
this.renderBioHeart();
|
||||
break;
|
||||
case 'retro-oscilloscope':
|
||||
this.renderRetroOscilloscope();
|
||||
break;
|
||||
case 'singularity-core':
|
||||
this.renderSingularityCore();
|
||||
break;
|
||||
case 'warp-core':
|
||||
default:
|
||||
this.renderWarpCore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Decay pulse energy smoothly
|
||||
this.pulseEnergy = Math.max(0.15, this.pulseEnergy * 0.94);
|
||||
this.pulseEnergy = Math.max(0.15, this.pulseEnergy * Math.pow(0.94, elapsed * 60));
|
||||
|
||||
this.animationFrameId = requestAnimationFrame(render);
|
||||
};
|
||||
@@ -135,7 +156,7 @@ class StarshipVisualizer {
|
||||
if (this.mode === 'time-rotor' || this.mode === 'singularity-core') gridCol = 'rgba(0, 229, 255, 0.12)';
|
||||
else if (this.mode === 'bio-heart') gridCol = 'rgba(16, 185, 129, 0.12)';
|
||||
else if (this.mode === 'retro-oscilloscope') gridCol = 'rgba(34, 197, 94, 0.15)';
|
||||
else if (this.mode === 'industrial-reactor') gridCol = 'rgba(245, 158, 11, 0.15)';
|
||||
else if (this.mode === 'industrial-reactor' || this.mode === 'compression-furnace' || this.mode === 'station-hub') gridCol = 'rgba(245, 158, 11, 0.15)';
|
||||
|
||||
ctx.strokeStyle = gridCol;
|
||||
ctx.lineWidth = 1;
|
||||
@@ -171,7 +192,7 @@ class StarshipVisualizer {
|
||||
else col = '#c084fc';
|
||||
} else if (this.mode === 'retro-oscilloscope') {
|
||||
col = i < 28 ? '#22c55e' : '#86efac';
|
||||
} else if (this.mode === 'industrial-reactor') {
|
||||
} else if (this.mode === 'industrial-reactor' || this.mode === 'compression-furnace' || this.mode === 'station-hub') {
|
||||
if (i < 8) col = '#d97706';
|
||||
else if (i < 20) col = '#f59e0b';
|
||||
else if (i < 28) col = '#fbbf24';
|
||||
@@ -502,8 +523,8 @@ class StarshipVisualizer {
|
||||
* Retro Oscilloscope & Analog Astrogator (Jupiter 2, Discovery One)
|
||||
* 1950s/60s green phosphor CRT screen with glowing Lissajous audio wave rings
|
||||
*/
|
||||
renderRetroOscilloscope() {
|
||||
if (!this.warpCoreCanvas || !this.warpCoreCtx || !this.am.analyser) return;
|
||||
renderRetroOscilloscope() {
|
||||
if (!this.warpCoreCanvas || !this.warpCoreCtx) return;
|
||||
const ctx = this.warpCoreCtx;
|
||||
const w = this.warpCoreCanvas.width / window.devicePixelRatio;
|
||||
const h = this.warpCoreCanvas.height / window.devicePixelRatio;
|
||||
@@ -532,10 +553,12 @@ class StarshipVisualizer {
|
||||
ctx.lineTo(centerX, centerY + crtRadius);
|
||||
ctx.stroke();
|
||||
|
||||
// Lissajous audio waveform
|
||||
const bufferLength = this.am.analyser.fftSize;
|
||||
const dataArray = new Uint8Array(bufferLength);
|
||||
this.am.analyser.getByteTimeDomainData(dataArray);
|
||||
// Draw a neutral trace before audio initialization, replacing the previous theme.
|
||||
const analyser = this.am.analyser;
|
||||
const bufferLength = analyser ? analyser.fftSize : 128;
|
||||
const dataArray = new Uint8Array(bufferLength);
|
||||
if (analyser) analyser.getByteTimeDomainData(dataArray);
|
||||
else dataArray.fill(128);
|
||||
|
||||
ctx.strokeStyle = '#86efac';
|
||||
ctx.shadowColor = '#22c55e';
|
||||
@@ -547,16 +570,23 @@ class StarshipVisualizer {
|
||||
for (let i = 0; i < points; i++) {
|
||||
const idx = Math.floor((i / points) * (bufferLength / 2));
|
||||
const v = (dataArray[idx] / 128.0) - 1.0;
|
||||
const angle = (i / points) * Math.PI * 2 + this.rotorPhase;
|
||||
const angle = (i / points) * Math.PI * 2 + this.coreTime;
|
||||
const r = (crtRadius * 0.65) + (v * 28 * (0.8 + this.pulseEnergy));
|
||||
const x = centerX + Math.cos(angle) * r;
|
||||
const y = centerY + Math.sin(angle) * r;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
// A sweep marker makes rotation legible even when the audio trace is silent.
|
||||
const sweepRadius = (crtRadius * 0.65) + ((dataArray[0] / 128.0) - 1.0) * 28 * (0.8 + this.pulseEnergy);
|
||||
ctx.fillStyle = '#d1fae5';
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX + Math.cos(this.coreTime) * sweepRadius,
|
||||
centerY + Math.sin(this.coreTime) * sweepRadius, 2.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user