This commit is contained in:
2026-03-25 17:28:56 +08:00
commit e17b09ff6d
13 changed files with 2466 additions and 0 deletions

142
src/TerrainGenerator.js Normal file
View File

@@ -0,0 +1,142 @@
import * as THREE from 'three';
import { SimplexNoise } from './utils/SimplexNoise.js';
export class TerrainGenerator {
constructor(options = {}) {
this.size = options.size || 1000;
this.segments = options.segments || 256;
this.maxHeight = options.maxHeight || 50;
this.waterLevel = options.waterLevel || 0;
this.beachWidth = options.beachWidth || 20;
this.noise = new SimplexNoise(options.seed || 42);
this.terrain = null;
}
generate() {
const geometry = new THREE.PlaneGeometry(
this.size,
this.size,
this.segments,
this.segments
);
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 y = positions[i + 1];
let height = this.getHeight(x, y);
positions[i + 2] = height;
const color = this.getTerrainColor(x, y, height);
colors[i] = color.r;
colors[i + 1] = color.g;
colors[i + 2] = color.b;
}
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.computeVertexNormals();
geometry.rotateX(-Math.PI / 2);
const material = new THREE.MeshStandardMaterial({
vertexColors: true,
roughness: 0.85,
metalness: 0.0,
flatShading: false
});
this.terrain = new THREE.Mesh(geometry, material);
this.terrain.receiveShadow = true;
this.terrain.castShadow = true;
return this.terrain;
}
getHeight(x, y) {
const scale = 0.0008;
let height = this.noise.fbm(x * scale, y * scale, 3, 2.0, 0.5);
const detail = this.noise.fbm(x * scale * 3, y * scale * 3, 2, 2.0, 0.5) * 0.2;
height += detail;
const distFromCenter = Math.sqrt(x * x + y * y);
const maxLandDist = this.size * 0.45;
const falloffStart = this.size * 0.25;
let continentMask = 1.0;
if (distFromCenter > falloffStart) {
const t = (distFromCenter - falloffStart) / (maxLandDist - falloffStart);
continentMask = Math.max(0, 1 - Math.pow(t, 1.5));
}
height = height * continentMask;
height = height * this.maxHeight * 0.4;
const beachZone = 2;
if (height > this.waterLevel && height < this.waterLevel + beachZone) {
const blend = Math.max(0, Math.min(1, (height - this.waterLevel) / beachZone));
height = this.waterLevel + Math.pow(blend, 0.5) * beachZone;
}
return height;
}
getTerrainColor(x, y, height) {
const normalizedHeight = (height - this.waterLevel) / this.maxHeight;
const deepWater = new THREE.Color(0x1a3d5c);
const shallowWater = new THREE.Color(0x2d6b8a);
const beach = new THREE.Color(0xc2b280);
const grass = new THREE.Color(0x3d6b3d);
const darkGrass = new THREE.Color(0x2d4a2d);
const rock = new THREE.Color(0x5a5a5a);
const snow = new THREE.Color(0xe8e8e8);
let color = new THREE.Color();
if (normalizedHeight < -0.1) {
color.copy(deepWater);
} else if (normalizedHeight < 0) {
color.lerpColors(deepWater, shallowWater, (normalizedHeight + 0.1) / 0.1);
} else if (normalizedHeight < 0.05) {
const beachBlend = Math.min(1, normalizedHeight / 0.05);
color.lerpColors(shallowWater, beach, beachBlend);
} else if (normalizedHeight < 0.15) {
const sandToGrass = (normalizedHeight - 0.05) / 0.1;
color.lerpColors(beach, grass, sandToGrass);
} else if (normalizedHeight < 0.4) {
const grassBlend = (normalizedHeight - 0.15) / 0.25;
color.lerpColors(grass, darkGrass, grassBlend);
} else if (normalizedHeight < 0.6) {
const rockBlend = (normalizedHeight - 0.4) / 0.2;
color.lerpColors(darkGrass, rock, rockBlend);
} else if (normalizedHeight < 0.8) {
color.copy(rock);
} else {
const snowBlend = (normalizedHeight - 0.8) / 0.2;
color.lerpColors(rock, snow, snowBlend);
}
const noiseVariation = this.noise.noise2D(x * 0.05, y * 0.05) * 0.1;
color.r = Math.max(0, Math.min(1, color.r + noiseVariation));
color.g = Math.max(0, Math.min(1, color.g + noiseVariation));
color.b = Math.max(0, Math.min(1, color.b + noiseVariation));
return color;
}
getTerrain() {
return this.terrain;
}
getHeightAt(x, z) {
return this.getHeight(x, -z);
}
isLand(x, z) {
return this.getHeight(x, z) > this.waterLevel;
}
}