EG/demo/projection_picking_test.py
2025-12-12 16:16:15 +08:00

172 lines
5.9 KiB
Python

#!/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("投影检测测试完成")