""" Morphing和变形动画插件 - 优化模块 提供动画压缩、LOD系统、缓存管理等优化功能 """ import math import json import numpy as np from typing import Dict, List, Tuple, Optional, Any, Union from dataclasses import dataclass, field from enum import Enum from panda3d.core import * from direct.actor.Actor import Actor # 定义常量 OPTIMIZATION_SETTINGS = { 'compression_threshold': 0.001, 'lod_distance_thresholds': [10.0, 25.0, 50.0], 'lod_frame_rates': [1.0, 0.5, 0.25, 0.1], 'cache_size': 500, 'max_buffer_size': 100 } # 枚举定义 class CompressionMethod(Enum): """压缩方法枚举""" KEYFRAME_REDUCTION = 0 # 关键帧简化 QUANTIZATION = 1 # 量化压缩 DELTA_COMPRESSION = 2 # 差分压缩 RLE = 3 # 游程编码 class LODStrategy(Enum): """LOD策略枚举""" DISTANCE_BASED = 0 # 基于距离 PERFORMANCE_BASED = 1 # 基于性能 HYBRID = 2 # 混合策略 @dataclass class CompressedMorphData: """压缩的Morph数据类""" method: CompressionMethod original_size: int compressed_size: int compression_ratio: float data: Any # 压缩后的数据 @dataclass class LODLevel: """LOD级别数据类""" level: int distance_threshold: float frame_rate: float quality_factor: float active: bool = True @dataclass class CacheEntry: """缓存条目数据类""" key: str data: Any size: int access_time: float access_count: int = 1 class AnimationCompressor: """ 动画压缩系统 支持多种压缩算法减少动画数据大小 """ def __init__(self): """初始化动画压缩系统""" self.compression_threshold = OPTIMIZATION_SETTINGS['compression_threshold'] self.stats = { 'animations_compressed': 0, 'data_saved': 0, 'compression_time': 0.0 } print("动画压缩系统初始化完成") def compress_morph_data(self, morph_data: List[Tuple[float, float, float]], method: CompressionMethod = CompressionMethod.KEYFRAME_REDUCTION) -> Optional[CompressedMorphData]: """ 压缩Morph数据 :param morph_data: Morph数据列表 :param method: 压缩方法 :return: 压缩后的数据 """ import time start_time = time.time() original_size = len(morph_data) * 3 * 4 # 3个float,每个4字节 try: if method == CompressionMethod.KEYFRAME_REDUCTION: compressed_data = self._compress_keyframe_reduction(morph_data) elif method == CompressionMethod.QUANTIZATION: compressed_data = self._compress_quantization(morph_data) elif method == CompressionMethod.DELTA_COMPRESSION: compressed_data = self._compress_delta(morph_data) elif method == CompressionMethod.RLE: compressed_data = self._compress_rle(morph_data) else: compressed_data = morph_data compressed_size = len(str(compressed_data)) compression_ratio = original_size / max(1, compressed_size) # 创建压缩数据对象 result = CompressedMorphData( method=method, original_size=original_size, compressed_size=compressed_size, compression_ratio=compression_ratio, data=compressed_data ) # 更新统计信息 self.stats['animations_compressed'] += 1 self.stats['data_saved'] += original_size - compressed_size self.stats['compression_time'] += time.time() - start_time print(f"Morph数据压缩完成: 原始大小={original_size}字节, 压缩后={compressed_size}字节, 压缩比={compression_ratio:.2f}") return result except Exception as e: print(f"压缩Morph数据时出错: {e}") import traceback traceback.print_exc() return None def _compress_keyframe_reduction(self, morph_data: List[Tuple[float, float, float]]) -> List[Tuple[float, float, float]]: """ 关键帧简化压缩 :param morph_data: Morph数据列表 :return: 压缩后的数据 """ if len(morph_data) <= 2: return morph_data compressed = [morph_data[0]] # 总是保留第一帧 last_frame = morph_data[0] for i in range(1, len(morph_data) - 1): current_frame = morph_data[i] next_frame = morph_data[i + 1] # 检查当前帧是否可以被简化 if not self._is_frame_necessary(last_frame, current_frame, next_frame): continue compressed.append(current_frame) last_frame = current_frame compressed.append(morph_data[-1]) # 总是保留最后一帧 return compressed def _is_frame_necessary(self, prev_frame: Tuple[float, float, float], current_frame: Tuple[float, float, float], next_frame: Tuple[float, float, float]) -> bool: """ 判断帧是否必要 :param prev_frame: 前一帧 :param current_frame: 当前帧 :param next_frame: 后一帧 :return: 是否必要 """ # 计算线性插值 t = 0.5 # 简化处理,实际应该基于时间计算 interpolated = ( prev_frame[0] + t * (next_frame[0] - prev_frame[0]), prev_frame[1] + t * (next_frame[1] - prev_frame[1]), prev_frame[2] + t * (next_frame[2] - prev_frame[2]) ) # 计算与实际值的差异 diff = math.sqrt( (current_frame[0] - interpolated[0]) ** 2 + (current_frame[1] - interpolated[1]) ** 2 + (current_frame[2] - interpolated[2]) ** 2 ) return diff > self.compression_threshold def _compress_quantization(self, morph_data: List[Tuple[float, float, float]]) -> List[Tuple[int, int, int]]: """ 量化压缩 :param morph_data: Morph数据列表 :return: 压缩后的数据 """ # 找到数据的范围 min_vals = [float('inf')] * 3 max_vals = [float('-inf')] * 3 for frame in morph_data: for i in range(3): min_vals[i] = min(min_vals[i], frame[i]) max_vals[i] = max(max_vals[i], frame[i]) # 量化到16位整数 quantized_data = [] for frame in morph_data: quantized_frame = [] for i in range(3): # 将浮点数映射到0-65535范围 if max_vals[i] != min_vals[i]: quantized_value = int((frame[i] - min_vals[i]) / (max_vals[i] - min_vals[i]) * 65535) else: quantized_value = 0 quantized_frame.append(quantized_value) quantized_data.append(tuple(quantized_frame)) return quantized_data def _compress_delta(self, morph_data: List[Tuple[float, float, float]]) -> List[Tuple[float, float, float]]: """ 差分压缩 :param morph_data: Morph数据列表 :return: 压缩后的数据 """ if not morph_data: return morph_data delta_data = [morph_data[0]] # 第一帧保持原样 for i in range(1, len(morph_data)): delta = ( morph_data[i][0] - morph_data[i-1][0], morph_data[i][1] - morph_data[i-1][1], morph_data[i][2] - morph_data[i-1][2] ) delta_data.append(delta) return delta_data def _compress_rle(self, morph_data: List[Tuple[float, float, float]]) -> List[Tuple[int, Tuple[float, float, float]]]: """ 游程编码压缩 :param morph_data: Morph数据列表 :return: 压缩后的数据 """ if not morph_data: return [] rle_data = [] current_value = morph_data[0] count = 1 for i in range(1, len(morph_data)): if morph_data[i] == current_value: count += 1 else: rle_data.append((count, current_value)) current_value = morph_data[i] count = 1 rle_data.append((count, current_value)) return rle_data def decompress_morph_data(self, compressed_data: CompressedMorphData) -> Optional[List[Tuple[float, float, float]]]: """ 解压缩Morph数据 :param compressed_data: 压缩的数据 :return: 解压后的数据 """ try: method = compressed_data.method data = compressed_data.data if method == CompressionMethod.KEYFRAME_REDUCTION: # 关键帧简化数据不需要特殊解压 return data elif method == CompressionMethod.QUANTIZATION: return self._decompress_quantization(data) elif method == CompressionMethod.DELTA_COMPRESSION: return self._decompress_delta(data) elif method == CompressionMethod.RLE: return self._decompress_rle(data) else: return data except Exception as e: print(f"解压缩Morph数据时出错: {e}") import traceback traceback.print_exc() return None def _decompress_quantization(self, quantized_data: List[Tuple[int, int, int]]) -> List[Tuple[float, float, float]]: """ 解压量化数据 :param quantized_data: 量化数据 :return: 解压后的数据 """ # 这里需要存储原始的min_vals和max_vals来进行解压 # 由于简化实现,我们返回原始数据 return [(float(x), float(y), float(z)) for x, y, z in quantized_data] def _decompress_delta(self, delta_data: List[Tuple[float, float, float]]) -> List[Tuple[float, float, float]]: """ 解压差分数据 :param delta_data: 差分数据 :return: 解压后的数据 """ if not delta_data: return [] original_data = [delta_data[0]] # 第一帧保持原样 for i in range(1, len(delta_data)): original_frame = ( original_data[i-1][0] + delta_data[i][0], original_data[i-1][1] + delta_data[i][1], original_data[i-1][2] + delta_data[i][2] ) original_data.append(original_frame) return original_data def _decompress_rle(self, rle_data: List[Tuple[int, Tuple[float, float, float]]]) -> List[Tuple[float, float, float]]: """ 解压RLE数据 :param rle_data: RLE数据 :return: 解压后的数据 """ original_data = [] for count, value in rle_data: original_data.extend([value] * count) return original_data def get_compression_stats(self, compressed_data: Optional[CompressedMorphData] = None) -> Dict[str, Any]: """ 获取压缩统计信息 :param compressed_data: 压缩数据(可选) :return: 统计信息字典 """ stats = self.stats.copy() if compressed_data: stats['current_compression_ratio'] = compressed_data.compression_ratio stats['current_original_size'] = compressed_data.original_size stats['current_compressed_size'] = compressed_data.compressed_size return stats class AnimationLODSystem: """ 动画LOD系统 根据距离和性能自动调整动画质量 """ def __init__(self, world): """ 初始化动画LOD系统 :param world: 世界对象 """ self.world = world self.strategy = LODStrategy.DISTANCE_BASED self.distance_thresholds = OPTIMIZATION_SETTINGS['lod_distance_thresholds'] self.frame_rates = OPTIMIZATION_SETTINGS['lod_frame_rates'] self.lod_levels: List[LODLevel] = [] self.model_lod_levels: Dict[str, int] = {} # {model_id: lod_level} self.camera_node = None # 创建LOD级别 self._create_lod_levels() print("动画LOD系统初始化完成") def _create_lod_levels(self) -> None: """ 创建LOD级别 """ self.lod_levels = [] for i, (distance, frame_rate) in enumerate(zip(self.distance_thresholds, self.frame_rates)): level = LODLevel( level=i, distance_threshold=distance, frame_rate=frame_rate, quality_factor=1.0 - (i / len(self.distance_thresholds)) ) self.lod_levels.append(level) # 添加最高LOD级别(最近距离,最高质量) highest_level = LODLevel( level=len(self.lod_levels), distance_threshold=0.0, frame_rate=1.0, quality_factor=1.0 ) self.lod_levels.append(highest_level) def set_lod_strategy(self, strategy: LODStrategy) -> None: """ 设置LOD策略 :param strategy: LOD策略 """ self.strategy = strategy print(f"LOD策略设置为: {strategy.name}") def set_distance_thresholds(self, thresholds: List[float]) -> None: """ 设置距离阈值 :param thresholds: 距离阈值列表 """ self.distance_thresholds = thresholds self._create_lod_levels() print(f"距离阈值已更新: {thresholds}") def set_frame_rates(self, frame_rates: List[float]) -> None: """ 设置帧率级别 :param frame_rates: 帧率列表 """ self.frame_rates = frame_rates self._create_lod_levels() print(f"帧率级别已更新: {frame_rates}") def set_camera(self, camera_node: NodePath) -> None: """ 设置相机节点 :param camera_node: 相机节点 """ self.camera_node = camera_node def update_lod_levels(self, models: Dict[str, Any]) -> None: """ 更新LOD级别 :param models: 模型字典 {model_id: model_data} """ if not self.camera_node: return camera_pos = self.camera_node.getPos() # 根据策略更新每个模型的LOD级别 for model_id, model_data in models.items(): if self.strategy == LODStrategy.DISTANCE_BASED: self._update_distance_based_lod(model_id, model_data, camera_pos) elif self.strategy == LODStrategy.PERFORMANCE_BASED: self._update_performance_based_lod(model_id, model_data) elif self.strategy == LODStrategy.HYBRID: self._update_hybrid_lod(model_id, model_data, camera_pos) def _update_distance_based_lod(self, model_id: str, model_data: Any, camera_pos: Vec3) -> None: """ 更新基于距离的LOD :param model_id: 模型ID :param model_data: 模型数据 :param camera_pos: 相机位置 """ try: model_pos = model_data['node_path'].getPos() distance = (camera_pos - model_pos).length() # 找到合适的LOD级别 lod_level = len(self.lod_levels) - 1 # 默认最高质量 for i, level in enumerate(self.lod_levels): if distance <= level.distance_threshold: lod_level = i break self.model_lod_levels[model_id] = lod_level except Exception as e: print(f"更新模型 {model_id} 的距离LOD时出错: {e}") def _update_performance_based_lod(self, model_id: str, model_data: Any) -> None: """ 更新基于性能的LOD :param model_id: 模型ID :param model_data: 模型数据 """ # 基于FPS或其他性能指标调整LOD # 这里简化处理,实际项目中需要获取性能数据 current_fps = 60.0 # 假设值 # 根据FPS调整LOD级别 if current_fps < 30: lod_level = min(2, len(self.lod_levels) - 1) elif current_fps < 45: lod_level = min(1, len(self.lod_levels) - 1) else: lod_level = 0 self.model_lod_levels[model_id] = lod_level def _update_hybrid_lod(self, model_id: str, model_data: Any, camera_pos: Vec3) -> None: """ 更新混合LOD :param model_id: 模型ID :param model_data: 模型数据 :param camera_pos: 相机位置 """ # 结合距离和性能的混合策略 self._update_distance_based_lod(model_id, model_data, camera_pos) # 可以进一步根据性能调整 def get_model_lod_level(self, model_id: str) -> Optional[int]: """ 获取模型的LOD级别 :param model_id: 模型ID :return: LOD级别 """ return self.model_lod_levels.get(model_id) def get_lod_info(self, model_id: str) -> Optional[Dict[str, Any]]: """ 获取模型的LOD信息 :param model_id: 模型ID :return: LOD信息字典 """ lod_level = self.model_lod_levels.get(model_id) if lod_level is None or lod_level >= len(self.lod_levels): return None level_info = self.lod_levels[lod_level] return { 'model_id': model_id, 'lod_level': lod_level, 'distance_threshold': level_info.distance_threshold, 'frame_rate': level_info.frame_rate, 'quality_factor': level_info.quality_factor } def apply_lod_to_model(self, model_id: str, morphing_core) -> None: """ 应用LOD到模型 :param model_id: 模型ID :param morphing_core: Morphing核心系统 """ lod_level = self.model_lod_levels.get(model_id) if lod_level is None or lod_level >= len(self.lod_levels): return level_info = self.lod_levels[lod_level] # 这里可以应用LOD调整,例如: # 1. 降低更新频率 # 2. 减少变形精度 # 3. 简化morph目标 # 4. 降低渲染质量 # 示例:根据质量因子调整morph目标权重 if model_id in morphing_core.morph_targets: for target_name, morph_target in morphing_core.morph_targets[model_id].items(): # 简化处理,实际项目中需要更复杂的逻辑 adjusted_weights = [w * level_info.quality_factor for w in morph_target.weights] morph_target.weights = adjusted_weights class AnimationCacheManager: """ 动画缓存管理器 提供高效的动画数据缓存机制 """ def __init__(self): """初始化动画缓存管理器""" self.max_size = OPTIMIZATION_SETTINGS['cache_size'] self.cache: Dict[str, CacheEntry] = {} self.access_order: List[str] = [] # 最近最少使用顺序 self.stats = { 'hits': 0, 'misses': 0, 'evictions': 0 } print("动画缓存管理器初始化完成") def get(self, key: str) -> Optional[Any]: """ 获取缓存项 :param key: 缓存键 :return: 缓存数据 """ if key in self.cache: # 缓存命中 self.stats['hits'] += 1 # 更新访问时间 entry = self.cache[key] entry.access_time = globalClock.getRealTime() entry.access_count += 1 # 更新访问顺序 if key in self.access_order: self.access_order.remove(key) self.access_order.append(key) return entry.data else: # 缓存未命中 self.stats['misses'] += 1 return None def put(self, key: str, data: Any, size: int = 1) -> None: """ 放入缓存项 :param key: 缓存键 :param data: 缓存数据 :param size: 数据大小 """ # 如果缓存已满,移除最久未使用的项 while len(self.cache) >= self.max_size and self.cache: # 找到最久未使用的项 if self.access_order: oldest_key = self.access_order.pop(0) del self.cache[oldest_key] self.stats['evictions'] += 1 else: # 如果访问顺序列表为空,移除任意项 oldest_key = next(iter(self.cache)) del self.cache[oldest_key] self.stats['evictions'] += 1 # 创建缓存条目 entry = CacheEntry( key=key, data=data, size=size, access_time=globalClock.getRealTime(), access_count=1 ) # 存储缓存条目 self.cache[key] = entry self.access_order.append(key) def invalidate(self, key: str) -> None: """ 使缓存项失效 :param key: 缓存键 """ if key in self.cache: del self.cache[key] if key in self.access_order: self.access_order.remove(key) def clear(self) -> None: """ 清空缓存 """ self.cache.clear() self.access_order.clear() def get_stats(self) -> Dict[str, Any]: """ 获取缓存统计信息 :return: 统计信息字典 """ total_requests = self.stats['hits'] + self.stats['misses'] hit_rate = self.stats['hits'] / max(1, total_requests) return { 'size': len(self.cache), 'max_size': self.max_size, 'hits': self.stats['hits'], 'misses': self.stats['misses'], 'evictions': self.stats['evictions'], 'hit_rate': hit_rate, 'total_requests': total_requests } def get_cache_info(self, key: str) -> Optional[Dict[str, Any]]: """ 获取缓存项信息 :param key: 缓存键 :return: 缓存项信息字典 """ if key not in self.cache: return None entry = self.cache[key] return { 'key': entry.key, 'size': entry.size, 'access_time': entry.access_time, 'access_count': entry.access_count } # 使用示例和测试代码 def example_compressor_usage(): """ 动画压缩系统使用示例 """ print("=== 动画压缩系统使用示例 ===") # 创建压缩器 compressor = AnimationCompressor() # 创建测试数据 test_data = [ (0.0, 0.0, 0.0), (0.1, 0.05, 0.02), (0.2, 0.1, 0.04), (0.3, 0.15, 0.06), (0.4, 0.2, 0.08), (0.5, 0.25, 0.1), (0.6, 0.3, 0.12), (0.7, 0.35, 0.14), (0.8, 0.4, 0.16), (0.9, 0.45, 0.18), (1.0, 0.5, 0.2) ] # 测试不同压缩方法 methods = [ CompressionMethod.KEYFRAME_REDUCTION, CompressionMethod.QUANTIZATION, CompressionMethod.DELTA_COMPRESSION, CompressionMethod.RLE ] for method in methods: print(f"\n测试 {method.name} 压缩:") compressed = compressor.compress_morph_data(test_data, method) if compressed: print(f" 原始大小: {compressed.original_size} 字节") print(f" 压缩后大小: {compressed.compressed_size} 字节") print(f" 压缩比: {compressed.compression_ratio:.2f}") # 测试解压缩 decompressed = compressor.decompress_morph_data(compressed) if decompressed: print(f" 解压数据点数: {len(decompressed)}") # 获取统计信息 stats = compressor.get_compression_stats() print(f"\n压缩统计: {stats}") print("=== 动画压缩系统示例结束 ===\n") def example_lod_usage(world): """ 动画LOD系统使用示例 """ print("=== 动画LOD系统使用示例 ===") # 创建LOD系统 lod_system = AnimationLODSystem(world) # 设置LOD策略 lod_system.set_lod_strategy(LODStrategy.DISTANCE_BASED) # 设置距离阈值 lod_system.set_distance_thresholds([5.0, 15.0, 30.0]) # 设置帧率 lod_system.set_frame_rates([1.0, 0.7, 0.4, 0.1]) print("LOD系统配置完成") print("=== 动画LOD系统示例结束 ===\n") return lod_system def example_cache_usage(): """ 动画缓存管理器使用示例 """ print("=== 动画缓存管理器使用示例 ===") # 创建缓存管理器 cache_manager = AnimationCacheManager() # 添加缓存项 cache_manager.put("test_key_1", "test_data_1", 10) cache_manager.put("test_key_2", "test_data_2", 20) cache_manager.put("test_key_3", "test_data_3", 30) # 获取缓存项 data1 = cache_manager.get("test_key_1") print(f"获取缓存项1: {data1}") data2 = cache_manager.get("test_key_2") print(f"获取缓存项2: {data2}") # 测试缓存未命中 data4 = cache_manager.get("test_key_4") print(f"获取不存在的缓存项: {data4}") # 获取统计信息 stats = cache_manager.get_stats() print(f"缓存统计: {stats}") # 获取缓存项信息 info = cache_manager.get_cache_info("test_key_1") print(f"缓存项信息: {info}") print("=== 动画缓存管理器示例结束 ===\n") if __name__ == "__main__": example_compressor_usage() # example_lod_usage(None) # 需要world对象 example_cache_usage()