""" 地形编辑工具模块 """ import math import random from panda3d.core import PNMImage class TerrainTools: """ 地形编辑工具类 提供完整的地形编辑功能,包括抬高、降低、平坦化、平滑、噪声等工具 """ def __init__(self, world): self.world = world self.current_tool = "抬高工具" self.radius = 10.0 self.strength = 50.0 self.operation = "添加" self.is_editing = False self.last_edit_pos = None self.brush_spacing = 1.0 # 画笔间隔,用于连续编辑 # 工具参数 self.smooth_factor = 0.5 self.noise_scale = 1.0 self.erosion_factor = 0.1 self.thermal_erosion_factor = 0.05 def set_tool(self, tool_name): """ 设置当前工具 """ valid_tools = ["抬高工具", "降低工具", "平坦化工具", "平滑工具", "噪声工具", "侵蚀工具", "热侵蚀工具"] if tool_name in valid_tools: self.current_tool = tool_name print(f"设置地形编辑工具: {tool_name}") return True else: print(f"无效的工具名称: {tool_name}") return False def set_radius(self, radius): """ 设置工具半径 """ self.radius = max(0.1, min(100.0, float(radius))) def set_strength(self, strength): """ 设置工具强度 """ self.strength = max(0.0, min(100.0, float(strength))) def set_operation(self, operation): """ 设置操作类型 """ valid_operations = ["添加", "减去", "设置"] if operation in valid_operations: self.operation = operation else: print(f"无效的操作类型: {operation}") def set_smooth_factor(self, factor): """ 设置平滑因子 """ self.smooth_factor = max(0.0, min(1.0, float(factor))) def set_noise_scale(self, scale): """ 设置噪声缩放 """ self.noise_scale = max(0.1, min(10.0, float(scale))) def set_erosion_factor(self, factor): """ 设置侵蚀因子 """ self.erosion_factor = max(0.0, min(1.0, float(factor))) def set_thermal_erosion_factor(self, factor): """ 设置热侵蚀因子 """ self.thermal_erosion_factor = max(0.0, min(1.0, float(factor))) def set_brush_spacing(self, spacing): """ 设置画笔间隔 """ self.brush_spacing = max(0.1, min(10.0, float(spacing))) def start_editing(self, x, y): """ 开始编辑地形 """ self.is_editing = True self.last_edit_pos = (x, y) self._apply_tool(x, y) def update_editing(self, x, y): """ 更新编辑过程 """ if self.is_editing: # 检查是否需要应用工具(基于画笔间隔) if self.last_edit_pos: distance = math.sqrt((x - self.last_edit_pos[0])**2 + (y - self.last_edit_pos[1])**2) if distance >= self.brush_spacing: self._apply_tool(x, y) self.last_edit_pos = (x, y) else: self._apply_tool(x, y) self.last_edit_pos = (x, y) def stop_editing(self): """ 停止编辑地形 """ self.is_editing = False self.last_edit_pos = None def _apply_tool(self, x, y): """ 应用当前工具到指定位置 """ # 获取当前选中的地形 selected_terrain = self._get_selected_terrain() if not selected_terrain: # 如果没有选中地形,尝试获取场景中的第一个地形 selected_terrain = self._get_first_terrain() if not selected_terrain: print("没有可用的地形") return # 根据当前工具类型应用不同的操作 if self.current_tool == "抬高工具": self._raise_terrain(selected_terrain, x, y) elif self.current_tool == "降低工具": self._lower_terrain(selected_terrain, x, y) elif self.current_tool == "平坦化工具": self._flatten_terrain(selected_terrain, x, y) elif self.current_tool == "平滑工具": self._smooth_terrain(selected_terrain, x, y) elif self.current_tool == "噪声工具": self._noise_terrain(selected_terrain, x, y) elif self.current_tool == "侵蚀工具": self._erode_terrain(selected_terrain, x, y) elif self.current_tool == "热侵蚀工具": self._thermal_erode_terrain(selected_terrain, x, y) def _get_selected_terrain(self): """ 获取当前选中的地形 """ # 如果有选择系统,获取选中的地形 if hasattr(self.world, 'selection') and self.world.selection.selectedNode: selected_node = self.world.selection.selectedNode # 检查选中的节点是否是地形节点 if selected_node.getTag("tree_item_type") == "TERRAIN_NODE": # 在地形管理器中查找对应的地形信息 if hasattr(self.world, 'terrain_manager'): for terrain_info in self.world.terrain_manager.terrains: if terrain_info['node'] == selected_node: return terrain_info return None def _get_first_terrain(self): """ 获取场景中的第一个地形 """ if hasattr(self.world, 'terrain_manager') and self.world.terrain_manager.terrains: return self.world.terrain_manager.terrains[0] return None def _raise_terrain(self, terrain_info, x, y): """ 抬高地形 """ if hasattr(self.world, 'terrain_manager'): operation = "add" if self.operation == "减去": operation = "subtract" elif self.operation == "设置": operation = "set" self.world.terrain_manager.modifyTerrainHeight( terrain_info, x, y, self.radius, self.strength, operation ) def _lower_terrain(self, terrain_info, x, y): """ 降低地形 """ if hasattr(self.world, 'terrain_manager'): operation = "subtract" if self.operation == "添加": operation = "add" elif self.operation == "设置": operation = "set" self.world.terrain_manager.modifyTerrainHeight( terrain_info, x, y, self.radius, self.strength, operation ) def _flatten_terrain(self, terrain_info, x, y): """ 平坦化地形 """ if hasattr(self.world, 'terrain_manager'): # 平坦化工具使用特殊的"set"操作 self.world.terrain_manager.modifyTerrainHeight( terrain_info, x, y, self.radius, self.strength, "set" ) def _smooth_terrain(self, terrain_info, x, y): """ 平滑地形 """ if not hasattr(self.world, 'terrain_manager'): return try: terrain_node = terrain_info['node'] heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return # 获取高度图尺寸 width = heightfield.getXSize() height = heightfield.getYSize() # 获取地形节点信息 terrain_pos = terrain_node.getPos() terrain_scale = terrain_node.getScale() # 坐标转换 relative_x = x - terrain_pos.getX() relative_y = y - terrain_pos.getY() scaled_x = relative_x / terrain_scale.getX() scaled_y = relative_y / terrain_scale.getY() center_offset = (width - 1) / 2 center_x = int(scaled_x + center_offset) center_y = int(scaled_y + center_offset) # 检查中心点是否在有效范围内 if not (0 <= center_x < width and 0 <= center_y < height): return # 计算有效编辑半径 effective_radius = max(1, int(self.radius / max(terrain_scale.getX(), terrain_scale.getY()))) effective_radius = min(effective_radius, width // 4, height // 4) # 创建临时高度图用于存储平滑结果 temp_heightfield = PNMImage(width, height) temp_heightfield.copyFrom(heightfield) # 应用平滑算法 modified = False for dx in range(-effective_radius, effective_radius + 1): for dy in range(-effective_radius, effective_radius + 1): distance = math.sqrt(dx*dx + dy*dy) if distance <= effective_radius: target_x = center_x + dx target_y = center_y + dy # 检查边界 if 0 <= target_x < width and 0 <= target_y < height: # 计算邻域平均值 total_height = 0.0 count = 0 # 检查周围的8个邻居 for nx in range(-1, 2): for ny in range(-1, 2): neighbor_x = target_x + nx neighbor_y = target_y + ny # 检查邻居是否在边界内 if 0 <= neighbor_x < width and 0 <= neighbor_y < height: total_height += heightfield.getRed(neighbor_x, neighbor_y) count += 1 if count > 0: average_height = total_height / count current_height = heightfield.getRed(target_x, target_y) # 应用平滑因子 new_height = current_height * (1 - self.smooth_factor) + average_height * self.smooth_factor # 设置新高度到临时高度图 temp_heightfield.setRed(target_x, target_y, new_height) temp_heightfield.setGreen(target_x, target_y, new_height) temp_heightfield.setBlue(target_x, target_y, new_height) modified = True # 如果有修改,则更新地形 if modified: # 更新高度图 terrain_info['heightfield'] = temp_heightfield # 重新设置高度图并生成地形 terrain = terrain_info['terrain'] terrain.setHeightfield(temp_heightfield) terrain.generate() # 重新创建碰撞体 self._recreate_collision(terrain_node) print(f"✓ 地形已平滑: 位置({x}, {y}), 半径{self.radius}") except Exception as e: print(f"平滑地形时出错: {e}") def _noise_terrain(self, terrain_info, x, y): """ 添加噪声到地形 """ if not hasattr(self.world, 'terrain_manager'): return try: terrain_node = terrain_info['node'] heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return # 获取高度图尺寸 width = heightfield.getXSize() height = heightfield.getYSize() # 获取地形节点信息 terrain_pos = terrain_node.getPos() terrain_scale = terrain_node.getScale() # 坐标转换 relative_x = x - terrain_pos.getX() relative_y = y - terrain_pos.getY() scaled_x = relative_x / terrain_scale.getX() scaled_y = relative_y / terrain_scale.getY() center_offset = (width - 1) / 2 center_x = int(scaled_x + center_offset) center_y = int(scaled_y + center_offset) # 检查中心点是否在有效范围内 if not (0 <= center_x < width and 0 <= center_y < height): return # 计算有效编辑半径 effective_radius = max(1, int(self.radius / max(terrain_scale.getX(), terrain_scale.getY()))) effective_radius = min(effective_radius, width // 4, height // 4) # 应用噪声算法 modified = False for dx in range(-effective_radius, effective_radius + 1): for dy in range(-effective_radius, effective_radius + 1): distance = math.sqrt(dx*dx + dy*dy) if distance <= effective_radius: # 计算影响权重 weight = 1.0 - (distance / effective_radius) if effective_radius > 0 else 1.0 target_x = center_x + dx target_y = center_y + dy # 检查边界 if 0 <= target_x < width and 0 <= target_y < height: # 生成噪声值 noise_value = random.uniform(-1.0, 1.0) * self.noise_scale # 获取当前高度 current_height = heightfield.getRed(target_x, target_y) # 应用噪声 new_height = max(0.0, min(1.0, current_height + noise_value * weight * self.strength * 0.01)) # 设置新高度 heightfield.setRed(target_x, target_y, new_height) heightfield.setGreen(target_x, target_y, new_height) heightfield.setBlue(target_x, target_y, new_height) modified = True # 如果有修改,则更新地形 if modified: # 重新设置高度图并生成地形 terrain = terrain_info['terrain'] terrain.setHeightfield(heightfield) terrain.generate() # 重新创建碰撞体 self._recreate_collision(terrain_node) print(f"✓ 地形已添加噪声: 位置({x}, {y}), 半径{self.radius}") except Exception as e: print(f"添加噪声到地形时出错: {e}") def _erode_terrain(self, terrain_info, x, y): """ 水力侵蚀地形 """ if not hasattr(self.world, 'terrain_manager'): return try: terrain_node = terrain_info['node'] heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return # 获取高度图尺寸 width = heightfield.getXSize() height = heightfield.getYSize() # 获取地形节点信息 terrain_pos = terrain_node.getPos() terrain_scale = terrain_node.getScale() # 坐标转换 relative_x = x - terrain_pos.getX() relative_y = y - terrain_pos.getY() scaled_x = relative_x / terrain_scale.getX() scaled_y = relative_y / terrain_scale.getY() center_offset = (width - 1) / 2 center_x = int(scaled_x + center_offset) center_y = int(scaled_y + center_offset) # 检查中心点是否在有效范围内 if not (0 <= center_x < width and 0 <= center_y < height): return # 计算有效编辑半径 effective_radius = max(1, int(self.radius / max(terrain_scale.getX(), terrain_scale.getY()))) effective_radius = min(effective_radius, width // 4, height // 4) # 应用简单的侵蚀算法 modified = False for dx in range(-effective_radius, effective_radius + 1): for dy in range(-effective_radius, effective_radius + 1): distance = math.sqrt(dx*dx + dy*dy) if distance <= effective_radius: # 计算影响权重 weight = 1.0 - (distance / effective_radius) if effective_radius > 0 else 1.0 target_x = center_x + dx target_y = center_y + dy # 检查边界 if 0 <= target_x < width and 0 <= target_y < height: # 简单的侵蚀模型:降低高点,填充低点 current_height = heightfield.getRed(target_x, target_y) # 根据高度确定侵蚀效果 # 高度越高,侵蚀越强 erosion_amount = self.erosion_factor * weight * (1.0 - current_height) * self.strength * 0.01 # 应用侵蚀 new_height = max(0.0, min(1.0, current_height - erosion_amount)) # 设置新高度 heightfield.setRed(target_x, target_y, new_height) heightfield.setGreen(target_x, target_y, new_height) heightfield.setBlue(target_x, target_y, new_height) modified = True # 如果有修改,则更新地形 if modified: # 重新设置高度图并生成地形 terrain = terrain_info['terrain'] terrain.setHeightfield(heightfield) terrain.generate() # 重新创建碰撞体 self._recreate_collision(terrain_node) print(f"✓ 地形已应用侵蚀: 位置({x}, {y}), 半径{self.radius}") except Exception as e: print(f"侵蚀地形时出错: {e}") def _thermal_erode_terrain(self, terrain_info, x, y): """ 热侵蚀地形(模拟重力作用) """ if not hasattr(self.world, 'terrain_manager'): return try: terrain_node = terrain_info['node'] heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return # 获取高度图尺寸 width = heightfield.getXSize() height = heightfield.getYSize() # 获取地形节点信息 terrain_pos = terrain_node.getPos() terrain_scale = terrain_node.getScale() # 坐标转换 relative_x = x - terrain_pos.getX() relative_y = y - terrain_pos.getY() scaled_x = relative_x / terrain_scale.getX() scaled_y = relative_y / terrain_scale.getY() center_offset = (width - 1) / 2 center_x = int(scaled_x + center_offset) center_y = int(scaled_y + center_offset) # 检查中心点是否在有效范围内 if not (0 <= center_x < width and 0 <= center_y < height): return # 计算有效编辑半径 effective_radius = max(1, int(self.radius / max(terrain_scale.getX(), terrain_scale.getY()))) effective_radius = min(effective_radius, width // 4, height // 4) # 应用热侵蚀算法 modified = False for dx in range(-effective_radius, effective_radius + 1): for dy in range(-effective_radius, effective_radius + 1): distance = math.sqrt(dx*dx + dy*dy) if distance <= effective_radius: # 计算影响权重 weight = 1.0 - (distance / effective_radius) if effective_radius > 0 else 1.0 target_x = center_x + dx target_y = center_y + dy # 检查边界 if 0 <= target_x < width and 0 <= target_y < height: # 检查周围的邻居,找到最低点 current_height = heightfield.getRed(target_x, target_y) lowest_neighbor_height = current_height lowest_neighbor_x = target_x lowest_neighbor_y = target_y # 检查8个邻居 for nx in range(-1, 2): for ny in range(-1, 2): if nx == 0 and ny == 0: continue # 跳过中心点 neighbor_x = target_x + nx neighbor_y = target_y + ny # 检查邻居是否在边界内 if 0 <= neighbor_x < width and 0 <= neighbor_y < height: neighbor_height = heightfield.getRed(neighbor_x, neighbor_y) if neighbor_height < lowest_neighbor_height: lowest_neighbor_height = neighbor_height lowest_neighbor_x = neighbor_x lowest_neighbor_y = neighbor_y # 如果有更低的邻居,移动部分物质 height_diff = current_height - lowest_neighbor_height if height_diff > 0: # 移动物质 move_amount = min(height_diff, self.thermal_erosion_factor * weight * self.strength * 0.01) # 降低当前点 new_height = max(0.0, current_height - move_amount) heightfield.setRed(target_x, target_y, new_height) heightfield.setGreen(target_x, target_y, new_height) heightfield.setBlue(target_x, target_y, new_height) # 提高最低邻居点 neighbor_new_height = min(1.0, lowest_neighbor_height + move_amount * 0.5) heightfield.setRed(lowest_neighbor_x, lowest_neighbor_y, neighbor_new_height) heightfield.setGreen(lowest_neighbor_x, lowest_neighbor_y, neighbor_new_height) heightfield.setBlue(lowest_neighbor_x, lowest_neighbor_y, neighbor_new_height) modified = True # 如果有修改,则更新地形 if modified: # 重新设置高度图并生成地形 terrain = terrain_info['terrain'] terrain.setHeightfield(heightfield) terrain.generate() # 重新创建碰撞体 self._recreate_collision(terrain_node) print(f"✓ 地形已应用热侵蚀: 位置({x}, {y}), 半径{self.radius}") except Exception as e: print(f"热侵蚀地形时出错: {e}") 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}") def apply_brush(self, terrain_info, x, y, brush_data): """ 应用画笔数据到地形 """ if not hasattr(self.world, 'terrain_manager'): return try: terrain_node = terrain_info['node'] heightfield = terrain_info['heightfield'] if not heightfield: print("无法获取地形高度图数据") return # 获取高度图尺寸 width = heightfield.getXSize() height = heightfield.getYSize() # 获取地形节点信息 terrain_pos = terrain_node.getPos() terrain_scale = terrain_node.getScale() # 坐标转换 relative_x = x - terrain_pos.getX() relative_y = y - terrain_pos.getY() scaled_x = relative_x / terrain_scale.getX() scaled_y = relative_y / terrain_scale.getY() center_offset = (width - 1) / 2 center_x = int(scaled_x + center_offset) center_y = int(scaled_y + center_offset) # 检查中心点是否在有效范围内 if not (0 <= center_x < width and 0 <= center_y < height): return # 应用画笔数据 modified = False brush_size = len(brush_data) brush_radius = brush_size // 2 for dx in range(-brush_radius, brush_radius + 1): for dy in range(-brush_radius, brush_radius + 1): if 0 <= brush_radius + dx < brush_size and 0 <= brush_radius + dy < brush_size: brush_value = brush_data[brush_radius + dy][brush_radius + dx] target_x = center_x + dx target_y = center_y + dy # 检查边界 if 0 <= target_x < width and 0 <= target_y < height: # 获取当前高度 current_height = heightfield.getRed(target_x, target_y) # 应用画笔值 new_height = max(0.0, min(1.0, current_height + brush_value * self.strength * 0.01)) # 设置新高度 heightfield.setRed(target_x, target_y, new_height) heightfield.setGreen(target_x, target_y, new_height) heightfield.setBlue(target_x, target_y, new_height) modified = True # 如果有修改,则更新地形 if modified: # 重新设置高度图并生成地形 terrain = terrain_info['terrain'] terrain.setHeightfield(heightfield) terrain.generate() # 重新创建碰撞体 self._recreate_collision(terrain_node) print(f"✓ 应用画笔数据到地形: 位置({x}, {y})") except Exception as e: print(f"应用画笔数据到地形时出错: {e}")