init
This commit is contained in:
142
src/TerrainGenerator.js
Normal file
142
src/TerrainGenerator.js
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user