import * as THREE from 'three';
import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const skyColor = 0x0e1730;
scene.background = new THREE.Color(skyColor);
scene.fog = new THREE.Fog(skyColor, 4, 13);
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 60);
scene.add(new THREE.AmbientLight(0x33406a, 1.1));
const sun = new THREE.DirectionalLight(0xffe3b0, 1.3);
sun.position.set(4, 6, 2);
scene.add(sun);
const SIZE = 8, SEG = 90;
const geo = new THREE.PlaneGeometry(SIZE, SIZE, SEG, SEG);
geo.rotateX(-Math.PI / 2);
const simplex = new SimplexNoise();
const pos = geo.attributes.position;
const colors = [];
const low = new THREE.Color(0x1c5a3c), mid = new THREE.Color(0x6b7a4a), high = new THREE.Color(0xf4f1e6);
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
let h = 0, amp = 1, freq = 0.35, norm = 0;
for (let o = 0; o < 4; o++) {
h += simplex.noise(x * freq, z * freq) * amp;
norm += amp;
amp *= 0.5;
freq *= 2.1;
}
h = (h / norm) * 1.4;
pos.setY(i, h);
const tone = h < 0.15
? low.clone().lerp(mid, THREE.MathUtils.clamp((h + 0.3) / 0.45, 0, 1))
: mid.clone().lerp(high, THREE.MathUtils.clamp((h - 0.15) / 0.55, 0, 1));
colors.push(tone.r, tone.g, tone.b);
}
geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
geo.computeVertexNormals();
const terrain = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.95 }));
scene.add(terrain);
function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
const time = t * 0.00012;
camera.position.set(Math.sin(time) * 4.2, 2.1, Math.cos(time) * 4.2 + 1.2);
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
});