EG/plugins/user/navigation_mesh/tools/navmesh_analyzer.py
2025-10-30 11:46:41 +08:00

492 lines
17 KiB
Python

"""
导航网格分析工具
提供导航网格质量分析、优化建议和性能评估功能
"""
import math
import time
from typing import List, Dict, Tuple, Optional, Set
from collections import defaultdict, deque
import json
from panda3d.core import Point3
class NavMeshAnalyzer:
"""
导航网格分析器
提供导航网格质量分析、优化建议和性能评估功能
"""
def __init__(self, navmesh_manager):
"""
初始化导航网格分析器
Args:
navmesh_manager: 导航网格管理器实例
"""
self.navmesh_manager = navmesh_manager
self.analysis_results = {}
self.optimization_suggestions = []
def analyze_mesh_quality(self) -> Dict:
"""
分析导航网格质量
Returns:
质量分析结果
"""
if not self.navmesh_manager or not self.navmesh_manager.polygons:
return {}
results = {
'polygon_count': len(self.navmesh_manager.polygons),
'vertex_count': 0,
'edge_count': 0,
'isolated_polygons': 0,
'overlapping_polygons': 0,
'small_polygons': 0,
'irregular_polygons': 0,
'connectivity_issues': 0,
'portal_issues': 0,
'area_statistics': {},
'vertex_statistics': {},
'performance_metrics': {}
}
# 统计顶点和边
total_vertices = 0
total_edges = 0
polygon_areas = []
vertex_counts = []
isolated_count = 0
small_polygon_count = 0
irregular_polygon_count = 0
overlapping_count = 0
connectivity_issues = 0
portal_issues = 0
# 分析每个多边形
for polygon_id, polygon in self.navmesh_manager.polygons.items():
vertex_count = len(polygon.vertices)
total_vertices += vertex_count
vertex_counts.append(vertex_count)
# 边数等于顶点数
total_edges += vertex_count
# 面积统计
area = polygon.area
polygon_areas.append(area)
# 检查小多边形
if area < 0.1: # 面积极小阈值
small_polygon_count += 1
# 检查不规则多边形(顶点数过多)
if vertex_count > 8: # 顶点数过多阈值
irregular_polygon_count += 1
# 检查孤立多边形(没有邻居)
if not polygon.neighbors:
isolated_count += 1
# 检查连接性问题
for neighbor_id in polygon.neighbors:
if neighbor_id not in self.navmesh_manager.polygons:
connectivity_issues += 1
# 检查门户点问题
for neighbor_id, portal_points in polygon.portal_points.items():
if neighbor_id not in polygon.neighbors:
portal_issues += 1
# 计算面积统计
if polygon_areas:
results['area_statistics'] = {
'total_area': sum(polygon_areas),
'average_area': sum(polygon_areas) / len(polygon_areas),
'min_area': min(polygon_areas),
'max_area': max(polygon_areas),
'area_variance': self._calculate_variance(polygon_areas)
}
# 计算顶点统计
if vertex_counts:
results['vertex_statistics'] = {
'total_vertices': total_vertices,
'average_vertices': sum(vertex_counts) / len(vertex_counts),
'min_vertices': min(vertex_counts),
'max_vertices': max(vertex_counts),
'vertex_variance': self._calculate_variance(vertex_counts)
}
# 设置结果
results['vertex_count'] = total_vertices
results['edge_count'] = total_edges
results['isolated_polygons'] = isolated_count
results['small_polygons'] = small_polygon_count
results['irregular_polygons'] = irregular_polygon_count
results['overlapping_polygons'] = overlapping_count
results['connectivity_issues'] = connectivity_issues
results['portal_issues'] = portal_issues
self.analysis_results = results
return results
def _calculate_variance(self, values: List[float]) -> float:
"""计算方差"""
if not values:
return 0.0
mean = sum(values) / len(values)
variance = sum((x - mean) ** 2 for x in values) / len(values)
return variance
def suggest_optimizations(self) -> List[Dict]:
"""
提供优化建议
Returns:
优化建议列表
"""
suggestions = []
if not self.analysis_results:
self.analyze_mesh_quality()
results = self.analysis_results
# 检查多边形数量
if results.get('polygon_count', 0) > 10000:
suggestions.append({
'type': 'performance',
'severity': 'high',
'description': '导航网格包含过多的多边形',
'recommendation': '考虑简化网格或使用分层寻路'
})
# 检查孤立多边形
if results.get('isolated_polygons', 0) > 0:
suggestions.append({
'type': 'connectivity',
'severity': 'medium',
'description': '存在孤立的多边形',
'recommendation': '检查并连接孤立的多边形或移除它们'
})
# 检查小多边形
if results.get('small_polygons', 0) > results.get('polygon_count', 0) * 0.1:
suggestions.append({
'type': 'quality',
'severity': 'medium',
'description': '存在过多极小的多边形',
'recommendation': '合并相邻的小多边形或移除它们'
})
# 检查不规则多边形
if results.get('irregular_polygons', 0) > results.get('polygon_count', 0) * 0.2:
suggestions.append({
'type': 'quality',
'severity': 'low',
'description': '存在过多顶点的不规则多边形',
'recommendation': '简化复杂多边形以提高性能'
})
# 检查连接性问题
if results.get('connectivity_issues', 0) > 0:
suggestions.append({
'type': 'correctness',
'severity': 'high',
'description': '存在连接性问题',
'recommendation': '修复多边形间的连接关系'
})
# 检查门户点问题
if results.get('portal_issues', 0) > 0:
suggestions.append({
'type': 'correctness',
'severity': 'high',
'description': '存在门户点问题',
'recommendation': '重新计算多边形间的门户点'
})
self.optimization_suggestions = suggestions
return suggestions
def analyze_pathfinding_performance(self, sample_count: int = 100) -> Dict:
"""
分析寻路性能
Args:
sample_count: 采样次数
Returns:
性能分析结果
"""
if not self.navmesh_manager or not hasattr(self.navmesh_manager, 'world'):
return {}
from .pathfinder import Pathfinder
pathfinder = Pathfinder(self.navmesh_manager)
performance_results = {
'total_time': 0.0,
'average_time': 0.0,
'min_time': float('inf'),
'max_time': 0.0,
'success_count': 0,
'failure_count': 0,
'average_path_length': 0.0,
'path_lengths': []
}
total_length = 0.0
path_lengths = []
# 执行多次寻路测试
for _ in range(sample_count):
start_point = self.navmesh_manager.get_random_point()
end_point = self.navmesh_manager.get_random_point()
if start_point and end_point:
start_time = time.time()
path = pathfinder.find_path(start_point, end_point)
end_time = time.time()
search_time = end_time - start_time
performance_results['total_time'] += search_time
performance_results['min_time'] = min(performance_results['min_time'], search_time)
performance_results['max_time'] = max(performance_results['max_time'], search_time)
if path:
performance_results['success_count'] += 1
path_length = sum((path[i+1] - path[i]).length() for i in range(len(path)-1))
total_length += path_length
path_lengths.append(path_length)
else:
performance_results['failure_count'] += 1
# 计算平均值
if sample_count > 0:
performance_results['average_time'] = performance_results['total_time'] / sample_count
if path_lengths:
performance_results['average_path_length'] = total_length / len(path_lengths)
performance_results['path_lengths'] = path_lengths
return performance_results
def detect_bottlenecks(self) -> List[Dict]:
"""
检测导航网格中的瓶颈区域
Returns:
瓶颈区域列表
"""
bottlenecks = []
if not self.navmesh_manager or not self.navmesh_manager.polygons:
return bottlenecks
# 计算每个多边形的"狭窄度"
for polygon_id, polygon in self.navmesh_manager.polygons.items():
# 狭窄度基于面积和周长的比值
if len(polygon.vertices) >= 3:
perimeter = self._calculate_perimeter(polygon.vertices)
if perimeter > 0:
compactness = polygon.area / (perimeter ** 2)
# 如果紧凑度很低,可能是狭窄区域
if compactness < 0.01: # 阈值需要根据实际情况调整
bottlenecks.append({
'polygon_id': polygon_id,
'position': polygon.center,
'compactness': compactness,
'area': polygon.area,
'perimeter': perimeter
})
# 按紧凑度排序
bottlenecks.sort(key=lambda x: x['compactness'])
return bottlenecks[:10] # 返回最狭窄的10个区域
def _calculate_perimeter(self, vertices: List[Point3]) -> float:
"""计算多边形周长"""
if len(vertices) < 2:
return 0.0
perimeter = 0.0
for i in range(len(vertices)):
p1 = vertices[i]
p2 = vertices[(i + 1) % len(vertices)]
perimeter += (p2 - p1).length()
return perimeter
def analyze_coverage(self) -> Dict:
"""
分析导航网格覆盖情况
Returns:
覆盖情况分析结果
"""
coverage_results = {
'covered_area': 0.0,
'total_area': 0.0,
'coverage_ratio': 0.0,
'uncovered_regions': []
}
if not self.navmesh_manager or not self.navmesh_manager.polygons:
return coverage_results
# 计算总覆盖面积(简单近似)
total_area = 0.0
for polygon in self.navmesh_manager.polygons.values():
total_area += polygon.area
coverage_results['covered_area'] = total_area
coverage_results['total_area'] = total_area # 简化处理
coverage_results['coverage_ratio'] = 1.0 if total_area > 0 else 0.0
return coverage_results
def generate_report(self) -> Dict:
"""
生成完整的分析报告
Returns:
分析报告
"""
report = {
'timestamp': time.time(),
'mesh_quality': self.analyze_mesh_quality(),
'optimization_suggestions': self.suggest_optimizations(),
'coverage_analysis': self.analyze_coverage(),
'bottlenecks': self.detect_bottlenecks()
}
return report
def export_report(self, filepath: str) -> bool:
"""
导出分析报告到文件
Args:
filepath: 导出文件路径
Returns:
是否导出成功
"""
try:
report = self.generate_report()
# 转换不可序列化的对象
serializable_report = self._make_serializable(report)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(serializable_report, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"导出报告失败: {e}")
return False
def _make_serializable(self, obj):
"""将对象转换为可序列化的格式"""
if isinstance(obj, dict):
return {key: self._make_serializable(value) for key, value in obj.items()}
elif isinstance(obj, list):
return [self._make_serializable(item) for item in obj]
elif isinstance(obj, Point3):
return [obj.x, obj.y, obj.z]
elif isinstance(obj, float) and (math.isnan(obj) or math.isinf(obj)):
return str(obj)
else:
return obj
def get_complexity_score(self) -> float:
"""
计算导航网格复杂度评分
Returns:
复杂度评分 (0-100)
"""
if not self.analysis_results:
self.analyze_mesh_quality()
results = self.analysis_results
score = 50.0 # 基础分数
# 根据多边形数量调整
polygon_count = results.get('polygon_count', 0)
if polygon_count > 5000:
score += min(30, (polygon_count - 5000) / 1000)
elif polygon_count < 1000:
score -= min(20, (1000 - polygon_count) / 100)
# 根据问题数量调整
issues = (results.get('isolated_polygons', 0) +
results.get('connectivity_issues', 0) +
results.get('portal_issues', 0))
score += min(20, issues / 10)
# 根据不规则多边形调整
irregular = results.get('irregular_polygons', 0)
score += min(10, irregular / 50)
return max(0, min(100, score))
def recommend_simplification(self) -> Dict:
"""
推荐网格简化策略
Returns:
简化策略建议
"""
if not self.analysis_results:
self.analyze_mesh_quality()
results = self.analysis_results
recommendations = {
'should_simplify': False,
'reasons': [],
'simplification_level': 'none', # none, light, medium, heavy
'target_polygon_count': results.get('polygon_count', 0)
}
polygon_count = results.get('polygon_count', 0)
isolated_count = results.get('isolated_polygons', 0)
small_count = results.get('small_polygons', 0)
irregular_count = results.get('irregular_polygons', 0)
# 检查是否需要简化
if polygon_count > 5000:
recommendations['should_simplify'] = True
recommendations['reasons'].append('多边形数量过多')
if isolated_count > polygon_count * 0.05:
recommendations['should_simplify'] = True
recommendations['reasons'].append('存在大量孤立多边形')
if small_count > polygon_count * 0.1:
recommendations['should_simplify'] = True
recommendations['reasons'].append('存在大量极小多边形')
if irregular_count > polygon_count * 0.2:
recommendations['should_simplify'] = True
recommendations['reasons'].append('存在大量不规则多边形')
# 确定简化级别
if recommendations['should_simplify']:
if polygon_count > 10000:
recommendations['simplification_level'] = 'heavy'
recommendations['target_polygon_count'] = int(polygon_count * 0.5)
elif polygon_count > 5000:
recommendations['simplification_level'] = 'medium'
recommendations['target_polygon_count'] = int(polygon_count * 0.7)
else:
recommendations['simplification_level'] = 'light'
recommendations['target_polygon_count'] = int(polygon_count * 0.8)
return recommendations