""" 高级地形编辑工具 提供噪声生成、侵蚀模拟、傅里叶地形等高级功能 """ import math import random import numpy as np from panda3d.core import PNMImage class AdvancedTerrainTools: """ 高级地形编辑工具类 提供噪声生成、侵蚀模拟、傅里叶地形等高级功能 """ def __init__(self, world): self.world = world def generate_perlin_noise(self, terrain_info, scale=1.0, octaves=4, persistence=0.5, lacunarity=2.0): """ 生成Perlin噪声地形 """ try: terrain_node = terrain_info['node'] heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return False # 获取高度图尺寸 width = heightfield.getXSize() height = heightfield.getYSize() # 生成Perlin噪声 for y in range(height): for x in range(width): # 计算噪声坐标 nx = x / width * scale ny = y / height * scale # 生成分形噪声 noise_value = self._fractal_noise(nx, ny, octaves, persistence, lacunarity) # 将噪声值映射到0-1范围 normalized_value = (noise_value + 1) / 2 heightfield.setRed(x, y, normalized_value) heightfield.setGreen(x, y, normalized_value) heightfield.setBlue(x, y, normalized_value) # 更新地形 return self._update_terrain(terrain_info) except Exception as e: print(f"生成Perlin噪声时出错: {e}") return False def _fractal_noise(self, x, y, octaves, persistence, lacunarity): """ 生成分形噪声 """ total = 0.0 frequency = 1.0 amplitude = 1.0 maxValue = 0.0 # 用于归一化 for i in range(octaves): total += self._perlin_noise(x * frequency, y * frequency) * amplitude maxValue += amplitude amplitude *= persistence frequency *= lacunarity return total / maxValue def _perlin_noise(self, x, y): """ 生成Perlin噪声 """ # 获取整数部分 X = int(x) & 255 Y = int(y) & 255 # 获取小数部分 x -= int(x) y -= int(y) # 计算平滑曲线 u = self._fade(x) v = self._fade(y) # 计算梯度 a = self._gradient(X, Y, x, y) b = self._gradient(X + 1, Y, x - 1, y) c = self._gradient(X, Y + 1, x, y - 1) d = self._gradient(X + 1, Y + 1, x - 1, y - 1) # 插值 return self._lerp(v, self._lerp(u, a, b), self._lerp(u, c, d)) def _fade(self, t): """ 平滑曲线函数 """ return t * t * t * (t * (t * 6 - 15) + 10) def _lerp(self, t, a, b): """ 线性插值 """ return a + t * (b - a) def _gradient(self, hash_val, x, y): """ 梯度函数 """ h = hash_val & 15 u = x if h < 8 else y v = y if h < 4 else (x if h in [12, 14] else 0) return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v) def simulate_hydraulic_erosion(self, terrain_info, iterations=100, rainfall=0.1, solubility=0.01, evaporation=0.01): """ 模拟水力侵蚀 """ try: heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return False width = heightfield.getXSize() height = heightfield.getYSize() # 初始化水流和沉积物数组 water = np.zeros((width, height)) sediment = np.zeros((width, height)) # 执行侵蚀迭代 for iteration in range(iterations): # 添加降雨 water += rainfall # 计算水流 for y in range(height): for x in range(width): current_height = heightfield.getRed(x, y) + water[x, y] # 查找最低邻居 lowest_height = current_height lowest_x, lowest_y = x, y for dx in [-1, 0, 1]: for dy in [-1, 0, 1]: if dx == 0 and dy == 0: continue nx, ny = x + dx, y + dy if 0 <= nx < width and 0 <= ny < height: neighbor_height = heightfield.getRed(nx, ny) + water[nx, ny] if neighbor_height < lowest_height: lowest_height = neighbor_height lowest_x, lowest_y = nx, ny # 移动水流 if lowest_x != x or lowest_y != y: flow_amount = min(water[x, y], (current_height - lowest_height) * 0.5) water[x, y] -= flow_amount water[lowest_x, lowest_y] += flow_amount # 溶解物质 dissolve_amount = flow_amount * solubility sediment[x, y] -= dissolve_amount sediment[lowest_x, lowest_y] += dissolve_amount # 沉积和蒸发 for y in range(height): for x in range(width): # 蒸发 evaporation_amount = min(water[x, y], evaporation) water[x, y] -= evaporation_amount # 沉积 if sediment[x, y] > 0: deposit_amount = min(sediment[x, y], 0.01) sediment[x, y] -= deposit_amount new_height = heightfield.getRed(x, y) + deposit_amount heightfield.setRed(x, y, new_height) heightfield.setGreen(x, y, new_height) heightfield.setBlue(x, y, new_height) # 更新地形 return self._update_terrain(terrain_info) except Exception as e: print(f"模拟水力侵蚀时出错: {e}") return False def generate_voronoi_terrain(self, terrain_info, points=100, feature_size=10.0): """ 生成Voronoi图地形 """ try: heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return False width = heightfield.getXSize() height = heightfield.getYSize() # 生成随机点 voronoi_points = [] for i in range(points): x = random.randint(0, width - 1) y = random.randint(0, height - 1) height_value = random.random() voronoi_points.append((x, y, height_value)) # 为每个像素分配最近点的高度 for y in range(height): for x in range(width): min_distance = float('inf') closest_height = 0.0 for px, py, ph in voronoi_points: distance = math.sqrt((x - px)**2 + (y - py)**2) if distance < min_distance: min_distance = distance closest_height = ph # 应用特征大小 normalized_distance = min(1.0, min_distance / feature_size) final_height = closest_height * (1.0 - normalized_distance) heightfield.setRed(x, y, final_height) heightfield.setGreen(x, y, final_height) heightfield.setBlue(x, y, final_height) # 更新地形 return self._update_terrain(terrain_info) except Exception as e: print(f"生成Voronoi地形时出错: {e}") return False def generate_fourier_terrain(self, terrain_info, frequency_count=10): """ 生成傅里叶地形 """ try: heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return False width = heightfield.getXSize() height_value = heightfield.getYSize() # 初始化高度图 for y in range(height_value): for x in range(width): heightfield.setRed(x, y, 0.5) heightfield.setGreen(x, y, 0.5) heightfield.setBlue(x, y, 0.5) # 添加不同频率的波 for i in range(frequency_count): frequency = 2 ** i amplitude = 1.0 / frequency for y in range(height_value): for x in range(width): # 添加正弦波 wave_x = math.sin(2 * math.pi * x * frequency / width) * amplitude wave_y = math.sin(2 * math.pi * y * frequency / height_value) * amplitude current_height = heightfield.getRed(x, y) new_height = current_height + (wave_x + wave_y) * 0.1 new_height = max(0.0, min(1.0, new_height)) heightfield.setRed(x, y, new_height) heightfield.setGreen(x, y, new_height) heightfield.setBlue(x, y, new_height) # 更新地形 return self._update_terrain(terrain_info) except Exception as e: print(f"生成傅里叶地形时出错: {e}") return False def generate_fault_formation(self, terrain_info, iterations=50, displacement=0.1): """ 生成断层地形 """ try: heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return False width = heightfield.getXSize() height_value = heightfield.getYSize() # 执行断层迭代 for i in range(iterations): # 随机生成断层线 angle = random.random() * 2 * math.pi center_x = random.randint(0, width - 1) center_y = random.randint(0, height_value - 1) # 计算断层方向向量 dx = math.cos(angle) dy = math.sin(angle) # 移动断层一侧的点 for y in range(height_value): for x in range(width): # 计算点到断层线的距离和方向 distance = (x - center_x) * dx + (y - center_y) * dy if distance > 0: # 提升这一侧的高度 current_height = heightfield.getRed(x, y) new_height = current_height + displacement * (iterations - i) / iterations new_height = max(0.0, min(1.0, new_height)) heightfield.setRed(x, y, new_height) heightfield.setGreen(x, y, new_height) heightfield.setBlue(x, y, new_height) # 更新地形 return self._update_terrain(terrain_info) except Exception as e: print(f"生成断层地形时出错: {e}") return False def generate_thermal_erosion(self, terrain_info, iterations=100, talus_angle=0.1): """ 生成热侵蚀地形 """ try: heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return False width = heightfield.getXSize() height_value = heightfield.getYSize() # 执行热侵蚀迭代 for iteration in range(iterations): # 创建高度变化数组 height_changes = np.zeros((width, height_value)) # 计算每个点的侵蚀 for y in range(height_value): for x in range(width): current_height = heightfield.getRed(x, y) # 检查周围点 for dx in [-1, 0, 1]: for dy in [-1, 0, 1]: if dx == 0 and dy == 0: continue nx, ny = x + dx, y + dy if 0 <= nx < width and 0 <= ny < height_value: neighbor_height = heightfield.getRed(nx, ny) height_diff = current_height - neighbor_height # 如果高度差超过临界角度,发生滑坡 if height_diff > talus_angle: # 移动部分物质到邻居 move_amount = (height_diff - talus_angle) * 0.1 height_changes[x, y] -= move_amount height_changes[nx, ny] += move_amount * 0.5 # 部分沉积 # 应用高度变化 for y in range(height_value): for x in range(width): if height_changes[x, y] != 0: current_height = heightfield.getRed(x, y) new_height = current_height + height_changes[x, y] new_height = max(0.0, min(1.0, new_height)) heightfield.setRed(x, y, new_height) heightfield.setGreen(x, y, new_height) heightfield.setBlue(x, y, new_height) # 更新地形 return self._update_terrain(terrain_info) except Exception as e: print(f"生成热侵蚀地形时出错: {e}") return False def _update_terrain(self, terrain_info): """ 更新地形 """ try: terrain = terrain_info['terrain'] terrain.setHeightfield(terrain_info['heightfield']) terrain.generate() # 重新创建碰撞体 self._recreate_collision(terrain_info['node']) return True except Exception as e: print(f"更新地形时出错: {e}") return False def _recreate_collision(self, terrain_node): """ 重新创建地形碰撞体 """ try: from panda3d.core import BitMask32 # 移除旧的地形碰撞体 for child in terrain_node.getChildren(): if child.getName().startswith("terrain_collision_"): child.removeNode() # 设置地形节点的碰撞掩码 terrain_node.setCollideMask(BitMask32.bit(2)) # 为地形的每个子节点也设置碰撞掩码 for child in terrain_node.getChildren(): child.setCollideMask(BitMask32.bit(2)) except Exception as e: print(f"重新创建碰撞体时出错: {e}")