| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>Procedural Terrain</title> |
| <style> |
| body { margin: 0; overflow: hidden; } |
| #controls { |
| position: fixed; |
| top: 20px; |
| left: 20px; |
| background: rgba(0,0,0,0.7); |
| padding: 20px; |
| border-radius: 10px; |
| color: white; |
| font-family: Arial, sans-serif; |
| } |
| .control-group { |
| margin: 10px 0; |
| } |
| label { |
| display: inline-block; |
| width: 120px; |
| } |
| input[type="range"] { |
| width: 200px; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="controls"> |
| <div class="control-group"> |
| <label>Height Scale:</label> |
| <input type="range" id="heightScale" min="0" max="100" value="20"> |
| </div> |
| <div class="control-group"> |
| <label>Water Level:</label> |
| <input type="range" id="waterLevel" min="0" max="50" value="10"> |
| </div> |
| <div class="control-group"> |
| <label>Roughness:</label> |
| <input type="range" id="roughness" min="0" max="10" value="3" step="0.1"> |
| </div> |
| </div> |
|
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script> |
| let scene, camera, renderer, terrain, water; |
| const size = 128; |
| |
| function init() { |
| scene = new THREE.Scene(); |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); |
| renderer = new THREE.WebGLRenderer(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| document.body.appendChild(renderer.domElement); |
| |
| |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
| scene.add(ambientLight); |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); |
| directionalLight.position.set(1, 1, 1); |
| scene.add(directionalLight); |
| |
| camera.position.set(50, 50, 50); |
| camera.lookAt(0, 0, 0); |
| |
| createTerrain(); |
| createWater(); |
| animate(); |
| } |
| |
| function createTerrain() { |
| const geometry = new THREE.PlaneGeometry(100, 100, size, size); |
| const material = new THREE.MeshPhongMaterial({ |
| color: 0x808080, |
| wireframe: false, |
| flatShading: true |
| }); |
| |
| terrain = new THREE.Mesh(geometry, material); |
| terrain.rotation.x = -Math.PI / 2; |
| scene.add(terrain); |
| |
| updateTerrain(); |
| } |
| |
| function createWater() { |
| const geometry = new THREE.PlaneGeometry(100, 100); |
| const material = new THREE.MeshPhongMaterial({ |
| color: 0x0077ff, |
| transparent: true, |
| opacity: 0.6 |
| }); |
| |
| water = new THREE.Mesh(geometry, material); |
| water.rotation.x = -Math.PI / 2; |
| scene.add(water); |
| } |
| |
| function updateTerrain() { |
| const heightScale = document.getElementById('heightScale').value; |
| const roughness = document.getElementById('roughness').value; |
| const vertices = terrain.geometry.attributes.position.array; |
| |
| for(let i = 0; i < vertices.length; i += 3) { |
| const x = i / 3 % (size + 1); |
| const y = Math.floor(i / 3 / (size + 1)); |
| vertices[i + 2] = generateHeight(x, y) * heightScale / 20; |
| } |
| |
| terrain.geometry.attributes.position.needsUpdate = true; |
| terrain.geometry.computeVertexNormals(); |
| } |
| |
| function generateHeight(x, y) { |
| const roughness = document.getElementById('roughness').value; |
| return (Math.sin(x/10 * roughness) + Math.sin(y/10 * roughness)) / 2; |
| } |
| |
| function updateWater() { |
| const waterLevel = document.getElementById('waterLevel').value; |
| water.position.y = waterLevel / 5; |
| } |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| renderer.render(scene, camera); |
| updateWater(); |
| } |
| |
| |
| window.addEventListener('resize', () => { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| |
| document.getElementById('heightScale').addEventListener('input', updateTerrain); |
| document.getElementById('roughness').addEventListener('input', updateTerrain); |
| |
| init(); |
| </script> |
| </body> |
| </html> |