197 lines
6.6 KiB
Python
197 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import sys
|
||
from direct.showbase.ShowBase import ShowBase
|
||
from panda3d.core import (
|
||
Point3, Vec3, CardMaker, Texture, GraphicsOutput,
|
||
FrameBufferProperties, WindowProperties, RenderState,
|
||
ColorWriteAttrib, DepthTestAttrib, ColorBlendAttrib,
|
||
BitMask32, PNMImage, Filename
|
||
)
|
||
|
||
class DepthPickingTest(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.setupDepthBuffer()
|
||
|
||
# 测试深度检测
|
||
self.testDepthPicking()
|
||
|
||
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),
|
||
'id': i + 1 # 用于颜色编码
|
||
}
|
||
self.objects.append(obj_info)
|
||
|
||
print(f"创建 {name} 在位置 ({x}, {y}, {z})")
|
||
|
||
def setupDepthBuffer(self):
|
||
"""设置深度缓冲区读取"""
|
||
# 获取主窗口的深度纹理
|
||
self.depth_tex = Texture()
|
||
self.depth_tex.setFormat(Texture.FDepthComponent)
|
||
self.depth_tex.setComponentType(Texture.TFloat)
|
||
|
||
# 将深度纹理绑定到主窗口
|
||
self.win.addRenderTexture(self.depth_tex, GraphicsOutput.RTPDepth)
|
||
|
||
print("深度缓冲区设置完成")
|
||
|
||
def getDepthAtPixel(self, mouseX, mouseY):
|
||
"""获取指定像素的深度值"""
|
||
# 获取窗口大小
|
||
winWidth = self.win.getXSize()
|
||
winHeight = self.win.getYSize()
|
||
|
||
# 确保坐标在窗口范围内
|
||
if mouseX < 0 or mouseX >= winWidth or mouseY < 0 or mouseY >= winHeight:
|
||
return None
|
||
|
||
# 强制渲染以更新深度缓冲区
|
||
self.graphicsEngine.renderFrame()
|
||
|
||
# 读取深度纹理
|
||
if self.depth_tex.hasRamImage():
|
||
# 获取深度图像
|
||
pnm = PNMImage()
|
||
self.depth_tex.store(pnm)
|
||
|
||
# Y坐标需要翻转(OpenGL坐标系)
|
||
flippedY = winHeight - 1 - mouseY
|
||
|
||
if flippedY < pnm.getYSize() and mouseX < pnm.getXSize():
|
||
# 获取深度值(0.0 = 近平面,1.0 = 远平面)
|
||
depth = pnm.getGray(mouseX, flippedY)
|
||
return depth
|
||
|
||
return None
|
||
|
||
def depthToWorldDistance(self, depth):
|
||
"""将深度值转换为世界距离"""
|
||
if depth is None:
|
||
return None
|
||
|
||
# 获取相机镜头参数
|
||
lens = self.cam.node().getLens()
|
||
near = lens.getNear()
|
||
far = lens.getFar()
|
||
|
||
# 将归一化深度值转换为线性距离
|
||
# 这是透视投影的逆变换
|
||
z_ndc = 2.0 * depth - 1.0 # 转换到 [-1, 1] 范围
|
||
z_eye = 2.0 * near * far / (far + near - z_ndc * (far - near))
|
||
|
||
return abs(z_eye)
|
||
|
||
def pickObjectByDepth(self, mouseX, mouseY):
|
||
"""使用深度检测选择对象"""
|
||
print(f"\n=== 深度检测: 鼠标位置 ({mouseX}, {mouseY}) ===")
|
||
|
||
# 获取点击位置的深度
|
||
depth = self.getDepthAtPixel(mouseX, mouseY)
|
||
if depth is None:
|
||
print("无法读取深度值")
|
||
return None
|
||
|
||
# 转换为世界距离
|
||
worldDistance = self.depthToWorldDistance(depth)
|
||
print(f"深度值: {depth:.6f}, 世界距离: {worldDistance:.2f}")
|
||
|
||
# 查找距离相机最近且在该深度附近的对象
|
||
closest_obj = None
|
||
min_distance_diff = float('inf')
|
||
|
||
cam_pos = self.cam.getPos()
|
||
|
||
for obj_info in self.objects:
|
||
# 计算对象到相机的距离
|
||
obj_distance = (obj_info['world_pos'] - cam_pos).length()
|
||
distance_diff = abs(obj_distance - worldDistance)
|
||
|
||
print(f"{obj_info['name']}: 距离相机 {obj_distance:.2f}, 差值 {distance_diff:.2f}")
|
||
|
||
# 使用一定的容差(因为对象有体积)
|
||
tolerance = 3.0 # 容差范围
|
||
if distance_diff <= tolerance and distance_diff < min_distance_diff:
|
||
min_distance_diff = distance_diff
|
||
closest_obj = obj_info
|
||
print(f" -> 可能被选中 (差值: {distance_diff:.2f})")
|
||
|
||
if closest_obj:
|
||
print(f"\n选中对象: {closest_obj['name']}")
|
||
return closest_obj
|
||
else:
|
||
print(f"\n没有找到匹配的对象")
|
||
return None
|
||
|
||
def testDepthPicking(self):
|
||
"""测试深度点击检测"""
|
||
print(f"\n=== 深度点击检测测试 ===")
|
||
|
||
# 等待几帧以确保场景渲染完成
|
||
for i in range(5):
|
||
self.graphicsEngine.renderFrame()
|
||
|
||
# 模拟不同的点击位置
|
||
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.pickObjectByDepth(mouseX, mouseY)
|
||
|
||
if __name__ == "__main__":
|
||
app = DepthPickingTest()
|
||
print("深度检测测试完成") |