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

195
src/VegetationSystem.js Normal file
View File

@@ -0,0 +1,195 @@
import * as THREE from 'three';
import { SimplexNoise } from './utils/SimplexNoise.js';
export class VegetationSystem {
constructor(terrain, options = {}) {
this.terrain = terrain;
this.options = {
grassCount: options.grassCount || 50000,
treeCount: options.treeCount || 500,
terrainSize: options.terrainSize || 1000,
waterLevel: options.waterLevel || 0
};
this.noise = new SimplexNoise(12345);
this.grass = null;
this.trees = [];
}
generate() {
this.generateGrass();
this.generateTrees();
return {
grass: this.grass,
trees: this.trees
};
}
generateGrass() {
const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];
const uvs = [];
const indices = [];
const grassBladeHeight = 0.8;
const grassBladeWidth = 0.1;
let vertexIndex = 0;
for (let i = 0; i < this.options.grassCount; i++) {
const x = (Math.random() - 0.5) * this.options.terrainSize * 0.7;
const z = (Math.random() - 0.5) * this.options.terrainSize * 0.7;
const y = this.terrain.getHeightAt(x, z);
if (y < this.options.waterLevel + 0.5 || y > this.options.waterLevel + 10) continue;
const bendX = this.noise.noise2D(x * 0.1, z * 0.1) * 0.3;
const bendZ = this.noise.noise2D(x * 0.1 + 100, z * 0.1) * 0.3;
positions.push(
x - grassBladeWidth / 2, y, z,
x + grassBladeWidth / 2, y, z,
x + bendX + grassBladeWidth / 4, y + grassBladeHeight, z + bendZ
);
const greenVariation = 0.7 + Math.random() * 0.3;
const baseColor = new THREE.Color().setHSL(0.3, 0.6 * greenVariation, 0.25 * greenVariation);
colors.push(
baseColor.r, baseColor.g, baseColor.b,
baseColor.r * 0.9, baseColor.g * 0.9, baseColor.b * 0.9,
baseColor.r * 0.8, baseColor.g * 0.8, baseColor.b * 0.8
);
uvs.push(0, 0, 1, 0, 0.5, 1);
indices.push(
vertexIndex, vertexIndex + 1, vertexIndex + 2,
vertexIndex + 2, vertexIndex + 1, vertexIndex
);
vertexIndex += 3;
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
geometry.setIndex(indices);
geometry.computeVertexNormals();
const material = new THREE.MeshStandardMaterial({
vertexColors: true,
side: THREE.DoubleSide,
roughness: 0.9,
metalness: 0.0
});
this.grass = new THREE.Mesh(geometry, material);
this.grass.castShadow = true;
this.grass.receiveShadow = true;
}
generateTrees() {
const treePositions = [];
for (let i = 0; i < this.options.treeCount * 10; i++) {
if (treePositions.length >= this.options.treeCount) break;
const x = (Math.random() - 0.5) * this.options.terrainSize * 0.6;
const z = (Math.random() - 0.5) * this.options.terrainSize * 0.6;
const y = this.terrain.getHeightAt(x, z);
if (y < this.options.waterLevel + 1 || y > this.options.waterLevel + 10) continue;
const densityNoise = this.noise.noise2D(x * 0.005, z * 0.005);
if (densityNoise < 0.3) continue;
let tooClose = false;
for (const pos of treePositions) {
const dist = Math.sqrt((pos.x - x) ** 2 + (pos.z - z) ** 2);
if (dist < 10) {
tooClose = true;
break;
}
}
if (!tooClose) {
treePositions.push({ x, y, z });
}
}
for (const pos of treePositions) {
const tree = this.createTree(pos);
this.trees.push(tree);
}
}
createTree(pos) {
const tree = new THREE.Group();
const trunkHeight = 5 + Math.random() * 5;
const trunkRadius = 0.3 + Math.random() * 0.2;
const trunkGeometry = new THREE.CylinderGeometry(
trunkRadius * 0.7,
trunkRadius,
trunkHeight,
8
);
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x4a3728,
roughness: 0.9,
metalness: 0.0
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = trunkHeight / 2;
trunk.castShadow = true;
trunk.receiveShadow = true;
tree.add(trunk);
const foliageLayers = 3 + Math.floor(Math.random() * 2);
let foliageY = trunkHeight;
for (let i = 0; i < foliageLayers; i++) {
const foliageRadius = (2.5 - i * 0.4) + Math.random() * 0.5;
const foliageHeight = 2 + Math.random() * 1;
const foliageGeometry = new THREE.ConeGeometry(
foliageRadius,
foliageHeight,
8
);
const greenVariation = 0.8 + Math.random() * 0.2;
const foliageMaterial = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(0.28 + Math.random() * 0.05, 0.5 * greenVariation, 0.2 * greenVariation),
roughness: 0.8,
metalness: 0.0
});
const foliage = new THREE.Mesh(foliageGeometry, foliageMaterial);
foliage.position.y = foliageY;
foliage.castShadow = true;
foliage.receiveShadow = true;
tree.add(foliage);
foliageY += foliageHeight * 0.6;
}
tree.position.set(pos.x, pos.y, pos.z);
const scale = 0.8 + Math.random() * 0.4;
tree.scale.setScalar(scale);
tree.rotation.y = Math.random() * Math.PI * 2;
return tree;
}
addToScene(scene) {
if (this.grass) scene.add(this.grass);
this.trees.forEach(tree => scene.add(tree));
}
}