import * as THREE from 'three'; const EGA = { GREEN: new THREE.Color(0.20, 0.67, 0.13), DARK_GREEN: new THREE.Color(0.15, 0.40, 0.10), BROWN: new THREE.Color(0.45, 0.30, 0.15), TAN: new THREE.Color(0.55, 0.45, 0.30), WHITE: new THREE.Color(1, 1, 1), BLUE: new THREE.Color(0, 0, 0.67), }; export class Terrain { constructor(size = 2000, segments = 100) { this.size = size; this.segments = segments; this.heightData = null; this.cellSize = size / segments; } hash(x, z) { const n = Math.sin(x * 127.1 + z * 311.7) * 43758.5453; return n - Math.floor(n); } smoothNoise(x, z) { const ix = Math.floor(x); const iz = Math.floor(z); const fx = x - ix; const fz = z - iz; const sfx = fx * fx * (3 - 2 * fx); const sfz = fz * fz * (3 - 2 * fz); const n00 = this.hash(ix, iz); const n10 = this.hash(ix + 1, iz); const n01 = this.hash(ix, iz + 1); const n11 = this.hash(ix + 1, iz + 1); const nx0 = n00 + (n10 - n00) * sfx; const nx1 = n01 + (n11 - n01) * sfx; return nx0 + (nx1 - nx0) * sfz; } getHeightRaw(x, z) { let h = 0; h += this.smoothNoise(x * 0.002, z * 0.002) * 50; h += this.smoothNoise(x * 0.006, z * 0.006) * 20; h += this.smoothNoise(x * 0.015, z * 0.015) * 8; return Math.max(0, h - 15); } buildHeightData() { const { segments: seg, size } = this; this.heightData = new Float32Array((seg + 1) * (seg + 1)); const half = size / 2; for (let iz = 0; iz <= seg; iz++) { for (let ix = 0; ix <= seg; ix++) { const x = -half + ix * this.cellSize; const z = -half + iz * this.cellSize; this.heightData[iz * (seg + 1) + ix] = this.getHeightRaw(x, z); } } } getHeightAt(x, z) { if (!this.heightData) return this.getHeightRaw(x, z); const { size, segments: seg } = this; const half = size / 2; const gx = ((x + half) / size) * seg; const gz = ((z + half) / size) * seg; const ix = Math.floor(gx); const iz = Math.floor(gz); if (ix < 0 || ix >= seg || iz < 0 || iz >= seg) return 0; const fx = gx - ix; const fz = gz - iz; const row = seg + 1; const h00 = this.heightData[iz * row + Math.min(ix, seg)]; const h10 = this.heightData[iz * row + Math.min(ix + 1, seg)]; const h01 = this.heightData[(iz + 1) * row + Math.min(ix, seg)]; const h11 = this.heightData[(iz + 1) * row + Math.min(ix + 1, seg)]; const nx0 = h00 + (h10 - h00) * fx; const nx1 = h01 + (h11 - h01) * fx; return nx0 + (nx1 - nx0) * fz; } flattenAirport(radius) { if (!this.heightData) return; const { segments: seg } = this; const row = seg + 1; const center = seg / 2; for (let iz = 0; iz <= seg; iz++) { for (let ix = 0; ix <= seg; ix++) { const dx = ix - center; const dz = iz - center; const dist = Math.sqrt(dx * dx + dz * dz) * this.cellSize; if (dist < radius) { const fade = 1 - Math.min(1, dist / radius); this.heightData[iz * row + ix] *= (1 - fade * 0.95); } } } } createMesh() { this.buildHeightData(); this.flattenAirport(200); const { segments: seg, size } = this; const geometry = new THREE.PlaneGeometry(size, size, seg, seg); geometry.rotateX(-Math.PI / 2); const positions = geometry.attributes.position.array; const colors = new Float32Array(positions.length); for (let i = 0; i < positions.length; i += 3) { const x = positions[i]; const z = positions[i + 2]; const h = this.getHeightAt(x, z); positions[i + 1] = h; if (h < 3) { colors[i] = EGA.GREEN.r; colors[i + 1] = EGA.GREEN.g; colors[i + 2] = EGA.GREEN.b; } else if (h < 12) { colors[i] = EGA.DARK_GREEN.r; colors[i + 1] = EGA.DARK_GREEN.g; colors[i + 2] = EGA.DARK_GREEN.b; } else if (h < 30) { colors[i] = EGA.BROWN.r; colors[i + 1] = EGA.BROWN.g; colors[i + 2] = EGA.BROWN.b; } else if (h < 45) { colors[i] = EGA.TAN.r; colors[i + 1] = EGA.TAN.g; colors[i + 2] = EGA.TAN.b; } else { const mix = Math.min(1, (h - 45) / 20); colors[i] = EGA.TAN.r + (EGA.WHITE.r - EGA.TAN.r) * mix; colors[i + 1] = EGA.TAN.g + (EGA.WHITE.g - EGA.TAN.g) * mix; colors[i + 2] = EGA.TAN.b + (EGA.WHITE.b - EGA.TAN.b) * mix; } } geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); geometry.computeVertexNormals(); const material = new THREE.MeshLambertMaterial({ vertexColors: true, flatShading: true, }); return new THREE.Mesh(geometry, material); } createWater() { const geometry = new THREE.PlaneGeometry(this.size * 2, this.size * 2); geometry.rotateX(-Math.PI / 2); const material = new THREE.MeshLambertMaterial({ color: EGA.BLUE, transparent: true, opacity: 0.7, flatShading: true, }); const water = new THREE.Mesh(geometry, material); water.position.y = 0.5; return water; } }