1019 lines
37 KiB
Python
1019 lines
37 KiB
Python
"""
|
||
高级骨骼动画系统 - 工具模块
|
||
提供动画编辑、调试和分析工具
|
||
"""
|
||
|
||
import math
|
||
import json
|
||
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
|
||
from direct.interval.IntervalGlobal import *
|
||
|
||
# 定义常量
|
||
TOOL_SETTINGS = {
|
||
'analyzer_update_rate': 10,
|
||
'debug_visualization_enabled': True,
|
||
'profiler_sampling_rate': 60
|
||
}
|
||
|
||
# 枚举定义
|
||
class AnimationAnalysisType(Enum):
|
||
"""动画分析类型枚举"""
|
||
PERFORMANCE = 0
|
||
QUALITY = 1
|
||
MEMORY = 2
|
||
COMPRESSION = 3
|
||
|
||
class DebugVisualizationMode(Enum):
|
||
"""调试可视化模式枚举"""
|
||
SKELETON = 0
|
||
BOUNDS = 1
|
||
NORMALS = 2
|
||
WIREFRAME = 3
|
||
COLLISION = 4
|
||
|
||
class AnimationEditMode(Enum):
|
||
"""动画编辑模式枚举"""
|
||
VIEW = 0
|
||
EDIT_KEYFRAME = 1
|
||
EDIT_CURVE = 2
|
||
EDIT_BONE = 3
|
||
|
||
@dataclass
|
||
class AnimationAnalysisResult:
|
||
"""动画分析结果数据类"""
|
||
actor_id: str
|
||
anim_name: str
|
||
analysis_type: AnimationAnalysisType
|
||
metrics: Dict[str, Any]
|
||
timestamp: float
|
||
recommendations: List[str] = field(default_factory=list)
|
||
|
||
@dataclass
|
||
class AnimationEditorState:
|
||
"""动画编辑器状态数据类"""
|
||
actor_id: str
|
||
anim_name: str
|
||
current_frame: int
|
||
selected_bones: List[str]
|
||
edit_mode: AnimationEditMode
|
||
keyframe_selection: List[int]
|
||
curve_editor_state: Dict[str, Any]
|
||
|
||
@dataclass
|
||
class PerformanceMetrics:
|
||
"""性能指标数据类"""
|
||
fps: float
|
||
frame_time: float
|
||
animation_update_time: float
|
||
memory_usage: int
|
||
actor_count: int
|
||
active_animations: int
|
||
|
||
class AnimationAnalyzer:
|
||
"""
|
||
动画分析器
|
||
提供动画性能、质量和内存使用分析
|
||
"""
|
||
|
||
def __init__(self, core_system):
|
||
"""
|
||
初始化动画分析器
|
||
:param core_system: 核心动画系统
|
||
"""
|
||
self.core_system = core_system
|
||
self.analysis_history: List[AnimationAnalysisResult] = []
|
||
self.is_analyzing = False
|
||
self.update_rate = TOOL_SETTINGS['analyzer_update_rate']
|
||
self.last_update_time = 0.0
|
||
|
||
print("动画分析器初始化完成")
|
||
|
||
def start_analysis(self) -> None:
|
||
"""
|
||
开始分析
|
||
"""
|
||
self.is_analyzing = True
|
||
print("动画分析已启动")
|
||
|
||
def stop_analysis(self) -> None:
|
||
"""
|
||
停止分析
|
||
"""
|
||
self.is_analyzing = False
|
||
print("动画分析已停止")
|
||
|
||
def analyze_animation(self, actor_id: str, anim_name: str,
|
||
analysis_type: AnimationAnalysisType = AnimationAnalysisType.PERFORMANCE) -> Optional[AnimationAnalysisResult]:
|
||
"""
|
||
分析指定动画
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param analysis_type: 分析类型
|
||
:return: 分析结果
|
||
"""
|
||
if actor_id not in self.core_system.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return None
|
||
|
||
actor_info = self.core_system.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
if anim_name not in actor.getAnimNames():
|
||
print(f"错误: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
return None
|
||
|
||
metrics = {}
|
||
recommendations = []
|
||
|
||
# 根据分析类型执行不同的分析
|
||
if analysis_type == AnimationAnalysisType.PERFORMANCE:
|
||
result = self._analyze_performance(actor_info, anim_name)
|
||
metrics.update(result[0])
|
||
recommendations.extend(result[1])
|
||
elif analysis_type == AnimationAnalysisType.QUALITY:
|
||
result = self._analyze_quality(actor_info, anim_name)
|
||
metrics.update(result[0])
|
||
recommendations.extend(result[1])
|
||
elif analysis_type == AnimationAnalysisType.MEMORY:
|
||
result = self._analyze_memory(actor_info, anim_name)
|
||
metrics.update(result[0])
|
||
recommendations.extend(result[1])
|
||
elif analysis_type == AnimationAnalysisType.COMPRESSION:
|
||
result = self._analyze_compression(actor_info, anim_name)
|
||
metrics.update(result[0])
|
||
recommendations.extend(result[1])
|
||
|
||
# 创建分析结果
|
||
analysis_result = AnimationAnalysisResult(
|
||
actor_id=actor_id,
|
||
anim_name=anim_name,
|
||
analysis_type=analysis_type,
|
||
metrics=metrics,
|
||
timestamp=globalClock.getRealTime(),
|
||
recommendations=recommendations
|
||
)
|
||
|
||
# 添加到历史记录
|
||
self.analysis_history.append(analysis_result)
|
||
if len(self.analysis_history) > 100: # 限制历史记录大小
|
||
self.analysis_history.pop(0)
|
||
|
||
return analysis_result
|
||
|
||
def _analyze_performance(self, actor_info: Any, anim_name: str) -> Tuple[Dict[str, Any], List[str]]:
|
||
"""
|
||
分析动画性能
|
||
:param actor_info: Actor信息
|
||
:param anim_name: 动画名称
|
||
:return: (指标, 建议)
|
||
"""
|
||
actor = actor_info.actor
|
||
metrics = {}
|
||
recommendations = []
|
||
|
||
# 获取动画控制
|
||
control = actor.getAnimControl(anim_name)
|
||
if control:
|
||
# 基本性能指标
|
||
metrics['frame_rate'] = actor.getFrameRate(anim_name)
|
||
metrics['num_frames'] = actor.getNumFrames(anim_name)
|
||
metrics['duration'] = control.getDuration()
|
||
metrics['is_looping'] = control.getLoop()
|
||
|
||
# 性能建议
|
||
if metrics['frame_rate'] and metrics['frame_rate'] < 15:
|
||
recommendations.append("动画帧率过低,建议优化关键帧")
|
||
|
||
if metrics['num_frames'] and metrics['num_frames'] > 1000:
|
||
recommendations.append("动画帧数过多,考虑分割或简化")
|
||
|
||
# 骨骼数量分析
|
||
bundle = actor.getPartBundleDict().get('modelRoot')
|
||
if bundle:
|
||
bone_count = bundle.getNumChildren()
|
||
metrics['bone_count'] = bone_count
|
||
|
||
if bone_count > 100:
|
||
recommendations.append("骨骼数量较多,可能影响性能")
|
||
|
||
return metrics, recommendations
|
||
|
||
def _analyze_quality(self, actor_info: Any, anim_name: str) -> Tuple[Dict[str, Any], List[str]]:
|
||
"""
|
||
分析动画质量
|
||
:param actor_info: Actor信息
|
||
:param anim_name: 动画名称
|
||
:return: (指标, 建议)
|
||
"""
|
||
actor = actor_info.actor
|
||
metrics = {}
|
||
recommendations = []
|
||
|
||
# 检查动画是否存在突变
|
||
prev_frame_data = None
|
||
large_changes = 0
|
||
|
||
control = actor.getAnimControl(anim_name)
|
||
if control:
|
||
num_frames = control.getNumFrames()
|
||
for frame in range(min(100, num_frames)): # 采样前100帧
|
||
actor.pose(anim_name, frame)
|
||
|
||
# 检查所有骨骼的变化
|
||
bundle = actor.getPartBundleDict().get('modelRoot')
|
||
if bundle:
|
||
for i in range(min(10, bundle.getNumChildren())): # 检查前10个骨骼
|
||
child = bundle.getChild(i)
|
||
if hasattr(child, 'getName'):
|
||
bone_name = child.getName()
|
||
joint = actor.exposeJoint(None, "modelRoot", bone_name)
|
||
if joint:
|
||
current_pos = joint.getPos()
|
||
if prev_frame_data and bone_name in prev_frame_data:
|
||
prev_pos = prev_frame_data[bone_name]
|
||
distance = (current_pos - prev_pos).length()
|
||
if distance > 1.0: # 阈值
|
||
large_changes += 1
|
||
|
||
if bone_name not in prev_frame_data:
|
||
prev_frame_data = {}
|
||
prev_frame_data[bone_name] = current_pos
|
||
|
||
metrics['large_position_changes'] = large_changes
|
||
|
||
if large_changes > 10:
|
||
recommendations.append("检测到大量位置突变,可能存在动画质量问题")
|
||
|
||
return metrics, recommendations
|
||
|
||
def _analyze_memory(self, actor_info: Any, anim_name: str) -> Tuple[Dict[str, Any], List[str]]:
|
||
"""
|
||
分析动画内存使用
|
||
:param actor_info: Actor信息
|
||
:param anim_name: 动画名称
|
||
:return: (指标, 建议)
|
||
"""
|
||
actor = actor_info.actor
|
||
metrics = {}
|
||
recommendations = []
|
||
|
||
# 估算内存使用 (简化)
|
||
control = actor.getAnimControl(anim_name)
|
||
if control:
|
||
num_frames = control.getNumFrames()
|
||
bundle = actor.getPartBundleDict().get('modelRoot')
|
||
bone_count = bundle.getNumChildren() if bundle else 0
|
||
|
||
# 简化的内存估算公式
|
||
estimated_memory = num_frames * bone_count * 64 # 每个骨骼关键帧约64字节
|
||
metrics['estimated_memory_bytes'] = estimated_memory
|
||
metrics['estimated_memory_kb'] = estimated_memory / 1024
|
||
|
||
if estimated_memory > 1024 * 1024: # 超过1MB
|
||
recommendations.append("动画内存使用较大,考虑压缩或简化")
|
||
|
||
return metrics, recommendations
|
||
|
||
def _analyze_compression(self, actor_info: Any, anim_name: str) -> Tuple[Dict[str, Any], List[str]]:
|
||
"""
|
||
分析动画压缩潜力
|
||
:param actor_info: Actor信息
|
||
:param anim_name: 动画名称
|
||
:return: (指标, 建议)
|
||
"""
|
||
actor = actor_info.actor
|
||
metrics = {}
|
||
recommendations = []
|
||
|
||
# 检查关键帧密度
|
||
control = actor.getAnimControl(anim_name)
|
||
if control:
|
||
num_frames = control.getNumFrames()
|
||
duration = control.getDuration()
|
||
|
||
if duration > 0:
|
||
fps = num_frames / duration
|
||
metrics['actual_fps'] = fps
|
||
|
||
# 检查是否可以降低帧率
|
||
if fps > 30:
|
||
metrics['recommended_fps'] = 30
|
||
recommendations.append(f"可以将帧率从{fps:.1f}降低到30以节省内存")
|
||
elif fps > 15:
|
||
metrics['recommended_fps'] = 15
|
||
recommendations.append(f"可以将帧率从{fps:.1f}降低到15以节省内存")
|
||
|
||
return metrics, recommendations
|
||
|
||
def get_actor_analysis_summary(self, actor_id: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取Actor分析摘要
|
||
:param actor_id: Actor ID
|
||
:return: 分析摘要
|
||
"""
|
||
if actor_id not in self.core_system.actors:
|
||
return None
|
||
|
||
# 获取最新的分析结果
|
||
actor_analyses = [a for a in self.analysis_history if a.actor_id == actor_id]
|
||
if not actor_analyses:
|
||
return None
|
||
|
||
# 统计各类分析结果
|
||
performance_issues = len([a for a in actor_analyses
|
||
if a.analysis_type == AnimationAnalysisType.PERFORMANCE and a.recommendations])
|
||
quality_issues = len([a for a in actor_analyses
|
||
if a.analysis_type == AnimationAnalysisType.QUALITY and a.recommendations])
|
||
memory_issues = len([a for a in actor_analyses
|
||
if a.analysis_type == AnimationAnalysisType.MEMORY and a.recommendations])
|
||
|
||
return {
|
||
'actor_id': actor_id,
|
||
'total_analyses': len(actor_analyses),
|
||
'performance_issues': performance_issues,
|
||
'quality_issues': quality_issues,
|
||
'memory_issues': memory_issues,
|
||
'last_analysis_time': actor_analyses[-1].timestamp if actor_analyses else 0
|
||
}
|
||
|
||
def get_system_analysis_summary(self) -> Dict[str, Any]:
|
||
"""
|
||
获取系统分析摘要
|
||
:return: 分析摘要
|
||
"""
|
||
total_analyses = len(self.analysis_history)
|
||
actors_with_issues = len(set(a.actor_id for a in self.analysis_history if a.recommendations))
|
||
|
||
return {
|
||
'total_analyses': total_analyses,
|
||
'actors_analyzed': len(set(a.actor_id for a in self.analysis_history)),
|
||
'actors_with_issues': actors_with_issues,
|
||
'performance_analyses': len([a for a in self.analysis_history
|
||
if a.analysis_type == AnimationAnalysisType.PERFORMANCE]),
|
||
'quality_analyses': len([a for a in self.analysis_history
|
||
if a.analysis_type == AnimationAnalysisType.QUALITY]),
|
||
'memory_analyses': len([a for a in self.analysis_history
|
||
if a.analysis_type == AnimationAnalysisType.MEMORY])
|
||
}
|
||
|
||
class AnimationEditor:
|
||
"""
|
||
动画编辑器
|
||
提供关键帧编辑、曲线编辑等功能
|
||
"""
|
||
|
||
def __init__(self, core_system):
|
||
"""
|
||
初始化动画编辑器
|
||
:param core_system: 核心动画系统
|
||
"""
|
||
self.core_system = core_system
|
||
self.editor_states: Dict[str, AnimationEditorState] = {}
|
||
self.is_editing = False
|
||
self.edit_callbacks: List[Callable] = []
|
||
|
||
print("动画编辑器初始化完成")
|
||
|
||
def start_editing(self, actor_id: str, anim_name: str) -> bool:
|
||
"""
|
||
开始编辑动画
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:return: 是否成功开始编辑
|
||
"""
|
||
if actor_id not in self.core_system.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.core_system.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
if anim_name not in actor.getAnimNames():
|
||
print(f"错误: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
return False
|
||
|
||
# 创建编辑器状态
|
||
editor_state = AnimationEditorState(
|
||
actor_id=actor_id,
|
||
anim_name=anim_name,
|
||
current_frame=0,
|
||
selected_bones=[],
|
||
edit_mode=AnimationEditMode.VIEW,
|
||
keyframe_selection=[],
|
||
curve_editor_state={}
|
||
)
|
||
|
||
self.editor_states[f"{actor_id}:{anim_name}"] = editor_state
|
||
self.is_editing = True
|
||
|
||
print(f"开始编辑动画: {anim_name} (Actor: {actor_id})")
|
||
return True
|
||
|
||
def stop_editing(self, actor_id: str, anim_name: str) -> None:
|
||
"""
|
||
停止编辑动画
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
"""
|
||
key = f"{actor_id}:{anim_name}"
|
||
if key in self.editor_states:
|
||
del self.editor_states[key]
|
||
|
||
if not self.editor_states:
|
||
self.is_editing = False
|
||
|
||
print(f"停止编辑动画: {anim_name} (Actor: {actor_id})")
|
||
|
||
def set_edit_mode(self, actor_id: str, anim_name: str, mode: AnimationEditMode) -> None:
|
||
"""
|
||
设置编辑模式
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param mode: 编辑模式
|
||
"""
|
||
key = f"{actor_id}:{anim_name}"
|
||
if key in self.editor_states:
|
||
self.editor_states[key].edit_mode = mode
|
||
print(f"设置编辑模式为: {mode.name} (Actor: {actor_id}, 动画: {anim_name})")
|
||
|
||
def select_bones(self, actor_id: str, anim_name: str, bone_names: List[str]) -> None:
|
||
"""
|
||
选择骨骼
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param bone_names: 骨骼名称列表
|
||
"""
|
||
key = f"{actor_id}:{anim_name}"
|
||
if key in self.editor_states:
|
||
self.editor_states[key].selected_bones = bone_names
|
||
print(f"选择骨骼: {bone_names} (Actor: {actor_id}, 动画: {anim_name})")
|
||
|
||
def set_current_frame(self, actor_id: str, anim_name: str, frame: int) -> None:
|
||
"""
|
||
设置当前帧
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param frame: 帧号
|
||
"""
|
||
key = f"{actor_id}:{anim_name}"
|
||
if key in self.editor_states:
|
||
editor_state = self.editor_states[key]
|
||
editor_state.current_frame = frame
|
||
|
||
# 定位到指定帧
|
||
actor_info = self.core_system.actors[actor_id]
|
||
actor_info.actor.pose(anim_name, frame)
|
||
|
||
print(f"设置当前帧为: {frame} (Actor: {actor_id}, 动画: {anim_name})")
|
||
|
||
def add_keyframe(self, actor_id: str, anim_name: str, bone_name: str,
|
||
frame: int, position: Optional[Tuple[float, float, float]] = None,
|
||
rotation: Optional[Tuple[float, float, float]] = None,
|
||
scale: Optional[Tuple[float, float, float]] = None) -> None:
|
||
"""
|
||
添加关键帧
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param bone_name: 骨骼名称
|
||
:param frame: 帧号
|
||
:param position: 位置
|
||
:param rotation: 旋转
|
||
:param scale: 缩放
|
||
"""
|
||
# 这是一个简化的实现,实际项目中需要直接操作动画数据
|
||
print(f"添加关键帧: 骨骼={bone_name}, 帧={frame} (Actor: {actor_id}, 动画: {anim_name})")
|
||
print(f" 位置: {position}")
|
||
print(f" 旋转: {rotation}")
|
||
print(f" 缩放: {scale}")
|
||
|
||
# 触发编辑回调
|
||
for callback in self.edit_callbacks:
|
||
try:
|
||
callback('add_keyframe', {
|
||
'actor_id': actor_id,
|
||
'anim_name': anim_name,
|
||
'bone_name': bone_name,
|
||
'frame': frame,
|
||
'position': position,
|
||
'rotation': rotation,
|
||
'scale': scale
|
||
})
|
||
except Exception as e:
|
||
print(f"执行编辑回调失败: {e}")
|
||
|
||
def remove_keyframe(self, actor_id: str, anim_name: str, bone_name: str, frame: int) -> None:
|
||
"""
|
||
移除关键帧
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param bone_name: 骨骼名称
|
||
:param frame: 帧号
|
||
"""
|
||
print(f"移除关键帧: 骨骼={bone_name}, 帧={frame} (Actor: {actor_id}, 动画: {anim_name})")
|
||
|
||
# 触发编辑回调
|
||
for callback in self.edit_callbacks:
|
||
try:
|
||
callback('remove_keyframe', {
|
||
'actor_id': actor_id,
|
||
'anim_name': anim_name,
|
||
'bone_name': bone_name,
|
||
'frame': frame
|
||
})
|
||
except Exception as e:
|
||
print(f"执行编辑回调失败: {e}")
|
||
|
||
def modify_keyframe(self, actor_id: str, anim_name: str, bone_name: str, frame: int,
|
||
position: Optional[Tuple[float, float, float]] = None,
|
||
rotation: Optional[Tuple[float, float, float]] = None,
|
||
scale: Optional[Tuple[float, float, float]] = None) -> None:
|
||
"""
|
||
修改关键帧
|
||
:param actor_id: Actor ID
|
||
:param anim_name: 动画名称
|
||
:param bone_name: 骨骼名称
|
||
:param frame: 帧号
|
||
:param position: 新位置
|
||
:param rotation: 新旋转
|
||
:param scale: 新缩放
|
||
"""
|
||
print(f"修改关键帧: 骨骼={bone_name}, 帧={frame} (Actor: {actor_id}, 动画: {anim_name})")
|
||
print(f" 新位置: {position}")
|
||
print(f" 新旋转: {rotation}")
|
||
print(f" 新缩放: {scale}")
|
||
|
||
# 触发编辑回调
|
||
for callback in self.edit_callbacks:
|
||
try:
|
||
callback('modify_keyframe', {
|
||
'actor_id': actor_id,
|
||
'anim_name': anim_name,
|
||
'bone_name': bone_name,
|
||
'frame': frame,
|
||
'position': position,
|
||
'rotation': rotation,
|
||
'scale': scale
|
||
})
|
||
except Exception as e:
|
||
print(f"执行编辑回调失败: {e}")
|
||
|
||
def add_edit_callback(self, callback: Callable) -> None:
|
||
"""
|
||
添加编辑回调
|
||
:param callback: 回调函数
|
||
"""
|
||
self.edit_callbacks.append(callback)
|
||
print("添加编辑回调")
|
||
|
||
def remove_edit_callback(self, callback: Callable) -> None:
|
||
"""
|
||
移除编辑回调
|
||
:param callback: 回调函数
|
||
"""
|
||
if callback in self.edit_callbacks:
|
||
self.edit_callbacks.remove(callback)
|
||
print("移除编辑回调")
|
||
|
||
class DebugVisualizer:
|
||
"""
|
||
调试可视化工具
|
||
提供骨骼、边界框等可视化功能
|
||
"""
|
||
|
||
def __init__(self, world):
|
||
"""
|
||
初始化调试可视化工具
|
||
:param world: 世界对象
|
||
"""
|
||
self.world = world
|
||
self.visualizations: Dict[str, NodePath] = {}
|
||
self.enabled = TOOL_SETTINGS['debug_visualization_enabled']
|
||
self.mode = DebugVisualizationMode.SKELETON
|
||
|
||
print("调试可视化工具初始化完成")
|
||
|
||
def set_mode(self, mode: DebugVisualizationMode) -> None:
|
||
"""
|
||
设置可视化模式
|
||
:param mode: 模式
|
||
"""
|
||
self.mode = mode
|
||
print(f"调试可视化模式设置为: {mode.name}")
|
||
|
||
def enable_visualization(self, enabled: bool = True) -> None:
|
||
"""
|
||
启用/禁用可视化
|
||
:param enabled: 是否启用
|
||
"""
|
||
self.enabled = enabled
|
||
if not enabled:
|
||
self.clear_all_visualizations()
|
||
print(f"调试可视化已{'启用' if enabled else '禁用'}")
|
||
|
||
def visualize_actor_skeleton(self, actor_id: str, actor: Actor) -> None:
|
||
"""
|
||
可视化Actor骨骼
|
||
:param actor_id: Actor ID
|
||
:param actor: Actor对象
|
||
"""
|
||
if not self.enabled or self.mode != DebugVisualizationMode.SKELETON:
|
||
return
|
||
|
||
# 清除旧的可视化
|
||
self.clear_visualization(actor_id)
|
||
|
||
# 创建骨骼可视化节点
|
||
skeleton_node = self.world.render.attachNewNode(f"skeleton_vis_{actor_id}")
|
||
self.visualizations[actor_id] = skeleton_node
|
||
|
||
# 获取骨骼信息并绘制连线
|
||
bundle = actor.getPartBundleDict().get('modelRoot')
|
||
if bundle:
|
||
self._draw_skeleton_hierarchy(actor, bundle, skeleton_node)
|
||
|
||
def _draw_skeleton_hierarchy(self, actor: Actor, bundle, parent_node: NodePath) -> None:
|
||
"""
|
||
绘制骨骼层级
|
||
:param actor: Actor对象
|
||
:param bundle: 骨骼包
|
||
:param parent_node: 父节点
|
||
"""
|
||
# 这里简化实现,实际项目中需要遍历骨骼树并绘制连线
|
||
# 可以使用LineSegs或其他Panda3D绘图功能
|
||
|
||
# 示例:绘制一个简单的可视化表示
|
||
from panda3d.core import LineSegs
|
||
|
||
segs = LineSegs(f"skeleton_lines_{actor.getName()}")
|
||
segs.setColor(0, 1, 0, 1) # 绿色
|
||
segs.setThickness(2.0)
|
||
|
||
# 从根节点到各个子节点绘制线段(简化)
|
||
root_pos = Point3(0, 0, 0)
|
||
segs.moveTo(root_pos)
|
||
|
||
for i in range(min(5, bundle.getNumChildren())): # 只绘制前5个骨骼
|
||
child = bundle.getChild(i)
|
||
if hasattr(child, 'getName'):
|
||
bone_name = child.getName()
|
||
joint = actor.exposeJoint(None, "modelRoot", bone_name)
|
||
if joint:
|
||
joint_pos = joint.getPos()
|
||
segs.drawTo(joint_pos)
|
||
segs.moveTo(root_pos)
|
||
|
||
# 创建节点并附加到场景
|
||
lines_node = segs.create()
|
||
np = parent_node.attachNewNode(lines_node)
|
||
np.setBin("fixed", 40)
|
||
np.setDepthTest(False)
|
||
np.setDepthWrite(False)
|
||
|
||
def visualize_actor_bounds(self, actor_id: str, actor: Actor) -> None:
|
||
"""
|
||
可视化Actor边界框
|
||
:param actor_id: Actor ID
|
||
:param actor: Actor对象
|
||
"""
|
||
if not self.enabled or self.mode != DebugVisualizationMode.BOUNDS:
|
||
return
|
||
|
||
# 清除旧的可视化
|
||
self.clear_visualization(actor_id)
|
||
|
||
# 获取边界框
|
||
bounds = actor.getTightBounds()
|
||
if bounds:
|
||
min_point, max_point = bounds
|
||
|
||
# 创建边界框可视化
|
||
from panda3d.core import LineSegs
|
||
|
||
segs = LineSegs(f"bounds_vis_{actor_id}")
|
||
segs.setColor(1, 0, 0, 1) # 红色
|
||
segs.setThickness(1.0)
|
||
|
||
# 绘制立方体边界框
|
||
self._draw_wire_cube(segs, min_point, max_point)
|
||
|
||
# 创建节点并附加到场景
|
||
bounds_node = self.world.render.attachNewNode(segs.create())
|
||
bounds_node.setBin("fixed", 40)
|
||
bounds_node.setDepthTest(False)
|
||
bounds_node.setDepthWrite(False)
|
||
|
||
self.visualizations[actor_id] = bounds_node
|
||
|
||
def _draw_wire_cube(self, segs: LineSegs, min_point: Point3, max_point: Point3) -> None:
|
||
"""
|
||
绘制线框立方体
|
||
:param segs: LineSegs对象
|
||
:param min_point: 最小点
|
||
:param max_point: 最大点
|
||
"""
|
||
# 获取立方体的8个顶点
|
||
vertices = [
|
||
Point3(min_point.x, min_point.y, min_point.z),
|
||
Point3(max_point.x, min_point.y, min_point.z),
|
||
Point3(max_point.x, max_point.y, min_point.z),
|
||
Point3(min_point.x, max_point.y, min_point.z),
|
||
Point3(min_point.x, min_point.y, max_point.z),
|
||
Point3(max_point.x, min_point.y, max_point.z),
|
||
Point3(max_point.x, max_point.y, max_point.z),
|
||
Point3(min_point.x, max_point.y, max_point.z)
|
||
]
|
||
|
||
# 绘制12条边
|
||
edges = [
|
||
(0, 1), (1, 2), (2, 3), (3, 0), # 底面
|
||
(4, 5), (5, 6), (6, 7), (7, 4), # 顶面
|
||
(0, 4), (1, 5), (2, 6), (3, 7) # 垂直边
|
||
]
|
||
|
||
for start, end in edges:
|
||
segs.moveTo(vertices[start])
|
||
segs.drawTo(vertices[end])
|
||
|
||
def clear_visualization(self, actor_id: str) -> None:
|
||
"""
|
||
清除指定Actor的可视化
|
||
:param actor_id: Actor ID
|
||
"""
|
||
if actor_id in self.visualizations:
|
||
self.visualizations[actor_id].removeNode()
|
||
del self.visualizations[actor_id]
|
||
|
||
def clear_all_visualizations(self) -> None:
|
||
"""
|
||
清除所有可视化
|
||
"""
|
||
for np in self.visualizations.values():
|
||
np.removeNode()
|
||
self.visualizations.clear()
|
||
|
||
def update_visualizations(self, actors: Dict[str, Any]) -> None:
|
||
"""
|
||
更新所有可视化
|
||
:param actors: Actor字典
|
||
"""
|
||
if not self.enabled:
|
||
return
|
||
|
||
for actor_id, actor_info in actors.items():
|
||
actor = actor_info.actor
|
||
|
||
if self.mode == DebugVisualizationMode.SKELETON:
|
||
self.visualize_actor_skeleton(actor_id, actor)
|
||
elif self.mode == DebugVisualizationMode.BOUNDS:
|
||
self.visualize_actor_bounds(actor_id, actor)
|
||
|
||
class AnimationProfiler:
|
||
"""
|
||
动画性能分析器
|
||
提供详细的性能分析和优化建议
|
||
"""
|
||
|
||
def __init__(self, core_system):
|
||
"""
|
||
初始化动画性能分析器
|
||
:param core_system: 核心动画系统
|
||
"""
|
||
self.core_system = core_system
|
||
self.sampling_rate = TOOL_SETTINGS['profiler_sampling_rate']
|
||
self.is_profiling = False
|
||
self.profile_data: List[PerformanceMetrics] = []
|
||
self.last_sample_time = 0.0
|
||
|
||
print("动画性能分析器初始化完成")
|
||
|
||
def start_profiling(self) -> None:
|
||
"""
|
||
开始性能分析
|
||
"""
|
||
self.is_profiling = True
|
||
print("动画性能分析已启动")
|
||
|
||
def stop_profiling(self) -> None:
|
||
"""
|
||
停止性能分析
|
||
"""
|
||
self.is_profiling = False
|
||
print("动画性能分析已停止")
|
||
|
||
def sample_performance(self) -> None:
|
||
"""
|
||
采样性能数据
|
||
"""
|
||
if not self.is_profiling:
|
||
return
|
||
|
||
current_time = globalClock.getRealTime()
|
||
if current_time - self.last_sample_time < 1.0 / self.sampling_rate:
|
||
return
|
||
|
||
self.last_sample_time = current_time
|
||
|
||
# 收集性能指标
|
||
metrics = self._collect_metrics()
|
||
self.profile_data.append(metrics)
|
||
|
||
# 限制数据大小
|
||
if len(self.profile_data) > 1000:
|
||
self.profile_data.pop(0)
|
||
|
||
def _collect_metrics(self) -> PerformanceMetrics:
|
||
"""
|
||
收集性能指标
|
||
:return: 性能指标
|
||
"""
|
||
# 获取FPS和帧时间
|
||
fps = globalClock.getAverageFrameRate()
|
||
frame_time = globalClock.getDt()
|
||
|
||
# 估算动画更新时间(简化)
|
||
animation_update_time = frame_time * 0.3 # 假设动画更新占总帧时间的30%
|
||
|
||
# 估算内存使用(简化)
|
||
memory_usage = len(self.core_system.actors) * 1024 * 1024 # 假设每个Actor约1MB
|
||
|
||
# 统计Actor和动画数量
|
||
actor_count = len(self.core_system.actors)
|
||
active_animations = sum(len(info.active_animations) for info in self.core_system.actors.values())
|
||
|
||
return PerformanceMetrics(
|
||
fps=fps,
|
||
frame_time=frame_time,
|
||
animation_update_time=animation_update_time,
|
||
memory_usage=memory_usage,
|
||
actor_count=actor_count,
|
||
active_animations=active_animations
|
||
)
|
||
|
||
def get_performance_report(self) -> Dict[str, Any]:
|
||
"""
|
||
获取性能报告
|
||
:return: 性能报告
|
||
"""
|
||
if not self.profile_data:
|
||
return {}
|
||
|
||
# 计算统计数据
|
||
fps_values = [m.fps for m in self.profile_data]
|
||
frame_time_values = [m.frame_time for m in self.profile_data]
|
||
update_time_values = [m.animation_update_time for m in self.profile_data]
|
||
memory_values = [m.memory_usage for m in self.profile_data]
|
||
|
||
return {
|
||
'sample_count': len(self.profile_data),
|
||
'duration_seconds': len(self.profile_data) / self.sampling_rate,
|
||
'fps': {
|
||
'average': sum(fps_values) / len(fps_values),
|
||
'min': min(fps_values),
|
||
'max': max(fps_values),
|
||
'std_dev': self._calculate_std_dev(fps_values)
|
||
},
|
||
'frame_time': {
|
||
'average': sum(frame_time_values) / len(frame_time_values),
|
||
'min': min(frame_time_values),
|
||
'max': max(frame_time_values),
|
||
'std_dev': self._calculate_std_dev(frame_time_values)
|
||
},
|
||
'animation_update_time': {
|
||
'average': sum(update_time_values) / len(update_time_values),
|
||
'min': min(update_time_values),
|
||
'max': max(update_time_values),
|
||
'std_dev': self._calculate_std_dev(update_time_values)
|
||
},
|
||
'memory_usage': {
|
||
'average': sum(memory_values) / len(memory_values),
|
||
'min': min(memory_values),
|
||
'max': max(memory_values),
|
||
'current': memory_values[-1] if memory_values else 0
|
||
},
|
||
'actor_stats': {
|
||
'current_actor_count': self.profile_data[-1].actor_count if self.profile_data else 0,
|
||
'current_active_animations': self.profile_data[-1].active_animations if self.profile_data else 0
|
||
}
|
||
}
|
||
|
||
def _calculate_std_dev(self, values: List[float]) -> float:
|
||
"""
|
||
计算标准差
|
||
:param values: 数值列表
|
||
:return: 标准差
|
||
"""
|
||
if len(values) < 2:
|
||
return 0.0
|
||
|
||
mean = sum(values) / len(values)
|
||
variance = sum((x - mean) ** 2 for x in values) / (len(values) - 1)
|
||
return math.sqrt(variance)
|
||
|
||
def get_optimization_recommendations(self) -> List[str]:
|
||
"""
|
||
获取优化建议
|
||
:return: 优化建议列表
|
||
"""
|
||
if not self.profile_data:
|
||
return []
|
||
|
||
latest = self.profile_data[-1]
|
||
recommendations = []
|
||
|
||
# FPS相关建议
|
||
if latest.fps < 30:
|
||
recommendations.append("FPS过低,建议优化动画系统或减少同时播放的动画数量")
|
||
|
||
# 内存相关建议
|
||
if latest.memory_usage > 512 * 1024 * 1024: # 超过512MB
|
||
recommendations.append("内存使用过高,建议检查Actor加载和卸载逻辑")
|
||
|
||
# Actor数量建议
|
||
if latest.actor_count > 100:
|
||
recommendations.append("Actor数量较多,建议使用LOD或视锥剔除优化")
|
||
|
||
# 动画数量建议
|
||
if latest.active_animations > 200:
|
||
recommendations.append("同时播放的动画过多,建议限制同时活动的动画数量")
|
||
|
||
return recommendations
|
||
|
||
# 使用示例和测试代码
|
||
def example_analyzer_usage(core_system):
|
||
"""
|
||
动画分析器使用示例
|
||
"""
|
||
print("=== 动画分析器使用示例 ===")
|
||
|
||
# 创建分析器
|
||
analyzer = AnimationAnalyzer(core_system)
|
||
analyzer.start_analysis()
|
||
|
||
# 分析示例(假设有Actor)
|
||
# 在实际使用中,这里会分析真实的Actor和动画
|
||
print("动画分析器示例完成")
|
||
return analyzer
|
||
|
||
def example_editor_usage(core_system):
|
||
"""
|
||
动画编辑器使用示例
|
||
"""
|
||
print("=== 动画编辑器使用示例 ===")
|
||
|
||
# 创建编辑器
|
||
editor = AnimationEditor(core_system)
|
||
|
||
# 添加编辑回调
|
||
def edit_callback(event_type, data):
|
||
print(f"编辑事件: {event_type}, 数据: {data}")
|
||
|
||
editor.add_edit_callback(edit_callback)
|
||
|
||
print("动画编辑器示例完成")
|
||
return editor
|
||
|
||
def example_visualizer_usage(world):
|
||
"""
|
||
调试可视化工具使用示例
|
||
"""
|
||
print("=== 调试可视化工具使用示例 ===")
|
||
|
||
# 创建可视化工具
|
||
visualizer = DebugVisualizer(world)
|
||
visualizer.enable_visualization(True)
|
||
visualizer.set_mode(DebugVisualizationMode.SKELETON)
|
||
|
||
print("调试可视化工具示例完成")
|
||
return visualizer
|
||
|
||
def example_profiler_usage(core_system):
|
||
"""
|
||
动画性能分析器使用示例
|
||
"""
|
||
print("=== 动画性能分析器使用示例 ===")
|
||
|
||
# 创建分析器
|
||
profiler = AnimationProfiler(core_system)
|
||
profiler.start_profiling()
|
||
|
||
# 采样性能数据
|
||
profiler.sample_performance()
|
||
|
||
# 获取性能报告
|
||
report = profiler.get_performance_report()
|
||
print(f"性能报告: {report}")
|
||
|
||
# 获取优化建议
|
||
recommendations = profiler.get_optimization_recommendations()
|
||
print(f"优化建议: {recommendations}")
|
||
|
||
print("动画性能分析器示例完成")
|
||
return profiler
|
||
|
||
if __name__ == "__main__":
|
||
print("动画工具模块加载完成") |