#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys from direct.showbase.ShowBase import ShowBase from panda3d.core import ( Point3, Point2, Vec3, CardMaker, CollisionNode, CollisionSphere, BitMask32, PerspectiveLens ) class ProjectionPickingTest(ShowBase): def __init__(self): ShowBase.__init__(self) # 相机设置 self.cam.setPos(-3.87655, -188.38084, 82.602684) self.cam.lookAt(0, 0, 0) print("=== 屏幕投影点击检测测试 ===") # 创建测试对象 self.createTestObjects() # 测试投影检测 self.testProjectionPicking() def createTestObjects(self): """创建测试对象""" self.objects = [] # 创建几个测试立方体 positions = [ (0, 10, 3, "中心立方体"), (-5, 10, 3, "左侧立方体"), (5, 10, 3, "右侧立方体"), (0, 10, 8, "上方立方体"), (0, 15, 3, "远处立方体") ] colors = [ (1, 0, 0, 1), # 红色 (0, 1, 0, 1), # 绿色 (0, 0, 1, 1), # 蓝色 (1, 1, 0, 1), # 黄色 (1, 0, 1, 1), # 紫色 ] for i, ((x, y, z, name), color) in enumerate(zip(positions, colors)): # 创建立方体 cm = CardMaker(f"cube_{i}") cm.setFrame(-1, 1, -1, 1) cube = self.render.attachNewNode(cm.generate()) cube.setPos(x, y, z) cube.setScale(2, 2, 2) cube.setColor(*color) cube.setBillboardAxis() cube.setName(name) # 存储对象信息 obj_info = { 'node': cube, 'name': name, 'world_pos': Point3(x, y, z), 'radius': 2.0 # 立方体的半径 } self.objects.append(obj_info) print(f"创建 {name} 在位置 ({x}, {y}, {z})") def worldToScreen(self, worldPos): """将世界坐标转换为屏幕坐标""" # 获取相机的投影矩阵和模型视图矩阵 lens = self.cam.node().getLens() # 将世界坐标转换到相机坐标系 camPos = self.cam.getRelativePoint(self.render, worldPos) # 使用镜头投影到屏幕坐标 screenPoint = Point2() if lens.project(camPos, screenPoint): # 转换为窗口像素坐标 winWidth = self.win.getXSize() winHeight = self.win.getYSize() # 归一化坐标转换为像素坐标 pixelX = (screenPoint.getX() + 1.0) * winWidth * 0.5 pixelY = (1.0 - screenPoint.getY()) * winHeight * 0.5 return pixelX, pixelY, True else: return 0, 0, False def getObjectScreenRadius(self, obj_info): """计算对象在屏幕上的半径""" worldPos = obj_info['world_pos'] worldRadius = obj_info['radius'] # 计算对象中心在屏幕上的位置 centerX, centerY, visible = self.worldToScreen(worldPos) if not visible: return 0 # 计算对象边缘点在屏幕上的位置 # 使用对象右侧边缘点 edgePoint = worldPos + Vec3(worldRadius, 0, 0) edgeX, edgeY, edge_visible = self.worldToScreen(edgePoint) if edge_visible: # 计算屏幕半径 screenRadius = abs(edgeX - centerX) return screenRadius return 0 def pickObjectByProjection(self, mouseX, mouseY): """使用投影方法选择对象""" print(f"\n=== 投影检测: 鼠标位置 ({mouseX}, {mouseY}) ===") closest_obj = None closest_distance = float('inf') for obj_info in self.objects: # 计算对象在屏幕上的位置 screenX, screenY, visible = self.worldToScreen(obj_info['world_pos']) if not visible: print(f"{obj_info['name']}: 不可见") continue # 计算屏幕半径 screenRadius = self.getObjectScreenRadius(obj_info) # 计算鼠标到对象中心的距离 dx = mouseX - screenX dy = mouseY - screenY distance = (dx * dx + dy * dy) ** 0.5 print(f"{obj_info['name']}: 屏幕位置({screenX:.1f}, {screenY:.1f}), 半径{screenRadius:.1f}, 距离{distance:.1f}") # 检查是否在点击范围内 if distance <= screenRadius: if distance < closest_distance: closest_distance = distance closest_obj = obj_info print(f" -> 在点击范围内!") if closest_obj: print(f"\n选中对象: {closest_obj['name']}") return closest_obj else: print(f"\n没有选中任何对象") return None def testProjectionPicking(self): """测试投影点击检测""" print(f"\n=== 投影点击检测测试 ===") # 模拟不同的点击位置 test_clicks = [ (619, 362, "屏幕中心"), (400, 362, "屏幕偏左"), (800, 362, "屏幕偏右"), (619, 200, "屏幕上方"), (619, 500, "屏幕下方"), ] for mouseX, mouseY, description in test_clicks: print(f"\n--- 测试 {description}: ({mouseX}, {mouseY}) ---") selected = self.pickObjectByProjection(mouseX, mouseY) if __name__ == "__main__": app = ProjectionPickingTest() print("投影检测测试完成")