This commit is contained in:
Rowland 2026-01-23 15:05:57 +08:00
parent 35d28f4e2b
commit bde899a04f

685
demo.py
View File

@ -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: