diff --git a/index.html b/index.html
index f15e7e6..82d7b1f 100644
--- a/index.html
+++ b/index.html
@@ -335,6 +335,17 @@
1.00
+
+
+
+
+
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));
}