#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
独立的坐标轴点击测试demo
专门用于测试和调试坐标轴的点击检测和拖拽约束功能
"""

import sys
import math
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
    Vec3, Vec4, Point3, Point2, LineSegs, 
    AmbientLight, DirectionalLight, TextNode,
    CollisionTraverser, CollisionHandlerQueue, 
    CollisionNode, CollisionRay, GeomNode,
    CardMaker, Material
)
from direct.task import Task
from direct.gui.DirectGui import DirectFrame, DirectLabel

class GizmoTest(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # 关闭默认的鼠标控制
        self.disableMouse()
        
        print("坐标轴测试程序启动")
        
        # 设置背景色
        self.setBackgroundColor(0.2, 0.2, 0.3)
        
        # 设置相机位置
        self.cam.setPos(10, -20, 10)
        self.cam.lookAt(0, 0, 0)
        
        # 添加光照
        self.setupLighting()
        
        # 创建参考立方体
        self.createReferenceCube()
        
        # 创建坐标轴
        self.createGizmo()
        
        # 设置点击检测
        self.setupClickDetection()
        
        # 创建信息显示
        self.createInfoDisplay()
        
        # 拖拽状态变量
        self.isDragging = False
        self.dragAxis = None
        self.dragStartMousePos = None
        self.dragStartCubePos = None
        
        # 高亮状态
        self.highlightAxis = None
        
        # 设置事件处理
        self.accept("mouse1", self.onMouseClick)
        self.accept("mouse1-up", self.onMouseRelease)
        self.taskMgr.add(self.mouseTask, "mouseTask")
        
        # 添加颜色测试任务
        self.taskMgr.add(self.colorTestTask, "colorTest")
        self.test_time = 0
        
        print("=== Gizmo Click Test Demo Started ===")
        print("Instructions:")
        print("- ONLY click on red/green/blue gizmo axes to drag")
        print("- Each axis constrains movement to that direction only")
        print("- Click elsewhere should do nothing")
        print("- Real-time display of mouse and projection info")
        print("- Color test will cycle through colors every 3 seconds")
    
    def setupLighting(self):
        """设置光照"""
        # 环境光
        alight = AmbientLight('alight')
        alight.setColor((0.3, 0.3, 0.3, 1))
        alnp = self.render.attachNewNode(alight)
        self.render.setLight(alnp)
        
        # 定向光
        dlight = DirectionalLight('dlight')
        dlight.setColor((0.8, 0.8, 0.8, 1))
        dlnp = self.render.attachNewNode(dlight)
        dlnp.setHpr(45, -45, 0)
        self.render.setLight(dlnp)
    
    def createReferenceCube(self):
        """创建参考立方体"""
        # 创建立方体
        cm = CardMaker('cube')
        cm.setFrame(-1, 1, -1, 1)
        
        # 创建6个面
        faces = [
            ('front', (0, 1, 0), (0, 0, 0), (1, 0, 0, 1)),   # 红色前面
            
            ('back', (0, -1, 0), (0, 0, 180), (0, 1, 0, 1)),  # 绿色后面
            ('left', (-1, 0, 0), (0, 0, 90), (0, 0, 1, 1)),   # 蓝色左面
            ('right', (1, 0, 0), (0, 0, -90), (1, 1, 0, 1)),  # 黄色右面
            ('top', (0, 0, 1), (-90, 0, 0), (1, 0, 1, 1)),    # 紫色顶面
            ('bottom', (0, 0, -1), (90, 0, 0), (0, 1, 1, 1))  # 青色底面
        ]
        
        self.cube = self.render.attachNewNode("cube")
        
        for name, pos, hpr, color in faces:
            face = self.cube.attachNewNode(cm.generate())
            face.setPos(*pos)
            face.setHpr(*hpr)
            face.setColor(*color)
            face.setName(name)
        
        print(f"✓ 参考立方体创建完成，位置: {self.cube.getPos()}")
    
    def createGizmo(self):
        """创建坐标轴"""
        self.gizmo = self.render.attachNewNode("gizmo")
        self.gizmo.setPos(0, 0, 3)  # 放在立方体上方
        
        self.axis_length = 3.0
        self.click_threshold = 25  # 点击检测阈值（像素）
        
        # 创建X轴（不在LineSegs级别设置颜色）
        x_lines = LineSegs("x_axis")
        x_lines.setThickness(6.0)
        # 不在LineSegs级别设置颜色！
        x_lines.moveTo(0, 0, 0)
        x_lines.drawTo(self.axis_length, 0, 0)
        # 箭头
        x_lines.moveTo(self.axis_length - 0.3, -0.1, 0)
        x_lines.drawTo(self.axis_length, 0, 0)
        x_lines.drawTo(self.axis_length - 0.3, 0.1, 0)
        x_geom = x_lines.create()
        self.gizmoXAxis = self.gizmo.attachNewNode(x_geom)
        self.gizmoXAxis.setLightOff()
        # 使用RenderState强制设置颜色
        from panda3d.core import RenderState, ColorAttrib
        red_state = RenderState.make(ColorAttrib.makeFlat((1, 0, 0, 1)))
        self.gizmoXAxis.setState(red_state)
        self.gizmoXAxis.setColor(1, 0, 0, 1)
        
        # 创建Y轴（不在LineSegs级别设置颜色）
        y_lines = LineSegs("y_axis")
        y_lines.setThickness(6.0)
        # 不在LineSegs级别设置颜色！
        y_lines.moveTo(0, 0, 0)
        y_lines.drawTo(0, self.axis_length, 0)
        # 箭头
        y_lines.moveTo(-0.1, self.axis_length - 0.3, 0)
        y_lines.drawTo(0, self.axis_length, 0)
        y_lines.drawTo(0.1, self.axis_length - 0.3, 0)
        y_geom = y_lines.create()
        self.gizmoYAxis = self.gizmo.attachNewNode(y_geom)
        self.gizmoYAxis.setLightOff()
        # 使用RenderState强制设置颜色
        green_state = RenderState.make(ColorAttrib.makeFlat((0, 1, 0, 1)))
        self.gizmoYAxis.setState(green_state)
        self.gizmoYAxis.setColor(0, 1, 0, 1)
        
        # 创建Z轴（不在LineSegs级别设置颜色）
        z_lines = LineSegs("z_axis")
        z_lines.setThickness(6.0)
        # 不在LineSegs级别设置颜色！
        z_lines.moveTo(0, 0, 0)
        z_lines.drawTo(0, 0, self.axis_length)
        # 箭头
        z_lines.moveTo(-0.1, 0, self.axis_length - 0.3)
        z_lines.drawTo(0, 0, self.axis_length)
        z_lines.drawTo(0.1, 0, self.axis_length - 0.3)
        z_geom = z_lines.create()
        self.gizmoZAxis = self.gizmo.attachNewNode(z_geom)
        self.gizmoZAxis.setLightOff()
        # 使用RenderState强制设置颜色
        blue_state = RenderState.make(ColorAttrib.makeFlat((0, 0, 1, 1)))
        self.gizmoZAxis.setState(blue_state)
        self.gizmoZAxis.setColor(0, 0, 1, 1)
        
        # 轴颜色字典
        self.axis_colors = {
            "x": (1, 0, 0, 1),  # 红色
            "y": (0, 1, 0, 1),  # 绿色
            "z": (0, 0, 1, 1)   # 蓝色
        }
        
        print(f"✓ 坐标轴创建完成，长度={self.axis_length}")
        print(f"X轴节点颜色: {self.gizmoXAxis.getColor()}")
        print(f"Y轴节点颜色: {self.gizmoYAxis.getColor()}")
        print(f"Z轴节点颜色: {self.gizmoZAxis.getColor()}")
    
    def setupClickDetection(self):
        """设置点击检测"""
        self.picker = CollisionTraverser()
        self.pq = CollisionHandlerQueue()
        
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = self.camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.picker.addCollider(self.pickerNP, self.pq)
    
    def createInfoDisplay(self):
        """创建信息显示面板"""
        self.infoPanel = DirectFrame(
            frameColor=(0, 0, 0, 0.7),
            frameSize=(-0.4, 0.4, -0.3, 0.3),
            pos=(-0.6, 0, 0.8)
        )
        
        self.infoText = DirectLabel(
            parent=self.infoPanel,
            text="Mouse Info\nWaiting...",
            text_scale=0.05,
            text_fg=(1, 1, 1, 1),
            frameColor=(0, 0, 0, 0),
            pos=(0, 0, 0)
        )
    
    def worldToScreen(self, world_pos):
        """将世界坐标转换为屏幕坐标"""
        try:
            # 转换为相机坐标系
            cam_pos = self.cam.getRelativePoint(self.render, world_pos)
            
            # 检查是否在相机前方
            if cam_pos.getY() <= 0:
                return None
            
            # 使用相机lens进行投影
            screen_pos = Point2()
            if self.cam.node().getLens().project(cam_pos, screen_pos):
                # 转换为像素坐标
                win_x = (screen_pos.getX() + 1.0) * 0.5 * self.win.getXSize()
                win_y = (1.0 - screen_pos.getY()) * 0.5 * self.win.getYSize()
                return (win_x, win_y)
            return None
        except:
            return None
    
    def distanceToLine(self, point, line_start, line_end):
        """计算点到线段的距离"""
        try:
            px, py = point
            x1, y1 = line_start
            x2, y2 = line_end
            
            # 计算线段长度
            line_length_sq = (x2 - x1) ** 2 + (y2 - y1) ** 2
            if line_length_sq < 0.001:
                return math.sqrt((px - x1) ** 2 + (py - y1) ** 2)
            
            # 计算投影参数
            t = max(0, min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / line_length_sq))
            
            # 计算投影点
            projection_x = x1 + t * (x2 - x1)
            projection_y = y1 + t * (y2 - y1)
            
            # 返回距离
            distance = math.sqrt((px - projection_x) ** 2 + (py - projection_y) ** 2)
            return distance
        except:
            return float('inf')
    
    def checkGizmoClick(self, mouse_x, mouse_y):
        """检查是否点击了坐标轴"""
        if not self.gizmo:
            return None
        
        # 获取坐标轴中心的世界坐标
        gizmo_world_pos = self.gizmo.getPos(self.render)
        
        # 计算各轴端点的世界坐标
        x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0)
        y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0)
        z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length)
        
        # 投影到屏幕坐标
        center_screen = self.worldToScreen(gizmo_world_pos)
        x_screen = self.worldToScreen(x_end)
        y_screen = self.worldToScreen(y_end)
        z_screen = self.worldToScreen(z_end)
        
        if not center_screen:
            return None
        
        # 检测各个轴
        axes_data = [
            ("x", x_screen, "X轴"),
            ("y", y_screen, "Y轴"),
            ("z", z_screen, "Z轴")
        ]
        
        closest_axis = None
        closest_distance = float('inf')
        
        for axis_name, axis_screen, axis_label in axes_data:
            if axis_screen:
                distance = self.distanceToLine(
                    (mouse_x, mouse_y), center_screen, axis_screen
                )
                
                if distance < self.click_threshold and distance < closest_distance:
                    closest_axis = axis_name
                    closest_distance = distance
        
        return closest_axis
    
    def setAxisColor(self, axis, color):
        """设置坐标轴颜色 - 使用RenderState强制覆盖"""
        from panda3d.core import RenderState, ColorAttrib
        
        # 创建强制颜色状态
        color_state = RenderState.make(ColorAttrib.makeFlat(color))
        
        if axis == "x" and self.gizmoXAxis:
            self.gizmoXAxis.setState(color_state)
            self.gizmoXAxis.setColor(*color)
            self.gizmoXAxis.setColorScale(*color)
            print(f"✓ 强制设置X轴颜色: {color}")
        elif axis == "y" and self.gizmoYAxis:
            self.gizmoYAxis.setState(color_state)
            self.gizmoYAxis.setColor(*color)
            self.gizmoYAxis.setColorScale(*color)
            print(f"✓ 强制设置Y轴颜色: {color}")
        elif axis == "z" and self.gizmoZAxis:
            self.gizmoZAxis.setState(color_state)
            self.gizmoZAxis.setColor(*color)
            self.gizmoZAxis.setColorScale(*color)
            print(f"✓ 强制设置Z轴颜色: {color}")
    
    def mouseTask(self, task):
        """鼠标任务 - 处理高亮和拖拽"""
        if not self.mouseWatcherNode.hasMouse():
            return task.cont
        
        # 获取鼠标屏幕坐标
        mouse_x = (self.mouseWatcherNode.getMouseX() + 1.0) * 0.5 * self.win.getXSize()
        mouse_y = (1.0 - self.mouseWatcherNode.getMouseY()) * 0.5 * self.win.getYSize()
        
        # 检查是否在颜色测试周期内
        cycle_time = int(self.test_time) % 15
        is_color_testing = (cycle_time >= 3 and cycle_time < 12)  # 在黄、白、橙色测试期间
        
        # 更新信息显示
        info_text = f"Mouse Info:\n"
        info_text += f"Normalized: ({self.mouseWatcherNode.getMouseX():.3f}, {self.mouseWatcherNode.getMouseY():.3f})\n"
        info_text += f"Pixels: ({mouse_x:.1f}, {mouse_y:.1f})\n"
        info_text += f"Window Size: {self.win.getXSize()} x {self.win.getYSize()}\n"
        
        if is_color_testing:
            info_text += f"Color Test Active (cycle: {cycle_time})\n"
        
        # 只在不拖拽且不在颜色测试时检测高亮
        if not self.isDragging and not is_color_testing:
            # 简化的高亮检测
            hovered_axis = None
            
            if self.gizmo:
                # 获取坐标轴中心的世界坐标
                gizmo_world_pos = self.gizmo.getPos(self.render)
                
                # 计算各轴端点的世界坐标
                x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0)
                y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0)
                z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length)
                
                # 投影到屏幕坐标
                center_screen = self.worldToScreen(gizmo_world_pos)
                x_screen = self.worldToScreen(x_end)
                y_screen = self.worldToScreen(y_end)
                z_screen = self.worldToScreen(z_end)
                
                if center_screen:
                    # 检测距离最近的轴
                    min_distance = float('inf')
                    
                    for axis_name, axis_screen in [("x", x_screen), ("y", y_screen), ("z", z_screen)]:
                        if axis_screen:
                            distance = self.distanceToLine((mouse_x, mouse_y), center_screen, axis_screen)
                            if distance < self.click_threshold and distance < min_distance:
                                min_distance = distance
                                hovered_axis = axis_name
            
            # 处理高亮状态变化
            if hovered_axis != self.highlightAxis:
                # 恢复之前高亮的轴
                if self.highlightAxis:
                    self.setAxisColor(self.highlightAxis, self.axis_colors[self.highlightAxis])
                    print(f"恢复 {self.highlightAxis} 轴原色")
                
                # 高亮新的轴
                if hovered_axis:
                    self.setAxisColor(hovered_axis, (1, 1, 0, 1))  # 纯黄色高亮
                    print(f"高亮 {hovered_axis} 轴为黄色")
                    
                self.highlightAxis = hovered_axis
            
            # 更新信息显示
            if hovered_axis:
                info_text += f"Hover: {hovered_axis.upper()} axis (YELLOW)"
            else:
                info_text += "Hover: None"
        elif is_color_testing:
            info_text += "Hover: Disabled (Color Test)"
        else:
            info_text += f"Dragging: {self.dragAxis.upper()} axis"
        
        # 处理拖拽
        if self.isDragging:
            self.handleDrag(mouse_x, mouse_y)
        
        self.infoText.setText(info_text)
        return task.cont
    
    def onMouseClick(self):
        """鼠标点击事件 - 只有点击坐标轴才开始拖拽"""
        if not self.mouseWatcherNode.hasMouse():
            return
        
        # 检查是否在颜色测试期间
        cycle_time = int(self.test_time) % 15
        is_color_testing = (cycle_time >= 3 and cycle_time < 12)
        
        if is_color_testing:
            print("点击被禁用 - 颜色测试进行中")
            return
        
        # 获取鼠标坐标
        mouse_x = (self.mouseWatcherNode.getMouseX() + 1.0) * 0.5 * self.win.getXSize()
        mouse_y = (1.0 - self.mouseWatcherNode.getMouseY()) * 0.5 * self.win.getYSize()
        
        print(f"\n=== Mouse Click ===")
        print(f"Pixel Coords: ({mouse_x:.1f}, {mouse_y:.1f})")
        
        # 检测坐标轴点击
        clicked_axis = self.checkGizmoClick(mouse_x, mouse_y)
        
        if clicked_axis:
            print(f"✓ Clicked {clicked_axis.upper()} Axis - Starting drag")
            # 只有真正点击到轴才开始拖拽
            self.isDragging = True
            self.dragAxis = clicked_axis
            self.dragStartMousePos = (mouse_x, mouse_y)
            self.dragStartCubePos = self.cube.getPos()
            
            # 显示拖拽颜色 - 使用更明显的橙色
            self.setAxisColor(clicked_axis, (1, 0.5, 0, 1))  # 更亮的橙色表示拖拽
            
            print(f"  开始位置: {self.dragStartCubePos}")
        else:
            print("✗ No axis clicked - No action")
            # 确保没有点击到轴时不开始拖拽
            self.isDragging = False
            self.dragAxis = None
    
    def onMouseRelease(self):
        """鼠标释放事件"""
        if self.isDragging and self.dragAxis:
            print(f"Stop dragging {self.dragAxis.upper()} axis")
            
            # 恢复轴颜色
            self.setAxisColor(self.dragAxis, self.axis_colors[self.dragAxis])
        
        # 清理所有拖拽状态
        self.isDragging = False
        self.dragAxis = None
        self.dragStartMousePos = None
        self.dragStartCubePos = None
    
    def handleDrag(self, mouse_x, mouse_y):
        """处理拖拽 - 真正的轴约束拖拽"""
        if not self.dragAxis or not self.dragStartMousePos or not self.dragStartCubePos:
            return
        
        # 计算从拖拽开始到现在的总鼠标移动距离
        total_dx = mouse_x - self.dragStartMousePos[0]
        total_dy = mouse_y - self.dragStartMousePos[1]
        
        # 根据拖拽的轴，确定世界空间中的轴方向向量
        if self.dragAxis == "x":
            world_axis_dir = Vec3(1, 0, 0)  # X轴方向
        elif self.dragAxis == "y":
            world_axis_dir = Vec3(0, 1, 0)  # Y轴方向
        elif self.dragAxis == "z":
            world_axis_dir = Vec3(0, 0, 1)  # Z轴方向
        else:
            return
        
        # 将世界轴方向投影到屏幕空间
        axis_start_world = self.dragStartCubePos
        axis_end_world = self.dragStartCubePos + world_axis_dir
        
        axis_start_screen = self.worldToScreen(axis_start_world)
        axis_end_screen = self.worldToScreen(axis_end_world)
        
        if not axis_start_screen or not axis_end_screen:
            return
        
        # 计算轴在屏幕空间的方向向量
        screen_axis_dir = (
            axis_end_screen[0] - axis_start_screen[0],
            axis_end_screen[1] - axis_start_screen[1]
        )
        
        # 归一化屏幕轴方向
        screen_axis_length = math.sqrt(screen_axis_dir[0]**2 + screen_axis_dir[1]**2)
        if screen_axis_length > 0.001:
            screen_axis_dir = (
                screen_axis_dir[0] / screen_axis_length,
                screen_axis_dir[1] / screen_axis_length
            )
        else:
            return
        
        # 将总鼠标移动向量投影到屏幕轴方向上
        projected_distance = (
            total_dx * screen_axis_dir[0] + 
            total_dy * screen_axis_dir[1]
        )
        
        # 将投影距离转换为世界空间移动量
        move_scale = 0.02  # 移动灵敏度
        world_movement = world_axis_dir * projected_distance * move_scale
        
        # 从拖拽开始位置计算新位置（确保只沿选定轴移动）
        new_cube_pos = self.dragStartCubePos + world_movement
        self.cube.setPos(new_cube_pos)
        
        # 坐标轴跟随立方体移动
        self.gizmo.setPos(new_cube_pos + Vec3(0, 0, 3))

    def colorTestTask(self, task):
        """颜色测试任务 - 每3秒循环测试坐标轴颜色"""
        from panda3d.core import RenderState, ColorAttrib
        
        self.test_time += globalClock.getDt()
        
        # 每3秒切换一次测试
        cycle_time = int(self.test_time) % 15  # 15秒一个完整周期
        
        if cycle_time < 3:
            # 正常颜色
            if hasattr(self, 'gizmoXAxis'):
                self.setAxisColor("x", (1, 0, 0, 1))  # 红色
                self.setAxisColor("y", (0, 1, 0, 1))  # 绿色
                self.setAxisColor("z", (0, 0, 1, 1))  # 蓝色
        elif cycle_time < 6:
            # 全部黄色
            if hasattr(self, 'gizmoXAxis'):
                self.setAxisColor("x", (1, 1, 0, 1))
                self.setAxisColor("y", (1, 1, 0, 1))
                self.setAxisColor("z", (1, 1, 0, 1))
        elif cycle_time < 9:
            # 全部白色
            if hasattr(self, 'gizmoXAxis'):
                self.setAxisColor("x", (1, 1, 1, 1))
                self.setAxisColor("y", (1, 1, 1, 1))
                self.setAxisColor("z", (1, 1, 1, 1))
        elif cycle_time < 12:
            # 全部橙色
            if hasattr(self, 'gizmoXAxis'):
                self.setAxisColor("x", (1, 0.5, 0, 1))
                self.setAxisColor("y", (1, 0.5, 0, 1))
                self.setAxisColor("z", (1, 0.5, 0, 1))
        else:
            # 恢复正常颜色
            if hasattr(self, 'gizmoXAxis'):
                self.setAxisColor("x", (1, 0, 0, 1))  # 红色
                self.setAxisColor("y", (0, 1, 0, 1))  # 绿色
                self.setAxisColor("z", (0, 0, 1, 1))  # 蓝色
        
        return task.cont

if __name__ == "__main__":
    app = GizmoTest()
    app.run() 