|
<!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> |