diff --git a/demo.py b/demo.py index 7daa4e03..c2c6f951 100644 --- a/demo.py +++ b/demo.py @@ -174,6 +174,9 @@ class MyWorld(CoreWorld): # 默认启用模型间碰撞检测(可选) self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5) + + # 碰撞检测UI相关变量 + self._selected_collision_shape = "球形 (Sphere)" # 默认选择的碰撞形状 # 启动脚本系统 self.script_manager.start_system() @@ -1408,7 +1411,7 @@ class MyWorld(CoreWorld): imgui.spacing() # 物体名称组(使用Qt版本的样式) - if imgui.collapsing_header("物体名称", True): + if imgui.collapsing_header("物体名称"): # 第一行:可见性复选框和名称输入 user_visible = node.getPythonTag("user_visible") if user_visible is None: @@ -1447,7 +1450,7 @@ class MyWorld(CoreWorld): imgui.spacing() # 变换属性组 - if imgui.collapsing_header("变换 Transform", True): + if imgui.collapsing_header("变换 Transform"): self._draw_transform_properties(node) # 根据节点类型显示特定属性组 @@ -1465,6 +1468,10 @@ class MyWorld(CoreWorld): if imgui.collapsing_header("外观属性"): self._draw_appearance_properties(node) + # 碰撞检测组 + if imgui.collapsing_header("碰撞检测"): + self._draw_collision_properties(node) + # 操作按钮组 if imgui.collapsing_header("操作"): self._draw_property_actions(node) @@ -1554,7 +1561,7 @@ class MyWorld(CoreWorld): def _draw_transform_properties(self, node): """绘制变换属性""" # 位置组 - if imgui.collapsing_header("位置 Position", True): + if imgui.collapsing_header("位置 Position"): # 相对位置 imgui.text("相对位置") pos = node.getPos() @@ -1654,7 +1661,7 @@ class MyWorld(CoreWorld): imgui.text(f"GUI类型: {gui_type}") # 基本属性 - if imgui.collapsing_header("基本属性", True): + if imgui.collapsing_header("基本属性"): # 文本内容 (适用于按钮、标签等) if hasattr(gui_element, 'text'): changed, new_text = imgui.input_text("文本内容", gui_element.text, 256) @@ -1956,6 +1963,674 @@ class MyWorld(CoreWorld): # 材质数量 imgui.text("材质数量: (暂不支持显示)") + def _draw_collision_properties(self, node): + """绘制碰撞检测属性""" + if not node or node.isEmpty(): + return + + try: + # 检查节点是否已有碰撞 + has_collision = self._has_collision(node) + + # 碰撞状态徽章 + imgui.text("状态:") + imgui.same_line() + if has_collision: + imgui.text_colored((0.0, 0.8, 0.0, 1.0), "🟢 已启用") + else: + imgui.text_colored((0.8, 0.0, 0.0, 1.0), "🔴 未启用") + + imgui.separator() + + # 碰撞形状选择 + imgui.text("碰撞形状:") + imgui.same_line() + + # 碰撞形状选项 + collision_shapes = ["球形 (Sphere)", "盒型 (Box)", "胶囊体 (Capsule)", "平面 (Plane)", "自动选择 (Auto)"] + + # 获取当前形状 + current_shape = self._get_current_collision_shape(node) if has_collision else "球形 (Sphere)" + + # 形状选择下拉框 + current_index = collision_shapes.index(current_shape) if current_shape in collision_shapes else 0 + changed, selected_index = imgui.combo("##collision_shape", current_index, collision_shapes) + if changed: + # 始终更新选择的形状 + selected_shape = collision_shapes[selected_index] + self._selected_collision_shape = selected_shape + # 如果已经有碰撞体,询问用户是否要重新创建 + if has_collision: + print(f"形状已更改为 {selected_shape},点击'移除碰撞'后'添加碰撞'来应用新形状") + + imgui.separator() + + # 位置偏移控件 + imgui.text("位置偏移:") + + # 获取当前位置偏移 + pos_offset = self._get_collision_position_offset(node) + + # X位置 + changed, new_x = imgui.drag_float("X##collision_pos_x", pos_offset[0], 0.1, -100.0, 100.0, "%.2f") + if changed and has_collision: + self._update_collision_position(node, 'x', new_x) + + # Y位置 + changed, new_y = imgui.drag_float("Y##collision_pos_y", pos_offset[1], 0.1, -100.0, 100.0, "%.2f") + if changed and has_collision: + self._update_collision_position(node, 'y', new_y) + + # Z位置 + changed, new_z = imgui.drag_float("Z##collision_pos_z", pos_offset[2], 0.1, -100.0, 100.0, "%.2f") + if changed and has_collision: + self._update_collision_position(node, 'z', new_z) + + # 形状特定参数(始终显示,但根据状态启用/禁用) + shape_type = self._get_current_collision_shape_type(node) + self._draw_shape_specific_parameters(node, shape_type, has_collision) + + imgui.separator() + + # 操作按钮 + if has_collision: + # 显示/隐藏碰撞体按钮 + is_visible = self._is_collision_visible(node) + visibility_text = "隐藏碰撞体" if is_visible else "显示碰撞体" + if imgui.button(visibility_text): + self._toggle_collision_visibility(node) + + imgui.same_line() + + # 移除碰撞按钮 + if imgui.button("移除碰撞"): + self._remove_collision_from_node(node) + else: + # 添加碰撞按钮 + if imgui.button("添加碰撞"): + self._add_collision_to_node(node) + + imgui.separator() + + # 碰撞检测触发模式 + imgui.text("触发模式:") + + # 自动检测开关 + auto_enabled = self.collision_manager.model_collision_enabled if hasattr(self, 'collision_manager') else False + changed, new_auto = imgui.checkbox("自动检测", auto_enabled) + if changed and hasattr(self, 'collision_manager'): + self.collision_manager.enableModelCollisionDetection(new_auto, 0.1, 0.5) + + imgui.same_line() + + # 手动检测按钮 + if imgui.button("立即检测"): + if hasattr(self, 'collision_manager'): + self._manual_collision_detection() + + except Exception as e: + print(f"绘制碰撞属性失败: {e}") + import traceback + traceback.print_exc() + + def _has_collision(self, node): + """检查节点是否有碰撞体""" + try: + if not node or node.isEmpty(): + return False + + # 检查是否有碰撞节点 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + return True + + return False + except Exception as e: + print(f"检查碰撞状态失败: {e}") + return False + + def _get_current_collision_shape(self, node): + """获取当前碰撞形状""" + try: + if not self._has_collision(node): + return "球形 (Sphere)" + + # 查找碰撞节点并判断形状 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + # 根据碰撞节点名称判断形状 + if 'sphere' in name.lower(): + return "球形 (Sphere)" + elif 'box' in name.lower(): + return "盒型 (Box)" + elif 'capsule' in name.lower(): + return "胶囊体 (Capsule)" + elif 'plane' in name.lower(): + return "平面 (Plane)" + + return "球形 (Sphere)" # 默认 + except Exception as e: + print(f"获取碰撞形状失败: {e}") + return "球形 (Sphere)" + + def _get_current_collision_shape_type(self, node): + """获取当前碰撞形状类型(内部标识)""" + try: + shape_name = self._get_current_collision_shape(node) + if "Sphere" in shape_name: + return "sphere" + elif "Box" in shape_name: + return "box" + elif "Capsule" in shape_name: + return "capsule" + elif "Plane" in shape_name: + return "plane" + else: + return "sphere" + except Exception as e: + print(f"获取碰撞形状类型失败: {e}") + return "sphere" + + def _get_collision_position_offset(self, node): + """获取碰撞体位置偏移""" + try: + if not self._has_collision(node): + return (0.0, 0.0, 0.0) + + # 查找碰撞节点并获取位置 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + pos = child.getPos() + return (pos.x, pos.y, pos.z) + + return (0.0, 0.0, 0.0) + except Exception as e: + print(f"获取碰撞位置失败: {e}") + return (0.0, 0.0, 0.0) + + def _is_collision_visible(self, node): + """检查碰撞体是否可见""" + try: + if not self._has_collision(node): + return False + + # 查找碰撞节点并检查可见性 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + return child.isHidden() == False + + return False + except Exception as e: + print(f"检查碰撞可见性失败: {e}") + return False + + def _add_collision_to_node(self, node): + """为节点添加碰撞体""" + try: + if not node or node.isEmpty(): + print("无效的节点") + return + + if self._has_collision(node): + print("节点已有碰撞体") + return + + # 获取选择的形状 + shape_name = getattr(self, '_selected_collision_shape', '球形 (Sphere)') + + if hasattr(self, 'collision_manager'): + # 使用碰撞管理器添加碰撞体 + shape_type = self._get_shape_type_from_name(shape_name) + collision_node = self.collision_manager.setupAdvancedCollision( + node, + shape_type=shape_type, + mask_type='MODEL_COLLISION' + ) + + if collision_node: + print(f"成功为节点 {node.getName()} 添加 {shape_name} 碰撞体") + else: + print(f"添加碰撞体失败") + else: + print("碰撞管理器未初始化") + + except Exception as e: + print(f"添加碰撞体失败: {e}") + import traceback + traceback.print_exc() + + def _remove_collision_from_node(self, node): + """从节点移除碰撞体""" + try: + if not node or node.isEmpty(): + print("无效的节点") + return + + if not self._has_collision(node): + print("节点没有碰撞体") + return + + # 查找并移除碰撞节点 + children_to_remove = [] + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + children_to_remove.append(child) + + # 移除找到的碰撞节点 + for child in children_to_remove: + child.removeNode() + + if children_to_remove: + print(f"成功移除节点 {node.getName()} 的碰撞体") + else: + print(f"未找到碰撞体") + + except Exception as e: + print(f"移除碰撞体失败: {e}") + import traceback + traceback.print_exc() + + def _toggle_collision_visibility(self, node): + """切换碰撞体可见性""" + try: + if not node or node.isEmpty(): + return + + # 查找碰撞节点并切换可见性 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + if child.isHidden(): + child.show() + else: + child.hide() + break + + except Exception as e: + print(f"切换碰撞可见性失败: {e}") + + def _update_collision_position(self, node, axis, value): + """更新碰撞体位置""" + try: + if not node or node.isEmpty(): + return + + # 查找碰撞节点并更新位置 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + current_pos = child.getPos() + if axis == 'x': + child.setPos(value, current_pos.y, current_pos.z) + elif axis == 'y': + child.setPos(current_pos.x, value, current_pos.z) + elif axis == 'z': + child.setPos(current_pos.x, current_pos.y, value) + break + + except Exception as e: + print(f"更新碰撞位置失败: {e}") + + def _get_shape_type_from_name(self, shape_name): + """从形状名称获取形状类型""" + if "Sphere" in shape_name: + return "sphere" + elif "Box" in shape_name: + return "box" + elif "Capsule" in shape_name: + return "capsule" + elif "Plane" in shape_name: + return "plane" + else: + return "sphere" + + def _draw_shape_specific_parameters(self, node, shape_type, has_collision=True): + """绘制形状特定参数""" + try: + if shape_type == "sphere": + self._draw_sphere_parameters(node, has_collision) + elif shape_type == "box": + self._draw_box_parameters(node, has_collision) + elif shape_type == "capsule": + self._draw_capsule_parameters(node, has_collision) + elif shape_type == "plane": + self._draw_plane_parameters(node, has_collision) + except Exception as e: + print(f"绘制形状参数失败: {e}") + + def _draw_sphere_parameters(self, node, has_collision=True): + """绘制球形参数""" + try: + imgui.text("球形参数:") + imgui.same_line() + + # 获取当前半径 + radius = self._get_sphere_radius(node) + + # 半径调整 + changed, new_radius = imgui.drag_float("半径##sphere_radius", radius, 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_sphere_radius(node, new_radius) + + except Exception as e: + print(f"绘制球形参数失败: {e}") + + def _draw_box_parameters(self, node, has_collision=True): + """绘制盒型参数""" + try: + imgui.text("盒型参数:") + + # 获取当前尺寸 + size = self._get_box_size(node) + + # 尺寸调整 + changed, new_x = imgui.drag_float("长度##box_length", size[0], 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_box_size(node, 'x', new_x) + + changed, new_y = imgui.drag_float("宽度##box_width", size[1], 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_box_size(node, 'y', new_y) + + changed, new_z = imgui.drag_float("高度##box_height", size[2], 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_box_size(node, 'z', new_z) + + except Exception as e: + print(f"绘制盒型参数失败: {e}") + + def _draw_capsule_parameters(self, node, has_collision=True): + """绘制胶囊体参数""" + try: + imgui.text("胶囊体参数:") + + # 获取当前参数 + radius = self._get_capsule_radius(node) + height = self._get_capsule_height(node) + + # 半径调整 + changed, new_radius = imgui.drag_float("半径##capsule_radius", radius, 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_capsule_radius(node, new_radius) + + # 高度调整 + changed, new_height = imgui.drag_float("高度##capsule_height", height, 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_capsule_height(node, new_height) + + except Exception as e: + print(f"绘制胶囊体参数失败: {e}") + + def _draw_plane_parameters(self, node, has_collision=True): + """绘制平面参数""" + try: + imgui.text("平面参数:") + + # 获取当前法向量 + normal = self._get_plane_normal(node) + + # 法向量调整 + changed, new_x = imgui.drag_float("法向量 X##plane_normal_x", normal[0], 0.1, -1.0, 1.0, "%.2f") + if changed and has_collision: + self._update_plane_normal(node, 'x', new_x) + + changed, new_y = imgui.drag_float("法向量 Y##plane_normal_y", normal[1], 0.1, -1.0, 1.0, "%.2f") + if changed and has_collision: + self._update_plane_normal(node, 'y', new_y) + + changed, new_z = imgui.drag_float("法向量 Z##plane_normal_z", normal[2], 0.1, -1.0, 1.0, "%.2f") + if changed and has_collision: + self._update_plane_normal(node, 'z', new_z) + + except Exception as e: + print(f"绘制平面参数失败: {e}") + + def _get_sphere_radius(self, node): + """获取球形半径""" + try: + # 从碰撞节点获取半径信息 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0: + solid = child.node().getSolid(0) + from panda3d.core import CollisionSphere + if isinstance(solid, CollisionSphere): + return solid.getRadius() + return 1.0 + except Exception as e: + print(f"获取球形半径失败: {e}") + return 1.0 + + def _update_sphere_radius(self, node, radius): + """更新球形半径""" + try: + # 重新创建碰撞体来更新参数 + if hasattr(self, 'collision_manager'): + # 先移除旧的碰撞体 + self._remove_collision_from_node(node) + # 重新创建带有新参数的碰撞体 + self.collision_manager.setupAdvancedCollision( + node, + shape_type='sphere', + mask_type='MODEL_COLLISION', + radius=radius + ) + print(f"更新球形半径为: {radius}") + except Exception as e: + print(f"更新球形半径失败: {e}") + + def _get_box_size(self, node): + """获取盒型尺寸""" + try: + # 从碰撞节点获取尺寸信息 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + # 尝试从碰撞体获取尺寸 + if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0: + solid = child.node().getSolid(0) + from panda3d.core import CollisionBox + if isinstance(solid, CollisionBox): + min_p = solid.getMin() + max_p = solid.getMax() + return ( + max_p.x - min_p.x, + max_p.y - min_p.y, + max_p.z - min_p.z + ) + return (1.0, 1.0, 1.0) + except Exception as e: + print(f"获取盒型尺寸失败: {e}") + return (1.0, 1.0, 1.0) + + def _update_box_size(self, node, axis, value): + """更新盒型尺寸""" + try: + # 获取当前尺寸 + current_size = self._get_box_size(node) + new_size = list(current_size) + + # 更新指定轴的尺寸 + if axis == 'x': + new_size[0] = value + elif axis == 'y': + new_size[1] = value + elif axis == 'z': + new_size[2] = value + + # 重新创建碰撞体 + if hasattr(self, 'collision_manager'): + self._remove_collision_from_node(node) + self.collision_manager.setupAdvancedCollision( + node, + shape_type='box', + mask_type='MODEL_COLLISION', + width=new_size[0], + length=new_size[1], + height=new_size[2] + ) + print(f"更新盒型尺寸: {new_size}") + except Exception as e: + print(f"更新盒型尺寸失败: {e}") + + def _get_capsule_radius(self, node): + """获取胶囊体半径""" + try: + # 从碰撞节点获取半径信息 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0: + solid = child.node().getSolid(0) + from panda3d.core import CollisionCapsule + if isinstance(solid, CollisionCapsule): + return solid.getRadius() + return 1.0 + except Exception as e: + print(f"获取胶囊体半径失败: {e}") + return 1.0 + + def _update_capsule_radius(self, node, radius): + """更新胶囊体半径""" + try: + # 获取当前高度 + height = self._get_capsule_height(node) + + # 重新创建碰撞体 + if hasattr(self, 'collision_manager'): + self._remove_collision_from_node(node) + self.collision_manager.setupAdvancedCollision( + node, + shape_type='capsule', + mask_type='MODEL_COLLISION', + radius=radius, + height=height + ) + print(f"更新胶囊体半径为: {radius}") + except Exception as e: + print(f"更新胶囊体半径失败: {e}") + + def _get_capsule_height(self, node): + """获取胶囊体高度""" + try: + # 从碰撞节点获取高度信息 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0: + solid = child.node().getSolid(0) + from panda3d.core import CollisionCapsule + if isinstance(solid, CollisionCapsule): + point_a = solid.getPointA() + point_b = solid.getPointB() + return (point_b - point_a).length() + 2 * solid.getRadius() + return 2.0 + except Exception as e: + print(f"获取胶囊体高度失败: {e}") + return 2.0 + + def _update_capsule_height(self, node, height): + """更新胶囊体高度""" + try: + # 获取当前半径 + radius = self._get_capsule_radius(node) + + # 重新创建碰撞体 + if hasattr(self, 'collision_manager'): + self._remove_collision_from_node(node) + self.collision_manager.setupAdvancedCollision( + node, + shape_type='capsule', + mask_type='MODEL_COLLISION', + radius=radius, + height=height + ) + print(f"更新胶囊体高度为: {height}") + except Exception as e: + print(f"更新胶囊体高度失败: {e}") + + def _get_plane_normal(self, node): + """获取平面法向量""" + try: + # 从碰撞节点获取法向量信息 + for child in node.getChildren(): + if hasattr(child, 'getName') and child.getName(): + name = child.getName() + if 'collision' in name.lower() or 'Collision' in name: + if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0: + solid = child.node().getSolid(0) + from panda3d.core import CollisionPlane + if isinstance(solid, CollisionPlane): + plane = solid.getPlane() + normal = plane.getNormal() + return (normal.x, normal.y, normal.z) + return (0.0, 0.0, 1.0) + except Exception as e: + print(f"获取平面法向量失败: {e}") + return (0.0, 0.0, 1.0) + + def _update_plane_normal(self, node, axis, value): + """更新平面法向量""" + try: + # 获取当前法向量 + current_normal = self._get_plane_normal(node) + new_normal = list(current_normal) + + # 更新指定轴的值 + if axis == 'x': + new_normal[0] = value + elif axis == 'y': + new_normal[1] = value + elif axis == 'z': + new_normal[2] = value + + # 标准化法向量 + from panda3d.core import Vec3 + normal_vec = Vec3(*new_normal) + normal_vec.normalize() + + # 重新创建碰撞体 + if hasattr(self, 'collision_manager'): + self._remove_collision_from_node(node) + self.collision_manager.setupAdvancedCollision( + node, + shape_type='plane', + mask_type='MODEL_COLLISION', + normal=normal_vec + ) + print(f"更新平面法向量为: ({normal_vec.x:.2f}, {normal_vec.y:.2f}, {normal_vec.z:.2f})") + except Exception as e: + print(f"更新平面法向量失败: {e}") + + def _manual_collision_detection(self): + """手动执行碰撞检测""" + try: + if hasattr(self, 'collision_manager'): + results = self.collision_manager.detectModelCollisions(log_results=True) + if results: + print(f"手动碰撞检测完成,发现 {len(results)} 个碰撞") + else: + print("手动碰撞检测完成,未发现碰撞") + except Exception as e: + print(f"手动碰撞检测失败: {e}") + def _draw_property_actions(self, node): """绘制属性操作按钮""" # 重置变换 @@ -2082,7 +2757,7 @@ class MyWorld(CoreWorld): for i, material in enumerate(materials): material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}" - if imgui.collapsing_header(f"材质: {material_name}", True): + if imgui.collapsing_header(f"材质: {material_name}"): # 材质基础颜色 base_color = self._get_material_base_color(material) if base_color: