EG/demo/depth_picking_test.py
2025-07-02 09:49:59 +08:00

197 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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