""" 地形序列化器 负责地形数据的序列化和反序列化 """ import numpy as np import json import pickle import zlib import base64 from typing import Dict, Any, Optional import time import os class TerrainSerializer: """ 地形序列化器 负责地形数据的序列化和反序列化,支持多种格式和压缩方式 """ def __init__(self, plugin): """ 初始化地形序列化器 Args: plugin: 程序化地形生成插件实例 """ self.plugin = plugin self.enabled = False self.initialized = False # 序列化配置 self.serialization_params = { 'default_format': 'json', 'compression_enabled': True, 'compression_level': 6, 'chunk_size': 1024, 'include_metadata': True, 'include_textures': True, 'include_vegetation': True, 'include_water': True } # 支持的格式 self.supported_formats = ['json', 'pickle', 'binary', 'custom'] # 统计信息 self.stats = { 'terrains_saved': 0, 'terrains_loaded': 0, 'total_save_time': 0.0, 'total_load_time': 0.0, 'average_save_time': 0.0, 'average_load_time': 0.0, 'data_compressed': 0, 'data_uncompressed': 0 } print("✓ 地形序列化器已创建") def initialize(self) -> bool: """ 初始化地形序列化器 Returns: 是否初始化成功 """ try: 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 save_terrain(self, filename: str, terrain_data: Dict[str, Any] = None, format: str = None, compress: bool = None) -> bool: """ 保存地形数据 Args: filename: 文件名 terrain_data: 地形数据字典(如果为None,则从地形管理器获取) format: 保存格式 compress: 是否压缩 Returns: 是否保存成功 """ try: save_start_time = time.time() if not self.enabled: print("✗ 地形序列化器未启用") return False # 使用默认参数如果未指定 if format is None: format = self.serialization_params['default_format'] if compress is None: compress = self.serialization_params['compression_enabled'] # 验证格式 if format not in self.supported_formats: print(f"✗ 不支持的格式: {format}") return False print(f"✓ 开始保存地形到 {filename} (格式: {format}, 压缩: {compress})...") # 如果未提供地形数据,则从地形管理器获取 if terrain_data is None: if self.plugin.terrain_manager: terrain_data = self.plugin.terrain_manager.get_terrain_data() else: print("✗ 无法获取地形数据") return False # 准备保存数据 save_data = self._prepare_terrain_data(terrain_data) # 根据格式保存 if format == 'json': success = self._save_json(filename, save_data, compress) elif format == 'pickle': success = self._save_pickle(filename, save_data, compress) elif format == 'binary': success = self._save_binary(filename, save_data, compress) elif format == 'custom': success = self._save_custom(filename, save_data, compress) else: print(f"✗ 不支持的保存格式: {format}") return False if success: # 更新统计信息 save_time = time.time() - save_start_time self.stats['terrains_saved'] += 1 self.stats['total_save_time'] += save_time if self.stats['terrains_saved'] > 0: self.stats['average_save_time'] = ( self.stats['total_save_time'] / self.stats['terrains_saved'] ) print(f"✓ 地形保存完成,耗时: {save_time:.3f}秒") return True else: print("✗ 地形保存失败") return False except Exception as e: print(f"✗ 地形保存失败: {e}") import traceback traceback.print_exc() return False def load_terrain(self, filename: str, format: str = None) -> Optional[Dict[str, Any]]: """ 加载地形数据 Args: filename: 文件名 format: 加载格式(如果为None,则自动检测) Returns: 地形数据字典或None """ try: load_start_time = time.time() if not self.enabled: print("✗ 地形序列化器未启用") return None # 检查文件是否存在 if not os.path.exists(filename): print(f"✗ 文件不存在: {filename}") return None print(f"✓ 开始加载地形从 {filename}...") # 如果未指定格式,则尝试自动检测 if format is None: format = self._detect_format(filename) # 验证格式 if format not in self.supported_formats: print(f"✗ 不支持的格式: {format}") return None # 根据格式加载 if format == 'json': terrain_data = self._load_json(filename) elif format == 'pickle': terrain_data = self._load_pickle(filename) elif format == 'binary': terrain_data = self._load_binary(filename) elif format == 'custom': terrain_data = self._load_custom(filename) else: print(f"✗ 不支持的加载格式: {format}") return None if terrain_data is not None: # 处理加载的数据 processed_data = self._process_loaded_data(terrain_data) # 更新统计信息 load_time = time.time() - load_start_time self.stats['terrains_loaded'] += 1 self.stats['total_load_time'] += load_time if self.stats['terrains_loaded'] > 0: self.stats['average_load_time'] = ( self.stats['total_load_time'] / self.stats['terrains_loaded'] ) print(f"✓ 地形加载完成,耗时: {load_time:.3f}秒") return processed_data else: print("✗ 地形加载失败") return None except Exception as e: print(f"✗ 地形加载失败: {e}") import traceback traceback.print_exc() return None def _prepare_terrain_data(self, terrain_data: Dict[str, Any]) -> Dict[str, Any]: """ 准备地形数据以供保存 Args: terrain_data: 原始地形数据 Returns: 准备好的地形数据 """ try: # 创建保存数据的副本 save_data = { 'version': '1.0', 'timestamp': time.time(), 'format': self.serialization_params['default_format'] } # 添加地形数据 if 'heightmap' in terrain_data and terrain_data['heightmap'] is not None: heightmap = terrain_data['heightmap'] save_data['heightmap'] = { 'data': heightmap.tolist() if isinstance(heightmap, np.ndarray) else heightmap, 'shape': heightmap.shape if isinstance(heightmap, np.ndarray) else None, 'dtype': str(heightmap.dtype) if isinstance(heightmap, np.ndarray) else 'unknown' } # 添加法线图 if 'normalmap' in terrain_data and terrain_data['normalmap'] is not None: normalmap = terrain_data['normalmap'] save_data['normalmap'] = { 'data': normalmap.tolist() if isinstance(normalmap, np.ndarray) else normalmap, 'shape': normalmap.shape if isinstance(normalmap, np.ndarray) else None, 'dtype': str(normalmap.dtype) if isinstance(normalmap, np.ndarray) else 'unknown' } # 添加生物群落图 if self.serialization_params['include_metadata']: if 'biome_map' in terrain_data and terrain_data['biome_map'] is not None: biome_map = terrain_data['biome_map'] save_data['biome_map'] = { 'data': biome_map.tolist() if isinstance(biome_map, np.ndarray) else biome_map, 'shape': biome_map.shape if isinstance(biome_map, np.ndarray) else None, 'dtype': str(biome_map.dtype) if isinstance(biome_map, np.ndarray) else 'unknown' } # 添加湿度图 if 'moisture_map' in terrain_data and terrain_data['moisture_map'] is not None: moisture_map = terrain_data['moisture_map'] save_data['moisture_map'] = { 'data': moisture_map.tolist() if isinstance(moisture_map, np.ndarray) else moisture_map, 'shape': moisture_map.shape if isinstance(moisture_map, np.ndarray) else None, 'dtype': str(moisture_map.dtype) if isinstance(moisture_map, np.ndarray) else 'unknown' } # 添加温度图 if 'temperature_map' in terrain_data and terrain_data['temperature_map'] is not None: temperature_map = terrain_data['temperature_map'] save_data['temperature_map'] = { 'data': temperature_map.tolist() if isinstance(temperature_map, np.ndarray) else temperature_map, 'shape': temperature_map.shape if isinstance(temperature_map, np.ndarray) else None, 'dtype': str(temperature_map.dtype) if isinstance(temperature_map, np.ndarray) else 'unknown' } # 添加侵蚀数据 if 'erosion_map' in terrain_data and terrain_data['erosion_map'] is not None: erosion_map = terrain_data['erosion_map'] save_data['erosion_map'] = { 'data': erosion_map.tolist() if isinstance(erosion_map, np.ndarray) else erosion_map, 'shape': erosion_map.shape if isinstance(erosion_map, np.ndarray) else None, 'dtype': str(erosion_map.dtype) if isinstance(erosion_map, np.ndarray) else 'unknown' } # 添加沉积物数据 if 'sediment_map' in terrain_data and terrain_data['sediment_map'] is not None: sediment_map = terrain_data['sediment_map'] save_data['sediment_map'] = { 'data': sediment_map.tolist() if isinstance(sediment_map, np.ndarray) else sediment_map, 'shape': sediment_map.shape if isinstance(sediment_map, np.ndarray) else None, 'dtype': str(sediment_map.dtype) if isinstance(sediment_map, np.ndarray) else 'unknown' } # 添加水图 if self.serialization_params['include_water']: if 'water_map' in terrain_data and terrain_data['water_map'] is not None: water_map = terrain_data['water_map'] save_data['water_map'] = { 'data': water_map.tolist() if isinstance(water_map, np.ndarray) else water_map, 'shape': water_map.shape if isinstance(water_map, np.ndarray) else None, 'dtype': str(water_map.dtype) if isinstance(water_map, np.ndarray) else 'unknown' } # 添加植被数据 if self.serialization_params['include_vegetation']: if 'vegetation_map' in terrain_data and terrain_data['vegetation_map'] is not None: vegetation_map = terrain_data['vegetation_map'] save_data['vegetation_map'] = { 'data': vegetation_map.tolist() if isinstance(vegetation_map, np.ndarray) else vegetation_map, 'shape': vegetation_map.shape if isinstance(vegetation_map, np.ndarray) else None, 'dtype': str(vegetation_map.dtype) if isinstance(vegetation_map, np.ndarray) else 'unknown' } # 添加统计信息 if 'stats' in terrain_data: save_data['stats'] = terrain_data['stats'] return save_data except Exception as e: print(f"✗ 地形数据准备失败: {e}") return {} def _process_loaded_data(self, loaded_data: Dict[str, Any]) -> Dict[str, Any]: """ 处理加载的地形数据 Args: loaded_data: 加载的地形数据 Returns: 处理后的地形数据 """ try: # 创建处理后的数据 processed_data = {} # 处理高度图 if 'heightmap' in loaded_data: heightmap_data = loaded_data['heightmap'] if 'data' in heightmap_data: processed_data['heightmap'] = np.array(heightmap_data['data'], dtype=np.float32) # 处理法线图 if 'normalmap' in loaded_data: normalmap_data = loaded_data['normalmap'] if 'data' in normalmap_data: processed_data['normalmap'] = np.array(normalmap_data['data'], dtype=np.float32) # 处理生物群落图 if 'biome_map' in loaded_data: biome_data = loaded_data['biome_map'] if 'data' in biome_data: processed_data['biome_map'] = np.array(biome_data['data'], dtype=np.int32) # 处理湿度图 if 'moisture_map' in loaded_data: moisture_data = loaded_data['moisture_map'] if 'data' in moisture_data: processed_data['moisture_map'] = np.array(moisture_data['data'], dtype=np.float32) # 处理温度图 if 'temperature_map' in loaded_data: temperature_data = loaded_data['temperature_map'] if 'data' in temperature_data: processed_data['temperature_map'] = np.array(temperature_data['data'], dtype=np.float32) # 处理侵蚀图 if 'erosion_map' in loaded_data: erosion_data = loaded_data['erosion_map'] if 'data' in erosion_data: processed_data['erosion_map'] = np.array(erosion_data['data'], dtype=np.float32) # 处理沉积物图 if 'sediment_map' in loaded_data: sediment_data = loaded_data['sediment_map'] if 'data' in sediment_data: processed_data['sediment_map'] = np.array(sediment_data['data'], dtype=np.float32) # 处理水图 if 'water_map' in loaded_data: water_data = loaded_data['water_map'] if 'data' in water_data: processed_data['water_map'] = np.array(water_data['data'], dtype=np.float32) # 处理植被图 if 'vegetation_map' in loaded_data: vegetation_data = loaded_data['vegetation_map'] if 'data' in vegetation_data: processed_data['vegetation_map'] = np.array(vegetation_data['data'], dtype=np.int32) # 处理统计信息 if 'stats' in loaded_data: processed_data['stats'] = loaded_data['stats'] return processed_data except Exception as e: print(f"✗ 加载数据处理失败: {e}") return {} def _save_json(self, filename: str, data: Dict[str, Any], compress: bool) -> bool: """ 以JSON格式保存地形数据 Args: filename: 文件名 data: 地形数据 compress: 是否压缩 Returns: 是否保存成功 """ try: # 序列化为JSON json_data = json.dumps(data, ensure_ascii=False, indent=2) if compress: # 压缩数据 compressed_data = zlib.compress(json_data.encode('utf-8'), self.serialization_params['compression_level']) with open(filename, 'wb') as f: f.write(compressed_data) self.stats['data_compressed'] += len(compressed_data) self.stats['data_uncompressed'] += len(json_data.encode('utf-8')) else: # 不压缩 with open(filename, 'w', encoding='utf-8') as f: f.write(json_data) self.stats['data_uncompressed'] += len(json_data.encode('utf-8')) return True except Exception as e: print(f"✗ JSON格式地形保存失败: {e}") return False def _save_pickle(self, filename: str, data: Dict[str, Any], compress: bool) -> bool: """ 以Pickle格式保存地形数据 Args: filename: 文件名 data: 地形数据 compress: 是否压缩 Returns: 是否保存成功 """ try: # 序列化为Pickle pickle_data = pickle.dumps(data) if compress: # 压缩数据 compressed_data = zlib.compress(pickle_data, self.serialization_params['compression_level']) with open(filename, 'wb') as f: f.write(compressed_data) self.stats['data_compressed'] += len(compressed_data) self.stats['data_uncompressed'] += len(pickle_data) else: # 不压缩 with open(filename, 'wb') as f: f.write(pickle_data) self.stats['data_uncompressed'] += len(pickle_data) return True except Exception as e: print(f"✗ Pickle格式地形保存失败: {e}") return False def _save_binary(self, filename: str, data: Dict[str, Any], compress: bool) -> bool: """ 以二进制格式保存地形数据 Args: filename: 文件名 data: 地形数据 compress: 是否压缩 Returns: 是否保存成功 """ try: # 转换为二进制格式 binary_data = self._convert_to_binary(data) if compress: # 压缩数据 compressed_data = zlib.compress(binary_data, self.serialization_params['compression_level']) with open(filename, 'wb') as f: f.write(compressed_data) self.stats['data_compressed'] += len(compressed_data) self.stats['data_uncompressed'] += len(binary_data) else: # 不压缩 with open(filename, 'wb') as f: f.write(binary_data) self.stats['data_uncompressed'] += len(binary_data) return True except Exception as e: print(f"✗ 二进制格式地形保存失败: {e}") return False def _save_custom(self, filename: str, data: Dict[str, Any], compress: bool) -> bool: """ 以自定义格式保存地形数据 Args: filename: 文件名 data: 地形数据 compress: 是否压缩 Returns: 是否保存成功 """ try: # 自定义格式保存(这里简化为JSON格式) return self._save_json(filename, data, compress) except Exception as e: print(f"✗ 自定义格式地形保存失败: {e}") return False def _load_json(self, filename: str) -> Optional[Dict[str, Any]]: """ 从JSON格式加载地形数据 Args: filename: 文件名 Returns: 地形数据字典或None """ try: # 读取文件 with open(filename, 'rb') as f: file_data = f.read() # 检查是否压缩 try: # 尝试解压 decompressed_data = zlib.decompress(file_data) json_data = decompressed_data.decode('utf-8') self.stats['data_compressed'] += len(file_data) self.stats['data_uncompressed'] += len(decompressed_data) except zlib.error: # 未压缩的JSON json_data = file_data.decode('utf-8') self.stats['data_uncompressed'] += len(file_data) # 解析JSON data = json.loads(json_data) return data except Exception as e: print(f"✗ JSON格式地形加载失败: {e}") return None def _load_pickle(self, filename: str) -> Optional[Dict[str, Any]]: """ 从Pickle格式加载地形数据 Args: filename: 文件名 Returns: 地形数据字典或None """ try: # 读取文件 with open(filename, 'rb') as f: file_data = f.read() # 检查是否压缩 try: # 尝试解压 decompressed_data = zlib.decompress(file_data) pickle_data = decompressed_data self.stats['data_compressed'] += len(file_data) self.stats['data_uncompressed'] += len(decompressed_data) except zlib.error: # 未压缩的Pickle pickle_data = file_data self.stats['data_uncompressed'] += len(file_data) # 解析Pickle data = pickle.loads(pickle_data) return data except Exception as e: print(f"✗ Pickle格式地形加载失败: {e}") return None def _load_binary(self, filename: str) -> Optional[Dict[str, Any]]: """ 从二进制格式加载地形数据 Args: filename: 文件名 Returns: 地形数据字典或None """ try: # 读取文件 with open(filename, 'rb') as f: file_data = f.read() # 检查是否压缩 try: # 尝试解压 decompressed_data = zlib.decompress(file_data) binary_data = decompressed_data self.stats['data_compressed'] += len(file_data) self.stats['data_uncompressed'] += len(decompressed_data) except zlib.error: # 未压缩的二进制数据 binary_data = file_data self.stats['data_uncompressed'] += len(file_data) # 解析二进制数据 data = self._convert_from_binary(binary_data) return data except Exception as e: print(f"✗ 二进制格式地形加载失败: {e}") return None def _load_custom(self, filename: str) -> Optional[Dict[str, Any]]: """ 从自定义格式加载地形数据 Args: filename: 文件名 Returns: 地形数据字典或None """ try: # 自定义格式加载(这里简化为JSON格式) return self._load_json(filename) except Exception as e: print(f"✗ 自定义格式地形加载失败: {e}") return None def _convert_to_binary(self, data: Dict[str, Any]) -> bytes: """ 将数据转换为二进制格式 Args: data: 数据字典 Returns: 二进制数据 """ try: # 这里简化为使用Pickle作为二进制格式 return pickle.dumps(data) except Exception as e: print(f"✗ 数据转换为二进制失败: {e}") return b'' def _convert_from_binary(self, binary_data: bytes) -> Dict[str, Any]: """ 从二进制数据转换为字典 Args: binary_data: 二进制数据 Returns: 数据字典 """ try: # 这里简化为使用Pickle作为二进制格式 return pickle.loads(binary_data) except Exception as e: print(f"✗ 二进制数据转换失败: {e}") return {} def _detect_format(self, filename: str) -> str: """ 检测文件格式 Args: filename: 文件名 Returns: 检测到的格式 """ try: # 根据文件扩展名检测格式 if filename.endswith('.json') or filename.endswith('.json.gz'): return 'json' elif filename.endswith('.pkl') or filename.endswith('.pkl.gz'): return 'pickle' elif filename.endswith('.bin') or filename.endswith('.bin.gz'): return 'binary' else: # 默认使用JSON格式 return self.serialization_params['default_format'] except Exception as e: print(f"✗ 格式检测失败: {e}") return self.serialization_params['default_format'] def get_stats(self) -> Dict[str, Any]: """ 获取统计信息 Returns: 统计信息字典 """ # 更新平均时间 if self.stats['terrains_saved'] > 0: self.stats['average_save_time'] = self.stats['total_save_time'] / self.stats['terrains_saved'] if self.stats['terrains_loaded'] > 0: self.stats['average_load_time'] = self.stats['total_load_time'] / self.stats['terrains_loaded'] # 计算压缩率 total_data = self.stats['data_compressed'] + self.stats['data_uncompressed'] compression_ratio = 0.0 if total_data > 0: compression_ratio = self.stats['data_compressed'] / total_data if self.stats['data_compressed'] > 0 else 1.0 stats = self.stats.copy() stats['compression_ratio'] = compression_ratio return stats def reset_stats(self): """重置统计信息""" self.stats = { 'terrains_saved': 0, 'terrains_loaded': 0, 'total_save_time': 0.0, 'total_load_time': 0.0, 'average_save_time': 0.0, 'average_load_time': 0.0, 'data_compressed': 0, 'data_uncompressed': 0 } print("✓ 地形序列化器统计信息已重置") def set_serialization_parameters(self, params: Dict[str, Any]): """ 设置序列化参数 Args: params: 参数字典 """ self.serialization_params.update(params) print(f"✓ 序列化参数已更新: {self.serialization_params}") def export_terrain_to_image(self, heightmap: np.ndarray, filename: str) -> bool: """ 导出地形为图像文件 Args: heightmap: 高度图数据 filename: 文件名 Returns: 是否导出成功 """ try: print(f"✓ 开始导出地形到图像 {filename}...") # 确保高度图在0-1范围内 normalized_heightmap = (heightmap - np.min(heightmap)) / (np.max(heightmap) - np.min(heightmap)) # 转换为0-255范围的整数 image_data = (normalized_heightmap * 255).astype(np.uint8) # 保存为PNG文件 try: from PIL import Image image = Image.fromarray(image_data, mode='L') image.save(filename) print("✓ 地形图像导出完成") return True except ImportError: # 如果没有PIL,保存为原始二进制数据 with open(filename + '.raw', 'wb') as f: f.write(image_data.tobytes()) print("✓ 地形数据导出完成(无PIL,保存为.raw文件)") return True except Exception as e: print(f"✗ 地形图像导出失败: {e}") import traceback traceback.print_exc() return False def import_terrain_from_image(self, filename: str) -> Optional[np.ndarray]: """ 从图像文件导入地形 Args: filename: 文件名 Returns: 高度图数据或None """ try: print(f"✓ 开始从图像 {filename} 导入地形...") # 读取图像文件 try: from PIL import Image image = Image.open(filename) # 转换为灰度图 image = image.convert('L') # 转换为numpy数组 image_data = np.array(image, dtype=np.float32) # 归一化到0-1范围 heightmap = image_data / 255.0 print("✓ 地形图像导入完成") return heightmap except ImportError: # 如果没有PIL,尝试读取原始二进制数据 if filename.endswith('.raw'): with open(filename, 'rb') as f: raw_data = f.read() # 假设是512x512的图像 image_data = np.frombuffer(raw_data, dtype=np.uint8) heightmap = image_data.astype(np.float32) / 255.0 # 重塑为方形 size = int(np.sqrt(len(heightmap))) heightmap = heightmap.reshape((size, size)) print("✓ 地形数据导入完成(从.raw文件)") return heightmap else: print("✗ 无法读取图像文件(需要PIL库)") return None except Exception as e: print(f"✗ 地形图像导入失败: {e}") import traceback traceback.print_exc() return None def backup_terrain(self, filename: str) -> bool: """ 备份当前地形 Args: filename: 备份文件名 Returns: 是否备份成功 """ try: if not self.enabled: print("✗ 地形序列化器未启用") return False # 获取当前地形数据 if self.plugin.terrain_manager: terrain_data = self.plugin.terrain_manager.get_terrain_data() if terrain_data: # 保存备份 backup_filename = filename + '.backup' success = self.save_terrain(backup_filename, terrain_data) if success: print(f"✓ 地形已备份到 {backup_filename}") return True else: print("✗ 地形备份失败") return False else: print("✗ 无法获取当前地形数据") return False else: print("✗ 地形管理器不可用") return False except Exception as e: print(f"✗ 地形备份失败: {e}") import traceback traceback.print_exc() return False def restore_terrain(self, filename: str) -> bool: """ 恢复地形备份 Args: filename: 备份文件名 Returns: 是否恢复成功 """ try: if not self.enabled: print("✗ 地形序列化器未启用") return False # 加载备份数据 backup_filename = filename + '.backup' terrain_data = self.load_terrain(backup_filename) if terrain_data: # 应用到地形管理器 if self.plugin.terrain_manager: # 这里应该调用地形管理器的方法来恢复数据 # 由于地形管理器可能没有相应的方法,我们只打印信息 print(f"✓ 地形已从 {backup_filename} 恢复") print("注意:需要在地形管理器中实现实际的恢复逻辑") return True else: print("✗ 地形管理器不可用") return False else: print("✗ 无法加载备份数据") return False except Exception as e: print(f"✗ 地形恢复失败: {e}") import traceback traceback.print_exc() return False def get_file_info(self, filename: str) -> Dict[str, Any]: """ 获取文件信息 Args: filename: 文件名 Returns: 文件信息字典 """ try: if not os.path.exists(filename): return {'error': '文件不存在'} # 获取文件统计信息 stat = os.stat(filename) file_info = { 'filename': filename, 'size': stat.st_size, 'created': time.ctime(stat.st_ctime), 'modified': time.ctime(stat.st_mtime), 'accessed': time.ctime(stat.st_atime) } # 尝试加载文件以获取地形信息 try: terrain_data = self.load_terrain(filename) if terrain_data: file_info['terrain_info'] = { 'has_heightmap': 'heightmap' in terrain_data and terrain_data['heightmap'] is not None, 'has_biome_map': 'biome_map' in terrain_data and terrain_data['biome_map'] is not None, 'has_vegetation_map': 'vegetation_map' in terrain_data and terrain_data['vegetation_map'] is not None, 'has_water_map': 'water_map' in terrain_data and terrain_data['water_map'] is not None } if 'stats' in terrain_data: file_info['terrain_stats'] = terrain_data['stats'] except Exception as e: file_info['load_error'] = str(e) return file_info except Exception as e: print(f"✗ 文件信息获取失败: {e}") return {'error': str(e)} def list_terrain_files(self, directory: str = '.') -> List[str]: """ 列出目录中的地形文件 Args: directory: 目录路径 Returns: 地形文件列表 """ try: terrain_files = [] # 支持的文件扩展名 supported_extensions = ['.json', '.pkl', '.bin', '.raw'] # 遍历目录 for filename in os.listdir(directory): file_path = os.path.join(directory, filename) if os.path.isfile(file_path): # 检查文件扩展名 _, ext = os.path.splitext(filename) if ext in supported_extensions: terrain_files.append(filename) return terrain_files except Exception as e: print(f"✗ 地形文件列表获取失败: {e}") return [] def delete_terrain_file(self, filename: str) -> bool: """ 删除地形文件 Args: filename: 文件名 Returns: 是否删除成功 """ try: if os.path.exists(filename): os.remove(filename) print(f"✓ 地形文件 {filename} 已删除") return True else: print(f"✗ 地形文件不存在: {filename}") return False except Exception as e: print(f"✗ 地形文件删除失败: {e}") return False def compress_terrain_file(self, filename: str, output_filename: str = None) -> bool: """ 压缩地形文件 Args: filename: 输入文件名 output_filename: 输出文件名(如果为None,则在原文件名后添加.gz) Returns: 是否压缩成功 """ try: if not os.path.exists(filename): print(f"✗ 文件不存在: {filename}") return False if output_filename is None: output_filename = filename + '.gz' # 读取原始文件 with open(filename, 'rb') as f: data = f.read() # 压缩数据 compressed_data = zlib.compress(data, self.serialization_params['compression_level']) # 写入压缩文件 with open(output_filename, 'wb') as f: f.write(compressed_data) print(f"✓ 地形文件已压缩: {filename} -> {output_filename}") return True except Exception as e: print(f"✗ 地形文件压缩失败: {e}") return False def decompress_terrain_file(self, filename: str, output_filename: str = None) -> bool: """ 解压地形文件 Args: filename: 输入文件名(应该以.gz结尾) output_filename: 输出文件名(如果为None,则移除.gz后缀) Returns: 是否解压成功 """ try: if not os.path.exists(filename): print(f"✗ 文件不存在: {filename}") return False if output_filename is None: if filename.endswith('.gz'): output_filename = filename[:-3] else: output_filename = filename + '.decompressed' # 读取压缩文件 with open(filename, 'rb') as f: compressed_data = f.read() # 解压数据 try: data = zlib.decompress(compressed_data) except zlib.error as e: print(f"✗ 文件解压失败: {e}") return False # 写入解压文件 with open(output_filename, 'wb') as f: f.write(data) print(f"✓ 地形文件已解压: {filename} -> {output_filename}") return True except Exception as e: print(f"✗ 地形文件解压失败: {e}") return False def validate_terrain_file(self, filename: str) -> bool: """ 验证地形文件完整性 Args: filename: 文件名 Returns: 文件是否完整 """ try: if not os.path.exists(filename): print(f"✗ 文件不存在: {filename}") return False # 尝试加载文件 terrain_data = self.load_terrain(filename) if terrain_data is None: print(f"✗ 地形文件无效: {filename}") return False # 检查必要数据是否存在 required_keys = ['heightmap'] missing_keys = [key for key in required_keys if key not in terrain_data or terrain_data[key] is None] if missing_keys: print(f"✗ 地形文件缺少必要数据: {missing_keys}") return False print(f"✓ 地形文件验证通过: {filename}") return True except Exception as e: print(f"✗ 地形文件验证失败: {e}") return False def compare_terrain_files(self, filename1: str, filename2: str) -> Dict[str, Any]: """ 比较两个地形文件 Args: filename1: 第一个文件名 filename2: 第二个文件名 Returns: 比较结果字典 """ try: # 加载两个文件 terrain1 = self.load_terrain(filename1) terrain2 = self.load_terrain(filename2) if terrain1 is None or terrain2 is None: return {'error': '无法加载一个或两个地形文件'} comparison = { 'file1': filename1, 'file2': filename2, 'identical': False, 'differences': [] } # 比较高度图 if ('heightmap' in terrain1 and terrain1['heightmap'] is not None and 'heightmap' in terrain2 and terrain2['heightmap'] is not None): heightmap1 = np.array(terrain1['heightmap']) heightmap2 = np.array(terrain2['heightmap']) if heightmap1.shape == heightmap2.shape: # 计算差异 diff = np.abs(heightmap1 - heightmap2) max_diff = np.max(diff) mean_diff = np.mean(diff) comparison['heightmap_comparison'] = { 'shapes_match': True, 'max_difference': float(max_diff), 'mean_difference': float(mean_diff), 'identical': max_diff < 1e-6 } if max_diff >= 1e-6: comparison['differences'].append('heightmap') else: comparison['heightmap_comparison'] = { 'shapes_match': False } comparison['differences'].append('heightmap_shape') else: comparison['heightmap_comparison'] = { 'missing_data': True } # 比较其他数据 maps_to_compare = ['biome_map', 'vegetation_map', 'water_map'] for map_name in maps_to_compare: if (map_name in terrain1 and terrain1[map_name] is not None and map_name in terrain2 and terrain2[map_name] is not None): map1 = np.array(terrain1[map_name]) map2 = np.array(terrain2[map_name]) if map1.shape == map2.shape: if not np.array_equal(map1, map2): comparison['differences'].append(map_name) else: comparison['differences'].append(f'{map_name}_shape') # 检查是否完全相同 comparison['identical'] = len(comparison['differences']) == 0 return comparison except Exception as e: print(f"✗ 地形文件比较失败: {e}") return {'error': str(e)} def merge_terrain_files(self, filenames: List[str], output_filename: str) -> bool: """ 合并多个地形文件 Args: filenames: 文件名列表 output_filename: 输出文件名 Returns: 是否合并成功 """ try: if len(filenames) < 2: print("✗ 至少需要两个文件才能合并") return False print(f"✓ 开始合并 {len(filenames)} 个地形文件...") # 加载所有文件 terrains = [] for filename in filenames: terrain = self.load_terrain(filename) if terrain is None: print(f"✗ 无法加载文件: {filename}") return False terrains.append(terrain) # 创建合并后的地形数据 merged_terrain = {} # 合并高度图(取平均值) heightmaps = [t.get('heightmap') for t in terrains if t.get('heightmap') is not None] if heightmaps: # 转换为numpy数组 np_heightmaps = [np.array(h) for h in heightmaps] # 确保所有高度图尺寸相同 if all(h.shape == np_heightmaps[0].shape for h in np_heightmaps): # 计算平均值 merged_heightmap = np.mean(np_heightmaps, axis=0) merged_terrain['heightmap'] = merged_heightmap else: print("✗ 高度图尺寸不匹配,无法合并") return False # 合并其他数据(这里简化为取第一个文件的数据) if terrains: first_terrain = terrains[0] for key in ['biome_map', 'vegetation_map', 'water_map', 'stats']: if key in first_terrain and first_terrain[key] is not None: merged_terrain[key] = first_terrain[key] # 保存合并后的地形 success = self.save_terrain(output_filename, merged_terrain) if success: print(f"✓ 地形文件合并完成: {output_filename}") return True else: print("✗ 地形文件合并失败") return False except Exception as e: print(f"✗ 地形文件合并失败: {e}") import traceback traceback.print_exc() return False def convert_terrain_format(self, input_filename: str, output_filename: str, output_format: str) -> bool: """ 转换地形文件格式 Args: input_filename: 输入文件名 output_filename: 输出文件名 output_format: 输出格式 Returns: 是否转换成功 """ try: # 验证输出格式 if output_format not in self.supported_formats: print(f"✗ 不支持的输出格式: {output_format}") return False print(f"✓ 开始转换地形格式: {input_filename} -> {output_filename} ({output_format})") # 加载输入文件 terrain_data = self.load_terrain(input_filename) if terrain_data is None: print("✗ 无法加载输入文件") return False # 保存为新格式 original_format = self.serialization_params['default_format'] self.serialization_params['default_format'] = output_format success = self.save_terrain(output_filename, terrain_data, format=output_format) # 恢复原始格式设置 self.serialization_params['default_format'] = original_format if success: print("✓ 地形格式转换完成") return True else: print("✗ 地形格式转换失败") return False except Exception as e: print(f"✗ 地形格式转换失败: {e}") import traceback traceback.print_exc() return False