调整植被生成为指定模式
This commit is contained in:
@@ -310,11 +310,40 @@ export class OceanScene {
|
||||
|
||||
async initVegetation() {
|
||||
const vegSystem = new VegetationSystem(this.terrainGenerator, {
|
||||
grassCount: 28000, // 草簇数量,越大地表覆盖越密
|
||||
shrubCount: 12, // 灌木和低矮植物数量
|
||||
treeCount: 9, // 树木数量
|
||||
terrainSize: 1200, // 植被允许分布的地形范围
|
||||
waterLevel: 1 // 植被生成时参考的水位,避免贴近海边
|
||||
grassCount: 0, // 随机草簇数量,设为 0 时只使用 grassAreas
|
||||
shrubCount: 0, // 随机灌木数量,设为 0 时只使用 shrubPlacements
|
||||
lowPlantCount: 0, // 随机低矮植物数量,设为 0 时只使用 lowPlantPlacements
|
||||
treeCount: 0, // 随机树木数量,设为 0 时只使用 treePlacements
|
||||
terrainSize: 1200, // 随机植被允许分布的地形范围
|
||||
waterLevel: 1, // 植被生成时参考的水位,避免贴近海边
|
||||
treePlacements: [ // 手动指定树木坐标
|
||||
{ x: -180, z: -120, rotation: 0.4, scale: 1.6 },
|
||||
{ x: -120, z: -40, rotation: 1.2, scale: 1.35 },
|
||||
// { x: -40, z: -150, rotation: 2.1, scale: 1.75 },
|
||||
// { x: 70, z: -70, rotation: 2.8, scale: 1.45 },
|
||||
// { x: 135, z: 15, rotation: 4.1, scale: 1.55 },
|
||||
// { x: 30, z: 120, rotation: 5.2, scale: 1.7 }
|
||||
],
|
||||
shrubPlacements: [ // 手动指定灌木坐标
|
||||
{ x: -210, z: -65, rotation: 0.3, scale: 1.05 },
|
||||
{ x: -195, z: -75, rotation: 1.4, scale: 0.95 },
|
||||
// { x: -20, z: -95, rotation: 2.2, scale: 1.1 },
|
||||
// { x: 55, z: -5, rotation: 3.6, scale: 0.9 },
|
||||
// { x: 150, z: -55, rotation: 4.5, scale: 1.15 },
|
||||
// { x: 185, z: 75, rotation: 5.4, scale: 1.0 }
|
||||
],
|
||||
lowPlantPlacements: [ // 手动指定低矮植物坐标
|
||||
{ x: -235, z: -20, rotation: 0.6, scale: 0.58 },
|
||||
{ x: -205, z: 15, rotation: 1.8, scale: 0.52 },
|
||||
// { x: -10, z: -20, rotation: 2.7, scale: 0.48 },
|
||||
// { x: 82, z: -132, rotation: 3.4, scale: 0.62 },
|
||||
// { x: 118, z: 58, rotation: 4.2, scale: 0.56 },
|
||||
// { x: 225, z: 18, rotation: 5.1, scale: 0.6 }
|
||||
],
|
||||
grassAreas: [ // 手动指定草簇生成区域
|
||||
{ centerX: -140, centerZ: -10, width: 220, depth: 170, count: 4200 },
|
||||
{ centerX: 110, centerZ: 65, width: 210, depth: 160, count: 3800 }
|
||||
]
|
||||
});
|
||||
|
||||
this.vegetation = vegSystem.generate();
|
||||
|
||||
@@ -6,11 +6,16 @@ export class VegetationSystem {
|
||||
constructor(terrain, options = {}) {
|
||||
this.terrain = terrain;
|
||||
this.options = {
|
||||
grassCount: options.grassCount || 18000,
|
||||
shrubCount: options.shrubCount || 420,
|
||||
treeCount: options.treeCount || 120,
|
||||
terrainSize: options.terrainSize || 1000,
|
||||
waterLevel: options.waterLevel || 0
|
||||
grassCount: options.grassCount ?? 18000,
|
||||
shrubCount: options.shrubCount ?? 420,
|
||||
lowPlantCount: options.lowPlantCount ?? 260,
|
||||
treeCount: options.treeCount ?? 120,
|
||||
terrainSize: options.terrainSize ?? 1000,
|
||||
waterLevel: options.waterLevel ?? 0,
|
||||
treePlacements: options.treePlacements ?? [],
|
||||
shrubPlacements: options.shrubPlacements ?? [],
|
||||
lowPlantPlacements: options.lowPlantPlacements ?? [],
|
||||
grassAreas: options.grassAreas ?? []
|
||||
};
|
||||
|
||||
this.noise = new SimplexNoise(12345);
|
||||
@@ -19,6 +24,7 @@ export class VegetationSystem {
|
||||
this.plants = [];
|
||||
this.animatedPlants = [];
|
||||
this.treePositions = [];
|
||||
this.occupiedPlantPositions = [];
|
||||
}
|
||||
|
||||
generate() {
|
||||
@@ -26,10 +32,12 @@ export class VegetationSystem {
|
||||
this.plants = [];
|
||||
this.animatedPlants = [];
|
||||
this.treePositions = [];
|
||||
this.occupiedPlantPositions = [];
|
||||
|
||||
this.generateGrass();
|
||||
this.generateTrees();
|
||||
this.generateShrubs();
|
||||
this.generateLowPlants();
|
||||
|
||||
if (this.grass) {
|
||||
this.group.add(this.grass);
|
||||
@@ -41,14 +49,41 @@ export class VegetationSystem {
|
||||
}
|
||||
|
||||
generateGrass() {
|
||||
const placements = this.collectPlacements(this.options.grassCount, {
|
||||
const placements = [];
|
||||
|
||||
if (this.options.grassAreas.length > 0) {
|
||||
this.options.grassAreas.forEach((area, areaIndex) => {
|
||||
const areaCount = area.count ?? 2400;
|
||||
placements.push(...this.collectAreaPlacements(areaCount, {
|
||||
centerX: area.centerX ?? 0,
|
||||
centerZ: area.centerZ ?? 0,
|
||||
width: area.width ?? 120,
|
||||
depth: area.depth ?? 120,
|
||||
minHeight: area.minHeight ?? this.options.waterLevel + 1.2,
|
||||
maxHeight: area.maxHeight ?? this.options.waterLevel + 12,
|
||||
maxSlope: area.maxSlope ?? 1.35,
|
||||
densityScale: area.densityScale ?? 0.02,
|
||||
densityThreshold: area.densityThreshold ?? -0.18,
|
||||
jitterSeed: areaIndex * 13.17
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.grassCount > 0) {
|
||||
placements.push(...this.collectPlacements(this.options.grassCount, {
|
||||
areaRatio: 0.78,
|
||||
minHeight: this.options.waterLevel + 1.2,
|
||||
maxHeight: this.options.waterLevel + 12,
|
||||
maxSlope: 1.35,
|
||||
densityScale: 0.02,
|
||||
densityThreshold: -0.18
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if (placements.length === 0) {
|
||||
this.grass = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const grassGeometries = [
|
||||
this.createGrassBladeGeometry(0),
|
||||
@@ -96,38 +131,6 @@ export class VegetationSystem {
|
||||
this.grass = grassGroup;
|
||||
}
|
||||
|
||||
generateShrubs() {
|
||||
const prototypes = [
|
||||
this.createPlantPrototype('Bush 1', 2001, 0.85),
|
||||
this.createPlantPrototype('Bush 2', 2002, 0.95),
|
||||
this.createPlantPrototype('Bush 3', 2003, 1.05)
|
||||
];
|
||||
|
||||
const placements = this.collectPlacements(this.options.shrubCount, {
|
||||
areaRatio: 0.72,
|
||||
minHeight: this.options.waterLevel + 1.5,
|
||||
maxHeight: this.options.waterLevel + 13,
|
||||
maxSlope: 1.1,
|
||||
minSpacing: 5,
|
||||
densityScale: 0.006,
|
||||
densityThreshold: 0.05
|
||||
});
|
||||
|
||||
placements.forEach((placement, index) => {
|
||||
const prototype = prototypes[index % prototypes.length];
|
||||
const shrub = prototype.clone(true);
|
||||
shrub.position.set(placement.x, placement.y, placement.z);
|
||||
shrub.rotation.y = placement.rotation;
|
||||
shrub.scale.setScalar(THREE.MathUtils.lerp(0.75, 1.35, placement.scaleMix));
|
||||
shrub.traverse((child) => {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
});
|
||||
this.plants.push(shrub);
|
||||
this.animatedPlants.push(shrub);
|
||||
});
|
||||
}
|
||||
|
||||
generateTrees() {
|
||||
const prototypes = [
|
||||
this.createPlantPrototype('Pine Small', 3001, 1.05),
|
||||
@@ -136,7 +139,11 @@ export class VegetationSystem {
|
||||
this.createPlantPrototype('Pine Medium', 3004, 1.25)
|
||||
];
|
||||
|
||||
const placements = this.collectPlacements(this.options.treeCount, {
|
||||
const placements = [
|
||||
...this.normalizeManualPlacements(this.options.treePlacements, {
|
||||
defaultScaleRange: [1.15, 1.85]
|
||||
}),
|
||||
...this.collectPlacements(this.options.treeCount, {
|
||||
areaRatio: 0.66,
|
||||
minHeight: this.options.waterLevel + 2.5,
|
||||
maxHeight: this.options.waterLevel + 18,
|
||||
@@ -144,21 +151,116 @@ export class VegetationSystem {
|
||||
minSpacing: 15,
|
||||
densityScale: 0.0042,
|
||||
densityThreshold: 0.14
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
placements.forEach((placement, index) => {
|
||||
const prototype = prototypes[index % prototypes.length];
|
||||
const tree = prototype.clone(true);
|
||||
tree.position.set(placement.x, placement.y, placement.z);
|
||||
tree.rotation.y = placement.rotation;
|
||||
tree.scale.setScalar(THREE.MathUtils.lerp(1.1, 1.9, placement.scaleMix));
|
||||
tree.traverse((child) => {
|
||||
const tree = this.instantiatePlant(prototypes[index % prototypes.length], placement, 1.15, 1.9);
|
||||
this.plants.push(tree);
|
||||
this.animatedPlants.push(tree);
|
||||
this.treePositions.push({ x: placement.x, z: placement.z });
|
||||
this.occupiedPlantPositions.push({ x: placement.x, z: placement.z });
|
||||
});
|
||||
}
|
||||
|
||||
generateShrubs() {
|
||||
this.generateBushLayer({
|
||||
placements: this.options.shrubPlacements,
|
||||
count: this.options.shrubCount,
|
||||
placementScaleRange: [0.85, 1.35],
|
||||
instantiateScaleRange: [0.75, 1.35],
|
||||
config: {
|
||||
areaRatio: 0.72,
|
||||
minHeight: this.options.waterLevel + 1.5,
|
||||
maxHeight: this.options.waterLevel + 13,
|
||||
maxSlope: 1.1,
|
||||
minSpacing: 6,
|
||||
densityScale: 0.006,
|
||||
densityThreshold: 0.05
|
||||
},
|
||||
seedBase: 2001,
|
||||
tintJitterBase: 0.85
|
||||
});
|
||||
}
|
||||
|
||||
generateLowPlants() {
|
||||
this.generateBushLayer({
|
||||
placements: this.options.lowPlantPlacements,
|
||||
count: this.options.lowPlantCount,
|
||||
placementScaleRange: [0.42, 0.7],
|
||||
instantiateScaleRange: [0.42, 0.72],
|
||||
config: {
|
||||
areaRatio: 0.74,
|
||||
minHeight: this.options.waterLevel + 1.3,
|
||||
maxHeight: this.options.waterLevel + 10,
|
||||
maxSlope: 1.2,
|
||||
minSpacing: 4,
|
||||
densityScale: 0.008,
|
||||
densityThreshold: -0.02
|
||||
},
|
||||
seedBase: 4001,
|
||||
tintJitterBase: 0.7
|
||||
});
|
||||
}
|
||||
|
||||
generateBushLayer(layerOptions) {
|
||||
const prototypes = [
|
||||
this.createPlantPrototype('Bush 1', layerOptions.seedBase, layerOptions.tintJitterBase),
|
||||
this.createPlantPrototype('Bush 2', layerOptions.seedBase + 1, layerOptions.tintJitterBase + 0.08),
|
||||
this.createPlantPrototype('Bush 3', layerOptions.seedBase + 2, layerOptions.tintJitterBase + 0.02)
|
||||
];
|
||||
|
||||
const placements = [
|
||||
...this.normalizeManualPlacements(layerOptions.placements, {
|
||||
defaultScaleRange: layerOptions.placementScaleRange
|
||||
}),
|
||||
...this.collectPlacements(layerOptions.count, layerOptions.config)
|
||||
];
|
||||
|
||||
placements.forEach((placement, index) => {
|
||||
const plant = this.instantiatePlant(
|
||||
prototypes[index % prototypes.length],
|
||||
placement,
|
||||
layerOptions.instantiateScaleRange[0],
|
||||
layerOptions.instantiateScaleRange[1]
|
||||
);
|
||||
this.plants.push(plant);
|
||||
this.animatedPlants.push(plant);
|
||||
this.occupiedPlantPositions.push({ x: placement.x, z: placement.z });
|
||||
});
|
||||
}
|
||||
|
||||
instantiatePlant(prototype, placement, minScale, maxScale) {
|
||||
const plant = prototype.clone(true);
|
||||
plant.position.set(placement.x, placement.y, placement.z);
|
||||
plant.rotation.y = placement.rotation;
|
||||
plant.scale.setScalar(placement.scale ?? THREE.MathUtils.lerp(minScale, maxScale, placement.scaleMix ?? Math.random()));
|
||||
plant.traverse((child) => {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
});
|
||||
this.plants.push(tree);
|
||||
this.animatedPlants.push(tree);
|
||||
this.treePositions.push(new THREE.Vector2(placement.x, placement.z));
|
||||
return plant;
|
||||
}
|
||||
|
||||
normalizeManualPlacements(placements, config = {}) {
|
||||
return placements.map((placement, index) => {
|
||||
const y = placement.y ?? this.terrain.getHeightAt(placement.x, placement.z);
|
||||
const randomScale = THREE.MathUtils.lerp(
|
||||
config.defaultScaleRange?.[0] ?? 1,
|
||||
config.defaultScaleRange?.[1] ?? 1,
|
||||
((index * 37) % 100) / 100
|
||||
);
|
||||
|
||||
return {
|
||||
x: placement.x,
|
||||
y,
|
||||
z: placement.z,
|
||||
rotation: placement.rotation ?? (index * Math.PI * 0.37) % (Math.PI * 2),
|
||||
tilt: placement.tilt ?? 0,
|
||||
scale: placement.scale ?? randomScale,
|
||||
scaleMix: placement.scaleMix ?? (((index * 53) % 100) / 100),
|
||||
colorMix: placement.colorMix ?? 0.6
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,27 +328,56 @@ export class VegetationSystem {
|
||||
}
|
||||
|
||||
collectPlacements(targetCount, config) {
|
||||
if (!targetCount) return [];
|
||||
|
||||
const placements = [];
|
||||
const maxAttempts = targetCount * 14;
|
||||
|
||||
for (let i = 0; i < maxAttempts && placements.length < targetCount; i++) {
|
||||
const x = (Math.random() - 0.5) * this.options.terrainSize * config.areaRatio;
|
||||
const z = (Math.random() - 0.5) * this.options.terrainSize * config.areaRatio;
|
||||
const y = this.terrain.getHeightAt(x, z);
|
||||
|
||||
if (y < config.minHeight || y > config.maxHeight) continue;
|
||||
|
||||
const slope = this.getSlopeAt(x, z);
|
||||
if (slope > config.maxSlope) continue;
|
||||
|
||||
const densityNoise = this.noise.noise2D(x * config.densityScale, z * config.densityScale);
|
||||
if (densityNoise < config.densityThreshold) continue;
|
||||
const placement = this.buildPlacementFromPoint(x, z, config);
|
||||
|
||||
if (!placement) continue;
|
||||
if (config.minSpacing && !this.isFarEnoughFromPlants(placements, x, z, config.minSpacing)) continue;
|
||||
if (config.minSpacing && !this.isFarEnoughFromPlants(this.treePositions, x, z, config.minSpacing * 0.8)) continue;
|
||||
if (config.minSpacing && !this.isFarEnoughFromPlants(this.occupiedPlantPositions, x, z, config.minSpacing * 0.7)) continue;
|
||||
|
||||
placements.push(placement);
|
||||
}
|
||||
|
||||
return placements;
|
||||
}
|
||||
|
||||
collectAreaPlacements(targetCount, config) {
|
||||
const placements = [];
|
||||
const maxAttempts = targetCount * 10;
|
||||
|
||||
for (let i = 0; i < maxAttempts && placements.length < targetCount; i++) {
|
||||
const x = config.centerX + (Math.random() - 0.5) * config.width;
|
||||
const z = config.centerZ + (Math.random() - 0.5) * config.depth;
|
||||
const placement = this.buildPlacementFromPoint(x, z, config, config.jitterSeed);
|
||||
|
||||
if (placement) {
|
||||
placements.push(placement);
|
||||
}
|
||||
}
|
||||
|
||||
return placements;
|
||||
}
|
||||
|
||||
buildPlacementFromPoint(x, z, config, jitterSeed = 0) {
|
||||
const y = this.terrain.getHeightAt(x, z);
|
||||
if (y < config.minHeight || y > config.maxHeight) return null;
|
||||
|
||||
const slope = this.getSlopeAt(x, z);
|
||||
if (slope > config.maxSlope) return null;
|
||||
|
||||
const densityNoise = this.noise.noise2D(x * config.densityScale + jitterSeed, z * config.densityScale - jitterSeed);
|
||||
if (densityNoise < config.densityThreshold) return null;
|
||||
|
||||
const colorMix = THREE.MathUtils.clamp((densityNoise + 1) * 0.5, 0, 1);
|
||||
placements.push({
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
@@ -255,10 +386,7 @@ export class VegetationSystem {
|
||||
scale: THREE.MathUtils.lerp(0.75, 1.45, Math.random()),
|
||||
scaleMix: Math.random(),
|
||||
colorMix
|
||||
});
|
||||
}
|
||||
|
||||
return placements;
|
||||
};
|
||||
}
|
||||
|
||||
getSlopeAt(x, z) {
|
||||
@@ -275,10 +403,8 @@ export class VegetationSystem {
|
||||
const minSpacingSq = minSpacing * minSpacing;
|
||||
|
||||
for (const pos of positions) {
|
||||
const px = pos.x;
|
||||
const pz = pos.z ?? pos.y;
|
||||
const dx = px - x;
|
||||
const dz = pz - z;
|
||||
const dx = pos.x - x;
|
||||
const dz = pos.z - z;
|
||||
if (dx * dx + dz * dz < minSpacingSq) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user