492 lines
17 KiB
Python
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 |