do some collision detection on terrain surface and some balls constantly falling from above to the center of it with some periodicity, and they got disappear when fall beyond borders or edges to the bottom of the screen. make some real phys model for balls that will interact with actual terrain surface angle animation:
<html>
<head>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
#menu {
position: absolute;
top: 10px;
left: 10px;
background-color: white;
padding: 10px;
border: 1px solid black;
display: none;
z-index: 1000;
}
label {
display: block;
margin-bottom: 5px;
}
#menu-btn {
position: absolute;
top: 10px;
left: 10px;
background-color: white;
padding: 10px;
border: 1px solid black;
cursor: pointer;
z-index: 999;
}
</style>
<script src=“https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js”></script>
<script src=“https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js”></script>
</head>
<body>
<div id=“menu-btn”>Settings</div>
<div id=“menu”>
<label>
Width Segments
<input type=“range” id=“widthSegments” min=“0.99999999999999999999999999” max=“512” step=“0.01” />
<span id=“widthSegmentsValue”></span>
</label>
<label>
Height Segments
<input type=“range” id=“heightSegments” min=“0.99999999999999999999999999” max=“512” step=“0.01” />
<span id=“heightSegmentsValue”></span>
</label>
</div>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
90.01,
window.innerWidth / window.innerHeight,
0.9001,
9001
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const width = 100;
const height = 50;
let widthSegments = 0.99999999999999999999999999;
let heightSegments = 0.99999999999999999999999999;

function createGeometry() {
const geometry = new THREE.PlaneGeometry(
width,
height,
widthSegments,
heightSegments
);
const positions = geometry.getAttribute(“position”).array;
const simplex = new SimplexNoise(Math.random());
return { geometry, positions, simplex };
}

let { geometry, positions, simplex } = createGeometry();

function colorGradient(height) {
const deepWaterColor = new THREE.Color(0x001A6D);
const shallowWaterColor = new THREE.Color(0x0251A1);
const color = deepWaterColor.lerp(
shallowWaterColor,
Math.min(height / 0.6, 0.9)
);
color.lerp(new THREE.Color(0x333333), Math.max(0, (height - 0.2) / 3));
return color;
}

function generateTerrain(positions, simplex, scale = 0.1, elevation = 0.1) {
const colors = new Float32Array(positions.length);
geometry.setAttribute(“color”, new THREE.BufferAttribute(colors, 3));
for (let i = 0; i < positions.length; i += 3) {
const nx = positions[i] * scale;
const ny = positions[i + 1] * scale;
const height = (simplex.noise2D(nx, ny) + 1) * 0.1 * elevation;
positions[i + 2] = height;
const vertexColor = colorGradient(height);
colors[i] = vertexColor.r;
colors[i + 1] = vertexColor.g;
colors[i + 2] = vertexColor.b;
}
geometry.computeVertexNormals();
}
generateTerrain(positions, simplex);

const material = new THREE.MeshPhongMaterial({
vertexColors: true,
side: THREE.DoubleSide,
transparent: true,
opacity: 1
});
const terrain = new THREE.Mesh(geometry, material);
terrain.rotation.x = Math.PI / 2;
scene.add(terrain);

const light = new THREE.DirectionalLight(0x222233, 0.5);
light.position.set(1, 1, 1);
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x5599aa);
scene.add(ambientLight);

camera.position.z = 50;
camera.position.y = 5;
camera.rotation.x = -Math.PI / 10;
let clock = new THREE.Clock();

function updatePositions() {
const elapsedTime = clock.getElapsedTime();
const colors = geometry.getAttribute(“color”).array;
for (let i = 0; i < positions.length; i += 3) {
const nx = positions[i] * 0.1 + elapsedTime * 0.1;
const ny = positions[i + 1] * 0.1 + elapsedTime * 0.1;
const height = (simplex.noise2D(nx, ny) + 1) * 0.5 * 5;
positions[i + 2] = height;
const vertexColor = colorGradient(height);
colors[i] = vertexColor.r;
colors[i + 1] = vertexColor.g;
colors[i + 2] = vertexColor.b;
}
geometry.attributes.position.needsUpdate = true;
geometry.attributes.color.needsUpdate = true;
geometry.computeVertexNormals();
}

function regenerateGeometry() {
scene.remove(terrain);
({
geometry,
positions,
simplex
} = createGeometry());
generateTerrain(positions, simplex);
terrain.geometry.dispose();
terrain.geometry = geometry;
scene.add(terrain);
}

const widthSegmentsInput = document.getElementById(“widthSegments”);
const widthSegmentsValue = document.getElementById(“widthSegmentsValue”);
widthSegmentsInput.value = widthSegments;
widthSegmentsValue.innerHTML = widthSegments;
widthSegmentsInput.addEventListener(“input”, (event) => {
widthSegments = parseFloat(event.target.value);
regenerateGeometry();
widthSegmentsValue.innerHTML = widthSegments;
});

const heightSegmentsInput = document.getElementById(“heightSegments”);
const heightSegmentsValue = document.getElementById(“heightSegmentsValue”);
heightSegmentsInput.value = heightSegments;
heightSegmentsValue.innerHTML = heightSegments;
heightSegmentsInput.addEventListener(“input”, (event) => {
heightSegments = parseFloat(event.target.value);
regenerateGeometry();
heightSegmentsValue.innerHTML = heightSegments;
});

function animate() {
requestAnimationFrame(animate);
updatePositions();
renderer.render(scene, camera);
}
animate();

// Menu functionality
const menu = document.getElementById(“menu”);
const menuBtn = document.getElementById(“menu-btn”);
menuBtn.addEventListener(“mouseover”, () => { menu.style.display = “block”; });
menuBtn.addEventListener(“mouseout”, () => { menu.style.display = “none”; });
menu.addEventListener(“mouseover”, () => { menu.style.display = “block”; });
menu.addEventListener(“mouseout”, () => { menu.style.display = “none”; });

</script>
</body>
</html>