EG/plugins/user/procedural_terrain_generation/vegetation/vegetation_generator.py
2025-12-12 16:16:15 +08:00

1370 lines
53 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
植被生成器
负责生成程序化地形的植被分布
"""
import numpy as np
import math
from typing import Dict, Any, List, Tuple
import random
class VegetationGenerator:
"""
植被生成器
负责生成程序化地形的植被分布,基于生物群落、湿度、温度等因素
"""
def __init__(self, plugin):
"""
初始化植被生成器
Args:
plugin: 程序化地形生成插件实例
"""
self.plugin = plugin
self.enabled = False
self.initialized = False
# 植被配置
self.seed = plugin.config.get('seed', 12345)
# 植被类型定义
self.vegetation_types = {
0: {'name': 'none', 'density': 0.0, 'color': (0, 0, 0)},
1: {'name': 'grass', 'density': 0.3, 'color': (144, 238, 144)},
2: {'name': 'shrub', 'density': 0.2, 'color': (107, 142, 35)},
3: {'name': 'flower', 'density': 0.1, 'color': (255, 105, 180)},
4: {'name': 'tree', 'density': 0.15, 'color': (34, 139, 34)},
5: {'name': 'pine_tree', 'density': 0.12, 'color': (0, 128, 0)},
6: {'name': 'palm_tree', 'density': 0.08, 'color': (0, 100, 0)},
7: {'name': 'cactus', 'density': 0.05, 'color': (0, 100, 0)},
8: {'name': 'bush', 'density': 0.25, 'color': (107, 142, 35)},
9: {'name': 'fern', 'density': 0.2, 'color': (34, 139, 34)},
10: {'name': 'moss', 'density': 0.15, 'color': (144, 238, 144)},
11: {'name': 'vine', 'density': 0.1, 'color': (0, 128, 0)},
12: {'name': 'seaweed', 'density': 0.4, 'color': (0, 128, 0)},
13: {'name': 'algae', 'density': 0.35, 'color': (0, 100, 0)},
14: {'name': 'kelp', 'density': 0.3, 'color': (0, 128, 0)},
15: {'name': 'coral', 'density': 0.25, 'color': (255, 99, 71)}
}
# 生物群落植被分布
self.biome_vegetation = {
0: [0, 12, 13, 14, 15], # ocean
1: [0, 1, 2], # beach
2: [0, 1, 2, 8], # plains
3: [0, 1, 2, 4, 9], # forest
4: [0, 1, 2, 4, 6, 9, 11], # jungle
5: [0, 1, 2, 7], # desert
6: [0, 1, 2, 5], # mountain
7: [0, 1, 2], # snow
8: [0, 1, 2, 5, 10], # taiga
9: [0, 1, 2], # tundra
10: [0, 1, 2, 3, 9, 10], # swamp
11: [0, 1, 2, 8] # savanna
}
# 植被生成参数
self.vegetation_params = {
'density_scale': 10.0,
'clumping_factor': 0.7,
'slope_tolerance': 0.3,
'height_min': 0.0,
'height_max': 1.0,
'humidity_min': 0.0,
'humidity_max': 1.0,
'temperature_min': 0.0,
'temperature_max': 1.0,
'edge_blending': 0.05
}
# 噪声生成器引用
self.noise_generator = None
# 植被分布图
self.vegetation_map = None
# 统计信息
self.stats = {
'vegetation_generated': 0,
'total_generation_time': 0.0,
'average_generation_time': 0.0,
'vegetation_distribution': {}
}
print("✓ 植被生成器已创建")
def initialize(self) -> bool:
"""
初始化植被生成器
Returns:
是否初始化成功
"""
try:
# 获取噪声生成器引用
self.noise_generator = self.plugin.noise_generator
self.initialized = True
print("✓ 植被生成器初始化完成")
return True
except Exception as e:
print(f"✗ 植被生成器初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def enable(self) -> bool:
"""
启用植被生成器
Returns:
是否启用成功
"""
try:
if not self.initialized:
print("✗ 植被生成器未初始化")
return False
self.enabled = True
print("✓ 植被生成器已启用")
return True
except Exception as e:
print(f"✗ 植被生成器启用失败: {e}")
import traceback
traceback.print_exc()
return False
def disable(self):
"""禁用植被生成器"""
try:
self.enabled = False
print("✓ 植被生成器已禁用")
except Exception as e:
print(f"✗ 植被生成器禁用失败: {e}")
import traceback
traceback.print_exc()
def finalize(self):
"""清理植被生成器资源"""
try:
self.disable()
self.initialized = False
print("✓ 植被生成器资源已清理")
except Exception as e:
print(f"✗ 植被生成器资源清理失败: {e}")
import traceback
traceback.print_exc()
def update(self, dt: float):
"""
更新植被生成器状态
Args:
dt: 时间增量
"""
# 处理更新逻辑
pass
def generate_vegetation(self, heightmap: np.ndarray, biome_map: np.ndarray, seed: int = None) -> np.ndarray:
"""
生成植被分布
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
seed: 随机种子
Returns:
植被分布图
"""
try:
import time
generation_start_time = time.time()
if not self.enabled:
print("✗ 植被生成器未启用")
return np.zeros_like(heightmap, dtype=np.int32)
# 更新种子
if seed is not None:
self.seed = seed
print("✓ 开始生成植被分布...")
# 初始化随机数生成器
random.seed(self.seed)
np.random.seed(self.seed)
# 创建植被图
height, width = heightmap.shape
self.vegetation_map = np.zeros((height, width), dtype=np.int32)
# 生成基础植被噪声
vegetation_noise = self._generate_vegetation_noise(width, height)
# 为每个像素生成植被
print(" → 生成植被类型...")
for y in range(height):
for x in range(width):
# 获取当前位置的属性
height_value = heightmap[y, x]
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
# 生成植被
vegetation_type = self._generate_vegetation_at_position(
x, y, height_value, biome_type, vegetation_noise[y, x]
)
self.vegetation_map[y, x] = vegetation_type
# 应用后处理
print(" → 应用后处理...")
self.vegetation_map = self._post_process_vegetation(self.vegetation_map, heightmap, biome_map)
# 更新统计信息
generation_time = time.time() - generation_start_time
self.stats['vegetation_generated'] += 1
self.stats['total_generation_time'] += generation_time
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
# 更新植被分布统计
self._update_vegetation_distribution_stats(self.vegetation_map)
print(f"✓ 植被生成完成,耗时: {generation_time:.2f}")
return self.vegetation_map
except Exception as e:
print(f"✗ 植被生成失败: {e}")
import traceback
traceback.print_exc()
return np.zeros_like(heightmap, dtype=np.int32)
def _generate_vegetation_noise(self, width: int, height: int) -> np.ndarray:
"""
生成植被噪声图
Args:
width: 宽度
height: 高度
Returns:
植被噪声图
"""
try:
noise_map = np.zeros((height, width), dtype=np.float32)
if self.noise_generator:
# 生成分形噪声
for y in range(height):
for x in range(width):
noise_value = self.noise_generator.generate_fractal_noise(
x / self.vegetation_params['density_scale'],
y / self.vegetation_params['density_scale'],
self.seed,
octaves=3,
persistence=0.7,
lacunarity=2.0
)
noise_map[y, x] = (noise_value + 1.0) / 2.0 # 转换到0-1范围
else:
# 如果没有噪声生成器,使用随机噪声
noise_map = np.random.rand(height, width).astype(np.float32)
return noise_map
except Exception as e:
print(f"✗ 植被噪声生成失败: {e}")
return np.random.rand(height, width).astype(np.float32)
def _generate_vegetation_at_position(self, x: int, y: int, height: float, biome: int, noise: float) -> int:
"""
在指定位置生成植被
Args:
x: X坐标
y: Y坐标
height: 高度值
biome: 生物群落类型
noise: 噪声值
Returns:
植被类型ID
"""
try:
# 检查高度限制
if height < self.vegetation_params['height_min'] or height > self.vegetation_params['height_max']:
return 0 # 无植被
# 获取生物群落的植被类型
biome_veg_types = self.biome_vegetation.get(biome, [0])
# 根据噪声值选择植被类型
if noise > 0.7 and len(biome_veg_types) > 1:
# 高噪声值,选择较稀有的植被
weights = [self.vegetation_types.get(v, {'density': 0.1})['density'] for v in biome_veg_types]
# 反转权重以使稀有植被更容易被选中
weights = [1.0 / (w + 0.01) for w in weights]
vegetation_type = np.random.choice(biome_veg_types, p=np.array(weights) / sum(weights))
elif noise > 0.4 and len(biome_veg_types) > 1:
# 中等噪声值,随机选择
vegetation_type = np.random.choice(biome_veg_types)
elif len(biome_veg_types) > 0:
# 低噪声值,选择常见的植被
weights = [self.vegetation_types.get(v, {'density': 0.1})['density'] for v in biome_veg_types]
vegetation_type = np.random.choice(biome_veg_types, p=np.array(weights) / sum(weights))
else:
vegetation_type = 0 # 无植被
return vegetation_type
except Exception as e:
print(f"✗ 位置植被生成失败: {e}")
return 0
def _post_process_vegetation(self, vegetation_map: np.ndarray, heightmap: np.ndarray, biome_map: np.ndarray) -> np.ndarray:
"""
后处理植被图
Args:
vegetation_map: 原始植被图
heightmap: 高度图
biome_map: 生物群落图
Returns:
处理后的植被图
"""
try:
processed = vegetation_map.copy()
height, width = processed.shape
# 应用聚类效果
processed = self._apply_clumping(processed, heightmap)
# 应用坡度限制
processed = self._apply_slope_limit(processed, heightmap)
# 应用边缘混合
processed = self._apply_edge_blending(processed)
return processed
except Exception as e:
print(f"✗ 植被后处理失败: {e}")
return vegetation_map
def _apply_clumping(self, vegetation_map: np.ndarray, heightmap: np.ndarray) -> np.ndarray:
"""
应用植被聚类效果
Args:
vegetation_map: 植被图
heightmap: 高度图
Returns:
处理后的植被图
"""
try:
processed = vegetation_map.copy()
height, width = processed.shape
clumping_factor = self.vegetation_params['clumping_factor']
# 对每个植被类型应用聚类
for y in range(height):
for x in range(width):
if vegetation_map[y, x] != 0: # 如果有植被
# 检查邻居是否有相同类型的植被
same_type_neighbors = 0
total_neighbors = 0
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dy == 0 and dx == 0:
continue
ny, nx = y + dy, x + dx
if 0 <= ny < height and 0 <= nx < width:
total_neighbors += 1
if vegetation_map[ny, nx] == vegetation_map[y, x]:
same_type_neighbors += 1
# 根据邻居情况调整植被类型
if total_neighbors > 0:
similarity = same_type_neighbors / total_neighbors
if similarity < clumping_factor and np.random.random() > 0.5:
# 减少孤立的植被
processed[y, x] = 0
return processed
except Exception as e:
print(f"✗ 植被聚类应用失败: {e}")
return vegetation_map
def _apply_slope_limit(self, vegetation_map: np.ndarray, heightmap: np.ndarray) -> np.ndarray:
"""
应用坡度限制
Args:
vegetation_map: 植被图
heightmap: 高度图
Returns:
处理后的植被图
"""
try:
processed = vegetation_map.copy()
height, width = processed.shape
slope_tolerance = self.vegetation_params['slope_tolerance']
# 计算坡度并应用限制
for y in range(height):
for x in range(width):
if vegetation_map[y, x] != 0: # 如果有植被
# 计算当前位置的坡度
slope = self._calculate_slope(heightmap, x, y)
# 如果坡度太大,移除植被
if slope > slope_tolerance:
processed[y, x] = 0
return processed
except Exception as e:
print(f"✗ 坡度限制应用失败: {e}")
return vegetation_map
def _calculate_slope(self, heightmap: np.ndarray, x: int, y: int) -> float:
"""
计算指定位置的坡度
Args:
heightmap: 高度图
x: X坐标
y: Y坐标
Returns:
坡度值
"""
try:
height, width = heightmap.shape
# 计算梯度
dx = 0.0
dy = 0.0
# X方向梯度
if x > 0 and x < width - 1:
dx = (heightmap[y, x + 1] - heightmap[y, x - 1]) / 2.0
elif x == 0:
dx = heightmap[y, x + 1] - heightmap[y, x]
else:
dx = heightmap[y, x] - heightmap[y, x - 1]
# Y方向梯度
if y > 0 and y < height - 1:
dy = (heightmap[y + 1, x] - heightmap[y - 1, x]) / 2.0
elif y == 0:
dy = heightmap[y + 1, x] - heightmap[y, x]
else:
dy = heightmap[y, x] - heightmap[y - 1, x]
# 计算坡度(梯度的模)
slope = math.sqrt(dx * dx + dy * dy)
return slope
except Exception as e:
print(f"✗ 坡度计算失败: {e}")
return 0.0
def _apply_edge_blending(self, vegetation_map: np.ndarray) -> np.ndarray:
"""
应用边缘混合
Args:
vegetation_map: 植被图
Returns:
处理后的植被图
"""
try:
processed = vegetation_map.copy()
height, width = processed.shape
blend_width = max(1, int(min(height, width) * self.vegetation_params['edge_blending']))
# 简化的边缘混合
for y in range(height):
for x in range(width):
# 在边缘区域随机减少植被
if (x < blend_width or x > width - blend_width or
y < blend_width or y > height - blend_width):
if np.random.random() < 0.3: # 30%概率移除边缘植被
processed[y, x] = 0
return processed
except Exception as e:
print(f"✗ 边缘混合应用失败: {e}")
return vegetation_map
def _update_vegetation_distribution_stats(self, vegetation_map: np.ndarray):
"""更新植被分布统计"""
try:
unique, counts = np.unique(vegetation_map, return_counts=True)
for veg_id, count in zip(unique, counts):
veg_name = self.vegetation_types.get(veg_id, {}).get('name', f'vegetation_{veg_id}')
self.stats['vegetation_distribution'][veg_name] = self.stats['vegetation_distribution'].get(veg_name, 0) + count
except Exception as e:
print(f"✗ 植被分布统计更新失败: {e}")
def set_seed(self, seed: int):
"""
设置随机种子
Args:
seed: 随机种子
"""
self.seed = seed
print(f"✓ 植被生成器随机种子设置为: {seed}")
def set_vegetation_parameters(self, params: Dict[str, Any]):
"""
设置植被生成参数
Args:
params: 参数字典
"""
self.vegetation_params.update(params)
print(f"✓ 植被生成参数已更新: {self.vegetation_params}")
def get_stats(self) -> Dict[str, Any]:
"""
获取统计信息
Returns:
统计信息字典
"""
# 更新平均生成时间
if self.stats['vegetation_generated'] > 0:
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
return self.stats.copy()
def get_vegetation_info(self, vegetation_id: int) -> Dict[str, Any]:
"""
获取植被信息
Args:
vegetation_id: 植被ID
Returns:
植被信息字典
"""
return self.vegetation_types.get(vegetation_id, {
'name': f'unknown_{vegetation_id}',
'density': 0.1,
'color': (128, 128, 128)
})
def get_available_vegetation(self) -> List[Dict[str, Any]]:
"""
获取可用的植被类型列表
Returns:
植被信息列表
"""
return [self.get_vegetation_info(veg_id) for veg_id in sorted(self.vegetation_types.keys())]
def generate_vegetation_with_density(self, heightmap: np.ndarray, biome_map: np.ndarray,
density_map: np.ndarray, seed: int = None) -> np.ndarray:
"""
根据密度图生成植被
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
density_map: 植被密度图数据
seed: 随机种子
Returns:
植被分布图
"""
try:
import time
generation_start_time = time.time()
if not self.enabled:
print("✗ 植被生成器未启用")
return np.zeros_like(heightmap, dtype=np.int32)
# 更新种子
if seed is not None:
self.seed = seed
print("✓ 开始生成基于密度的植被分布...")
# 初始化随机数生成器
random.seed(self.seed)
np.random.seed(self.seed)
# 确保密度图尺寸匹配
if density_map.shape != heightmap.shape:
print("✗ 密度图尺寸与高度图不匹配")
return np.zeros_like(heightmap, dtype=np.int32)
# 创建植被图
height, width = heightmap.shape
self.vegetation_map = np.zeros((height, width), dtype=np.int32)
# 为每个像素生成植被
print(" → 生成密度控制的植被...")
for y in range(height):
for x in range(width):
# 获取当前位置的属性
height_value = heightmap[y, x]
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
density_value = density_map[y, x]
# 根据密度值决定是否生成植被
if np.random.random() < density_value:
# 生成植被
vegetation_type = self._generate_vegetation_at_position(
x, y, height_value, biome_type, np.random.random()
)
self.vegetation_map[y, x] = vegetation_type
# 应用后处理
print(" → 应用后处理...")
self.vegetation_map = self._post_process_vegetation(self.vegetation_map, heightmap, biome_map)
# 更新统计信息
generation_time = time.time() - generation_start_time
self.stats['vegetation_generated'] += 1
self.stats['total_generation_time'] += generation_time
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
# 更新植被分布统计
self._update_vegetation_distribution_stats(self.vegetation_map)
print(f"✓ 基于密度的植被生成完成,耗时: {generation_time:.2f}")
return self.vegetation_map
except Exception as e:
print(f"✗ 基于密度的植被生成失败: {e}")
import traceback
traceback.print_exc()
return np.zeros_like(heightmap, dtype=np.int32)
def generate_forest_vegetation(self, heightmap: np.ndarray, biome_map: np.ndarray,
tree_density: float = 0.15, seed: int = None) -> np.ndarray:
"""
生成森林植被(专门用于森林区域)
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
tree_density: 树木密度
seed: 随机种子
Returns:
植被分布图
"""
try:
import time
generation_start_time = time.time()
if not self.enabled:
print("✗ 植被生成器未启用")
return np.zeros_like(heightmap, dtype=np.int32)
# 更新种子
if seed is not None:
self.seed = seed
print("✓ 开始生成森林植被...")
# 初始化随机数生成器
random.seed(self.seed)
np.random.seed(self.seed)
# 创建植被图
height, width = heightmap.shape
self.vegetation_map = np.zeros((height, width), dtype=np.int32)
# 专门处理森林生物群落ID 3 和 4
forest_biomes = [3, 4] # forest 和 jungle
# 为每个像素生成森林植被
print(" → 生成森林植被...")
for y in range(height):
for x in range(width):
height_value = heightmap[y, x]
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
# 只在森林生物群落中生成植被
if biome_type in forest_biomes:
# 根据树木密度决定是否生成树
if np.random.random() < tree_density:
# 选择合适的树种
if biome_type == 4: # jungle
tree_type = 6 if np.random.random() < 0.3 else 4 # 30%概率生成棕榈树
else: # forest
tree_type = 5 if height_value > 0.8 else 4 # 高海拔生成松树
self.vegetation_map[y, x] = tree_type
else:
# 生成地面植被
ground_vegetation = [1, 2, 8, 9] # grass, shrub, bush, fern
self.vegetation_map[y, x] = np.random.choice(ground_vegetation)
# 应用后处理
print(" → 应用后处理...")
self.vegetation_map = self._post_process_vegetation(self.vegetation_map, heightmap, biome_map)
# 更新统计信息
generation_time = time.time() - generation_start_time
self.stats['vegetation_generated'] += 1
self.stats['total_generation_time'] += generation_time
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
# 更新植被分布统计
self._update_vegetation_distribution_stats(self.vegetation_map)
print(f"✓ 森林植被生成完成,耗时: {generation_time:.2f}")
return self.vegetation_map
except Exception as e:
print(f"✗ 森林植被生成失败: {e}")
import traceback
traceback.print_exc()
return np.zeros_like(heightmap, dtype=np.int32)
def generate_desert_vegetation(self, heightmap: np.ndarray, biome_map: np.ndarray,
cactus_density: float = 0.05, seed: int = None) -> np.ndarray:
"""
生成沙漠植被(专门用于沙漠区域)
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
cactus_density: 仙人掌密度
seed: 随机种子
Returns:
植被分布图
"""
try:
import time
generation_start_time = time.time()
if not self.enabled:
print("✗ 植被生成器未启用")
return np.zeros_like(heightmap, dtype=np.int32)
# 更新种子
if seed is not None:
self.seed = seed
print("✓ 开始生成沙漠植被...")
# 初始化随机数生成器
random.seed(self.seed)
np.random.seed(self.seed)
# 创建植被图
height, width = heightmap.shape
self.vegetation_map = np.zeros((height, width), dtype=np.int32)
# 专门处理沙漠生物群落ID 5
desert_biome = 5
# 为每个像素生成沙漠植被
print(" → 生成沙漠植被...")
for y in range(height):
for x in range(width):
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
# 只在沙漠生物群落中生成植被
if biome_type == desert_biome:
# 根据仙人掌密度决定是否生成仙人掌
if np.random.random() < cactus_density:
self.vegetation_map[y, x] = 7 # cactus
else:
# 生成稀疏的地面植被
if np.random.random() < 0.1: # 10%概率
ground_vegetation = [1, 2, 8] # grass, shrub, bush
self.vegetation_map[y, x] = np.random.choice(ground_vegetation)
# 应用后处理
print(" → 应用后处理...")
self.vegetation_map = self._post_process_vegetation(self.vegetation_map, heightmap, biome_map)
# 更新统计信息
generation_time = time.time() - generation_start_time
self.stats['vegetation_generated'] += 1
self.stats['total_generation_time'] += generation_time
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
# 更新植被分布统计
self._update_vegetation_distribution_stats(self.vegetation_map)
print(f"✓ 沙漠植被生成完成,耗时: {generation_time:.2f}")
return self.vegetation_map
except Exception as e:
print(f"✗ 沙漠植被生成失败: {e}")
import traceback
traceback.print_exc()
return np.zeros_like(heightmap, dtype=np.int32)
def generate_water_vegetation(self, heightmap: np.ndarray, biome_map: np.ndarray,
water_level: float = 0.2, seed: int = None) -> np.ndarray:
"""
生成水下植被(专门用于水域)
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
water_level: 水位
seed: 随机种子
Returns:
植被分布图
"""
try:
import time
generation_start_time = time.time()
if not self.enabled:
print("✗ 植被生成器未启用")
return np.zeros_like(heightmap, dtype=np.int32)
# 更新种子
if seed is not None:
self.seed = seed
print("✓ 开始生成水下植被...")
# 初始化随机数生成器
random.seed(self.seed)
np.random.seed(self.seed)
# 创建植被图
height, width = heightmap.shape
self.vegetation_map = np.zeros((height, width), dtype=np.int32)
# 专门处理水域生物群落ID 0
water_biome = 0
# 为每个像素生成水下植被
print(" → 生成水下植被...")
for y in range(height):
for x in range(width):
height_value = heightmap[y, x]
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
# 只在水域中且低于水位的地方生成植被
if biome_type == water_biome and height_value < water_level:
# 生成水下植被
water_vegetation = [12, 13, 14, 15] # seaweed, algae, kelp, coral
self.vegetation_map[y, x] = np.random.choice(water_vegetation)
# 应用后处理
print(" → 应用后处理...")
self.vegetation_map = self._post_process_vegetation(self.vegetation_map, heightmap, biome_map)
# 更新统计信息
generation_time = time.time() - generation_start_time
self.stats['vegetation_generated'] += 1
self.stats['total_generation_time'] += generation_time
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
# 更新植被分布统计
self._update_vegetation_distribution_stats(self.vegetation_map)
print(f"✓ 水下植被生成完成,耗时: {generation_time:.2f}")
return self.vegetation_map
except Exception as e:
print(f"✗ 水下植被生成失败: {e}")
import traceback
traceback.print_exc()
return np.zeros_like(heightmap, dtype=np.int32)
def blend_vegetation_maps(self, vegetation_maps: List[np.ndarray], weights: List[float] = None) -> np.ndarray:
"""
混合多个植被图
Args:
vegetation_maps: 植被图列表
weights: 权重列表如果为None则平均分配权重
Returns:
混合后的植被图
"""
try:
if not vegetation_maps:
return np.array([])
if len(vegetation_maps) == 1:
return vegetation_maps[0].copy()
# 确保所有植被图尺寸相同
base_shape = vegetation_maps[0].shape
for vm in vegetation_maps:
if vm.shape != base_shape:
raise ValueError("所有植被图必须具有相同的尺寸")
# 处理权重
if weights is None:
weights = [1.0 / len(vegetation_maps)] * len(vegetation_maps)
elif len(weights) != len(vegetation_maps):
raise ValueError("权重数量必须与植被图数量相同")
# 归一化权重
total_weight = sum(weights)
if total_weight > 0:
weights = [w / total_weight for w in weights]
# 混合植被图
height, width = base_shape
blended = np.zeros(base_shape, dtype=np.int32)
# 为每个像素计算加权植被类型
for y in range(height):
for x in range(width):
# 计算每个植被类型的加权投票
veg_votes = {}
for i, (vm, weight) in enumerate(zip(vegetation_maps, weights)):
veg_type = vm[y, x]
veg_votes[veg_type] = veg_votes.get(veg_type, 0.0) + weight
# 选择得票最多的植被类型
blended[y, x] = max(veg_votes, key=veg_votes.get)
return blended
except Exception as e:
print(f"✗ 植被图混合失败: {e}")
import traceback
traceback.print_exc()
# 返回第一个植被图的副本
return vegetation_maps[0].copy() if vegetation_maps else np.array([])
def smooth_vegetation_map(self, vegetation_map: np.ndarray, iterations: int = 1) -> np.ndarray:
"""
平滑植被图
Args:
vegetation_map: 原始植被图
iterations: 平滑迭代次数
Returns:
平滑后的植被图
"""
try:
smoothed = vegetation_map.copy()
height, width = smoothed.shape
for _ in range(iterations):
new_map = smoothed.copy()
# 对每个像素进行平滑处理
for y in range(height):
for x in range(width):
# 统计邻居的植被类型
neighbor_types = []
# 检查8个邻居
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dy == 0 and dx == 0:
continue
ny, nx = y + dy, x + dx
if 0 <= ny < height and 0 <= nx < width:
neighbor_types.append(smoothed[ny, nx])
# 如果大多数邻居是相同类型,则改变当前像素
if neighbor_types:
unique, counts = np.unique(neighbor_types, return_counts=True)
most_common_type = unique[np.argmax(counts)]
most_common_count = np.max(counts)
# 如果超过一半的邻居是相同类型,则采用该类型
if most_common_count > len(neighbor_types) / 2:
new_map[y, x] = most_common_type
smoothed = new_map
return smoothed
except Exception as e:
print(f"✗ 植被图平滑失败: {e}")
return vegetation_map
def generate_vegetation_colors(self, vegetation_map: np.ndarray) -> np.ndarray:
"""
为植被图生成颜色表示
Args:
vegetation_map: 植被图
Returns:
颜色图 (height, width, 3)
"""
try:
height, width = vegetation_map.shape
color_map = np.zeros((height, width, 3), dtype=np.uint8)
# 为每个植被类型分配颜色
for y in range(height):
for x in range(width):
veg_id = vegetation_map[y, x]
veg_info = self.vegetation_types.get(veg_id, {'color': (128, 128, 128)})
color_map[y, x] = veg_info['color']
return color_map
except Exception as e:
print(f"✗ 植被颜色图生成失败: {e}")
height, width = vegetation_map.shape
return np.zeros((height, width, 3), dtype=np.uint8)
def export_vegetation_legend(self, filename: str) -> bool:
"""
导出植被图例
Args:
filename: 文件名
Returns:
是否导出成功
"""
try:
import json
legend_data = {}
for veg_id, veg_info in self.vegetation_types.items():
legend_data[veg_id] = {
'name': veg_info['name'],
'color': veg_info['color'],
'density': veg_info['density']
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(legend_data, f, ensure_ascii=False, indent=2)
print(f"✓ 植被图例已导出到: {filename}")
return True
except Exception as e:
print(f"✗ 植被图例导出失败: {e}")
return False
def import_vegetation_legend(self, filename: str) -> bool:
"""
导入植被图例
Args:
filename: 文件名
Returns:
是否导入成功
"""
try:
import json
with open(filename, 'r', encoding='utf-8') as f:
legend_data = json.load(f)
# 更新植被类型
self.vegetation_types = {}
for veg_id, veg_info in legend_data.items():
self.vegetation_types[int(veg_id)] = {
'name': veg_info['name'],
'color': tuple(veg_info['color']),
'density': veg_info['density']
}
print(f"✓ 植被图例已从 {filename} 导入")
return True
except Exception as e:
print(f"✗ 植被图例导入失败: {e}")
return False
def get_vegetation_distribution_stats(self) -> Dict[str, int]:
"""
获取植被分布统计
Returns:
植被分布统计字典
"""
return self.stats['vegetation_distribution'].copy()
def reset_stats(self):
"""重置统计信息"""
self.stats = {
'vegetation_generated': 0,
'total_generation_time': 0.0,
'average_generation_time': 0.0,
'vegetation_distribution': {}
}
print("✓ 植被生成器统计信息已重置")
def set_vegetation_types(self, vegetation_types: Dict[int, Dict[str, Any]]):
"""
设置植被类型
Args:
vegetation_types: 植被类型字典
"""
self.vegetation_types = vegetation_types
print(f"✓ 植被类型已更新,共 {len(vegetation_types)} 种植被")
def add_vegetation_type(self, vegetation_id: int, vegetation_info: Dict[str, Any]):
"""
添加植被类型
Args:
vegetation_id: 植被ID
vegetation_info: 植被信息
"""
self.vegetation_types[vegetation_id] = vegetation_info
print(f"✓ 植被类型 {vegetation_info['name']} (ID: {vegetation_id}) 已添加")
def remove_vegetation_type(self, vegetation_id: int):
"""
移除植被类型
Args:
vegetation_id: 植被ID
"""
if vegetation_id in self.vegetation_types:
veg_name = self.vegetation_types[vegetation_id]['name']
del self.vegetation_types[vegetation_id]
print(f"✓ 植被类型 {veg_name} (ID: {vegetation_id}) 已移除")
else:
print(f"✗ 无效的植被ID: {vegetation_id}")
def modify_vegetation_type(self, vegetation_id: int, modifications: Dict[str, Any]):
"""
修改植被类型
Args:
vegetation_id: 植被ID
modifications: 修改内容
"""
if vegetation_id in self.vegetation_types:
self.vegetation_types[vegetation_id].update(modifications)
print(f"✓ 植被类型 (ID: {vegetation_id}) 已修改")
else:
print(f"✗ 无效的植被ID: {vegetation_id}")
def set_biome_vegetation(self, biome_id: int, vegetation_types: List[int]):
"""
设置生物群落的植被类型
Args:
biome_id: 生物群落ID
vegetation_types: 植被类型列表
"""
self.biome_vegetation[biome_id] = vegetation_types
print(f"✓ 生物群落 {biome_id} 的植被类型已设置为: {vegetation_types}")
def get_biome_vegetation(self, biome_id: int) -> List[int]:
"""
获取生物群落的植被类型
Args:
biome_id: 生物群落ID
Returns:
植被类型列表
"""
return self.biome_vegetation.get(biome_id, []).copy()
def generate_vegetation_density_map(self, heightmap: np.ndarray, biome_map: np.ndarray) -> np.ndarray:
"""
生成植被密度图
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
Returns:
植被密度图
"""
try:
height, width = heightmap.shape
density_map = np.zeros((height, width), dtype=np.float32)
# 为每个生物群落生成不同的密度模式
for y in range(height):
for x in range(width):
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
height_value = heightmap[y, x]
# 根据生物群落类型确定基础密度
if biome_type == 0: # ocean
density_map[y, x] = 0.0
elif biome_type == 1: # beach
density_map[y, x] = 0.1
elif biome_type == 2: # plains
density_map[y, x] = 0.4
elif biome_type in [3, 4]: # forest, jungle
density_map[y, x] = 0.8
elif biome_type == 5: # desert
density_map[y, x] = 0.05
elif biome_type == 6: # mountain
# 山地密度随高度变化
density_map[y, x] = max(0.0, 0.6 - height_value)
elif biome_type in [7, 8, 9]: # snow, taiga, tundra
density_map[y, x] = 0.3
elif biome_type == 10: # swamp
density_map[y, x] = 0.6
elif biome_type == 11: # savanna
density_map[y, x] = 0.3
else:
density_map[y, x] = 0.2
# 应用噪声以增加自然变化
if self.noise_generator:
for y in range(height):
for x in range(width):
noise_value = self.noise_generator.generate_noise(
x / 20.0, y / 20.0, self.seed + 5000
)
density_map[y, x] = max(0.0, min(1.0, density_map[y, x] + noise_value * 0.1))
return density_map
except Exception as e:
print(f"✗ 植被密度图生成失败: {e}")
return np.zeros_like(heightmap, dtype=np.float32)
def apply_vegetation_mask(self, vegetation_map: np.ndarray, mask: np.ndarray) -> np.ndarray:
"""
应用掩码到植被图
Args:
vegetation_map: 原始植被图
mask: 掩码0-1范围0表示移除植被
Returns:
处理后的植被图
"""
try:
if mask.shape != vegetation_map.shape:
print("✗ 掩码尺寸与植被图不匹配")
return vegetation_map
masked = vegetation_map.copy()
# 根据掩码值调整植被
for y in range(masked.shape[0]):
for x in range(masked.shape[1]):
# 掩码值越小,移除植被的概率越大
if np.random.random() > mask[y, x]:
masked[y, x] = 0 # 移除植被
return masked
except Exception as e:
print(f"✗ 植被掩码应用失败: {e}")
return vegetation_map
def generate_clustered_vegetation(self, heightmap: np.ndarray, biome_map: np.ndarray,
cluster_density: float = 0.2, seed: int = None) -> np.ndarray:
"""
生成聚类植被
Args:
heightmap: 高度图数据
biome_map: 生物群落图数据
cluster_density: 聚类密度
seed: 随机种子
Returns:
植被分布图
"""
try:
import time
generation_start_time = time.time()
if not self.enabled:
print("✗ 植被生成器未启用")
return np.zeros_like(heightmap, dtype=np.int32)
# 更新种子
if seed is not None:
self.seed = seed
print("✓ 开始生成聚类植被...")
# 初始化随机数生成器
random.seed(self.seed)
np.random.seed(self.seed)
# 创建植被图
height, width = heightmap.shape
self.vegetation_map = np.zeros((height, width), dtype=np.int32)
# 生成聚类中心点
num_clusters = int(width * height * cluster_density / 100)
clusters = []
for _ in range(num_clusters):
x = np.random.randint(0, width)
y = np.random.randint(0, height)
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
# 获取该生物群落的植被类型
veg_types = self.biome_vegetation.get(biome_type, [1, 2]) # 默认为草和灌木
veg_type = np.random.choice(veg_types) if veg_types else 1
cluster_size = np.random.randint(5, 20) # 聚类大小
clusters.append((x, y, veg_type, cluster_size))
# 在聚类中心点周围生成植被
print(" → 生成植被聚类...")
for cx, cy, veg_type, cluster_size in clusters:
for _ in range(cluster_size):
# 在聚类中心附近随机生成植被
angle = np.random.random() * 2 * np.pi
distance = np.random.random() * cluster_size / 2
x = int(cx + np.cos(angle) * distance)
y = int(cy + np.sin(angle) * distance)
# 确保坐标在有效范围内
if 0 <= x < width and 0 <= y < height:
# 检查该位置是否适合该植被类型
height_value = heightmap[y, x]
biome_type = biome_map[y, x] if y < biome_map.shape[0] and x < biome_map.shape[1] else 0
# 简单的适宜性检查
if height_value > 0.1 and biome_type != 0: # 不在水中
self.vegetation_map[y, x] = veg_type
# 应用后处理
print(" → 应用后处理...")
self.vegetation_map = self._post_process_vegetation(self.vegetation_map, heightmap, biome_map)
# 更新统计信息
generation_time = time.time() - generation_start_time
self.stats['vegetation_generated'] += 1
self.stats['total_generation_time'] += generation_time
self.stats['average_generation_time'] = self.stats['total_generation_time'] / self.stats['vegetation_generated']
# 更新植被分布统计
self._update_vegetation_distribution_stats(self.vegetation_map)
print(f"✓ 聚类植被生成完成,耗时: {generation_time:.2f}")
return self.vegetation_map
except Exception as e:
print(f"✗ 聚类植被生成失败: {e}")
import traceback
traceback.print_exc()
return np.zeros_like(heightmap, dtype=np.int32)