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

456 lines
16 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.

"""
导航网格优化器
提供自动优化导航网格的功能,包括多边形合并、简化和重构
"""
import math
from typing import List, Dict, Set, Tuple, Optional
from panda3d.core import Point3
class NavMeshOptimizer:
"""
导航网格优化器
提供自动优化导航网格的功能
"""
def __init__(self, navmesh_manager):
"""
初始化导航网格优化器
Args:
navmesh_manager: 导航网格管理器实例
"""
self.navmesh_manager = navmesh_manager
self.optimization_settings = {
'merge_threshold': 0.5, # 合并阈值
'simplify_threshold': 0.1, # 简化阈值
'min_polygon_area': 0.01, # 最小多边形面积
'max_vertices_per_polygon': 8, # 每个多边形最大顶点数
'preserve_boundaries': True, # 是否保留边界
'preserve_portals': True # 是否保留门户点
}
def set_optimization_settings(self, settings: Dict):
"""
设置优化参数
Args:
settings: 优化参数字典
"""
self.optimization_settings.update(settings)
def get_optimization_settings(self) -> Dict:
"""
获取优化参数
Returns:
优化参数字典
"""
return self.optimization_settings.copy()
def merge_adjacent_polygons(self) -> int:
"""
合并相邻的多边形
Returns:
合并的多边形数量
"""
if not self.navmesh_manager or not self.navmesh_manager.polygons:
return 0
merged_count = 0
processed_polygons = set()
# 创建多边形ID列表的副本避免在迭代时修改字典
polygon_ids = list(self.navmesh_manager.polygons.keys())
for polygon_id in polygon_ids:
if polygon_id in processed_polygons:
continue
if polygon_id not in self.navmesh_manager.polygons:
continue
polygon = self.navmesh_manager.polygons[polygon_id]
# 检查是否可以与其他多边形合并
for neighbor_id in polygon.neighbors[:]: # 使用副本避免修改时的问题
if neighbor_id in processed_polygons:
continue
if neighbor_id not in self.navmesh_manager.polygons:
continue
neighbor = self.navmesh_manager.polygons[neighbor_id]
# 检查合并条件
if self._can_merge_polygons(polygon, neighbor):
# 执行合并
if self._merge_two_polygons(polygon_id, neighbor_id):
merged_count += 1
processed_polygons.add(polygon_id)
processed_polygons.add(neighbor_id)
break
# 重新计算连接关系
if merged_count > 0:
self.navmesh_manager._calculate_polygon_connections()
self.navmesh_manager._calculate_portal_points()
self.navmesh_manager._update_navmesh_visualization()
return merged_count
def _can_merge_polygons(self, polygon1, polygon2) -> bool:
"""
检查两个多边形是否可以合并
Args:
polygon1: 第一个多边形
polygon2: 第二个多边形
Returns:
是否可以合并
"""
# 检查是否有共享边
if not self._polygons_share_full_edge(polygon1, polygon2):
return False
# 检查合并后的多边形是否过于复杂
merged_vertices = self._get_merged_vertices(polygon1, polygon2)
if len(merged_vertices) > self.optimization_settings['max_vertices_per_polygon']:
return False
# 检查面积
merged_area = self._calculate_polygon_area(merged_vertices)
if merged_area < self.optimization_settings['min_polygon_area']:
return False
return True
def _polygons_share_full_edge(self, polygon1, polygon2) -> bool:
"""
检查两个多边形是否共享完整的一条边
Args:
polygon1: 第一个多边形
polygon2: 第二个多边形
Returns:
是否共享完整边
"""
threshold = 0.01 # 点重合阈值
# 检查polygon1的每条边
for i in range(len(polygon1.vertices)):
v1_start = polygon1.vertices[i]
v1_end = polygon1.vertices[(i + 1) % len(polygon1.vertices)]
# 检查polygon2的每条边
for j in range(len(polygon2.vertices)):
v2_start = polygon2.vertices[j]
v2_end = polygon2.vertices[(j + 1) % len(polygon2.vertices)]
# 检查边是否重合(方向相同)
if (self._points_equal(v1_start, v2_start, threshold) and
self._points_equal(v1_end, v2_end, threshold)):
return True
# 检查边是否重合(方向相反)
if (self._points_equal(v1_start, v2_end, threshold) and
self._points_equal(v1_end, v2_start, threshold)):
return True
return False
def _points_equal(self, p1: Point3, p2: Point3, threshold: float) -> bool:
"""检查两点是否相等(在阈值范围内)"""
return (p1 - p2).length() < threshold
def _get_merged_vertices(self, polygon1, polygon2) -> List[Point3]:
"""
获取两个多边形合并后的顶点
Args:
polygon1: 第一个多边形
polygon2: 第二个多边形
Returns:
合并后的顶点列表
"""
# 这是一个简化的实现
# 实际应用中需要正确处理多边形合并的几何运算
merged_vertices = []
merged_vertices.extend(polygon1.vertices)
merged_vertices.extend(polygon2.vertices)
return merged_vertices
def _calculate_polygon_area(self, vertices: List[Point3]) -> float:
"""计算多边形面积(使用鞋带公式)"""
if len(vertices) < 3:
return 0.0
area = 0.0
n = len(vertices)
for i in range(n):
j = (i + 1) % n
area += vertices[i].x * vertices[j].z
area -= vertices[j].x * vertices[i].z
return abs(area) / 2.0
def _merge_two_polygons(self, polygon1_id: int, polygon2_id: int) -> bool:
"""
合并两个多边形
Args:
polygon1_id: 第一个多边形ID
polygon2_id: 第二个多边形ID
Returns:
是否合并成功
"""
try:
if (polygon1_id not in self.navmesh_manager.polygons or
polygon2_id not in self.navmesh_manager.polygons):
return False
polygon1 = self.navmesh_manager.polygons[polygon1_id]
polygon2 = self.navmesh_manager.polygons[polygon2_id]
# 创建合并后的新多边形
merged_vertices = self._merge_polygon_vertices(polygon1, polygon2)
if not merged_vertices:
return False
# 移除旧的多边形
self.navmesh_manager.remove_polygon(polygon2_id)
# 更新第一个多边形
polygon1.vertices = merged_vertices
polygon1.center = polygon1._calculate_center()
polygon1.area = polygon1._calculate_area()
polygon1.bounding_box = polygon1._calculate_bounding_box()
return True
except Exception as e:
print(f"合并多边形失败: {e}")
return False
def _merge_polygon_vertices(self, polygon1, polygon2) -> List[Point3]:
"""
合并两个多边形的顶点
Args:
polygon1: 第一个多边形
polygon2: 第二个多边形
Returns:
合并后的顶点列表
"""
# 这是一个简化的实现
# 实际应用中需要正确处理多边形合并的几何运算
# 包括移除共享边,正确排序顶点等
# 简单合并所有顶点
merged_vertices = []
merged_vertices.extend(polygon1.vertices)
merged_vertices.extend(polygon2.vertices)
# 去除重复顶点(简化处理)
unique_vertices = []
for vertex in merged_vertices:
is_duplicate = False
for existing_vertex in unique_vertices:
if self._points_equal(vertex, existing_vertex, 0.01):
is_duplicate = True
break
if not is_duplicate:
unique_vertices.append(vertex)
return unique_vertices[:self.optimization_settings['max_vertices_per_polygon']]
def simplify_polygons(self) -> int:
"""
简化多边形(移除不必要的顶点)
Returns:
简化的顶点数量
"""
if not self.navmesh_manager or not self.navmesh_manager.polygons:
return 0
simplified_count = 0
for polygon in self.navmesh_manager.polygons.values():
original_vertex_count = len(polygon.vertices)
simplified_vertices = self._simplify_polygon(polygon.vertices)
if len(simplified_vertices) < original_vertex_count:
polygon.vertices = simplified_vertices
polygon.center = polygon._calculate_center()
polygon.area = polygon._calculate_area()
polygon.bounding_box = polygon._calculate_bounding_box()
simplified_count += original_vertex_count - len(simplified_vertices)
# 重新计算连接关系
if simplified_count > 0:
self.navmesh_manager._calculate_polygon_connections()
self.navmesh_manager._calculate_portal_points()
self.navmesh_manager._update_navmesh_visualization()
return simplified_count
def _simplify_polygon(self, vertices: List[Point3]) -> List[Point3]:
"""
简化单个多边形
Args:
vertices: 多边形顶点列表
Returns:
简化后的顶点列表
"""
if len(vertices) <= 3:
return vertices
simplified = [vertices[0]] # 保留第一个顶点
i = 0
while i < len(vertices):
# 找到下一个需要保留的顶点
next_i = self._find_next_significant_vertex(vertices, i)
if next_i >= len(vertices):
break
simplified.append(vertices[next_i])
i = next_i
# 确保至少有3个顶点
if len(simplified) < 3:
return vertices[:3]
return simplified
def _find_next_significant_vertex(self, vertices: List[Point3], start_index: int) -> int:
"""
找到下一个重要的顶点(需要保留的顶点)
Args:
vertices: 顶点列表
start_index: 起始索引
Returns:
下一个重要顶点的索引
"""
threshold = self.optimization_settings['simplify_threshold']
# 简化处理:每隔几个顶点保留一个
step = max(1, int(len(vertices) / 8)) # 保持最多8个顶点
next_index = min(start_index + step, len(vertices) - 1)
return next_index
def remove_small_polygons(self) -> int:
"""
移除极小的多边形
Returns:
移除的多边形数量
"""
if not self.navmesh_manager or not self.navmesh_manager.polygons:
return 0
min_area = self.optimization_settings['min_polygon_area']
removed_count = 0
# 创建需要移除的多边形ID列表
to_remove = []
for polygon_id, polygon in self.navmesh_manager.polygons.items():
if polygon.area < min_area:
to_remove.append(polygon_id)
# 移除多边形
for polygon_id in to_remove:
self.navmesh_manager.remove_polygon(polygon_id)
removed_count += 1
# 更新连接关系
if removed_count > 0:
self.navmesh_manager._calculate_polygon_connections()
self.navmesh_manager._calculate_portal_points()
self.navmesh_manager._update_navmesh_visualization()
return removed_count
def optimize_mesh(self, optimization_level: str = 'medium') -> Dict:
"""
执行完整的网格优化
Args:
optimization_level: 优化级别 ('light', 'medium', 'heavy')
Returns:
优化结果统计
"""
results = {
'merged_polygons': 0,
'simplified_vertices': 0,
'removed_polygons': 0,
'total_optimizations': 0
}
# 根据优化级别设置参数
if optimization_level == 'heavy':
self.optimization_settings['merge_threshold'] = 0.3
self.optimization_settings['simplify_threshold'] = 0.2
self.optimization_settings['min_polygon_area'] = 0.05
elif optimization_level == 'light':
self.optimization_settings['merge_threshold'] = 0.7
self.optimization_settings['simplify_threshold'] = 0.05
self.optimization_settings['min_polygon_area'] = 0.005
# 执行优化步骤
if optimization_level in ['medium', 'heavy']:
results['merged_polygons'] = self.merge_adjacent_polygons()
results['simplified_vertices'] = self.simplify_polygons()
results['removed_polygons'] = self.remove_small_polygons()
results['total_optimizations'] = (results['merged_polygons'] +
results['simplified_vertices'] +
results['removed_polygons'])
return results
def fill_gaps(self) -> int:
"""
填补导航网格中的空隙
Returns:
填补的多边形数量
"""
# 这是一个复杂的功能,需要分析未覆盖的区域并生成新的多边形
# 简化实现返回0
return 0
def smooth_boundaries(self) -> int:
"""
平滑多边形边界
Returns:
平滑处理的边数
"""
# 这需要复杂的几何运算来平滑多边形边界
# 简化实现返回0
return 0
def balance_polygon_sizes(self) -> int:
"""
平衡多边形大小
Returns:
调整的多边形数量
"""
# 这需要分析多边形大小分布并调整过大或过小的多边形
# 简化实现返回0
return 0