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

419 lines
14 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 heapq
import math
from typing import List, Optional, Dict, Tuple
from panda3d.core import Point3
class PathfinderNode:
"""
寻路节点
用于A*算法中的节点表示
"""
def __init__(self, polygon_id: int, position: Point3):
self.polygon_id = polygon_id
self.position = position
self.g_cost = 0.0 # 从起点到当前节点的实际代价
self.h_cost = 0.0 # 启发式代价(到终点的估计代价)
self.f_cost = 0.0 # 总代价 (g_cost + h_cost)
self.parent = None # 父节点
def __lt__(self, other):
"""用于优先队列比较"""
return self.f_cost < other.f_cost
def __eq__(self, other):
"""用于节点比较"""
return self.polygon_id == other.polygon_id
class Pathfinder:
"""
路径查找器
实现基于导航网格的A*寻路算法
"""
def __init__(self, navmesh_manager):
"""
初始化路径查找器
Args:
navmesh_manager: 导航网格管理器
"""
self.navmesh_manager = navmesh_manager
# 寻路设置
self.heuristic_weight = 1.0 # 启发式权重
self.max_path_nodes = 10000 # 最大路径节点数
# 性能统计
self.stats = {
'paths_found': 0,
'paths_not_found': 0,
'total_search_time': 0.0,
'total_nodes_searched': 0
}
print("✓ 路径查找器初始化完成")
def find_path(self, start_point: Point3, end_point: Point3) -> Optional[List[Point3]]:
"""
查找从起点到终点的路径
Args:
start_point: 起点
end_point: 终点
Returns:
路径点列表如果找不到路径则返回None
"""
try:
# 检查导航网格是否可用
if not self.navmesh_manager or not self.navmesh_manager.polygons:
print("⚠ 导航网格不可用")
return None
# 获取起点和终点所在的多边形
start_polygon = self.navmesh_manager.get_polygon_containing_point(start_point)
end_polygon = self.navmesh_manager.get_polygon_containing_point(end_point)
# 如果起点或终点不在导航网格上,使用最近的点
if not start_polygon:
closest_start = self.navmesh_manager.get_closest_point(start_point)
if not closest_start:
print("⚠ 无法找到起点附近的可行走区域")
return None
start_point = closest_start
start_polygon = self.navmesh_manager.get_polygon_containing_point(start_point)
if not end_polygon:
closest_end = self.navmesh_manager.get_closest_point(end_point)
if not closest_end:
print("⚠ 无法找到终点附近的可行走区域")
return None
end_point = closest_end
end_polygon = self.navmesh_manager.get_polygon_containing_point(end_point)
# 如果起点和终点在同一个多边形内,直接连接
if start_polygon.polygon_id == end_polygon.polygon_id:
return [start_point, end_point]
# 使用A*算法查找路径
path = self._a_star_search(start_polygon.polygon_id, end_polygon.polygon_id,
start_point, end_point)
if path:
self.stats['paths_found'] += 1
print(f"✓ 找到路径,包含 {len(path)} 个节点")
return path
else:
self.stats['paths_not_found'] += 1
print("⚠ 未找到路径")
return None
except Exception as e:
print(f"✗ 路径查找失败: {e}")
return None
def _a_star_search(self, start_polygon_id: int, end_polygon_id: int,
start_point: Point3, end_point: Point3) -> Optional[List[Point3]]:
"""
A*寻路算法实现
Args:
start_polygon_id: 起始多边形ID
end_polygon_id: 目标多边形ID
start_point: 起点
end_point: 终点
Returns:
路径点列表如果找不到路径则返回None
"""
import time
start_time = time.time()
# 开放列表(优先队列)
open_list = []
# 关闭列表(已访问的节点)
closed_list = set()
# 创建起始节点
start_node = PathfinderNode(start_polygon_id, start_point)
start_node.g_cost = 0
start_node.h_cost = self._calculate_heuristic(start_point, end_point)
start_node.f_cost = start_node.g_cost + start_node.h_cost
# 将起始节点加入开放列表
heapq.heappush(open_list, start_node)
# 节点搜索计数
nodes_searched = 0
while open_list and nodes_searched < self.max_path_nodes:
# 取出f_cost最小的节点
current_node = heapq.heappop(open_list)
nodes_searched += 1
# 如果到达目标节点
if current_node.polygon_id == end_polygon_id:
# 重构路径
path = self._reconstruct_path(current_node)
# 添加终点
path.append(end_point)
self.stats['total_search_time'] += time.time() - start_time
self.stats['total_nodes_searched'] += nodes_searched
return path
# 将当前节点加入关闭列表
closed_list.add(current_node.polygon_id)
# 获取当前多边形
if current_node.polygon_id not in self.navmesh_manager.polygons:
continue
current_polygon = self.navmesh_manager.polygons[current_node.polygon_id]
# 检查所有相邻多边形
for neighbor_id in current_polygon.neighbors:
# 如果已在关闭列表,跳过
if neighbor_id in closed_list:
continue
# 获取邻居多边形
if neighbor_id not in self.navmesh_manager.polygons:
continue
neighbor_polygon = self.navmesh_manager.polygons[neighbor_id]
# 计算到邻居节点的代价
neighbor_position = neighbor_polygon.center
tentative_g_cost = current_node.g_cost + \
self._calculate_distance(current_node.position, neighbor_position)
# 检查是否需要更新邻居节点
existing_node = self._find_node_in_open_list(open_list, neighbor_id)
if existing_node is None:
# 创建新节点
neighbor_node = PathfinderNode(neighbor_id, neighbor_position)
neighbor_node.g_cost = tentative_g_cost
neighbor_node.h_cost = self._calculate_heuristic(neighbor_position, end_point)
neighbor_node.f_cost = neighbor_node.g_cost + neighbor_node.h_cost
neighbor_node.parent = current_node
heapq.heappush(open_list, neighbor_node)
elif tentative_g_cost < existing_node.g_cost:
# 更新现有节点
existing_node.g_cost = tentative_g_cost
existing_node.f_cost = existing_node.g_cost + existing_node.h_cost
existing_node.parent = current_node
# 未找到路径
self.stats['total_search_time'] += time.time() - start_time
self.stats['total_nodes_searched'] += nodes_searched
return None
def _calculate_heuristic(self, point1: Point3, point2: Point3) -> float:
"""
计算启发式代价(使用欧几里得距离)
Args:
point1: 起点
point2: 终点
Returns:
启发式代价
"""
return (point2 - point1).length() * self.heuristic_weight
def _calculate_distance(self, point1: Point3, point2: Point3) -> float:
"""
计算两点间距离
Args:
point1: 第一个点
point2: 第二个点
Returns:
两点间距离
"""
return (point2 - point1).length()
def _find_node_in_open_list(self, open_list: List[PathfinderNode], polygon_id: int) -> Optional[PathfinderNode]:
"""
在开放列表中查找指定多边形ID的节点
Args:
open_list: 开放列表
polygon_id: 多边形ID
Returns:
找到的节点如果未找到则返回None
"""
for node in open_list:
if node.polygon_id == polygon_id:
return node
return None
def _reconstruct_path(self, end_node: PathfinderNode) -> List[Point3]:
"""
重构路径
Args:
end_node: 终点节点
Returns:
路径点列表
"""
path = []
current = end_node
# 从终点回溯到起点
while current is not None:
path.append(current.position)
current = current.parent
# 反转路径(从起点到终点)
path.reverse()
return path
def smooth_path(self, path: List[Point3]) -> List[Point3]:
"""
平滑路径(可选优化)
Args:
path: 原始路径
Returns:
平滑后的路径
"""
if len(path) <= 2:
return path
smoothed_path = [path[0]]
# 简单的路径平滑算法
i = 0
while i < len(path) - 1:
# 尝试直接连接当前点和后面的点
j = len(path) - 1
while j > i:
# 检查两点间是否可以直接通行
if self._is_line_walkable(path[i], path[j]):
smoothed_path.append(path[j])
i = j
break
j -= 1
else:
# 如果不能直接连接,添加下一个点
i += 1
if i < len(path):
smoothed_path.append(path[i])
return smoothed_path
def _is_line_walkable(self, start: Point3, end: Point3) -> bool:
"""
检查两点间直线是否可行走
Args:
start: 起点
end: 终点
Returns:
是否可行走
"""
# 简化实现:检查线段上的几个点是否都在可行走区域
steps = 10
for i in range(steps + 1):
t = i / steps
point = start + (end - start) * t
if not self.navmesh_manager.is_point_walkable(point):
return False
return True
def get_pathfinding_stats(self) -> Dict:
"""
获取寻路统计信息
Returns:
统计信息字典
"""
return self.stats.copy()
def reset_stats(self):
"""重置统计信息"""
self.stats = {
'paths_found': 0,
'paths_not_found': 0,
'total_search_time': 0.0,
'total_nodes_searched': 0
}
def set_heuristic_weight(self, weight: float):
"""
设置启发式权重
Args:
weight: 权重值
"""
self.heuristic_weight = max(0.0, weight)
def set_max_path_nodes(self, max_nodes: int):
"""
设置最大路径节点数
Args:
max_nodes: 最大节点数
"""
self.max_path_nodes = max(1, max_nodes)
def find_path_with_multiple_waypoints(self, waypoints: List[Point3]) -> Optional[List[Point3]]:
"""
查找经过多个路点的路径
Args:
waypoints: 路点列表
Returns:
完整路径如果任何一段找不到则返回None
"""
if len(waypoints) < 2:
return None
# 查找每段路径并连接
full_path = []
for i in range(len(waypoints) - 1):
segment_path = self.find_path(waypoints[i], waypoints[i + 1])
if not segment_path:
return None # 如果任何一段找不到路径,则整个路径失败
# 避免重复点
if full_path:
full_path.pop() # 移除上一段的终点(与这一段的起点重复)
full_path.extend(segment_path)
return full_path
def find_random_path(self) -> Optional[List[Point3]]:
"""
查找随机路径(用于测试)
Returns:
随机路径
"""
if not self.navmesh_manager:
return None
start_point = self.navmesh_manager.get_random_point()
end_point = self.navmesh_manager.get_random_point()
if start_point and end_point:
return self.find_path(start_point, end_point)
return None