EG/plugins/user/advanced_skeletal_animation/optimization.py
2025-12-12 16:16:15 +08:00

763 lines
26 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.

"""
高级骨骼动画系统 - 优化和压缩模块
提供动画压缩、LOD、缓存优化等功能
"""
import math
import json
import os
from typing import Dict, List, Optional, Tuple, Any, Callable
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_max_size': 500,
'cache_cleanup_interval': 30.0
}
# 枚举定义
class CompressionMethod(Enum):
"""压缩方法枚举"""
KEYFRAME_REDUCTION = 0
QUANTIZATION = 1
DELTA_COMPRESSION = 2
RUN_LENGTH_ENCODING = 3
class LODStrategy(Enum):
"""LOD策略枚举"""
DISTANCE_BASED = 0
PERFORMANCE_BASED = 1
HYBRID = 2
class CachePolicy(Enum):
"""缓存策略枚举"""
LRU = 0 # 最近最少使用
LFU = 1 # 最不经常使用
FIFO = 2 # 先进先出
@dataclass
class CompressedAnimationData:
"""压缩动画数据类"""
name: str
frame_rate: float
duration: float
bone_data: Dict[str, List[Tuple[float, Tuple[float, float, float, float, float, float, float]]]] # 时间, (位置+旋转+缩放)
compression_ratio: float = 1.0
original_size: int = 0
compressed_size: int = 0
@dataclass
class LODSettings:
"""LOD设置数据类"""
strategy: LODStrategy
distance_thresholds: List[float]
frame_rates: List[float]
enable_auto_lod: bool = True
performance_threshold: float = 30.0 # FPS阈值
@dataclass
class CacheEntry:
"""缓存条目数据类"""
key: str
data: Any
timestamp: float
access_count: int = 1
size: int = 0
class AnimationCompressor:
"""
动画压缩系统
支持多种压缩算法
"""
def __init__(self):
"""初始化动画压缩系统"""
self.settings = {
'position_threshold': 0.001,
'rotation_threshold': 0.01,
'scale_threshold': 0.001,
'quantization_bits': 16,
'enable_quantization': True
}
print("动画压缩系统初始化完成")
def compress_animation(self, actor: Actor, anim_name: str,
method: CompressionMethod = CompressionMethod.KEYFRAME_REDUCTION) -> Optional[CompressedAnimationData]:
"""
压缩指定动画
:param actor: Actor对象
:param anim_name: 动画名称
:param method: 压缩方法
:return: 压缩后的动画数据
"""
if anim_name not in actor.getAnimNames():
print(f"错误: 动画 {anim_name} 不存在")
return None
print(f"开始压缩动画: {anim_name} (方法: {method.name})")
# 获取动画控制
anim_control = actor.getAnimControl(anim_name)
if not anim_control:
print(f"错误: 无法获取动画控制 {anim_name}")
return None
# 获取动画数据
num_frames = anim_control.getNumFrames()
frame_rate = anim_control.getFrameRate()
duration = anim_control.getDuration()
print(f" 动画信息: {num_frames} 帧, {frame_rate} FPS, {duration:.2f}")
# 提取骨骼动画数据
bone_data = self._extract_bone_animation_data(actor, anim_name, num_frames)
# 根据方法进行压缩
if method == CompressionMethod.KEYFRAME_REDUCTION:
compressed_bone_data = self._compress_keyframe_reduction(bone_data)
elif method == CompressionMethod.QUANTIZATION:
compressed_bone_data = self._compress_quantization(bone_data)
elif method == CompressionMethod.DELTA_COMPRESSION:
compressed_bone_data = self._compress_delta(bone_data)
elif method == CompressionMethod.RUN_LENGTH_ENCODING:
compressed_bone_data = self._compress_rle(bone_data)
else:
compressed_bone_data = bone_data
# 计算压缩比
original_size = self._calculate_data_size(bone_data)
compressed_size = self._calculate_data_size(compressed_bone_data)
compression_ratio = original_size / compressed_size if compressed_size > 0 else 1.0
# 创建压缩数据
compressed_data = CompressedAnimationData(
name=anim_name,
frame_rate=frame_rate,
duration=duration,
bone_data=compressed_bone_data,
compression_ratio=compression_ratio,
original_size=original_size,
compressed_size=compressed_size
)
print(f" 压缩完成: 原始大小={original_size}, 压缩大小={compressed_size}, 压缩比={compression_ratio:.2f}:1")
return compressed_data
def _extract_bone_animation_data(self, actor: Actor, anim_name: str, num_frames: int) -> Dict[str, List]:
"""
提取骨骼动画数据
:param actor: Actor对象
:param anim_name: 动画名称
:param num_frames: 帧数
:return: 骨骼动画数据
"""
bone_data = {}
# 获取所有骨骼
bundle = actor.getPartBundleDict().get('modelRoot')
if not bundle:
return bone_data
# 遍历骨骼
for i in range(bundle.getNumChildren()):
child = bundle.getChild(i)
if hasattr(child, 'getName'):
bone_name = child.getName()
bone_data[bone_name] = self._extract_bone_frames(actor, bone_name, anim_name, num_frames)
return bone_data
def _extract_bone_frames(self, actor: Actor, bone_name: str, anim_name: str, num_frames: int) -> List:
"""
提取单个骨骼的帧数据
:param actor: Actor对象
:param bone_name: 骨骼名称
:param anim_name: 动画名称
:param num_frames: 帧数
:return: 帧数据列表
"""
frames = []
# 为每一帧提取数据
for frame in range(num_frames):
# 设置到指定帧
actor.pose(anim_name, frame)
# 获取骨骼变换
joint = actor.exposeJoint(None, "modelRoot", bone_name)
if joint:
pos = joint.getPos()
hpr = joint.getHpr()
scale = joint.getScale()
# 存储为 (时间, (px, py, pz, rx, ry, rz, sx, sy, sz))
time = frame / actor.getFrameRate(anim_name)
frame_data = (
time,
(pos.x, pos.y, pos.z,
hpr.x, hpr.y, hpr.z,
scale.x, scale.y, scale.z)
)
frames.append(frame_data)
return frames
def _compress_keyframe_reduction(self, bone_data: Dict[str, List]) -> Dict[str, List]:
"""
关键帧简化压缩
:param bone_data: 骨骼数据
:return: 压缩后的骨骼数据
"""
compressed_data = {}
for bone_name, frames in bone_data.items():
if len(frames) <= 2:
compressed_data[bone_name] = frames
continue
compressed_frames = [frames[0]] # 总是保留第一帧
last_frame = frames[0]
for i in range(1, len(frames) - 1):
current_frame = frames[i]
next_frame = frames[i + 1]
# 检查当前帧是否可以被简化
if not self._is_frame_necessary(last_frame, current_frame, next_frame):
continue
compressed_frames.append(current_frame)
last_frame = current_frame
compressed_frames.append(frames[-1]) # 总是保留最后一帧
compressed_data[bone_name] = compressed_frames
return compressed_data
def _is_frame_necessary(self, prev_frame: Tuple, current_frame: Tuple, next_frame: Tuple) -> bool:
"""
判断帧是否必要
:param prev_frame: 前一帧
:param current_frame: 当前帧
:param next_frame: 后一帧
:return: 是否必要
"""
prev_time, prev_data = prev_frame
curr_time, curr_data = current_frame
next_time, next_data = next_frame
# 计算线性插值
t = (curr_time - prev_time) / (next_time - prev_time) if (next_time - prev_time) > 0 else 0
interpolated = tuple(prev_data[i] + t * (next_data[i] - prev_data[i]) for i in range(len(prev_data)))
# 计算与实际值的差异
total_diff = sum(abs(curr_data[i] - interpolated[i]) for i in range(len(curr_data)))
return total_diff > self.settings['position_threshold'] * 3 # 简化阈值检查
def _compress_quantization(self, bone_data: Dict[str, List]) -> Dict[str, List]:
"""
量化压缩
:param bone_data: 骨骼数据
:return: 量化后的骨骼数据
"""
# 量化实现 - 简化版本
return bone_data # 实际项目中需要实现具体的量化算法
def _compress_delta(self, bone_data: Dict[str, List]) -> Dict[str, List]:
"""
差分压缩
:param bone_data: 骨骼数据
:return: 差分压缩后的骨骼数据
"""
# 差分压缩实现 - 简化版本
return bone_data # 实际项目中需要实现具体的差分算法
def _compress_rle(self, bone_data: Dict[str, List]) -> Dict[str, List]:
"""
游程编码压缩
:param bone_data: 骨骼数据
:return: RLE压缩后的骨骼数据
"""
# RLE压缩实现 - 简化版本
return bone_data # 实际项目中需要实现具体的RLE算法
def _calculate_data_size(self, bone_data: Dict[str, List]) -> int:
"""
计算数据大小
:param bone_data: 骨骼数据
:return: 数据大小(字节)
"""
size = 0
for frames in bone_data.values():
size += len(frames) * 100 # 简化估算每帧约100字节
return size
def decompress_animation(self, compressed_data: CompressedAnimationData) -> Dict[str, List]:
"""
解压缩动画数据
:param compressed_data: 压缩数据
:return: 解压缩后的骨骼数据
"""
# 解压缩实现 - 简化版本
return compressed_data.bone_data
def get_compression_stats(self, compressed_data: CompressedAnimationData) -> Dict[str, Any]:
"""
获取压缩统计信息
:param compressed_data: 压缩数据
:return: 统计信息
"""
return {
'name': compressed_data.name,
'compression_ratio': compressed_data.compression_ratio,
'original_size': compressed_data.original_size,
'compressed_size': compressed_data.compressed_size,
'space_saved': compressed_data.original_size - compressed_data.compressed_size,
'percentage_saved': (1 - compressed_data.compressed_size / compressed_data.original_size) * 100 if compressed_data.original_size > 0 else 0
}
class AnimationLODSystem:
"""
动画LOD系统
根据距离和性能自动调整动画质量
"""
def __init__(self, world):
"""
初始化LOD系统
:param world: 世界对象
"""
self.world = world
self.settings = LODSettings(
strategy=LODStrategy.DISTANCE_BASED,
distance_thresholds=[10.0, 25.0, 50.0],
frame_rates=[1.0, 0.5, 0.25, 0.1],
enable_auto_lod=True,
performance_threshold=30.0
)
self.actor_lod_levels = {} # {actor_id: lod_level}
self.performance_monitor = {
'last_fps': 60.0,
'frame_times': [],
'last_update': 0.0
}
self.camera_node = None
print("动画LOD系统初始化完成")
def set_lod_strategy(self, strategy: LODStrategy) -> None:
"""
设置LOD策略
:param strategy: LOD策略
"""
self.settings.strategy = strategy
print(f"LOD策略设置为: {strategy.name}")
def set_distance_thresholds(self, thresholds: List[float]) -> None:
"""
设置距离阈值
:param thresholds: 距离阈值列表
"""
self.settings.distance_thresholds = sorted(thresholds)
print(f"距离阈值设置为: {thresholds}")
def set_frame_rates(self, frame_rates: List[float]) -> None:
"""
设置帧率级别
:param frame_rates: 帧率列表
"""
self.settings.frame_rates = frame_rates
print(f"帧率级别设置为: {frame_rates}")
def enable_auto_lod(self, enabled: bool = True) -> None:
"""
启用/禁用自动LOD
:param enabled: 是否启用
"""
self.settings.enable_auto_lod = enabled
status = "启用" if enabled else "禁用"
print(f"自动LOD已{status}")
def set_performance_threshold(self, threshold: float) -> None:
"""
设置性能阈值
:param threshold: FPS阈值
"""
self.settings.performance_threshold = threshold
print(f"性能阈值设置为: {threshold} FPS")
def set_camera_node(self, camera_node) -> None:
"""
设置摄像机节点
:param camera_node: 摄像机节点
"""
self.camera_node = camera_node
def update_lod_levels(self, actors: Dict[str, Any]) -> None:
"""
更新所有Actor的LOD级别
:param actors: Actor字典 {actor_id: actor_info}
"""
if not self.settings.enable_auto_lod:
return
current_time = globalClock.getRealTime()
# 每隔一定时间更新一次
if current_time - self.performance_monitor['last_update'] < 0.5:
return
self.performance_monitor['last_update'] = current_time
# 获取摄像机位置
camera_pos = Point3(0, 0, 0)
if self.camera_node:
camera_pos = self.camera_node.getPos()
elif hasattr(self.world, 'camera') and self.world.camera:
camera_pos = self.world.camera.getPos()
# 根据策略更新LOD
if self.settings.strategy == LODStrategy.DISTANCE_BASED:
self._update_distance_based_lod(actors, camera_pos)
elif self.settings.strategy == LODStrategy.PERFORMANCE_BASED:
self._update_performance_based_lod(actors)
elif self.settings.strategy == LODStrategy.HYBRID:
self._update_hybrid_lod(actors, camera_pos)
def _update_distance_based_lod(self, actors: Dict[str, Any], camera_pos: Point3) -> None:
"""
更新基于距离的LOD
:param actors: Actor字典
:param camera_pos: 摄像机位置
"""
for actor_id, actor_info in actors.items():
actor = actor_info.actor
# 计算距离
distance = (actor.getPos() - camera_pos).length()
# 确定LOD级别
lod_level = 0
for i, threshold in enumerate(self.settings.distance_thresholds):
if distance > threshold:
lod_level = i + 1
# 应用LOD设置
self._apply_lod_settings(actor_info, lod_level)
self.actor_lod_levels[actor_id] = lod_level
def _update_performance_based_lod(self, actors: Dict[str, Any]) -> None:
"""
更新基于性能的LOD
:param actors: Actor字典
"""
# 根据FPS调整LOD
fps = self.performance_monitor['last_fps']
if fps < self.settings.performance_threshold:
# 降低所有Actor的LOD级别
for actor_id, actor_info in actors.items():
current_lod = self.actor_lod_levels.get(actor_id, 0)
new_lod = min(current_lod + 1, len(self.settings.frame_rates) - 1)
self._apply_lod_settings(actor_info, new_lod)
self.actor_lod_levels[actor_id] = new_lod
def _update_hybrid_lod(self, actors: Dict[str, Any], camera_pos: Point3) -> None:
"""
更新混合LOD
:param actors: Actor字典
:param camera_pos: 摄像机位置
"""
# 结合距离和性能的LOD策略
self._update_distance_based_lod(actors, camera_pos)
self._update_performance_based_lod(actors)
def _apply_lod_settings(self, actor_info: Any, lod_level: int) -> None:
"""
应用LOD设置到Actor
:param actor_info: Actor信息
:param lod_level: LOD级别
"""
actor = actor_info.actor
frame_rate_multiplier = self.settings.frame_rates[lod_level] if lod_level < len(self.settings.frame_rates) else 0.1
# 调整所有动画的播放速度
for anim_name in actor.getAnimNames():
current_rate = actor.getPlayRate(anim_name)
new_rate = current_rate * frame_rate_multiplier
actor.setPlayRate(new_rate, anim_name)
# 更新Actor信息中的LOD级别
actor_info.lod_level = lod_level
def update_performance_stats(self, current_fps: float, frame_time: float) -> None:
"""
更新性能统计
:param current_fps: 当前FPS
:param frame_time: 帧时间
"""
self.performance_monitor['last_fps'] = current_fps
self.performance_monitor['frame_times'].append(frame_time)
# 保持最近30帧的统计数据
if len(self.performance_monitor['frame_times']) > 30:
self.performance_monitor['frame_times'] = self.performance_monitor['frame_times'][-30:]
def get_lod_stats(self) -> Dict[str, Any]:
"""
获取LOD统计信息
:return: 统计信息
"""
lod_counts = {}
for lod_level in self.actor_lod_levels.values():
if lod_level not in lod_counts:
lod_counts[lod_level] = 0
lod_counts[lod_level] += 1
return {
'actor_lod_distribution': lod_counts,
'current_fps': self.performance_monitor['last_fps'],
'average_frame_time': sum(self.performance_monitor['frame_times']) / len(self.performance_monitor['frame_times'])
if self.performance_monitor['frame_times'] else 0,
'lod_settings': {
'strategy': self.settings.strategy.name,
'distance_thresholds': self.settings.distance_thresholds,
'frame_rates': self.settings.frame_rates
}
}
class AnimationCacheManager:
"""
动画缓存管理器
提供高效的动画数据缓存
"""
def __init__(self, max_size: int = 500):
"""
初始化缓存管理器
:param max_size: 最大缓存大小
"""
self.max_size = max_size
self.cache: Dict[str, CacheEntry] = {}
self.policy = CachePolicy.LRU
self.cleanup_interval = 30.0
self.last_cleanup_time = 0.0
print(f"动画缓存管理器初始化完成 (最大大小: {max_size})")
def set_cache_policy(self, policy: CachePolicy) -> None:
"""
设置缓存策略
:param policy: 缓存策略
"""
self.policy = policy
print(f"缓存策略设置为: {policy.name}")
def set_max_size(self, max_size: int) -> None:
"""
设置最大缓存大小
:param max_size: 最大大小
"""
self.max_size = max_size
self._cleanup_cache()
print(f"最大缓存大小设置为: {max_size}")
def put(self, key: str, data: Any, size: int = 0) -> None:
"""
放入缓存项
:param key: 键
:param data: 数据
:param size: 数据大小
"""
current_time = globalClock.getRealTime()
if key in self.cache:
# 更新现有条目
entry = self.cache[key]
entry.data = data
entry.timestamp = current_time
entry.access_count += 1
entry.size = size or entry.size
else:
# 添加新条目
entry = CacheEntry(
key=key,
data=data,
timestamp=current_time,
access_count=1,
size=size
)
self.cache[key] = entry
# 检查是否需要清理缓存
self._check_cleanup()
def get(self, key: str) -> Optional[Any]:
"""
获取缓存项
:param key: 键
:return: 数据或None
"""
if key in self.cache:
entry = self.cache[key]
entry.access_count += 1
entry.timestamp = globalClock.getRealTime()
return entry.data
return None
def remove(self, key: str) -> bool:
"""
移除缓存项
:param key: 键
:return: 是否成功移除
"""
if key in self.cache:
del self.cache[key]
return True
return False
def clear(self) -> None:
"""清空缓存"""
self.cache.clear()
print("动画缓存已清空")
def _check_cleanup(self) -> None:
"""
检查是否需要清理缓存
"""
current_time = globalClock.getRealTime()
if current_time - self.last_cleanup_time > self.cleanup_interval:
self._cleanup_cache()
self.last_cleanup_time = current_time
def _cleanup_cache(self) -> None:
"""
清理缓存
"""
if len(self.cache) <= self.max_size:
return
# 根据策略选择要移除的条目
entries_to_remove = len(self.cache) - self.max_size
if self.policy == CachePolicy.LRU:
# 移除最近最少使用的条目
sorted_entries = sorted(self.cache.values(), key=lambda x: x.timestamp)
elif self.policy == CachePolicy.LFU:
# 移除最不经常使用的条目
sorted_entries = sorted(self.cache.values(), key=lambda x: x.access_count)
else: # FIFO
# 移除最早添加的条目
sorted_entries = sorted(self.cache.values(), key=lambda x: x.timestamp)
# 移除条目
for i in range(min(entries_to_remove, len(sorted_entries))):
entry = sorted_entries[i]
del self.cache[entry.key]
print(f"缓存清理完成,移除了 {entries_to_remove} 个条目")
def get_cache_stats(self) -> Dict[str, Any]:
"""
获取缓存统计信息
:return: 统计信息
"""
total_size = sum(entry.size for entry in self.cache.values())
total_access = sum(entry.access_count for entry in self.cache.values())
avg_access = total_access / len(self.cache) if self.cache else 0
return {
'size': len(self.cache),
'max_size': self.max_size,
'total_size': total_size,
'hit_rate': 0, # 简化实现,实际项目中需要跟踪命中率
'average_access_count': avg_access,
'policy': self.policy.name
}
# 使用示例和测试代码
def example_compression_usage(actor: Actor):
"""
动画压缩使用示例
"""
print("=== 动画压缩使用示例 ===")
compressor = AnimationCompressor()
# 压缩所有动画
for anim_name in actor.getAnimNames():
print(f"压缩动画: {anim_name}")
compressed_data = compressor.compress_animation(
actor,
anim_name,
CompressionMethod.KEYFRAME_REDUCTION
)
if compressed_data:
stats = compressor.get_compression_stats(compressed_data)
print(f" 压缩统计: {stats}")
print("动画压缩示例完成")
def example_lod_usage(world, actors: Dict[str, Any]):
"""
LOD系统使用示例
"""
print("=== 动画LOD使用示例 ===")
lod_system = AnimationLODSystem(world)
# 设置LOD参数
lod_system.set_lod_strategy(LODStrategy.HYBRID)
lod_system.set_distance_thresholds([15.0, 30.0, 60.0])
lod_system.set_frame_rates([1.0, 0.7, 0.4, 0.1])
# 更新LOD级别
lod_system.update_lod_levels(actors)
# 更新性能统计
lod_system.update_performance_stats(45, 0.022) # 45 FPS, 22ms per frame
# 获取LOD统计
stats = lod_system.get_lod_stats()
print(f" LOD统计: {stats}")
print("动画LOD示例完成")
def example_cache_usage():
"""
缓存管理使用示例
"""
print("=== 动画缓存管理使用示例 ===")
cache_manager = AnimationCacheManager(max_size=100)
# 设置缓存策略
cache_manager.set_cache_policy(CachePolicy.LRU)
# 添加缓存项
dummy_data = {'frames': 100, 'bones': 20, 'data': 'animation_data'}
cache_manager.put("actor_1_walk", dummy_data, size=1024)
cache_manager.put("actor_1_run", dummy_data, size=2048)
cache_manager.put("actor_2_idle", dummy_data, size=512)
# 获取缓存项
data = cache_manager.get("actor_1_walk")
if data:
print(" 成功获取缓存数据")
# 查看缓存统计
stats = cache_manager.get_cache_stats()
print(f" 缓存统计: {stats}")
print("动画缓存管理示例完成")
if __name__ == "__main__":
print("动画优化和压缩模块加载完成")