diff --git a/index.html b/index.html index 82d7b1f..aa9d6f4 100644 --- a/index.html +++ b/index.html @@ -337,7 +337,7 @@
- +
@@ -352,7 +352,7 @@
雷闪
- +
diff --git a/src/OceanScene.js b/src/OceanScene.js index cdbcc83..34c14a3 100644 --- a/src/OceanScene.js +++ b/src/OceanScene.js @@ -41,7 +41,11 @@ export class OceanScene { this.fogLayers = []; this.horizonFog = null; this.skyHazeBand = null; - this.rainAudio = null; + this.rainAudioPool = []; + this.rainAudioActiveIndex = 0; + this.rainAudioIsPlaying = false; + this.rainAudioCrossfading = false; + this.rainAudioCrossfadeDuration = 1.6; this.thunderAudioPool = []; this.thunderAudioIndex = 0; this.scheduledThunder = []; @@ -66,9 +70,9 @@ export class OceanScene { rainVeilIntensity: 1.15, rainDropSize: 1.0, rainSpeed: 1.0, - rainAudioEnabled: false, + rainAudioEnabled: true, rainAudioVolume: 0.35, - lightningEnabled: false, + lightningEnabled: true, lightningIntensity: 0.75, mieCoefficient: 0.005, mieDirectionalG: 0.8 @@ -191,11 +195,14 @@ export class OceanScene { } initAudio() { - this.rainAudio = new Audio(RAIN_AUDIO_URL); - this.rainAudio.loop = true; - this.rainAudio.preload = 'auto'; - this.rainAudio.volume = this.params.rainAudioVolume; - this.rainAudio.crossOrigin = 'anonymous'; + this.rainAudioPool = Array.from({ length: 2 }, () => { + const audio = new Audio(RAIN_AUDIO_URL); + audio.loop = false; + audio.preload = 'auto'; + audio.volume = 0; + audio.crossOrigin = 'anonymous'; + return audio; + }); this.thunderAudioPool = Array.from({ length: 3 }, () => { const audio = new Audio(THUNDER_AUDIO_URL); @@ -1054,6 +1061,16 @@ export class OceanScene { this.rainPass.enabled = value; } this.updateRainAudioState(); + if (!value) { + this.lightningFlash = 0; + this.lightningLocalFlash = 0; + this.lightningBurstEnd = 0; + this.nextLightningAt = 0; + this.lightningPulseSchedule = []; + this.scheduledThunder = []; + this.applyLightningState(0); + this.stopThunderAudio(); + } } setRainScreenIntensity(value) { @@ -1091,25 +1108,72 @@ export class OceanScene { setRainAudioVolume(value) { this.params.rainAudioVolume = value; - if (this.rainAudio) { - this.rainAudio.volume = value; - } this.updateRainAudioState(); } updateRainAudioState() { - if (!this.rainAudio) return; + if (this.rainAudioPool.length === 0) return; const shouldPlay = this.params.rainEnabled && this.params.rainAudioEnabled && this.params.rainAudioVolume > 0.001; if (shouldPlay) { - this.rainAudio.volume = this.params.rainAudioVolume; - const playPromise = this.rainAudio.play(); + if (!this.rainAudioIsPlaying) { + const active = this.rainAudioPool[this.rainAudioActiveIndex]; + active.currentTime = 0; + active.volume = this.params.rainAudioVolume; + const playPromise = active.play(); + if (playPromise?.catch) { + playPromise.catch(() => {}); + } + this.rainAudioIsPlaying = true; + this.rainAudioCrossfading = false; + } + } else { + for (const audio of this.rainAudioPool) { + audio.pause(); + audio.currentTime = 0; + audio.volume = 0; + } + this.rainAudioIsPlaying = false; + this.rainAudioCrossfading = false; + } + } + + updateRainAudioLoop() { + if (!this.rainAudioIsPlaying || this.rainAudioPool.length < 2) return; + + const active = this.rainAudioPool[this.rainAudioActiveIndex]; + const next = this.rainAudioPool[(this.rainAudioActiveIndex + 1) % this.rainAudioPool.length]; + const duration = Number.isFinite(active.duration) ? active.duration : 0; + if (duration <= 0) { + active.volume = this.params.rainAudioVolume; + return; + } + + const timeLeft = duration - active.currentTime; + if (!this.rainAudioCrossfading && timeLeft <= this.rainAudioCrossfadeDuration) { + next.currentTime = 0; + next.volume = 0; + const playPromise = next.play(); if (playPromise?.catch) { playPromise.catch(() => {}); } + this.rainAudioCrossfading = true; + } + + if (this.rainAudioCrossfading) { + const progress = THREE.MathUtils.clamp(1.0 - timeLeft / this.rainAudioCrossfadeDuration, 0, 1); + active.volume = this.params.rainAudioVolume * (1.0 - progress); + next.volume = this.params.rainAudioVolume * progress; + + if (progress >= 0.999 || active.ended) { + active.pause(); + active.currentTime = 0; + active.volume = 0; + this.rainAudioActiveIndex = (this.rainAudioActiveIndex + 1) % this.rainAudioPool.length; + this.rainAudioCrossfading = false; + } } else { - this.rainAudio.pause(); - this.rainAudio.currentTime = 0; + active.volume = this.params.rainAudioVolume; } } @@ -1193,7 +1257,7 @@ export class OceanScene { } playThunder(volume, playbackRate) { - if (!this.params.lightningEnabled || this.thunderAudioPool.length === 0) return; + if (!this.params.rainEnabled || !this.params.lightningEnabled || this.thunderAudioPool.length === 0) return; const audio = this.thunderAudioPool[this.thunderAudioIndex % this.thunderAudioPool.length]; this.thunderAudioIndex += 1; @@ -1208,7 +1272,7 @@ export class OceanScene { } updateThunder(time) { - if (!this.params.lightningEnabled || this.scheduledThunder.length === 0) return; + if (!this.params.rainEnabled || !this.params.lightningEnabled || this.scheduledThunder.length === 0) return; const pending = []; for (const thunder of this.scheduledThunder) { @@ -1222,7 +1286,7 @@ export class OceanScene { } updateLightning(time) { - if (!this.params.lightningEnabled) return; + if (!this.params.rainEnabled || !this.params.lightningEnabled) return; if (this.nextLightningAt === 0) { this.scheduleNextLightning(time); @@ -1361,6 +1425,7 @@ export class OceanScene { const time = this.clock.getElapsedTime(); this.updateLightning(time); this.updateThunder(time); + this.updateRainAudioLoop(); if (this.water) { this.water.material.uniforms['time'].value += 1.0 / 60.0;