EG/plugins/user/morphing_animation/tools.py
2025-12-12 16:16:15 +08:00

1150 lines
42 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.

"""
Morphing和变形动画插件 - 工具模块
提供开发和调试工具,包括分析器、编辑器、可视化工具等
"""
import json
import time
import math
from typing import Dict, List, Tuple, Optional, Any, Callable
from dataclasses import dataclass, field
from collections import defaultdict, deque
from panda3d.core import *
from direct.showbase.DirectObject import DirectObject
# 导入核心模块
from .core import MorphingCore
from .advanced import AnimationCurveEditor
from .optimization import AnimationCompressor
# 定义常量
TOOLS_SETTINGS = {
'analyzer_sample_count': 100,
'profiler_sample_rate': 60,
'visualizer_update_rate': 30
}
@dataclass
class PerformanceSample:
"""性能采样数据"""
timestamp: float
fps: float
frame_time: float
memory_usage: float
morph_targets_active: int
vertices_processed: int
@dataclass
class AnalysisResult:
"""分析结果数据"""
name: str
value: Any
severity: str # 'info', 'warning', 'error'
recommendation: Optional[str] = None
class AnimationAnalyzer(DirectObject):
"""
动画分析器
分析动画性能、质量和内存使用情况
"""
def __init__(self, morphing_core: MorphingCore):
"""
初始化动画分析器
:param morphing_core: Morphing核心系统
"""
super().__init__()
self.morphing_core = morphing_core
self.analysis_history: List[AnalysisResult] = []
self.is_analyzing = False
print("动画分析器初始化完成")
def analyze_performance(self, duration: float = 5.0) -> List[AnalysisResult]:
"""
分析动画性能
:param duration: 分析持续时间(秒)
:return: 分析结果列表
"""
results = []
# 获取系统统计信息
stats = self.morphing_core.get_system_stats()
# 检查模型数量
if stats['registered_models'] > 50:
results.append(AnalysisResult(
name="模型数量",
value=stats['registered_models'],
severity="warning",
recommendation="模型数量较多考虑使用LOD或减少同时变形的模型数量"
))
else:
results.append(AnalysisResult(
name="模型数量",
value=stats['registered_models'],
severity="info"
))
# 检查Morph目标数量
if stats['total_morph_targets'] > 500:
results.append(AnalysisResult(
name="Morph目标总数",
value=stats['total_morph_targets'],
severity="warning",
recommendation="Morph目标数量较多考虑合并相似目标或使用动画压缩"
))
else:
results.append(AnalysisResult(
name="Morph目标总数",
value=stats['total_morph_targets'],
severity="info"
))
# 检查动画数量
if stats['total_animations'] > 200:
results.append(AnalysisResult(
name="动画总数",
value=stats['total_animations'],
severity="warning",
recommendation="动画数量较多,考虑优化动画管理策略"
))
else:
results.append(AnalysisResult(
name="动画总数",
value=stats['total_animations'],
severity="info"
))
# 检查缓存命中率
cache_stats = stats['cache_stats']
if cache_stats['hit_rate'] < 0.7:
results.append(AnalysisResult(
name="缓存命中率",
value=f"{cache_stats['hit_rate']:.2%}",
severity="warning",
recommendation="缓存命中率较低,考虑调整缓存策略或增加缓存大小"
))
else:
results.append(AnalysisResult(
name="缓存命中率",
value=f"{cache_stats['hit_rate']:.2%}",
severity="info"
))
self.analysis_history.extend(results)
return results
def analyze_quality(self) -> List[AnalysisResult]:
"""
分析动画质量
:return: 分析结果列表
"""
results = []
# 检查每个模型的Morph目标
for model_id, targets in self.morphing_core.morph_targets.items():
model_info = self.morphing_core.get_model_info(model_id)
if not model_info:
continue
vertex_count = model_info['vertex_count']
# 检查Morph目标密度
target_count = len(targets)
if target_count > 0:
density = target_count / vertex_count
if density > 0.1: # 每10个顶点超过1个Morph目标
results.append(AnalysisResult(
name=f"模型 {model_id} Morph目标密度",
value=f"{density:.2f}",
severity="warning",
recommendation="Morph目标密度过高可能影响性能"
))
# 检查Morph目标完整性
for target_name, target in targets.items():
target_info = self.morphing_core.get_morph_target_info(model_id, target_name)
if target_info:
if not target_info['has_normals']:
results.append(AnalysisResult(
name=f"Morph目标 {target_name} 法线信息",
value="缺失",
severity="info",
recommendation="建议添加法线信息以获得更好的光照效果"
))
if not target_info['has_tangents']:
results.append(AnalysisResult(
name=f"Morph目标 {target_name} 切线信息",
value="缺失",
severity="info",
recommendation="建议添加切线信息以支持法线贴图"
))
self.analysis_history.extend(results)
return results
def analyze_memory(self) -> List[AnalysisResult]:
"""
分析内存使用情况
:return: 分析结果列表
"""
results = []
total_memory = 0
model_count = len(self.morphing_core.model_data)
# 估算每个模型的内存使用
for model_id, model_data in self.morphing_core.model_data.items():
# 估算顶点数据内存
vertex_count = sum(len(vertices) for vertices in model_data['original_vertices'])
# 位置数据 (3 floats * 4 bytes) + 法线数据 + 切线数据
vertex_memory = vertex_count * 3 * 4 # 位置
if model_data['original_normals']:
vertex_memory += vertex_count * 3 * 4 # 法线
if model_data['original_tangents']:
vertex_memory += vertex_count * 3 * 4 # 切线
# 估算Morph目标内存
morph_memory = 0
if model_id in self.morphing_core.morph_targets:
for target in self.morphing_core.morph_targets[model_id].values():
morph_memory += len(target.vertices) * 3 * 4 # 顶点偏移
if target.normals:
morph_memory += len(target.normals) * 3 * 4 # 法线偏移
if target.tangents:
morph_memory += len(target.tangents) * 3 * 4 # 切线偏移
model_memory = vertex_memory + morph_memory
total_memory += model_memory
results.append(AnalysisResult(
name=f"模型 {model_id} 内存使用",
value=f"{model_memory / 1024 / 1024:.2f} MB",
severity="info"
))
results.append(AnalysisResult(
name="总内存使用",
value=f"{total_memory / 1024 / 1024:.2f} MB",
severity="info"
))
# 根据内存使用给出建议
if total_memory > 500 * 1024 * 1024: # 500MB
results.append(AnalysisResult(
name="内存使用警告",
value="",
severity="warning",
recommendation="内存使用较高,考虑使用动画压缩或减少模型复杂度"
))
self.analysis_history.extend(results)
return results
def analyze_compression_potential(self, compressor: AnimationCompressor) -> List[AnalysisResult]:
"""
分析压缩潜力
:param compressor: 动画压缩器
:return: 分析结果列表
"""
results = []
total_savings = 0
compressible_targets = 0
# 分析每个Morph目标的压缩潜力
for model_id, targets in self.morphing_core.morph_targets.items():
for target_name, target in targets.items():
# 创建测试数据
test_data = target.vertices
# 测试不同压缩方法
for method_name, method in [
("关键帧简化", compressor._compress_keyframe_reduction),
("量化", compressor._compress_quantization),
("差分", compressor._compress_delta)
]:
try:
compressed = method(test_data)
original_size = len(test_data) * 3 * 4
compressed_size = len(str(compressed))
savings = original_size - compressed_size
ratio = original_size / max(1, compressed_size)
if savings > 1024: # 节省超过1KB才值得压缩
compressible_targets += 1
total_savings += savings
results.append(AnalysisResult(
name=f"Morph目标 {target_name} {method_name}压缩潜力",
value=f"节省 {savings / 1024:.1f} KB (比率: {ratio:.2f})",
severity="info"
))
except Exception as e:
results.append(AnalysisResult(
name=f"Morph目标 {target_name} {method_name}压缩错误",
value=str(e),
severity="error"
))
if compressible_targets > 0:
results.append(AnalysisResult(
name="总压缩潜力",
value=f"可压缩目标: {compressible_targets}, 预计节省: {total_savings / 1024 / 1024:.2f} MB",
severity="info",
recommendation="建议启用动画压缩以节省内存"
))
else:
results.append(AnalysisResult(
name="压缩潜力",
value="较低",
severity="info"
))
self.analysis_history.extend(results)
return results
def get_analysis_report(self) -> Dict[str, Any]:
"""
获取完整分析报告
:return: 分析报告字典
"""
report = {
'timestamp': time.time(),
'performance': self.analyze_performance(),
'quality': self.analyze_quality(),
'memory': self.analyze_memory(),
'history': [vars(result) for result in self.analysis_history[-50:]] # 最近50条记录
}
return report
def clear_analysis_history(self) -> None:
"""
清除分析历史
"""
self.analysis_history.clear()
print("分析历史已清除")
class AnimationEditor(DirectObject):
"""
动画编辑器
提供Morph目标和动画的编辑功能
"""
def __init__(self, morphing_core: MorphingCore, curve_editor: AnimationCurveEditor):
"""
初始化动画编辑器
:param morphing_core: Morphing核心系统
:param curve_editor: 动画曲线编辑器
"""
super().__init__()
self.morphing_core = morphing_core
self.curve_editor = curve_editor
self.edit_mode = "view" # "view", "keyframe", "curve", "vertex"
self.selected_model = None
self.selected_target = None
self.selected_vertex = None
self.edit_callbacks: List[Callable] = []
print("动画编辑器初始化完成")
def set_edit_mode(self, mode: str) -> bool:
"""
设置编辑模式
:param mode: 编辑模式 ("view", "keyframe", "curve", "vertex")
:return: 是否成功设置
"""
valid_modes = ["view", "keyframe", "curve", "vertex"]
if mode not in valid_modes:
print(f"错误: 无效的编辑模式 {mode},有效模式: {valid_modes}")
return False
self.edit_mode = mode
print(f"编辑模式设置为: {mode}")
return True
def select_model(self, model_id: str) -> bool:
"""
选择模型
:param model_id: 模型ID
:return: 是否成功选择
"""
if model_id not in self.morphing_core.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
self.selected_model = model_id
print(f"选择模型: {model_id}")
self._trigger_edit_callbacks("model_selected", model_id)
return True
def select_morph_target(self, target_name: str) -> bool:
"""
选择Morph目标
:param target_name: Morph目标名称
:return: 是否成功选择
"""
if not self.selected_model:
print("错误: 请先选择模型")
return False
if (self.selected_model not in self.morphing_core.morph_targets or
target_name not in self.morphing_core.morph_targets[self.selected_model]):
print(f"错误: Morph目标 {target_name} 不存在于模型 {self.selected_model}")
return False
self.selected_target = target_name
print(f"选择Morph目标: {target_name}")
self._trigger_edit_callbacks("target_selected", target_name)
return True
def select_vertex(self, vertex_index: int) -> bool:
"""
选择顶点
:param vertex_index: 顶点索引
:return: 是否成功选择
"""
if not self.selected_model:
print("错误: 请先选择模型")
return False
model_data = self.morphing_core.model_data[self.selected_model]
total_vertices = sum(len(vertices) for vertices in model_data['original_vertices'])
if vertex_index >= total_vertices:
print(f"错误: 顶点索引 {vertex_index} 超出范围 (0-{total_vertices-1})")
return False
self.selected_vertex = vertex_index
print(f"选择顶点: {vertex_index}")
self._trigger_edit_callbacks("vertex_selected", vertex_index)
return True
def create_morph_target(self, target_name: str,
vertex_offsets: List[Tuple[float, float, float]],
normal_offsets: Optional[List[Tuple[float, float, float]]] = None,
tangent_offsets: Optional[List[Tuple[float, float, float]]] = None) -> bool:
"""
创建Morph目标
:param target_name: 目标名称
:param vertex_offsets: 顶点偏移列表
:param normal_offsets: 法线偏移列表(可选)
:param tangent_offsets: 切线偏移列表(可选)
:return: 是否成功创建
"""
if not self.selected_model:
print("错误: 请先选择模型")
return False
success = self.morphing_core.create_morph_target(
self.selected_model, target_name, vertex_offsets, normal_offsets, tangent_offsets
)
if success:
print(f"Morph目标创建成功: {target_name}")
self._trigger_edit_callbacks("target_created", target_name)
else:
print(f"Morph目标创建失败: {target_name}")
return success
def modify_morph_target(self, target_name: str, vertex_index: int,
new_offset: Tuple[float, float, float]) -> bool:
"""
修改Morph目标
:param target_name: 目标名称
:param vertex_index: 顶点索引
:param new_offset: 新的偏移值
:return: 是否成功修改
"""
if not self.selected_model:
print("错误: 请先选择模型")
return False
if (self.selected_model not in self.morphing_core.morph_targets or
target_name not in self.morphing_core.morph_targets[self.selected_model]):
print(f"错误: Morph目标 {target_name} 不存在")
return False
# 获取Morph目标
morph_target = self.morphing_core.morph_targets[self.selected_model][target_name]
if vertex_index >= len(morph_target.vertices):
print(f"错误: 顶点索引 {vertex_index} 超出范围")
return False
# 修改偏移值
morph_target.vertices[vertex_index] = new_offset
# 使相关缓存失效
if self.selected_model in self.morphing_core.cache.cache:
cache_key = f"{self.selected_model}_{target_name}"
self.morphing_core.cache.invalidate(cache_key)
print(f"修改Morph目标 {target_name} 的顶点 {vertex_index}")
self._trigger_edit_callbacks("target_modified", {
'target_name': target_name,
'vertex_index': vertex_index,
'new_offset': new_offset
})
return True
def delete_morph_target(self, target_name: str) -> bool:
"""
删除Morph目标
:param target_name: 目标名称
:return: 是否成功删除
"""
if not self.selected_model:
print("错误: 请先选择模型")
return False
success = self.morphing_core.remove_morph_target(self.selected_model, target_name)
if success:
print(f"Morph目标删除成功: {target_name}")
self._trigger_edit_callbacks("target_deleted", target_name)
else:
print(f"Morph目标删除失败: {target_name}")
return success
def create_animation_curve(self, curve_name: str, control_points: List[Tuple[float, float]]) -> bool:
"""
创建动画曲线
:param curve_name: 曲线名称
:param control_points: 控制点列表
:return: 是否成功创建
"""
from .advanced import CurveType
from .core import InterpolationMode
success = self.curve_editor.create_curve(
curve_name, CurveType.BEZIER, control_points, InterpolationMode.LINEAR
)
if success:
print(f"动画曲线创建成功: {curve_name}")
self._trigger_edit_callbacks("curve_created", curve_name)
else:
print(f"动画曲线创建失败: {curve_name}")
return success
def modify_animation_curve(self, curve_name: str, control_points: List[Tuple[float, float]]) -> bool:
"""
修改动画曲线
:param curve_name: 曲线名称
:param control_points: 新的控制点列表
:return: 是否成功修改
"""
# 删除旧曲线
self.curve_editor.remove_curve(curve_name)
# 创建新曲线
return self.create_animation_curve(curve_name, control_points)
def delete_animation_curve(self, curve_name: str) -> bool:
"""
删除动画曲线
:param curve_name: 曲线名称
:return: 是否成功删除
"""
success = self.curve_editor.remove_curve(curve_name)
if success:
print(f"动画曲线删除成功: {curve_name}")
self._trigger_edit_callbacks("curve_deleted", curve_name)
else:
print(f"动画曲线删除失败: {curve_name}")
return success
def add_edit_callback(self, callback: Callable) -> None:
"""
添加编辑回调函数
:param callback: 回调函数
"""
self.edit_callbacks.append(callback)
def remove_edit_callback(self, callback: Callable) -> None:
"""
移除编辑回调函数
:param callback: 回调函数
"""
if callback in self.edit_callbacks:
self.edit_callbacks.remove(callback)
def _trigger_edit_callbacks(self, event_type: str, data: Any) -> None:
"""
触发编辑回调函数
:param event_type: 事件类型
:param data: 事件数据
"""
for callback in self.edit_callbacks:
try:
callback(event_type, data)
except Exception as e:
print(f"编辑回调函数执行出错: {e}")
def get_editor_state(self) -> Dict[str, Any]:
"""
获取编辑器状态
:return: 状态字典
"""
return {
'edit_mode': self.edit_mode,
'selected_model': self.selected_model,
'selected_target': self.selected_target,
'selected_vertex': self.selected_vertex,
'available_models': list(self.morphing_core.model_data.keys()),
'available_targets': list(self.morphing_core.morph_targets.get(self.selected_model, {}).keys()) if self.selected_model else []
}
class DebugVisualizer(DirectObject):
"""
调试可视化工具
提供骨骼、边界框等可视化功能
"""
def __init__(self, world, morphing_core: MorphingCore):
"""
初始化调试可视化工具
:param world: 世界对象
:param morphing_core: Morphing核心系统
"""
super().__init__()
self.world = world
self.morphing_core = morphing_core
self.visualizations: Dict[str, NodePath] = {}
self.is_visualizing = False
self.update_rate = TOOLS_SETTINGS['visualizer_update_rate']
self.last_update_time = 0.0
print("调试可视化工具初始化完成")
def visualize_model_skeleton(self, model_id: str, show_vertices: bool = True,
show_normals: bool = False) -> bool:
"""
可视化模型骨架
:param model_id: 模型ID
:param show_vertices: 是否显示顶点
:param show_normals: 是否显示法线
:return: 是否成功可视化
"""
if model_id not in self.morphing_core.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
model_data = self.morphing_core.model_data[model_id]
model_node = model_data['node_path']
# 创建可视化节点
vis_node_name = f"skeleton_vis_{model_id}"
if vis_node_name in self.visualizations:
self.visualizations[vis_node_name].removeNode()
vis_node = self.world.render.attachNewNode(vis_node_name)
self.visualizations[vis_node_name] = vis_node
# 可视化顶点
if show_vertices:
self._visualize_vertices(model_id, vis_node)
# 可视化法线
if show_normals:
self._visualize_normals(model_id, vis_node)
# 可视化Morph目标影响
self._visualize_morph_influences(model_id, vis_node)
print(f"模型骨架可视化完成: {model_id}")
return True
def _visualize_vertices(self, model_id: str, parent_node: NodePath) -> None:
"""
可视化顶点
:param model_id: 模型ID
:param parent_node: 父节点
"""
model_data = self.morphing_core.model_data[model_id]
# 创建顶点可视化点
vertex_points = PointNode("vertex_points")
vertex_points.setColor(1, 0, 0, 1) # 红色
vertex_points.setPointSize(3)
vertex_geom = GeomVertexWriter(parent_node.node().getGeom(0).getVertexData(), 'vertex')
# 注意:这里简化处理,实际项目中需要更复杂的实现
def _visualize_normals(self, model_id: str, parent_node: NodePath) -> None:
"""
可视化法线
:param model_id: 模型ID
:param parent_node: 父节点
"""
# 法线可视化实现
pass # 简化处理
def _visualize_morph_influences(self, model_id: str, parent_node: NodePath) -> None:
"""
可视化Morph影响
:param model_id: 模型ID
:param parent_node: 父节点
"""
# Morph影响可视化实现
pass # 简化处理
def visualize_bounding_boxes(self, model_ids: Optional[List[str]] = None) -> bool:
"""
可视化边界框
:param model_ids: 模型ID列表如果为None则可视化所有模型
:return: 是否成功可视化
"""
if model_ids is None:
model_ids = list(self.morphing_core.model_data.keys())
for model_id in model_ids:
if model_id not in self.morphing_core.model_data:
print(f"警告: 模型 {model_id} 未注册,跳过")
continue
model_data = self.morphing_core.model_data[model_id]
model_node = model_data['node_path']
# 创建边界框可视化
bbox_node_name = f"bbox_vis_{model_id}"
if bbox_node_name in self.visualizations:
self.visualizations[bbox_node_name].removeNode()
# 获取模型边界框
bounds = model_node.getBounds()
if not bounds.isEmpty():
# 创建线框立方体表示边界框
bbox_node = self.world.render.attachNewNode(bbox_node_name)
bbox_node.setColor(0, 1, 0, 1) # 绿色
# 创建边界框几何体(简化实现)
# 实际项目中需要根据bounds创建对应的线框几何体
self.visualizations[bbox_node_name] = bbox_node
print(f"边界框可视化完成: {model_id}")
return True
def visualize_morph_weights(self, model_id: str) -> bool:
"""
可视化Morph权重
:param model_id: 模型ID
:return: 是否成功可视化
"""
if model_id not in self.morphing_core.morph_targets:
print(f"错误: 模型 {model_id} 没有Morph目标")
return False
# 创建权重可视化节点
weight_node_name = f"weights_vis_{model_id}"
if weight_node_name in self.visualizations:
self.visualizations[weight_node_name].removeNode()
weight_node = self.world.render.attachNewNode(weight_node_name)
self.visualizations[weight_node_name] = weight_node
# 可视化每个Morph目标的权重简化实现
targets = self.morphing_core.morph_targets[model_id]
for i, (target_name, target) in enumerate(targets.items()):
# 计算平均权重
if target.weights:
avg_weight = sum(target.weights) / len(target.weights)
# 创建文本显示权重(简化实现)
# 实际项目中需要使用Panda3D的文本系统
print(f"Morph权重可视化完成: {model_id}")
return True
def set_visualization_mode(self, mode: str, enabled: bool = True) -> None:
"""
设置可视化模式
:param mode: 可视化模式 ('skeleton', 'bounds', 'normals', 'wireframe', 'collision')
:param enabled: 是否启用
"""
valid_modes = ['skeleton', 'bounds', 'normals', 'wireframe', 'collision']
if mode not in valid_modes:
print(f"错误: 无效的可视化模式 {mode},有效模式: {valid_modes}")
return
# 根据模式启用/禁用相应的可视化
if mode == 'wireframe':
if enabled:
self.world.render.setRenderModeWireframe()
else:
self.world.render.setRenderModeFilled()
else:
# 其他模式的处理
print(f"可视化模式 {mode} {'启用' if enabled else '禁用'}")
def hide_all_visualizations(self) -> None:
"""
隐藏所有可视化
"""
for node in self.visualizations.values():
node.hide()
print("所有可视化已隐藏")
def show_all_visualizations(self) -> None:
"""
显示所有可视化
"""
for node in self.visualizations.values():
node.show()
print("所有可视化已显示")
def clear_visualizations(self) -> None:
"""
清除所有可视化
"""
for node in self.visualizations.values():
node.removeNode()
self.visualizations.clear()
print("所有可视化已清除")
def update(self, dt: float) -> None:
"""
更新可视化
:param dt: 时间增量
"""
if not self.is_visualizing:
return
current_time = globalClock.getRealTime()
if current_time - self.last_update_time < 1.0 / self.update_rate:
return
self.last_update_time = current_time
# 更新动态可视化内容
# 例如:实时更新权重显示、动画状态等
pass
class AnimationProfiler(DirectObject):
"""
动画性能分析器
实时监控和分析动画性能
"""
def __init__(self, world, morphing_core: MorphingCore):
"""
初始化动画性能分析器
:param world: 世界对象
:param morphing_core: Morphing核心系统
"""
super().__init__()
self.world = world
self.morphing_core = morphing_core
self.sample_rate = TOOLS_SETTINGS['profiler_sample_rate']
self.sample_buffer = deque(maxlen=TOOLS_SETTINGS['analyzer_sample_count'])
self.is_profiling = False
self.last_sample_time = 0.0
self.profiling_stats = {
'total_samples': 0,
'avg_fps': 0.0,
'min_fps': float('inf'),
'max_fps': 0.0,
'avg_frame_time': 0.0,
'min_frame_time': float('inf'),
'max_frame_time': 0.0
}
print("动画性能分析器初始化完成")
def start_profiling(self) -> None:
"""
开始性能分析
"""
if self.is_profiling:
return
self.is_profiling = True
self.sample_buffer.clear()
self.profiling_stats = {
'total_samples': 0,
'avg_fps': 0.0,
'min_fps': float('inf'),
'max_fps': 0.0,
'avg_frame_time': 0.0,
'min_frame_time': float('inf'),
'max_frame_time': 0.0
}
print("动画性能分析已启动")
def stop_profiling(self) -> None:
"""
停止性能分析
"""
if not self.is_profiling:
return
self.is_profiling = False
print("动画性能分析已停止")
def sample_performance(self, dt: float) -> None:
"""
采样性能数据
:param dt: 时间增量
"""
if not self.is_profiling:
return
current_time = globalClock.getRealTime()
if current_time - self.last_sample_time < 1.0 / self.sample_rate:
return
self.last_sample_time = current_time
# 获取性能数据
fps = 1.0 / max(0.001, dt)
frame_time = dt * 1000 # 转换为毫秒
# 获取内存使用(简化实现)
memory_usage = 0.0 # 实际项目中需要获取真实内存使用
# 获取活跃的Morph目标数量
active_targets = 0
for model_targets in self.morphing_core.morph_targets.values():
active_targets += len([t for t in model_targets.values() if t.active])
# 获取处理的顶点数量
vertices_processed = 0
for model_data in self.morphing_core.model_data.values():
vertices_processed += sum(len(vertices) for vertices in model_data['original_vertices'])
# 创建采样数据
sample = PerformanceSample(
timestamp=current_time,
fps=fps,
frame_time=frame_time,
memory_usage=memory_usage,
morph_targets_active=active_targets,
vertices_processed=vertices_processed
)
# 添加到缓冲区
self.sample_buffer.append(sample)
# 更新统计信息
self.profiling_stats['total_samples'] += 1
self.profiling_stats['avg_fps'] = (
(self.profiling_stats['avg_fps'] * (self.profiling_stats['total_samples'] - 1) + fps)
/ self.profiling_stats['total_samples']
)
self.profiling_stats['min_fps'] = min(self.profiling_stats['min_fps'], fps)
self.profiling_stats['max_fps'] = max(self.profiling_stats['max_fps'], fps)
self.profiling_stats['avg_frame_time'] = (
(self.profiling_stats['avg_frame_time'] * (self.profiling_stats['total_samples'] - 1) + frame_time)
/ self.profiling_stats['total_samples']
)
self.profiling_stats['min_frame_time'] = min(self.profiling_stats['min_frame_time'], frame_time)
self.profiling_stats['max_frame_time'] = max(self.profiling_stats['max_frame_time'], frame_time)
def get_current_stats(self) -> Dict[str, Any]:
"""
获取当前性能统计
:return: 统计信息字典
"""
return {
'current_fps': 1.0 / max(0.001, globalClock.getDt()),
'current_frame_time': globalClock.getDt() * 1000,
'samples_collected': len(self.sample_buffer),
'profiling_active': self.is_profiling
}
def get_profiling_report(self) -> Dict[str, Any]:
"""
获取性能分析报告
:return: 分析报告字典
"""
if not self.sample_buffer:
return {
'error': '没有采样数据',
'stats': self.profiling_stats
}
# 计算详细统计信息
fps_values = [s.fps for s in self.sample_buffer]
frame_time_values = [s.frame_time for s in self.sample_buffer]
# 计算标准差
fps_variance = sum((x - self.profiling_stats['avg_fps']) ** 2 for x in fps_values) / len(fps_values)
fps_std_dev = math.sqrt(fps_variance)
frame_time_variance = sum((x - self.profiling_stats['avg_frame_time']) ** 2 for x in frame_time_values) / len(frame_time_values)
frame_time_std_dev = math.sqrt(frame_time_variance)
report = {
'period': {
'start': self.sample_buffer[0].timestamp if self.sample_buffer else 0,
'end': self.sample_buffer[-1].timestamp if self.sample_buffer else 0,
'duration': (self.sample_buffer[-1].timestamp - self.sample_buffer[0].timestamp) if len(self.sample_buffer) > 1 else 0
},
'fps': {
'average': self.profiling_stats['avg_fps'],
'minimum': self.profiling_stats['min_fps'],
'maximum': self.profiling_stats['max_fps'],
'std_deviation': fps_std_dev
},
'frame_time': {
'average': self.profiling_stats['avg_frame_time'],
'minimum': self.profiling_stats['min_frame_time'],
'maximum': self.profiling_stats['max_frame_time'],
'std_deviation': frame_time_std_dev
},
'morphing_stats': {
'average_active_targets': sum(s.morph_targets_active for s in self.sample_buffer) / len(self.sample_buffer),
'average_vertices_processed': sum(s.vertices_processed for s in self.sample_buffer) / len(self.sample_buffer)
},
'samples': len(self.sample_buffer),
'recommendations': self._generate_recommendations()
}
return report
def _generate_recommendations(self) -> List[str]:
"""
生成性能优化建议
:return: 建议列表
"""
recommendations = []
# FPS相关建议
if self.profiling_stats['avg_fps'] < 30:
recommendations.append("平均FPS低于30建议优化模型复杂度或启用LOD")
elif self.profiling_stats['avg_fps'] < 60:
recommendations.append("平均FPS低于60可以考虑进一步优化")
# FPS稳定性建议
fps_values = [s.fps for s in self.sample_buffer]
if fps_values:
fps_variance = sum((x - self.profiling_stats['avg_fps']) ** 2 for x in fps_values) / len(fps_values)
fps_std_dev = math.sqrt(fps_variance)
if fps_std_dev > 10: # FPS波动较大
recommendations.append("FPS波动较大检查是否有性能瓶颈")
# Morph目标相关建议
avg_targets = sum(s.morph_targets_active for s in self.sample_buffer) / len(self.sample_buffer)
if avg_targets > 100:
recommendations.append(f"平均活跃Morph目标数较高({avg_targets:.1f}),考虑减少同时活跃的目标数")
return recommendations
def reset_profiling(self) -> None:
"""
重置性能分析
"""
self.sample_buffer.clear()
self.profiling_stats = {
'total_samples': 0,
'avg_fps': 0.0,
'min_fps': float('inf'),
'max_fps': 0.0,
'avg_frame_time': 0.0,
'min_frame_time': float('inf'),
'max_frame_time': 0.0
}
print("性能分析已重置")
# 使用示例和测试代码
def example_analyzer_usage(morphing_core):
"""
动画分析器使用示例
"""
print("=== 动画分析器使用示例 ===")
# 创建分析器
analyzer = AnimationAnalyzer(morphing_core)
# 执行各种分析
performance_results = analyzer.analyze_performance()
print(f"性能分析结果数量: {len(performance_results)}")
quality_results = analyzer.analyze_quality()
print(f"质量分析结果数量: {len(quality_results)}")
memory_results = analyzer.analyze_memory()
print(f"内存分析结果数量: {len(memory_results)}")
# 获取完整报告
report = analyzer.get_analysis_report()
print(f"分析报告生成完成,包含 {len(report['history'])} 条历史记录")
print("=== 动画分析器示例结束 ===\n")
def example_editor_usage(morphing_core, curve_editor):
"""
动画编辑器使用示例
"""
print("=== 动画编辑器使用示例 ===")
# 创建编辑器
editor = AnimationEditor(morphing_core, curve_editor)
# 设置编辑模式
editor.set_edit_mode("keyframe")
# 添加编辑回调
def edit_callback(event_type, data):
print(f"编辑事件: {event_type}, 数据: {data}")
editor.add_edit_callback(edit_callback)
# 获取编辑器状态
state = editor.get_editor_state()
print(f"编辑器状态: {state}")
print("=== 动画编辑器示例结束 ===\n")
def example_visualizer_usage(world, morphing_core):
"""
调试可视化工具使用示例
"""
print("=== 调试可视化工具使用示例 ===")
# 创建可视化工具
visualizer = DebugVisualizer(world, morphing_core)
# 设置可视化模式
visualizer.set_visualization_mode("wireframe", True)
print("=== 调试可视化工具示例结束 ===\n")
def example_profiler_usage(world, morphing_core):
"""
动画性能分析器使用示例
"""
print("=== 动画性能分析器使用示例 ===")
# 创建性能分析器
profiler = AnimationProfiler(world, morphing_core)
# 开始分析
profiler.start_profiling()
# 采样性能数据
profiler.sample_performance(globalClock.getDt())
# 获取当前统计
stats = profiler.get_current_stats()
print(f"当前统计: {stats}")
# 停止分析
profiler.stop_profiling()
print("=== 动画性能分析器示例结束 ===\n")
if __name__ == "__main__":
print("Morphing动画工具模块加载完成")