diff --git a/index.html b/index.html index f15e7e6..82d7b1f 100644 --- a/index.html +++ b/index.html @@ -335,6 +335,17 @@
1.00
+
+ + +
+
+
+ + +
0.35
+
+
diff --git a/public/audio/rain-calming.mp3 b/public/audio/rain-calming.mp3 new file mode 100644 index 0000000..a97f334 Binary files /dev/null and b/public/audio/rain-calming.mp3 differ diff --git a/src/OceanScene.js b/src/OceanScene.js index 1b6c7df..46562f3 100644 --- a/src/OceanScene.js +++ b/src/OceanScene.js @@ -9,6 +9,8 @@ import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { TerrainGenerator } from './TerrainGenerator.js'; import { VegetationSystem } from './VegetationSystem.js'; +const RAIN_AUDIO_URL = '/audio/rain-calming.mp3'; + export class OceanScene { constructor(container) { this.container = container; @@ -38,6 +40,7 @@ export class OceanScene { this.fogLayers = []; this.horizonFog = null; this.skyHazeBand = null; + this.rainAudio = null; this.params = { elevation: 2, @@ -59,6 +62,8 @@ export class OceanScene { rainVeilIntensity: 1.15, rainDropSize: 1.0, rainSpeed: 1.0, + rainAudioEnabled: false, + rainAudioVolume: 0.35, lightningEnabled: false, lightningIntensity: 0.75, mieCoefficient: 0.005, @@ -82,6 +87,7 @@ export class OceanScene { this.initControls(); this.initLighting(); this.initPostProcessing(); + this.initAudio(); await this.initSky(); this.initClouds(); this.initFog(); @@ -180,6 +186,14 @@ export class OceanScene { this.composer.addPass(this.rainPass); } + 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'; + } + createRainShader() { return { uniforms: { @@ -1028,6 +1042,7 @@ export class OceanScene { if (this.rainPass) { this.rainPass.enabled = value; } + this.updateRainAudioState(); } setRainScreenIntensity(value) { @@ -1058,6 +1073,35 @@ export class OceanScene { } } + setRainAudioEnabled(value) { + this.params.rainAudioEnabled = value; + this.updateRainAudioState(); + } + + setRainAudioVolume(value) { + this.params.rainAudioVolume = value; + if (this.rainAudio) { + this.rainAudio.volume = value; + } + this.updateRainAudioState(); + } + + updateRainAudioState() { + if (!this.rainAudio) 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 (playPromise?.catch) { + playPromise.catch(() => {}); + } + } else { + this.rainAudio.pause(); + this.rainAudio.currentTime = 0; + } + } + setLightningEnabled(value) { this.params.lightningEnabled = value; if (!value) { diff --git a/src/main.js b/src/main.js index f19c042..121cb94 100644 --- a/src/main.js +++ b/src/main.js @@ -68,8 +68,10 @@ function setupControls(oceanScene) { bindSlider('rain-veil-intensity', (value) => value.toFixed(2), (value) => oceanScene.setRainVeilIntensity(value)); bindSlider('rain-drop-size', (value) => value.toFixed(2), (value) => oceanScene.setRainDropSize(value)); bindSlider('rain-speed', (value) => value.toFixed(2), (value) => oceanScene.setRainSpeed(value)); + bindSlider('rain-audio-volume', (value) => value.toFixed(2), (value) => oceanScene.setRainAudioVolume(value)); bindSlider('lightning-intensity', (value) => value.toFixed(2), (value) => oceanScene.setLightningIntensity(value)); bindCheckbox('rain-enabled', (value) => oceanScene.setRainEnabled(value)); + bindCheckbox('rain-audio-enabled', (value) => oceanScene.setRainAudioEnabled(value)); bindCheckbox('lightning-enabled', (value) => oceanScene.setLightningEnabled(value)); }