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