1150 lines
42 KiB
Python
1150 lines
42 KiB
Python
"""
|
||
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动画工具模块加载完成") |