#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
    Point3, Point2, Vec3, Vec4, CardMaker, CollisionTraverser,
    CollisionHandlerQueue, CollisionNode, CollisionRay, CollisionSphere,
    BitMask32, PerspectiveLens
)

class ImprovedPickingTest(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.testAllPickingMethods()
    
    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)
            
            # 为每个对象添加球形碰撞体（用于精确的碰撞检测）
            collider = cube.attachNewNode(CollisionNode(f"cube_collider_{i}"))
            collider.node().addSolid(CollisionSphere(0, 0, 0, 2.0))
            collider.node().setFromCollideMask(BitMask32.bit(0))
            
            # 存储对象信息
            obj_info = {
                'node': cube,
                'name': name,
                'world_pos': Point3(x, y, z),
                'radius': 2.0,
                'collider': collider
            }
            self.objects.append(obj_info)
            
            print(f"创建 {name} 在位置 ({x}, {y}, {z})")
    
    # 方法1：改进的射线检测（从相机发出）
    def pickByRayFromCamera(self, mouseX, mouseY):
        """使用从相机发出的射线进行检测"""
        print(f"\n=== 方法1: 从相机发出的射线检测 ===")
        
        # 获取窗口尺寸
        winWidth = self.win.getXSize()
        winHeight = self.win.getYSize()
        
        # 转换为归一化坐标
        normalizedX = (2.0 * mouseX / winWidth) - 1.0
        normalizedY = 1.0 - (2.0 * mouseY / winHeight)
        
        print(f"鼠标位置: ({mouseX}, {mouseY})")
        print(f"归一化坐标: ({normalizedX:.6f}, {normalizedY:.6f})")
        
        # 获取相机镜头
        lens = self.cam.node().getLens()
        
        # 计算射线在相机坐标系中的方向
        # 使用镜头的逆投影来获取正确的射线方向
        near_point = Point3()
        far_point = Point3()
        
        if lens.extrude(Point2(normalizedX, normalizedY), near_point, far_point):
            # 转换到世界坐标系
            near_world = self.cam.getRelativePoint(self.render, near_point)
            far_world = self.cam.getRelativePoint(self.render, far_point)
            
            # 计算射线方向
            ray_origin = near_world
            ray_direction = (far_world - near_world).normalized()
            
            print(f"射线起点 (相机近平面): {near_world}")
            print(f"射线方向: {ray_direction}")
            
            # 手动检测与对象的交集
            return self.intersectRayWithObjects(ray_origin, ray_direction)
        else:
            print("无法生成射线")
            return None
    
    def intersectRayWithObjects(self, ray_origin, ray_direction):
        """手动计算射线与对象的交集"""
        closest_obj = None
        closest_distance = float('inf')
        
        for obj_info in self.objects:
            # 计算射线与球体的交点
            center = obj_info['world_pos']
            radius = obj_info['radius']
            
            # 射线到球心的向量
            oc = ray_origin - center
            
            # 二次方程系数
            a = ray_direction.dot(ray_direction)
            b = 2.0 * oc.dot(ray_direction)
            c = oc.dot(oc) - radius * radius
            
            # 判别式
            discriminant = b * b - 4 * a * c
            
            if discriminant >= 0:
                # 有交点，计算距离
                sqrt_discriminant = discriminant ** 0.5
                t1 = (-b - sqrt_discriminant) / (2 * a)
                t2 = (-b + sqrt_discriminant) / (2 * a)
                
                # 取最近的正值交点
                t = t1 if t1 > 0 else t2
                if t > 0:
                    distance = t
                    if distance < closest_distance:
                        closest_distance = distance
                        closest_obj = obj_info
                    print(f"{obj_info['name']}: 交点距离 {distance:.2f}")
                else:
                    print(f"{obj_info['name']}: 在射线后方")
            else:
                print(f"{obj_info['name']}: 无交点")
        
        if closest_obj:
            print(f"选中: {closest_obj['name']}")
        else:
            print("没有选中任何对象")
        
        return closest_obj
    
    # 方法2：基于屏幕投影的检测
    def pickByProjection(self, mouseX, mouseY):
        """使用屏幕投影进行检测"""
        print(f"\n=== 方法2: 屏幕投影检测 ===")
        
        closest_obj = None
        closest_distance = float('inf')
        
        for obj_info in self.objects:
            # 将3D位置投影到屏幕
            screenPos = self.worldToScreen(obj_info['world_pos'])
            if screenPos:
                screenX, screenY = screenPos
                
                # 计算屏幕半径
                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 and distance < closest_distance:
                    closest_distance = distance
                    closest_obj = obj_info
            else:
                print(f"{obj_info['name']}: 不在屏幕内")
        
        if closest_obj:
            print(f"选中: {closest_obj['name']}")
        else:
            print("没有选中任何对象")
        
        return closest_obj
    
    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
        return None
    
    def getObjectScreenRadius(self, obj_info):
        """计算对象在屏幕上的半径"""
        worldPos = obj_info['world_pos']
        worldRadius = obj_info['radius']
        
        centerScreen = self.worldToScreen(worldPos)
        if not centerScreen:
            return 0
        
        edgePos = worldPos + Vec3(worldRadius, 0, 0)
        edgeScreen = self.worldToScreen(edgePos)
        
        if edgeScreen:
            return abs(edgeScreen[0] - centerScreen[0])
        return 0
    
    # 方法3：混合检测（投影预选 + 射线精确）
    def pickByHybrid(self, mouseX, mouseY):
        """使用混合方法进行检测"""
        print(f"\n=== 方法3: 混合检测（投影预选 + 射线精确）===")
        
        # 第一步：使用投影快速预选
        candidates = []
        
        for obj_info in self.objects:
            screenPos = self.worldToScreen(obj_info['world_pos'])
            if screenPos:
                screenX, screenY = screenPos
                screenRadius = self.getObjectScreenRadius(obj_info)
                
                dx = mouseX - screenX
                dy = mouseY - screenY
                distance = (dx * dx + dy * dy) ** 0.5
                
                # 使用更大的容差进行预选
                tolerance = screenRadius * 1.5
                if distance <= tolerance:
                    candidates.append((obj_info, distance))
                    print(f"预选候选: {obj_info['name']}, 屏幕距离: {distance:.1f}")
        
        if not candidates:
            print("投影预选: 没有候选对象")
            return None
        
        # 第二步：对候选对象使用精确射线检测
        print("对候选对象进行精确射线检测:")
        winWidth = self.win.getXSize()
        winHeight = self.win.getYSize()
        
        normalizedX = (2.0 * mouseX / winWidth) - 1.0
        normalizedY = 1.0 - (2.0 * mouseY / winHeight)
        
        lens = self.cam.node().getLens()
        near_point = Point3()
        far_point = Point3()
        
        if lens.extrude(Point2(normalizedX, normalizedY), near_point, far_point):
            near_world = self.cam.getRelativePoint(self.render, near_point)
            far_world = self.cam.getRelativePoint(self.render, far_point)
            
            ray_origin = near_world
            ray_direction = (far_world - near_world).normalized()
            
            # 只检测候选对象
            closest_obj = None
            closest_distance = float('inf')
            
            for obj_info, _ in candidates:
                center = obj_info['world_pos']
                radius = obj_info['radius']
                
                oc = ray_origin - center
                a = ray_direction.dot(ray_direction)
                b = 2.0 * oc.dot(ray_direction)
                c = oc.dot(oc) - radius * radius
                
                discriminant = b * b - 4 * a * c
                
                if discriminant >= 0:
                    sqrt_discriminant = discriminant ** 0.5
                    t1 = (-b - sqrt_discriminant) / (2 * a)
                    t2 = (-b + sqrt_discriminant) / (2 * a)
                    
                    t = t1 if t1 > 0 else t2
                    if t > 0 and t < closest_distance:
                        closest_distance = t
                        closest_obj = obj_info
                        print(f"  {obj_info['name']}: 精确射线距离 {t:.2f}")
            
            if closest_obj:
                print(f"最终选中: {closest_obj['name']}")
            else:
                print("精确检测: 没有有效交点")
            
            return closest_obj
        
        return None
    
    def testAllPickingMethods(self):
        """测试所有点击检测方法"""
        print(f"\n=== 测试所有点击检测方法 ===")
        
        # 测试点击位置（模拟点击左侧立方体附近）
        test_clicks = [
            (372, 265, "左侧立方体附近"),
            (399, 265, "中心立方体附近"),
            (425, 265, "右侧立方体附近"),
        ]
        
        for mouseX, mouseY, description in test_clicks:
            print(f"\n{'='*60}")
            print(f"测试位置: {description} ({mouseX}, {mouseY})")
            print(f"{'='*60}")
            
            # 方法1：从相机发出的射线
            result1 = self.pickByRayFromCamera(mouseX, mouseY)
            
            # 方法2：屏幕投影
            result2 = self.pickByProjection(mouseX, mouseY)
            
            # 方法3：混合方法
            result3 = self.pickByHybrid(mouseX, mouseY)
            
            # 比较结果
            print(f"\n--- 结果比较 ---")
            print(f"射线检测: {result1['name'] if result1 else '无'}")
            print(f"投影检测: {result2['name'] if result2 else '无'}")
            print(f"混合检测: {result3['name'] if result3 else '无'}")

if __name__ == "__main__":
    app = ImprovedPickingTest()
    print("改进的点击检测测试完成") 