EG/plugins/user/pathfinding_algorithms/visualization/visualization_manager.py
2025-12-12 16:16:15 +08:00

679 lines
24 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.

"""
可视化管理器
负责路径规划算法的可视化显示
"""
from typing import List, Tuple, Optional, Dict, Any
from panda3d.core import NodePath, GeomNode, GeomVertexFormat, GeomVertexData
from panda3d.core import GeomVertexWriter, GeomTriangles, GeomLines, GeomPoints
from panda3d.core import LColor, Point3, Material, TransparencyAttrib
import math
class VisualizationManager:
"""
可视化管理器
负责路径规划算法的可视化显示
"""
def __init__(self, world):
"""
初始化可视化管理器
Args:
world: 3D世界对象
"""
self.world = world
self.enabled = False
# 可视化节点
self.grid_node = None
self.path_node = None
self.start_node = None
self.goal_node = None
self.visited_nodes_node = None
self.open_list_node = None
# 可视化设置
self.settings = {
'grid_visible': True,
'path_visible': True,
'start_visible': True,
'goal_visible': True,
'visited_nodes_visible': False,
'open_list_visible': False,
'grid_color': LColor(0.5, 0.5, 0.5, 1.0),
'path_color': LColor(1.0, 0.0, 0.0, 1.0),
'start_color': LColor(0.0, 1.0, 0.0, 1.0),
'goal_color': LColor(0.0, 0.0, 1.0, 1.0),
'visited_nodes_color': LColor(0.0, 0.5, 1.0, 0.7),
'open_list_color': LColor(1.0, 1.0, 0.0, 0.7),
'obstacle_color': LColor(0.2, 0.2, 0.2, 1.0),
'cell_size': 1.0,
'cell_height': 0.1,
'path_thickness': 4.0,
'show_coordinates': False,
'animation_speed': 1.0
}
# 当前显示的数据
self.current_grid = None
self.current_path = None
self.current_start = None
self.current_goal = None
self.visited_nodes = []
self.open_list = []
# 动画相关
self.animation_enabled = False
self.animation_progress = 0.0
self.animation_path = []
self.animation_timer = 0.0
# 材质
self.grid_material = None
self.path_material = None
self.start_material = None
self.goal_material = None
def enable(self):
"""启用可视化管理器"""
if self.enabled:
return
self.enabled = True
self._create_visualization_nodes()
self._create_materials()
print("✓ 可视化管理器已启用")
def disable(self):
"""禁用可视化管理器"""
if not self.enabled:
return
self.enabled = False
self._cleanup_visualization_nodes()
print("✓ 可视化管理器已禁用")
def cleanup(self):
"""清理资源"""
self.disable()
self.current_grid = None
self.current_path = None
self.current_start = None
self.current_goal = None
self.visited_nodes = []
self.open_list = []
self.animation_path = []
def _create_visualization_nodes(self):
"""创建可视化节点"""
if not self.world or not self.world.render:
return
# 创建网格可视化节点
self.grid_node = self.world.render.attachNewNode("pathfinding_grid")
# 创建路径可视化节点
self.path_node = self.world.render.attachNewNode("pathfinding_path")
# 创建起点可视化节点
self.start_node = self.world.render.attachNewNode("pathfinding_start")
# 创建终点可视化节点
self.goal_node = self.world.render.attachNewNode("pathfinding_goal")
# 创建已访问节点可视化节点
self.visited_nodes_node = self.world.render.attachNewNode("pathfinding_visited_nodes")
# 创建开放列表可视化节点
self.open_list_node = self.world.render.attachNewNode("pathfinding_open_list")
def _cleanup_visualization_nodes(self):
"""清理可视化节点"""
nodes = [
self.grid_node, self.path_node, self.start_node, self.goal_node,
self.visited_nodes_node, self.open_list_node
]
for node in nodes:
if node:
node.removeNode()
self.grid_node = None
self.path_node = None
self.start_node = None
self.goal_node = None
self.visited_nodes_node = None
self.open_list_node = None
def _create_materials(self):
"""创建材质"""
# 网格材质
self.grid_material = Material()
self.grid_material.setAmbient(self.settings['grid_color'])
self.grid_material.setDiffuse(self.settings['grid_color'])
# 路径材质
self.path_material = Material()
self.path_material.setAmbient(self.settings['path_color'])
self.path_material.setDiffuse(self.settings['path_color'])
# 起点材质
self.start_material = Material()
self.start_material.setAmbient(self.settings['start_color'])
self.start_material.setDiffuse(self.settings['start_color'])
# 终点材质
self.goal_material = Material()
self.goal_material.setAmbient(self.settings['goal_color'])
self.goal_material.setDiffuse(self.settings['goal_color'])
def toggle_visibility(self):
"""切换可视化可见性"""
if not self.enabled:
return
self.settings['grid_visible'] = not self.settings['grid_visible']
self.settings['path_visible'] = not self.settings['path_visible']
self.settings['start_visible'] = not self.settings['start_visible']
self.settings['goal_visible'] = not self.settings['goal_visible']
self.settings['visited_nodes_visible'] = not self.settings['visited_nodes_visible']
self.settings['open_list_visible'] = not self.settings['open_list_visible']
self._update_visibility()
print("✓ 可视化可见性已切换")
def _update_visibility(self):
"""更新可视化元素的可见性"""
visibility_map = {
self.grid_node: self.settings['grid_visible'],
self.path_node: self.settings['path_visible'],
self.start_node: self.settings['start_visible'],
self.goal_node: self.settings['goal_visible'],
self.visited_nodes_node: self.settings['visited_nodes_visible'],
self.open_list_node: self.settings['open_list_visible']
}
for node, visible in visibility_map.items():
if node:
if visible:
node.show()
else:
node.hide()
def visualize_grid(self, grid: List[List[int]]):
"""
可视化网格
Args:
grid: 网格数据
"""
if not self.enabled or not self.grid_node:
return
self.current_grid = grid
self._create_grid_geometry()
print(f"✓ 网格已可视化 ({len(grid)}x{len(grid[0]) if grid else 0})")
def _create_grid_geometry(self):
"""创建网格几何体"""
if not self.current_grid or not self.grid_node:
return
# 清除现有几何体
self.grid_node.node().removeAllGeoms()
rows = len(self.current_grid)
cols = len(self.current_grid[0]) if rows > 0 else 0
if rows == 0 or cols == 0:
return
cell_size = self.settings['cell_size']
cell_height = self.settings['cell_height']
grid_color = self.settings['grid_color']
obstacle_color = self.settings['obstacle_color']
# 创建顶点格式
vertex_format = GeomVertexFormat.getV3c4()
vertex_data = GeomVertexData("grid", vertex_format, Geom.UHStatic)
# 计算总顶点数每个格子4个顶点
total_vertices = rows * cols * 4
vertex_data.setNumRows(total_vertices)
# 创建顶点写入器
vertex_writer = GeomVertexWriter(vertex_data, "vertex")
color_writer = GeomVertexWriter(vertex_data, "color")
# 创建几何体
geom = Geom(vertex_data)
triangles = GeomTriangles(Geom.UHStatic)
vertex_index = 0
for row in range(rows):
for col in range(cols):
# 计算格子位置
x = col * cell_size - (cols * cell_size) / 2
z = row * cell_size - (rows * cell_size) / 2
y = 0 if self.current_grid[row][col] == 0 else cell_height
# 选择颜色(可通行区域或障碍物)
color = grid_color if self.current_grid[row][col] == 0 else obstacle_color
# 创建格子的四个顶点
vertices = [
Point3(x, y, z),
Point3(x + cell_size, y, z),
Point3(x + cell_size, y, z + cell_size),
Point3(x, y, z + cell_size)
]
# 写入顶点数据
for vertex in vertices:
vertex_writer.addData3f(vertex.x, vertex.y, vertex.z)
color_writer.addData4f(color)
# 添加两个三角形
triangles.addVertex(vertex_index)
triangles.addVertex(vertex_index + 1)
triangles.addVertex(vertex_index + 2)
triangles.closePrimitive()
triangles.addVertex(vertex_index)
triangles.addVertex(vertex_index + 2)
triangles.addVertex(vertex_index + 3)
triangles.closePrimitive()
vertex_index += 4
geom.addPrimitive(triangles)
self.grid_node.node().addGeom(geom)
# 应用材质
if self.grid_material:
self.grid_node.setMaterial(self.grid_material, 1)
# 添加坐标文本(如果启用)
if self.settings['show_coordinates']:
self._add_coordinate_labels(rows, cols, cell_size)
def _add_coordinate_labels(self, rows: int, cols: int, cell_size: float):
"""
添加坐标标签
Args:
rows: 行数
cols: 列数
cell_size: 单元格大小
"""
# 这里可以添加坐标标签的实现
# 为了简化,我们只打印信息
print(f" 坐标标签已启用 ({rows}x{cols})")
def visualize_path(self, path: List[Tuple[int, int]], grid: Optional[List[List[int]]] = None):
"""
可视化路径
Args:
path: 路径坐标列表
grid: 网格数据(可选)
"""
if not self.enabled:
return
self.current_path = path
if grid is not None:
self.current_grid = grid
self._create_path_geometry()
self._update_visibility()
print(f"✓ 路径已可视化 (长度: {len(path) if path else 0})")
def _create_path_geometry(self):
"""创建路径几何体"""
if not self.current_path or not self.path_node:
return
# 清除现有几何体
self.path_node.node().removeAllGeoms()
if len(self.current_path) < 2:
return
cell_size = self.settings['cell_size']
rows = len(self.current_grid) if self.current_grid else 0
cols = len(self.current_grid[0]) if self.current_grid and rows > 0 else 0
# 创建顶点格式
vertex_format = GeomVertexFormat.getV3c4()
vertex_data = GeomVertexData("path", vertex_format, Geom.UHStatic)
vertex_data.setNumRows(len(self.current_path))
# 创建顶点写入器
vertex_writer = GeomVertexWriter(vertex_data, "vertex")
color_writer = GeomVertexWriter(vertex_data, "color")
# 写入路径点
for i, (row, col) in enumerate(self.current_path):
x = col * cell_size - (cols * cell_size) / 2 + cell_size / 2
z = row * cell_size - (rows * cell_size) / 2 + cell_size / 2
y = 0.2 # 稍微抬高以避免与网格重叠
vertex_writer.addData3f(x, y, z)
color_writer.addData4f(self.settings['path_color'])
# 创建几何体
geom = Geom(vertex_data)
lines = GeomLines(Geom.UHStatic)
# 添加线段
for i in range(len(self.current_path) - 1):
lines.addVertex(i)
lines.addVertex(i + 1)
lines.closePrimitive()
geom.addPrimitive(lines)
self.path_node.node().addGeom(geom)
# 设置线宽和材质
self.path_node.setRenderModeThickness(self.settings['path_thickness'])
if self.path_material:
self.path_node.setMaterial(self.path_material, 1)
def visualize_visited_nodes(self, visited_nodes: List[Tuple[int, int]]):
"""
可视化已访问节点
Args:
visited_nodes: 已访问节点列表
"""
if not self.enabled:
return
self.visited_nodes = visited_nodes
self._create_visited_nodes_geometry()
print(f"✓ 已访问节点已可视化 (数量: {len(visited_nodes)})")
def _create_visited_nodes_geometry(self):
"""创建已访问节点几何体"""
if not self.visited_nodes or not self.visited_nodes_node:
return
# 清除现有几何体
self.visited_nodes_node.node().removeAllGeoms()
cell_size = self.settings['cell_size']
rows = len(self.current_grid) if self.current_grid else 0
cols = len(self.current_grid[0]) if self.current_grid and rows > 0 else 0
# 创建顶点格式
vertex_format = GeomVertexFormat.getV3c4()
vertex_data = GeomVertexData("visited_nodes", vertex_format, Geom.UHStatic)
vertex_data.setNumRows(len(self.visited_nodes))
# 创建顶点写入器
vertex_writer = GeomVertexWriter(vertex_data, "vertex")
color_writer = GeomVertexWriter(vertex_data, "color")
# 写入节点点
for i, (row, col) in enumerate(self.visited_nodes):
x = col * cell_size - (cols * cell_size) / 2 + cell_size / 2
z = row * cell_size - (rows * cell_size) / 2 + cell_size / 2
y = 0.15 # 稍微抬高以避免与网格重叠
vertex_writer.addData3f(x, y, z)
color_writer.addData4f(self.settings['visited_nodes_color'])
# 创建几何体
geom = Geom(vertex_data)
points = GeomPoints(Geom.UHStatic)
points.addNextVertices(len(self.visited_nodes))
geom.addPrimitive(points)
self.visited_nodes_node.node().addGeom(geom)
# 设置点大小和透明度
self.visited_nodes_node.setRenderModeThickness(3.0)
self.visited_nodes_node.setTransparency(TransparencyAttrib.MAlpha)
def visualize_open_list(self, open_list: List[Tuple[int, int]]):
"""
可视化开放列表节点
Args:
open_list: 开放列表节点列表
"""
if not self.enabled:
return
self.open_list = open_list
self._create_open_list_geometry()
print(f"✓ 开放列表节点已可视化 (数量: {len(open_list)})")
def _create_open_list_geometry(self):
"""创建开放列表节点几何体"""
if not self.open_list or not self.open_list_node:
return
# 清除现有几何体
self.open_list_node.node().removeAllGeoms()
cell_size = self.settings['cell_size']
rows = len(self.current_grid) if self.current_grid else 0
cols = len(self.current_grid[0]) if self.current_grid and rows > 0 else 0
# 创建顶点格式
vertex_format = GeomVertexFormat.getV3c4()
vertex_data = GeomVertexData("open_list", vertex_format, Geom.UHStatic)
vertex_data.setNumRows(len(self.open_list))
# 创建顶点写入器
vertex_writer = GeomVertexWriter(vertex_data, "vertex")
color_writer = GeomVertexWriter(vertex_data, "color")
# 写入节点点
for i, (row, col) in enumerate(self.open_list):
x = col * cell_size - (cols * cell_size) / 2 + cell_size / 2
z = row * cell_size - (rows * cell_size) / 2 + cell_size / 2
y = 0.18 # 稍微抬高以避免与网格重叠
vertex_writer.addData3f(x, y, z)
color_writer.addData4f(self.settings['open_list_color'])
# 创建几何体
geom = Geom(vertex_data)
points = GeomPoints(Geom.UHStatic)
points.addNextVertices(len(self.open_list))
geom.addPrimitive(points)
self.open_list_node.node().addGeom(geom)
# 设置点大小和透明度
self.open_list_node.setRenderModeThickness(2.0)
self.open_list_node.setTransparency(TransparencyAttrib.MAlpha)
def clear_visualization(self):
"""清除所有可视化"""
self.clear_grid_visualization()
self.clear_path_visualization()
self.clear_start_visualization()
self.clear_goal_visualization()
self.clear_visited_nodes_visualization()
self.clear_open_list_visualization()
print("✓ 所有可视化已清除")
def clear_grid_visualization(self):
"""清除网格可视化"""
if self.grid_node:
self.grid_node.node().removeAllGeoms()
self.current_grid = None
def clear_path_visualization(self):
"""清除路径可视化"""
if self.path_node:
self.path_node.node().removeAllGeoms()
self.current_path = None
def clear_start_visualization(self):
"""清除起点可视化"""
if self.start_node:
self.start_node.node().removeAllGeoms()
self.current_start = None
def clear_goal_visualization(self):
"""清除终点可视化"""
if self.goal_node:
self.goal_node.node().removeAllGeoms()
self.current_goal = None
def clear_visited_nodes_visualization(self):
"""清除已访问节点可视化"""
if self.visited_nodes_node:
self.visited_nodes_node.node().removeAllGeoms()
self.visited_nodes = []
def clear_open_list_visualization(self):
"""清除开放列表可视化"""
if self.open_list_node:
self.open_list_node.node().removeAllGeoms()
self.open_list = []
def set_settings(self, settings: Dict[str, Any]):
"""
设置可视化参数
Args:
settings: 参数字典
"""
old_settings = self.settings.copy()
self.settings.update(settings)
self._update_visualization()
# 如果颜色设置发生变化,更新材质
color_keys = ['grid_color', 'path_color', 'start_color', 'goal_color']
if any(key in settings for key in color_keys):
self._create_materials()
print("✓ 可视化设置已更新")
def get_settings(self) -> Dict[str, Any]:
"""
获取可视化参数
Returns:
参数字典
"""
return self.settings.copy()
def _update_visualization(self):
"""更新可视化显示"""
if self.current_grid:
self._create_grid_geometry()
if self.current_path:
self._create_path_geometry()
if self.visited_nodes:
self._create_visited_nodes_geometry()
if self.open_list:
self._create_open_list_geometry()
self._update_visibility()
def enable_animation(self, enable: bool):
"""
启用/禁用路径动画
Args:
enable: 是否启用动画
"""
self.animation_enabled = enable
if enable:
self.animation_progress = 0.0
self.animation_timer = 0.0
self.animation_path = self.current_path.copy() if self.current_path else []
print("✓ 路径动画已启用")
else:
print("✓ 路径动画已禁用")
def update_animation(self, dt: float):
"""
更新动画
Args:
dt: 时间增量
"""
if not self.animation_enabled or not self.animation_path:
return
self.animation_timer += dt * self.settings['animation_speed']
self.animation_progress = min(1.0, self.animation_timer / 2.0) # 2秒完成动画
# 更新路径显示
path_length = len(self.animation_path)
visible_length = int(path_length * self.animation_progress)
if visible_length > 1:
visible_path = self.animation_path[:visible_length]
self._update_animated_path(visible_path)
def _update_animated_path(self, visible_path: List[Tuple[int, int]]):
"""
更新动画路径显示
Args:
visible_path: 可见路径部分
"""
if not self.path_node:
return
# 清除现有几何体
self.path_node.node().removeAllGeoms()
if len(visible_path) < 2:
return
cell_size = self.settings['cell_size']
rows = len(self.current_grid) if self.current_grid else 0
cols = len(self.current_grid[0]) if self.current_grid and rows > 0 else 0
# 创建顶点格式
vertex_format = GeomVertexFormat.getV3c4()
vertex_data = GeomVertexData("path", vertex_format, Geom.UHStatic)
vertex_data.setNumRows(len(visible_path))
# 创建顶点写入器
vertex_writer = GeomVertexWriter(vertex_data, "vertex")
color_writer = GeomVertexWriter(vertex_data, "color")
# 写入路径点
for i, (row, col) in enumerate(visible_path):
x = col * cell_size - (cols * cell_size) / 2 + cell_size / 2
z = row * cell_size - (rows * cell_size) / 2 + cell_size / 2
y = 0.2 # 稍微抬高以避免与网格重叠
# 根据进度调整颜色
progress = i / len(visible_path)
base_color = self.settings['path_color']
color = LColor(
base_color.x * (1 - progress) + 1.0 * progress, # 逐渐变为白色
base_color.y * (1 - progress),
base_color.z * (1 - progress),
base_color.w
)
vertex_writer.addData3f(x, y, z)
color_writer.addData4f(color)
# 创建几何体
geom = Geom(vertex_data)
lines = GeomLines(Geom.UHStatic)
# 添加线段
for i in range(len(visible_path) - 1):
lines.addVertex(i)
lines.addVertex(i + 1)
lines.closePrimitive()
geom.addPrimitive(lines)
self.path_node.node().addGeom(geom)
# 设置线宽和材质
self.path_node.setRenderModeThickness(self.settings['path_thickness'])
if self.path_material:
self.path_node.setMaterial(self.path_material, 1)