478 lines
18 KiB
Python
478 lines
18 KiB
Python
"""
|
||
导航网格编辑器
|
||
提供可视化编辑导航网格的功能
|
||
"""
|
||
|
||
from typing import List, Optional, Dict, Any
|
||
from panda3d.core import Vec3, Point3, KeyboardButton, MouseWatcher
|
||
|
||
class NavMeshEditor:
|
||
"""
|
||
导航网格编辑器
|
||
提供可视化编辑导航网格的功能
|
||
"""
|
||
|
||
def __init__(self, world, navmesh_manager):
|
||
"""
|
||
初始化导航网格编辑器
|
||
|
||
Args:
|
||
world: 3D世界对象
|
||
navmesh_manager: 导航网格管理器
|
||
"""
|
||
self.world = world
|
||
self.navmesh_manager = navmesh_manager
|
||
self.visible = False
|
||
|
||
# 编辑器状态
|
||
self.edit_mode = False
|
||
self.selected_polygon = None
|
||
self.edit_tool = "select" # select, add, delete, paint
|
||
self.brush_size = 1.0
|
||
|
||
# 编辑器GUI元素
|
||
self.gui_elements = []
|
||
self.tool_buttons = {}
|
||
self.property_controls = {}
|
||
|
||
# 鼠标交互
|
||
self.mouse_watcher = None
|
||
self.last_mouse_pos = None
|
||
|
||
# 编辑历史(用于撤销/重做)
|
||
self.edit_history = []
|
||
self.history_index = -1
|
||
self.max_history_size = 50
|
||
|
||
print("✓ 导航网格编辑器初始化完成")
|
||
|
||
def toggle_visibility(self):
|
||
"""切换编辑器可见性"""
|
||
if self.visible:
|
||
self.hide()
|
||
else:
|
||
self.show()
|
||
|
||
def show(self):
|
||
"""显示编辑器"""
|
||
if self.visible:
|
||
return
|
||
|
||
self.visible = True
|
||
self._create_editor_gui()
|
||
print("✓ 导航网格编辑器已显示")
|
||
|
||
def hide(self):
|
||
"""隐藏编辑器"""
|
||
if not self.visible:
|
||
return
|
||
|
||
self.visible = False
|
||
self._cleanup_editor_gui()
|
||
print("✓ 导航网格编辑器已隐藏")
|
||
|
||
def _create_editor_gui(self):
|
||
"""创建编辑器GUI"""
|
||
try:
|
||
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
|
||
print("⚠ GUI管理器不可用,无法创建编辑器界面")
|
||
return
|
||
|
||
gui_manager = self.world.gui_manager
|
||
|
||
# 创建编辑器面板标题
|
||
panel = gui_manager.createGUIButton(
|
||
pos=(0.7, 0, 0.95),
|
||
text="导航网格编辑器",
|
||
size=0.06
|
||
)
|
||
self.gui_elements.append(panel)
|
||
|
||
# 创建工具按钮
|
||
tools = [
|
||
("选择", "select"),
|
||
("添加", "add"),
|
||
("删除", "delete"),
|
||
("绘制", "paint")
|
||
]
|
||
|
||
for i, (name, tool) in enumerate(tools):
|
||
button = gui_manager.createGUIButton(
|
||
pos=(0.7, 0, 0.88 - i * 0.06),
|
||
text=name,
|
||
size=0.04
|
||
)
|
||
self.gui_elements.append(button)
|
||
self.tool_buttons[tool] = button
|
||
self.world.accept(f"navmesh_tool_{tool}",
|
||
lambda t=tool: self.set_edit_tool(t))
|
||
|
||
# 创建编辑功能按钮
|
||
functions = [
|
||
("生成网格", self.generate_navmesh),
|
||
("加载网格", self.load_navmesh),
|
||
("保存网格", self.save_navmesh),
|
||
("清除网格", self.clear_navmesh),
|
||
("撤销", self.undo),
|
||
("重做", self.redo)
|
||
]
|
||
|
||
for i, (name, callback) in enumerate(functions):
|
||
button = gui_manager.createGUIButton(
|
||
pos=(0.82, 0, 0.88 - i * 0.06),
|
||
text=name,
|
||
size=0.04
|
||
)
|
||
self.gui_elements.append(button)
|
||
self.world.accept(f"navmesh_func_{name}", callback)
|
||
|
||
# 创建属性控制
|
||
self._create_property_controls()
|
||
|
||
# 显示网格
|
||
self.navmesh_manager.toggle_visibility()
|
||
|
||
except Exception as e:
|
||
print(f"⚠ 编辑器GUI创建失败: {e}")
|
||
|
||
def _create_property_controls(self):
|
||
"""创建属性控制面板"""
|
||
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
|
||
return
|
||
|
||
gui_manager = self.world.gui_manager
|
||
|
||
# 网格属性
|
||
properties = [
|
||
("网格大小", "cell_size", 0.1, 2.0, 0.1),
|
||
("网格高度", "cell_height", 0.1, 1.0, 0.1),
|
||
("代理高度", "agent_height", 0.5, 3.0, 0.1),
|
||
("代理半径", "agent_radius", 0.1, 2.0, 0.1),
|
||
("最大坡度", "max_slope", 0.0, 89.0, 1.0),
|
||
("笔刷大小", "brush_size", 0.1, 5.0, 0.1)
|
||
]
|
||
|
||
for i, (name, prop, min_val, max_val, step) in enumerate(properties):
|
||
y_pos = 0.5 - i * 0.055
|
||
|
||
# 属性标签
|
||
label = gui_manager.createGUIButton(
|
||
pos=(0.75, 0, y_pos),
|
||
text=f"{name}:",
|
||
size=0.03
|
||
)
|
||
self.gui_elements.append(label)
|
||
|
||
# 属性值显示
|
||
value_label = gui_manager.createGUIButton(
|
||
pos=(0.88, 0, y_pos),
|
||
text=f"{getattr(self.navmesh_manager, prop, 0.0):.2f}",
|
||
size=0.03
|
||
)
|
||
self.gui_elements.append(value_label)
|
||
|
||
# 减少按钮
|
||
dec_button = gui_manager.createGUIButton(
|
||
pos=(0.83, 0, y_pos),
|
||
text="-",
|
||
size=0.03
|
||
)
|
||
self.gui_elements.append(dec_button)
|
||
|
||
# 增加按钮
|
||
inc_button = gui_manager.createGUIButton(
|
||
pos=(0.93, 0, y_pos),
|
||
text="+",
|
||
size=0.03
|
||
)
|
||
self.gui_elements.append(inc_button)
|
||
|
||
# 注册事件
|
||
self.world.accept(f"dec_{prop}", lambda p=prop, s=step: self._decrease_property(p, s))
|
||
self.world.accept(f"inc_{prop}", lambda p=prop, s=step: self._increase_property(p, s))
|
||
|
||
# 存储控制信息
|
||
self.property_controls[prop] = {
|
||
'name': name,
|
||
'min': min_val,
|
||
'max': max_val,
|
||
'step': step,
|
||
'value_label': value_label,
|
||
'dec_button': dec_button,
|
||
'inc_button': inc_button
|
||
}
|
||
|
||
def _cleanup_editor_gui(self):
|
||
"""清理编辑器GUI"""
|
||
try:
|
||
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
|
||
gui_manager = self.world.gui_manager
|
||
|
||
for element in self.gui_elements:
|
||
gui_manager.deleteGUIElement(element)
|
||
|
||
# 清理事件监听
|
||
tools = ["select", "add", "delete", "paint"]
|
||
for tool in tools:
|
||
self.world.ignore(f"navmesh_tool_{tool}")
|
||
|
||
functions = ["生成网格", "加载网格", "保存网格", "清除网格", "撤销", "重做"]
|
||
for func in functions:
|
||
self.world.ignore(f"navmesh_func_{func}")
|
||
|
||
# 清理属性控制事件
|
||
properties = ["cell_size", "cell_height", "agent_height", "agent_radius",
|
||
"max_slope", "brush_size"]
|
||
for prop in properties:
|
||
self.world.ignore(f"dec_{prop}")
|
||
self.world.ignore(f"inc_{prop}")
|
||
|
||
self.gui_elements.clear()
|
||
self.tool_buttons.clear()
|
||
self.property_controls.clear()
|
||
|
||
except Exception as e:
|
||
print(f"⚠ 编辑器GUI清理失败: {e}")
|
||
|
||
def _decrease_property(self, prop_name: str, step: float):
|
||
"""减少属性值"""
|
||
if hasattr(self.navmesh_manager, prop_name):
|
||
current_value = getattr(self.navmesh_manager, prop_name)
|
||
control = self.property_controls[prop_name]
|
||
new_value = max(control['min'], current_value - step)
|
||
setattr(self.navmesh_manager, prop_name, new_value)
|
||
|
||
# 更新显示
|
||
if 'value_label' in control:
|
||
control['value_label'].setText(f"{new_value:.2f}")
|
||
|
||
def _increase_property(self, prop_name: str, step: float):
|
||
"""增加属性值"""
|
||
if hasattr(self.navmesh_manager, prop_name):
|
||
current_value = getattr(self.navmesh_manager, prop_name)
|
||
control = self.property_controls[prop_name]
|
||
new_value = min(control['max'], current_value + step)
|
||
setattr(self.navmesh_manager, prop_name, new_value)
|
||
|
||
# 更新显示
|
||
if 'value_label' in control:
|
||
control['value_label'].setText(f"{new_value:.2f}")
|
||
|
||
def set_edit_tool(self, tool: str):
|
||
"""设置编辑工具"""
|
||
self.edit_tool = tool
|
||
print(f"✓ 切换到 {tool} 工具")
|
||
|
||
# 更新按钮状态
|
||
for tool_name, button in self.tool_buttons.items():
|
||
if tool_name == tool:
|
||
# 高亮选中工具
|
||
button.setColor(0.5, 1.0, 0.5, 1.0) # 绿色
|
||
else:
|
||
# 恢复默认颜色
|
||
button.setColor(1.0, 1.0, 1.0, 1.0) # 白色
|
||
|
||
def generate_navmesh(self):
|
||
"""生成导航网格"""
|
||
if self.navmesh_manager:
|
||
success = self.navmesh_manager.generate_from_scene()
|
||
if success:
|
||
print("✓ 导航网格生成完成")
|
||
else:
|
||
print("✗ 导航网格生成失败")
|
||
|
||
def load_navmesh(self):
|
||
"""加载导航网格"""
|
||
# 简化实现:加载测试网格
|
||
if self.navmesh_manager:
|
||
self.navmesh_manager._create_test_navmesh()
|
||
print("✓ 导航网格已加载(测试数据)")
|
||
|
||
def save_navmesh(self):
|
||
"""保存导航网格"""
|
||
print("⚠ 保存功能需要实现文件对话框")
|
||
|
||
def clear_navmesh(self):
|
||
"""清除导航网格"""
|
||
if self.navmesh_manager:
|
||
self.navmesh_manager.polygons.clear()
|
||
self.navmesh_manager._update_navmesh_visualization()
|
||
print("✓ 导航网格已清除")
|
||
|
||
def undo(self):
|
||
"""撤销操作"""
|
||
if self.history_index >= 0:
|
||
self.history_index -= 1
|
||
print("✓ 撤销操作")
|
||
else:
|
||
print("⚠ 没有可撤销的操作")
|
||
|
||
def redo(self):
|
||
"""重做操作"""
|
||
if self.history_index < len(self.edit_history) - 1:
|
||
self.history_index += 1
|
||
print("✓ 重做操作")
|
||
else:
|
||
print("⚠ 没有可重做的操作")
|
||
|
||
def _add_to_history(self, operation: Dict[str, Any]):
|
||
"""添加操作到历史记录"""
|
||
# 移除历史记录中超过当前索引的部分
|
||
if self.history_index < len(self.edit_history) - 1:
|
||
self.edit_history = self.edit_history[:self.history_index + 1]
|
||
|
||
# 添加新操作
|
||
self.edit_history.append(operation)
|
||
|
||
# 限制历史记录大小
|
||
if len(self.edit_history) > self.max_history_size:
|
||
self.edit_history.pop(0)
|
||
else:
|
||
self.history_index += 1
|
||
|
||
def select_polygon(self, polygon_id: int):
|
||
"""选择多边形"""
|
||
self.selected_polygon = polygon_id
|
||
print(f"✓ 选择多边形: {polygon_id}")
|
||
|
||
def add_polygon(self, vertices: List[Point3]):
|
||
"""添加多边形"""
|
||
if self.navmesh_manager:
|
||
polygon_id = self.navmesh_manager.add_polygon(vertices)
|
||
print(f"✓ 添加多边形: {polygon_id}")
|
||
|
||
# 添加到历史记录
|
||
operation = {
|
||
'type': 'add_polygon',
|
||
'polygon_id': polygon_id
|
||
}
|
||
self._add_to_history(operation)
|
||
|
||
def delete_polygon(self, polygon_id: int):
|
||
"""删除多边形"""
|
||
if self.navmesh_manager:
|
||
success = self.navmesh_manager.remove_polygon(polygon_id)
|
||
if success:
|
||
print(f"✓ 删除多边形: {polygon_id}")
|
||
|
||
# 添加到历史记录
|
||
operation = {
|
||
'type': 'delete_polygon',
|
||
'polygon_id': polygon_id
|
||
}
|
||
self._add_to_history(operation)
|
||
else:
|
||
print(f"⚠ 无法删除多边形: {polygon_id}")
|
||
|
||
def paint_walkable_area(self, center: Point3):
|
||
"""绘制可行走区域"""
|
||
print(f"✓ 在 {center} 附近绘制可行走区域")
|
||
|
||
def toggle_edit_mode(self):
|
||
"""切换编辑模式"""
|
||
self.edit_mode = not self.edit_mode
|
||
if self.edit_mode:
|
||
print("✓ 进入编辑模式")
|
||
else:
|
||
print("✓ 退出编辑模式")
|
||
|
||
def set_brush_size(self, size: float):
|
||
"""设置笔刷大小"""
|
||
self.brush_size = max(0.1, size)
|
||
print(f"✓ 笔刷大小设置为: {self.brush_size:.2f}")
|
||
|
||
def get_editor_state(self) -> Dict[str, Any]:
|
||
"""获取编辑器状态"""
|
||
return {
|
||
'visible': self.visible,
|
||
'edit_mode': self.edit_mode,
|
||
'selected_tool': self.edit_tool,
|
||
'selected_polygon': self.selected_polygon,
|
||
'brush_size': self.brush_size,
|
||
'polygon_count': len(self.navmesh_manager.polygons) if self.navmesh_manager else 0
|
||
}
|
||
|
||
def set_editor_state(self, state: Dict[str, Any]):
|
||
"""设置编辑器状态"""
|
||
try:
|
||
if 'edit_mode' in state:
|
||
self.edit_mode = state['edit_mode']
|
||
|
||
if 'selected_tool' in state:
|
||
self.set_edit_tool(state['selected_tool'])
|
||
|
||
if 'brush_size' in state:
|
||
self.set_brush_size(state['brush_size'])
|
||
|
||
print("✓ 编辑器状态已恢复")
|
||
|
||
except Exception as e:
|
||
print(f"⚠ 恢复编辑器状态失败: {e}")
|
||
|
||
def handle_mouse_click(self, mouse_x: float, mouse_y: float):
|
||
"""处理鼠标点击事件"""
|
||
if not self.edit_mode:
|
||
return
|
||
|
||
# 将屏幕坐标转换为世界坐标(简化实现)
|
||
world_x = mouse_x * 20 # 假设场景大小为20x20
|
||
world_z = mouse_y * 20
|
||
click_point = Point3(world_x, 0, world_z)
|
||
|
||
if self.edit_tool == "add":
|
||
# 添加多边形(简化为正方形)
|
||
half_size = 0.5
|
||
vertices = [
|
||
Point3(world_x - half_size, 0, world_z - half_size),
|
||
Point3(world_x + half_size, 0, world_z - half_size),
|
||
Point3(world_x + half_size, 0, world_z + half_size),
|
||
Point3(world_x - half_size, 0, world_z + half_size)
|
||
]
|
||
self.add_polygon(vertices)
|
||
|
||
elif self.edit_tool == "delete":
|
||
# 删除多边形
|
||
if self.navmesh_manager:
|
||
polygon = self.navmesh_manager.get_polygon_containing_point(click_point)
|
||
if polygon:
|
||
self.delete_polygon(polygon.polygon_id)
|
||
|
||
elif self.edit_tool == "paint":
|
||
# 绘制可行走区域
|
||
self.paint_walkable_area(click_point)
|
||
|
||
elif self.edit_tool == "select":
|
||
# 选择多边形
|
||
if self.navmesh_manager:
|
||
polygon = self.navmesh_manager.get_polygon_containing_point(click_point)
|
||
if polygon:
|
||
self.select_polygon(polygon.polygon_id)
|
||
|
||
def update(self, dt: float):
|
||
"""更新编辑器状态"""
|
||
# 处理键盘输入
|
||
if self.world.mouseWatcherNode.hasMouse():
|
||
mouse_pos = self.world.mouseWatcherNode.getMouse()
|
||
|
||
# 检查鼠标按键
|
||
if self.world.mouseWatcherNode.isButtonDown(MouseWatcher.getGlobalPointer()):
|
||
if self.last_mouse_pos is None or \
|
||
(mouse_pos - self.last_mouse_pos).length() > 0.01:
|
||
self.handle_mouse_click(mouse_pos.x, mouse_pos.y)
|
||
self.last_mouse_pos = mouse_pos
|
||
|
||
# 检查键盘快捷键
|
||
if self.world.mouseWatcherNode.isButtonDown(KeyboardButton.asciiKey('e')):
|
||
self.toggle_edit_mode()
|
||
|
||
def cleanup(self):
|
||
"""清理编辑器资源"""
|
||
self.hide()
|
||
|
||
# 清理历史记录
|
||
self.edit_history.clear()
|
||
self.history_index = -1
|
||
|
||
# 清理状态
|
||
self.selected_polygon = None
|
||
self.edit_mode = False |