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

"""
测试动画模型 - Panda3D应用程序
使用Panda3D引擎编辑器创建
"""

from __future__ import print_function

import json

from direct.actor.Actor import Actor
from direct.showbase.ShowBaseGlobal import globalClock
from panda3d.core import TextNode, CardMaker, TextureStage, NodePath, Texture, TransparencyAttrib, CollisionTraverser, \
    Point3
from core.InfoPanelManager import InfoPanelManager
# 获取渲染管线路径
# 在文件开头添加sys导入（如果还没有的话）
import sys
import os

# 修改工作目录设置部分
if getattr(sys, 'frozen', False):
    # 打包后的环境
    project_root = os.path.dirname(sys.executable)
else:
    # 开发环境
    try:
        project_root = os.path.dirname(os.path.abspath(__file__))
    except NameError:
        project_root = os.getcwd()

os.chdir(project_root)

render_pipeline_path = 'RenderPipelineFile'
sys.path.insert(0, render_pipeline_path)

# 改进路径设置逻辑
pipeline_path = os.path.join(project_root, "RenderPipelineFile")
if os.path.exists(pipeline_path) and pipeline_path not in sys.path:
    sys.path.insert(0, pipeline_path)
    # 同时添加子目录以确保所有模块都能正确导入
    for root, dirs, files in os.walk(pipeline_path):
        if root not in sys.path:
            sys.path.insert(0, root)


import math
from random import random, randint, seed
from panda3d.core import Vec3, load_prc_file_data, Filename
from direct.showbase.ShowBase import ShowBase
from direct.task.TaskManagerGlobal import taskMgr

# os.chdir(os.path.dirname(os.path.realpath(__file__)))
from core.script_system import ScriptManager
from core.CustomMouseController import CustomMouseController
from panda3d.core import CollisionTraverser

class MainApp(ShowBase):
    def __init__(self):
        # 在调用父类构造函数前确保必要的属性存在
        if not hasattr(self, 'appRunner'):
            self.appRunner = None

        load_prc_file_data("", """
            win-size 1380 750
            window-title 
            support-threads #t
        """)

        # 简化 sys.path 设置逻辑
        pipeline_path = os.path.join(project_root, "RenderPipelineFile")
        if os.path.exists(pipeline_path):
            if pipeline_path not in sys.path:
                sys.path.insert(0, pipeline_path)
        else:
            print(f"错误: 找不到渲染管线目录: {pipeline_path}")
            return

        try:
            from rpcore import RenderPipeline
            self.render_pipeline = RenderPipeline()
            self.render_pipeline.create(self)

        except ImportError as e:

            print(f"导入RenderPipeline模块失败: {e}")
            import traceback
            traceback.print_exc()
            ShowBase.__init__(self)
            self.render_pipeline = None
            return

        self.script_manager = ScriptManager(self)
        self.script_manager.start_system()

        # 加载所有脚本e
        self.script_manager.load_all_scripts_from_directory()

        self.info_panel_manager = InfoPanelManager(self)

        try:
            # 再导入controller模块
            from rpcore.util.movement_controller import MovementController

            self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam

            self.controller = MovementController(self)
            self.camLens.set_fov(80)
            self.controller.set_initial_position(
                Vec3(0, -50, 20), Vec3(0, 0, 0))
            self.controller.setup()
        except ImportError as e:
            print(f"导入MovementController失败: {e}")
            self.controller = None

        self._last_click_time = 0
        self._last_clicked_node = None
        self._double_click_threshold = 0.3

        self.cameraSpeed = 20.0
        self.cameraRotateSpeed=10.0

        self.lastMouseX=0
        self.lastMouseY=0
        self.mouseRightPressed=False

        self._loadFont()
        self.loadFullScene()
        self.loadGUIFromJSON()
        self.setupMouseClickHandler()
        self.cTrav = CollisionTraverser()

        self.createFrameRateDisplay()

        self.women_actor = None
        self.current_actor = None

        if hasattr(self, 'accept'):
            base.accept("l", self.tour)

    def _loadFont(self):
        """加载中文字体"""
        self.chinese_font = None
        try:
            self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc')
            if not self.chinese_font:
                print("警告: 无法加载中文字体，将使用默认字体")
            else:
                print("✓ 中文字体加载成功")
        except:
            print("警告: 无法加载中文字体，将使用默认字体")
            self.chinese_font = None

    def getChineseFont(self):
        """获取中文字体"""
        return self.chinese_font

    def getResourcePath(self,relative_path):
        if getattr(sys,'frozen',False):
            base_path = os.path.dirname(sys.executable)
        else:
            base_path = os.path.dirname(os.path.abspath(__file__))

        return os.path.join(base_path,relative_path)

    def loadFullScene(self):
        if not hasattr(self, 'loader') or not hasattr(self, 'render'):
            print("错误: Panda3D核心组件未正确初始化")
            return
        try:
            scene_file = self.getResourcePath("scene.bam")

            if os.path.exists(scene_file):
                # 使用readBamFile加载完整场景
                from panda3d.core import BamCache
                BamCache.getGlobalPtr().setActive(False)  # 禁用缓存以避免问题

                scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file))
                if scene:
                    scene.reparentTo(self.render)
                    self.render_pipeline.prepare_scene(scene)
                    print("✓ 完整场景加载成功")

                    # 检测并播放模型动画
                    #self._processModelAnimations(scene)

                    # 处理场景中的各种元素
                    self.processSceneElements(scene)
                else:
                    print("⚠️ 场景文件加载失败")
            else:
                print("⚠️ 未找到场景文件")
        except Exception as e:
            print(f"加载完整场景时出错: {str(e)}")
            import traceback
            traceback.print_exc()

    def checkAnimationStatus(self):
        """检查场景中所有Actor的动画状态"""
        try:
            all_actors = self.render.findAllMatches("**/+ActorNode")
            print(f"=== 动画状态检查 ===")
            print(f"场景中总共有 {len(all_actors)} 个Actor")

            for i, actor_np in enumerate(all_actors):
                actor_node = actor_np.node()
                if isinstance(actor_node, Actor):
                    # 确保Actor可见
                    actor_np.show()

                    current_anim = actor_node.getCurrentAnim()
                    anim_names = actor_node.getAnimNames()
                    print(f"Actor {i + 1} ({actor_np.getName()}):")
                    print(f"  位置: {actor_np.getPos()}")
                    print(f"  可见性: {actor_np.isHidden()}")
                    print(f"  可用动画: {anim_names}")
                    print(f"  当前播放: {current_anim}")
                    if current_anim:
                        frame = actor_node.getCurrentFrame(current_anim)
                        num_frames = actor_node.getNumFrames(current_anim)
                        play_rate = actor_node.getPlayRate(current_anim)
                        print(f"  播放进度: {frame}/{num_frames} 帧")
                        print(f"  播放速度: {play_rate}")
                else:
                    print(f"节点 {actor_np.getName()} 不是Actor类型")
            print("==================")
        except Exception as e:
            print(f"检查动画状态时出错: {str(e)}")

    def _processModelAnimations(self, node_path):
        """处理节点中的动画模型"""
        try:
            # 查找场景中所有可能的动画模型
            char_nodes = node_path.findAllMatches("**/+Character")

            for char_node in char_nodes:
                try:
                    # 获取父节点（通常是模型根节点）
                    model_root = char_node.getParent()
                    model_name = model_root.getName()

                    print(f"检测到可能的动画模型: {model_name}")

                    # 尝试创建Actor
                    actor = Actor(model_root)
                    actor.reparentTo(self.render)

                    actor.setPos(node_path.getPos())
                    actor.setHpr(node_path.getHpr())
                    actor.setScale(node_path.getScale())



                    # 获取动画名称
                    anim_names = actor.getAnimNames()
                    if anim_names:
                        print(f"✓ 成功创建动画模型: {model_name}")
                        print(f"  可用动画: {anim_names}")

                        # 循环播放所有动画
                        for anim_name in anim_names:
                            print(f"  循环播放动画: {anim_name}")
                            actor.loop(anim_name)
                            break  # 只播放第一个动画，避免同时播放多个动画

                        # 替换原始模型
                        model_root.detachNode()

                    else:
                        # 没有动画，使用原始模型
                        print(f"模型 {model_name} 不包含动画")
                        actor.detachNode()  # 移除创建的Actor

                except Exception as e:
                    print(f"处理动画模型 {char_node.getName()} 时出错: {str(e)}")

        except Exception as e:
            print(f"处理模型动画时出错: {str(e)}")
            import traceback
            traceback.print_exc()

    def processSceneElements(self, scene):
        """处理场景中的各种元素"""
        try:
            processed_lights = []
            loaded_nodes = {}

            def processNode(nodePath, depth=0):
                loaded_nodes[nodePath.getName()] = nodePath

                # 为模型添加碰撞体 - 修复版本
                if nodePath.hasTag("is_scene_element") and not nodePath.hasTag("is_gizmo"):
                    if nodePath.hasTag("gui_type"):
                        gui_type = nodePath.getTag("gui_type")
                        if gui_type in ["video_screen"]:
                            print(f"移除GUI视频节点: {nodePath.getName()}")
                            nodePath.removeNode()  # 移除视频屏幕节点
                            return
                    # 使用更精确的包围盒
                    from panda3d.core import CollisionNode, CollisionBox
                    bounds = nodePath.getBounds()
                    if not bounds.isEmpty():
                        min_point = bounds.getMin()
                        max_point = bounds.getMax()

                        # 创建碰撞节点
                        collision_node = CollisionNode(f'collision_{nodePath.getName()}')
                        collision_node.addSolid(CollisionBox(min_point, max_point))
                        collision_np = nodePath.attachNewNode(collision_node)
                        # 隐藏碰撞体
                        collision_np.hide()

                if nodePath.hasTag("scripts_info"):
                    try:
                        import json
                        scripts_info = json.loads(nodePath.getTag("scripts_info"))
                        self.processScripts(nodePath, scripts_info)
                    except Exception as e:
                        print(f"处理节点 {nodePath.getName()} 的脚本时出错: {str(e)}")

                if nodePath.hasTag("light_type"):
                    light_type = nodePath.getTag("light_type")

                    if nodePath.hasTag("is_auxiliary_light") and nodePath.getTag("is_auxiliary_light").lower() == "true":
                        print(f"跳过辅助灯光节点：{nodePath.getName()}")
                        return

                    if nodePath not in processed_lights:
                        if light_type == "spot_light":
                            self._recreateSpotLight(nodePath)
                        elif light_type == "point_light":
                            self._recreatePointLight(nodePath)
                        processed_lights.append(nodePath)

                for child in nodePath.getChildren():
                    processNode(child, depth + 1)

            processNode(scene)

        except Exception as e:
            print(f"处理场景元素时出错: {str(e)}")

    def _recreateSpotLight(self,light_node):
        try:
            from RenderPipelineFile.rpcore import SpotLight
            from panda3d.core import Vec3

            light = SpotLight()
            light.direction = Vec3(0,0,-1)
            light.fov = 70
            light.set_color_from_temperature(5*1000.0)

            if light_node.hasTag("light_energy"):
                light.energy = float(light_node.getTag("light_energy"))
            else:
                light.energy = 5000

            light.radius = 1000
            light.casts_shadows = True
            light.shadow_map_resolution = 256

            light_pos = light_node.getPos()
            light.setPos(light_pos)

            self.render_pipeline.add_light(light)
        except Exception as e:
            print(f"创建点光源 {light_node.getName()} 失败: {str(e)}")
            import traceback
            traceback.print_exc()

    def _recreatePointLight(self,light_node):
        try:
            from RenderPipelineFile.rpcore import PointLight

            light = PointLight()

            if light_node.hasTag("light_energy"):
                light.energy = float(light_node.getTag("light_energy"))
            else:
                light.energy = 5000

            light.radius = 1000
            light.inner_radius = 0.4
            light.set_color_from_temperature(5*1000.0)
            light.casts_shadows = True
            light.shadow_map_resolution = 256

            light.setPos(light_node.getPos())

            self.render_pipeline.add_light(light)
        except Exception as e:
            print(f"创建点光源 {light_node.getName()} 失败: {str(e)}")
            import traceback
            traceback.print_exc()

    def processGUIElements(self, scene):
        """处理场景中的GUI元素"""
        try:
            # 查找并处理2D图像
            images_2d = scene.findAllMatches("**/=gui_type=image_2d")
            for img_node in images_2d:
                try:
                    # GUI元素通常在场景加载时自动处理
                    print(f"✓ 2D图像 {img_node.getName()} 已加载")
                except Exception as e:
                    print(f"处理2D图像 {img_node.getName()} 失败: {str(e)}")

        except Exception as e:
            print(f"处理GUI元素时出错: {str(e)}")

    def tour(self):
        mopath = (
            (Vec3(-10.8645000458, 9.76458263397, 2.13306283951), Vec3(-133.556228638, -4.23447799683, 0.0)),
            (Vec3(-10.6538448334, -5.98406457901, 1.68028640747), Vec3(-59.3999938965, -3.32706642151, 0.0)),
            (Vec3(9.58458328247, -5.63625621796, 2.63269257545), Vec3(58.7906494141, -9.40668964386, 0.0)),
            (Vec3(6.8135137558, 11.0153560638, 2.25509500504), Vec3(148.762527466, -6.41223621368, 0.0)),
            (Vec3(-9.07093334198, 3.65908527374, 1.42396306992), Vec3(245.362503052, -3.59927511215, 0.0)),
            (Vec3(-8.75390911102, -3.82727789879, 0.990055501461), Vec3(296.090484619, -0.604830980301, 0.0)),
        )
        self.controller.play_motion_path(mopath, 3.0)

    def loadGUIFromJSON(self):
        try:
            gui_json_path = self.getResourcePath("gui/gui_elements.json")
            if os.path.exists(gui_json_path):
                with open(gui_json_path, 'r', encoding='utf-8') as f:
                    content = f.read().strip()
                    if content:
                        gui_data = json.loads(content)
                        self.createGUIElement(gui_data)
                    else:
                        print("GUI配置文件为空 ")
        except Exception as e:
            print(f"加载GUI元素时出错: {str(e)}")
            import traceback
            traceback.print_exc()

    def createGUIElement(self, element_data):
        try:
            processed_names = set()
            element_original_data = {}

            for i, gui_info in enumerate(element_data):
                name = gui_info.get("name", f"gui_element_{i}")
                element_original_data[name] = {
                    "scale": gui_info.get("scale", [1, 1, 1]),
                    "position": gui_info.get("position", [0, 0, 0]),
                    "parent_name": gui_info.get("parent_name")
                }
            valid_parents = set()
            for gui_info in element_data:
                name = gui_info.get("name", f"gui_element_{gui_info.get('index', 0)}")
                valid_parents.add(name)

            for i, gui_info in enumerate(element_data):
                try:
                    gui_type = gui_info.get("type", "unknown")
                    name = gui_info.get("name", f"gui_element_{i}")
                    position = gui_info.get("position", [0, 0, 0])
                    scale = gui_info.get("scale", [1, 1, 1])
                    tags = gui_info.get("tags", {})
                    text = gui_info.get("text", "")
                    image_path = gui_info.get("image_path", "")
                    video_path = gui_info.get("video_path", "")
                    bg_image_path = gui_info.get("bg_image_path", "")
                    parent_name = gui_info.get("parent_name")

                    if name in processed_names:
                        continue

                    processed_names.add(name)

                    absolute_position = list(position)
                    absolute_scale = list(scale)

                    if parent_name and parent_name in element_original_data:
                        parent_data = element_original_data[parent_name]
                        parent_scale = parent_data["scale"]

                        if gui_type in ["3d_text", "3d_image", "button", "label", "entry", "2d_image",
                                        "2d_video_screen"]:
                            # 位置需要乘以父级缩放来得到绝对位置
                            for j in range(min(len(absolute_position), len(parent_scale))):
                                absolute_position[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0]

                            # 缩放需要乘以父级缩放来得到绝对缩放
                            for j in range(min(len(absolute_scale), len(parent_scale))):
                                absolute_scale[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0]

                    new_element = None

                    if gui_type == "3d_text":
                        size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5
                        new_element = self.createGUI3DText(
                            pos=tuple(absolute_position),
                            text=text,
                            size=size
                        )
                    elif gui_type == "button":
                        new_element = self.createGUIButton(
                            pos=tuple(absolute_position),
                            text=text,
                            size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0,
                            command=self.playModelAnimation
                        )
                    elif gui_type == "label":
                        new_element = self.createGUILabel(
                            pos=tuple(absolute_position),
                            text=text,
                            size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0
                        )
                    elif gui_type == "entry":
                        new_element = self.createGUIEntry(
                            pos=tuple(absolute_position),
                            placeholder=text,
                            size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0,
                            command=self.onGUIEntrySubmit
                        )
                    elif gui_type == "2d_image":

                        scale_value = absolute_scale[0]
                        print(f"2d_image{scale_value}")
                        new_element = self.createGUI2DImage(
                            pos=tuple(absolute_position),
                            image_path=image_path,
                            size=absolute_scale
                        )
                    elif gui_type == "2d_video_screen":
                        new_element = self.createGUI2DVideoScreen(
                            pos=tuple(absolute_position),
                            video_path=video_path,
                            size=absolute_scale
                        )
                    elif gui_type == "video_screen":
                        new_element = self.createGUIVideoScreen(
                            pos=tuple(absolute_position),
                            video_path=video_path,
                            size=absolute_scale
                        )
                    elif gui_type == "info_panel":
                        new_element = self.info_panel_manager.onCreateSampleInfoPanel()

                    if "scripts" in gui_info and new_element:
                        self.processScripts(new_element,gui_info["scripts"])

                except Exception as e:
                    print(f"重建GUI元素失败 {name}: {e}")
                    import traceback
                    traceback.print_exc()
                    continue

        except Exception as e:
            print(f"创建GUI元素时出错: {str(e)}")

    def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1, command=None):
        from direct.gui.DirectGui import DirectButton

        button = DirectButton(
            text=text,
            pos=(pos[0], pos[1], pos[2]),  # 保持正确的坐标格式
            scale=size,  # size 应该是数值而不是元组
            frameColor=(0.2, 0.6, 0.8, 1),
            text_font=self.getChineseFont() if self.getChineseFont() else None,
            rolloverSound=None,
            clickSound=None,
            parent=None,
            command=command
        )

    def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
        """创建3D文本GUI元素"""
        try:
            # 创建文本节点
            text_node = TextNode("gui_3d_text")
            text_node.setText(text)
            text_node.setAlign(TextNode.ACenter)

            # 设置字体（如果可用）
            if self.getChineseFont():
                text_node.setFont(self.getChineseFont())

            # 创建节点路径并添加到场景
            text_np = self.render.attachNewNode(text_node)

            # 设置位置和大小
            text_np.setPos(Vec3(pos[0], pos[1], pos[2]))
            text_np.setScale(size)

            # 设置面向摄像机
            # text_np.setBillboardPointEye()

            # 设置渲染属性
            text_np.setBin("fixed", 40)
            text_np.setDepthWrite(False)

            return text_np
        except Exception as e:
            print(f"❌ 创建3D文本失败: {str(e)}")
            import traceback
            traceback.print_exc()
            return None

    def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08):
        from direct.gui.DirectGui import DirectLabel
        label = DirectLabel(
            text=text,
            pos=(pos[0], pos[1], pos[2]),
            scale=size,
            frameColor=(0, 0, 0, 0),
            text_fg=(1, 1, 1, 1),
            text_font=self.getChineseFont() if self.getChineseFont() else None,
            text_align=TextNode.ACenter,
            text_wordwrap=None,
            text_mayChange=True,
            parent=None
        )

    def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08, command=None):
        from direct.gui.DirectGui import DirectEntry

        entry = DirectEntry(
            text="",
            pos=(pos[0], pos[1], pos[2]),
            scale=size,
            command=command,
            initialText=placeholder,
            numLines=1,
            width=12,
            focus=0,
            frameColor=(0, 0, 0, 0),
            text_fg=(1, 1, 1, 1),
            text_font=self.getChineseFont() if self.getChineseFont() else None,
            text_align=TextNode.ACenter,
            text_wordwrap=None,
            text_mayChange=True,
            parent=None,
            rolloverSound=None,
            clickSound=None,
            # 添加焦点管理命令
            focusInCommand=self.disableCameraControl,
            focusOutCommand=self.enableCameraControl,
            # 确保输入框能正确捕获所有键盘事件
            suppressKeys=True,  # 这个参数很重要，它会阻止按键事件传播到其他处理器
            suppressMouse=True
        )
        return entry

    def disableCameraControl(self):
        """禁用相机控制"""
        try:
            if hasattr(self, 'controller'):
                # 如果控制器有内置的禁用方法
                if hasattr(self.controller, 'disable'):
                    self.controller.disable()
                else:
                    # 否则手动禁用事件监听
                    self.controller.ignoreAll()  # 忽略所有已注册的事件
            print("相机控制已禁用")
        except Exception as e:
            print(f"禁用相机控制时出错: {e}")

    def enableCameraControl(self):
        """启用相机控制"""
        try:
            if hasattr(self, 'controller'):
                # 如果控制器有内置的启用方法
                if hasattr(self.controller, 'enable'):
                    self.controller.enable()
                else:
                    # 重新设置控制器
                    self.controller.setup()
            print("相机控制已启用")
        except Exception as e:
            print(f"启用相机控制时出错: {e}")

    def onGUIEntrySubmit(self, text, entry_id=None):
        """GUI输入框提交事件处理"""
        try:
            print(f"GUI输入框提交: {entry_id} = {text}")

            # 重新启用相机控制
            self.enableCameraControl()

            # 清除输入框焦点（如果需要）
            # base.win.focus()

            # 在这里添加您需要的文本处理逻辑
            # 例如保存文本、更新UI等

        except Exception as e:
            print(f"处理输入框提交时出错: {e}")
            import traceback
            traceback.print_exc()

    def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2):
        # 添加属性检查
        if not hasattr(self, 'aspect2d'):
            print("错误: aspect2d未初始化")
            return None
        if image_path and not os.path.isabs(image_path):
            image_path = self.getResourcePath(image_path)
        # 处理非均匀缩放
        if isinstance(size, (list, tuple)) and len(size) >= 2:
            # 分别处理宽度和高度的缩放
            width_scale = size[0] * 0.2
            height_scale = size[2] * 0.2
        else:
            # 如果只提供了一个缩放值，则使用相同值
            width_scale = size * 0.1 if isinstance(size, (int, float)) else 0.2
            height_scale = width_scale

        cm = CardMaker("gui-2d-image")
        cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
        #cm.setFrame(-size, size, -size, size)

        image_node = self.aspect2d.attachNewNode(cm.generate())
        image_node.setPos(pos)
        image_node.setBin("fixed", 0)
        image_node.setDepthWrite(False)
        image_node.setDepthTest(False)
        image_node.setColor(1, 1, 1, 1)

        # 设置透明度支持
        image_node.setTransparency(TransparencyAttrib.MAlpha)

        if image_path:
            try:
                texture = self.loader.loadTexture(image_path)
                if texture:
                    image_node.setTexture(texture, 1)
                    texture.setWrapU(Texture.WM_clamp)
                    texture.setWrapV(Texture.WM_clamp)
                    texture.setMinfilter(Texture.FT_linear)
                    texture.setMagfilter(Texture.FT_linear)
                    image_node.setColor(1, 1, 1, 1)
                else:
                    print(f"无法加载图片: {image_path}")
            except Exception as e:
                print(f"加载图片时出错: {e}")
        return image_node

    def createGUIVideoScreen(self,pos=(0,0,0),size=1,video_path=None):
        import os
        from panda3d.core import TransparencyAttrib,Texture,TextureStage

        if isinstance(size,(list,tuple)) and len(size) >= 2:
            width_scale = size[0]
            height_scale = size[2]
        else:
            width_scale = size * 0.1 if isinstance(size, (int, float)) else 0.2
            height_scale = width_scale

        cm = CardMaker("gui-video-screen")
        cm.setFrame(-width_scale,width_scale,-height_scale,height_scale)

        video_screen = self.render.attachNewNode(cm.generate())
        video_screen.setPos(pos)

        video_screen.setBin("fixed", 0)

        #video_screen.setTransparency(TransparencyAttrib.MAlpha)

        video_screen.setColor(1, 1, 1, 1)

        self._ensureVideoScreenMaterial(video_screen)

        if video_path:
            if video_path.startswith(("http://", "https://")):
                try:
                    success = self._loadVideoFromURLWithOpenCV3D(video_screen, video_path)
                    if success:
                        print("视频已从URL加载成功")
                    else:
                        print("从URL加载视频时出错")
                except Exception as e:
                    print(f"从URL加载视频时出错: {e}")
            elif os.path.exists(video_path):
                movie_texture = self._loadMovieTexture(video_path)
                if movie_texture:
                    video_screen.setTexture(movie_texture, 1)
            else:
                print(f"视频文件不存在: {video_path}")
        else:
            # 设置占位符纹理
            placeholder_texture = Texture()
            placeholder_texture.setup2dTexture(1, 1, Texture.TUnsignedByte, Texture.FRgb)
            placeholder_data = b'\x19\x19\x4c'  # 深蓝色占位符
            placeholder_texture.setRamImage(placeholder_data)
            video_screen.setTexture(placeholder_texture, 1)

        return video_screen

    def createGUI2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None):
        import os
        from direct.gui.DirectGui import DirectFrame
        from panda3d.core import TransparencyAttrib, Texture, TextureStage

        if isinstance(size,(list,tuple)) and len(size) >= 2:
            width_scale = size[0]*0.2
            height_scale = size[2]*0.2
        else:
            width_scale = size*0.1 if isinstance(size,(int,float)) else 0.2
            height_scale = width_scale

        video_screen = DirectFrame(
            frameSize=(-width_scale, width_scale, -height_scale, height_scale),
            frameColor=(1, 1, 1, 1),
            pos=pos,
            parent=None,
            suppressMouse=True
        )
        video_screen.setTransparency(TransparencyAttrib.MAlpha)

        placeholder_texture = Texture()
        placeholder_texture.setup2dTexture(1, 1, Texture.TUnsignedByte, Texture.FRgb)
        placeholder_data = b'\x19\x19\x4c'
        placeholder_texture.setRamImage(placeholder_data)

        if video_path:
            if video_path.startswith(("http://","https://")):
                try:
                    success = self._loadVideoFromURLWithOpenCV(video_screen,video_path)
                    if success:
                        print("视频已从URL加载成功")
                    else:
                        print("从URL加载视频时出错")
                except Exception as e:
                    print(f"从URL加载视频时出错: {e}")
            elif os.path.exists(video_path):
                movie_texture = self._loadMovieTexture(video_path)
                if movie_texture:
                    video_screen["frameTexture"] = movie_texture

        return video_screen

    def _loadMovieTexture(self, video_path):
        from panda3d.core import Texture, MovieTexture, Filename
        import os
        
        # Convert Windows path to Panda3D compatible path format
        panda_path = Filename.fromOsSpecific(video_path)
        converted_path = str(panda_path)
        
        movie_texture = MovieTexture(converted_path)
        if movie_texture.read(converted_path):
            self._configureVideoTexture(movie_texture)
            return movie_texture

    def _configureVideoTexture(self, texture):
        from panda3d.core import Texture

        texture.setWrapU(Texture.WM_clamp)
        texture.setWrapV(Texture.WM_clamp)
        texture.setMinfilter(Texture.FT_linear)
        texture.setMagfilter(Texture.FT_linear)

        texture.setFormat(Texture.FRgb8)

        if hasattr(texture, 'set_loop') and hasattr(texture, 'set_play_rate'):
            texture.set_loop(True)
            texture.set_play_rate(1.0)

    def _loadVideoFromURLWithOpenCV(self, video_screen, url):
        try:
            import cv2
            import threading
            from panda3d.core import Texture,PNMImage
            import numpy as np
            import time

            def video_stream_thread():
                cap = cv2.VideoCapture(url)

                if not cap.isOpened():
                    print(f"无法打开视频流: {url}")
                    return False

                cap.set(cv2.CAP_PROP_BUFFERSIZE,1)

                fps = cap.get(cv2.CAP_PROP_FPS)
                frame_delay = 1.0 / fps if fps > 0 else 0.033  # 默认30fps

                while hasattr(self, 'video_stream_active') and self.video_stream_active:
                    ret, frame = cap.read()

                    if not ret:
                        print("视频流读取失败，尝试重新连接...")
                        cap.release()
                        time.sleep(1)
                        cap = cv2.VideoCapture(url)
                        continue

                    frame_height,frame_width = frame.shape[:2]
                    if frame_width > 256:
                        scale = 256.0/frame_width
                        new_width = int(frame_width * scale)
                        new_height = int(frame_height * scale)
                        frame = cv2.resize(frame,(new_width,new_height))

                    frame_rgb = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)

                    height,width = frame_rgb.shape[:2]

                    img = PNMImage(width,height,3)
                    img.set_maxval(255)

                    for y in range(height):
                        for x in range(width):
                            r,g,b = frame_rgb[y,x]
                            img.setXelVal(x,y,r,g,b)

                    texture = Texture("video_texture")
                    texture.setMagfilter(Texture.FTLinear)
                    texture.setMinfilter(Texture.FTLinear)
                    texture.setWrapU(Texture.WMClamp)
                    texture.setWrapV(Texture.WMClamp)
                    texture.load(img)

                    # 在主线程中更新GUI纹理
                    def update_texture():
                        if hasattr(self, 'aspect2d') and not video_screen.isEmpty():
                            video_screen["frameTexture"] = texture

                    # 使用taskMgr在主线程中更新纹理
                    if hasattr(self, 'taskMgr'):
                        self.taskMgr.doMethodLater(0, lambda task: update_texture() or task.done,
                                                   f"updateVideoTexture_{time.time()}")

                    # 控制帧率
                    time.sleep(frame_delay)

                cap.release()
                print("视频流线程结束")
                return True

            # 启动视频流线程
            self.video_stream_active = True
            self.video_thread = threading.Thread(target=video_stream_thread, daemon=True)
            self.video_thread.start()

            return True

        except Exception as e:
            print(f"加载视频流失败: {e}")
            import traceback
            traceback.print_exc()
            return False

    def _loadVideoFromURLWithOpenCV3D(self,video_screen,url):
        try:
            import cv2
            import threading
            from panda3d.core import Texture,PNMImage
            import time

            def video_stream_thread():
                cap = cv2.VideoCapture(url)

                if not cap.isOpened():
                    return False

                cap.set(cv2.CAP_PROP_BUFFERSIZE,1)

                fps = cap.get(cv2.CAP_PROP_FPS)
                frame_delay = 1.0 / fps if fps > 0 else 0.033

                while hasattr(self,'video_stream_active') and self.video_stream_active:
                    ret,frame = cap.read()

                    if not ret:
                        cap.release()
                        time.sleep(1)
                        cap = cv2.VideoCapture(url)
                        continue

                    frame_height,frame_width = frame.shape[:2]
                    if frame_width > 256:
                        scale = 256.0/frame_width
                        new_width = int(frame_width*scale)
                        new_height = int(frame_height*scale)
                        frame = cv2.resize(frame,(new_width,new_height))

                    frame_rgb = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)

                    height,width = frame_rgb.shape[:2]

                    img = PNMImage(width,height,3)
                    img.set_maxval(255)

                    for y in range(height):
                        for x in range(width):
                            r,g,b = frame_rgb[y,x]
                            img.setXelVal(x,y,r,g,b)

                    texture = Texture("video_texture_3d")
                    texture.setMagfilter(Texture.FTLinear)
                    texture.setMinfilter(Texture.FTLinear)
                    texture.setWrapU(Texture.WMClamp)
                    texture.setWrapV(Texture.WMClamp)
                    texture.load(img)

                    def update_texture():
                        if hasattr(self,'render') and not video_screen.isEmpty():
                            video_screen.setTexture(texture,1)

                    if hasattr(self,'taskMgr'):
                        self.taskMgr.doMethodLater(0,lambda task:update_texture() or task.done,f"updateVideoTexture_{time.time()}")

                    time.sleep(frame_delay)

                cap.release()
                return True
            self.video_stream_active = True
            self.video_thread_3d = threading.Thread(target=video_stream_thread,daemon=True)
            self.video_thread_3d.start()

            return True
        except Exception as e:
            print(f"加载3D视频流失败: {e}")
            import traceback
            traceback.print_exc()
            return False

    def _ensureVideoScreenMaterial(self, video_screen):
        """确保视频屏幕有正确的材质设置"""
        try:
            from panda3d.core import Material, LColor

            if not video_screen.hasMaterial():
                material = Material(f"video-material-{video_screen.getName()}")
                material.setBaseColor(LColor(1, 1, 1, 1))
                material.setDiffuse(LColor(1, 1, 1, 1))
                material.setAmbient(LColor(1, 1, 1, 1))  # 确保环境光为白色
                material.setEmission(LColor(0, 0, 0, 1))
                material.setSpecular(LColor(0, 0, 0, 1))
                material.setShininess(0)
                video_screen.setMaterial(material, 1)
            else:
                # 更新现有材质确保正确设置
                material = video_screen.getMaterial()
                material.setBaseColor(LColor(1, 1, 1, 1))
                material.setAmbient(LColor(1, 1, 1, 1))  # 确保环境光为白色
                video_screen.setMaterial(material, 1)

        except Exception as e:
            print(f"⚠️ 设置视频屏幕材质时出错: {e}")

    def playModelAnimation(self):
        """播放场景中所有 .glb 模型的动画（仅转换有动画的模型为 Actor）"""
        try:
            glb_models = self.render.findAllMatches("**/*.glb*")

            # 修复过滤逻辑，确保正确排除碰撞体节点
            filtered_models = []
            for model in glb_models:
                model_name = model.getName().lower()
                # 排除碰撞体节点
                if ("collision_" not in model_name and
                        "modelcollision_" not in model_name and
                        not model_name.endswith("_bounds")):
                    filtered_models.append(model)

            if not filtered_models:
                print("⚠️ 场景中没有找到 .glb 模型")
                return

            print(f"找到 {len(filtered_models)} 个 glb 模型")

            self.actors = []  # 存储所有 Actor，避免被垃圾回收

            for model in filtered_models:
                print(f"正在处理模型: {model.getName()}")

                actor = None

                # 尝试把模型当作Actor加载，判断是否有动画
                try:
                    # 在创建Actor之前先检查节点是否可能包含动画
                    if not self._isValidAnimationNode(model):
                        print(f"⚠️ {model.getName()} 不是有效的动画节点，跳过")
                        continue

                    test_actor = Actor(model)
                    anim_names = test_actor.getAnimNames()
                except Exception as e:
                    print(f"⚠️ {model.getName()} 无法作为Actor加载: {str(e)}")
                    anim_names = []

                if anim_names:  # ✅ 只有有动画才转为Actor
                    if not isinstance(model, Actor):
                        model_parent = model.getParent()
                        model_pos = model.getPos()
                        model_hpr = model.getHpr()
                        model_scale = model.getScale()

                        actor = Actor(model)
                        actor.reparentTo(model_parent)
                        actor.setPos(model_pos)
                        actor.setHpr(model_hpr)
                        actor.setScale(model_scale)

                        model.detachNode()
                    else:
                        actor = model

                    # 播放动画
                    actor.show()
                    self.actors.append(actor)

                    print(f"✓ {actor.getName()} 可用动画: {anim_names}")
                    first_anim = anim_names[0]

                    actor.stop()
                    actor.setPlayRate(1.0, first_anim)
                    actor.loop(first_anim)
                    print(f"✓ {actor.getName()} 正在播放动画: {first_anim}")

                    actor.update()

                else:  # 没有动画
                    print(f"⚠️ {model.getName()} 没有动画，不转为Actor")

        except Exception as e:
            print(f"播放模型动画时出错: {str(e)}")
            import traceback
            traceback.print_exc()

    def _isValidAnimationNode(self, nodePath):
        """检查节点是否可能是有效的动画节点"""
        # 排除明显不是动画节点的节点
        name = nodePath.getName().lower()
        if ("collision_" in name or
                "modelcollision_" in name or
                "bound" in name or
                name.endswith("_bounds")):
            return False

        # 检查节点是否有网格数据（简单判断）
        from panda3d.core import GeomNode
        geom_nodes = nodePath.findAllMatches("**/+GeomNode")
        if not geom_nodes:
            return False

        return True

    def focusOnWomenModel(self):
        """定位并聚焦到Women模型"""
        try:
            women_models = self.render.findAllMatches("**/Women_1.glb*")
            if women_models:
                model = women_models[0]
                # 确保模型可见
                model.show()

                # 将模型放置在原点附近
                model.setPos(0, 0, 0)
                model.setScale(1.0)

                # 确保是Actor的话设置动画
                if isinstance(model, Actor):
                    anim_names = model.getAnimNames()
                    if anim_names:
                        model.loop(anim_names[0])
                        model.setPlayRate(1.0, anim_names[0])
                        print(f"为模型设置动画: {anim_names[0]}")

                print(f"已定位模型: {model.getName()}")
            else:
                print("未找到Women模型")
        except Exception as e:
            print(f"定位模型时出错: {str(e)}")

    def processScripts(self, element, script_info_list):
        """处理元素上挂载的脚本 - 使用新的脚本系统"""
        try:
            print(f"正在为元素 {element.getName()} 挂载脚本")
            print(f"可用脚本列表: {self.script_manager.get_available_scripts()}")

            if not hasattr(self,'script_manager'):
                print("脚本管理器未初始化")
                return

            for script_info in script_info_list:
                script_name = script_info["name"]
                script_file = script_info.get("file", "")

                if script_name:
                    script_component = self.script_manager.add_script_to_object(element,script_name)

                    if script_component:
                        print(f"✓ 脚本 {script_name} 已挂载到元素 {element.getName()}")
                    else:
                        print(f"⚠️ 脚本 {script_name} 挂载失败")
                        # 列出可用脚本帮助调试
                        available_scripts = self.script_manager.get_available_scripts()
                        print(f"当前可用脚本: {available_scripts}")
                else:
                    print(f"⚠️ 脚本信息不完整: {script_info}")

                # # 从文件路径中提取脚本类名
                # if script_file:
                #     # 获取脚本文件名（不含路径和扩展名）
                #     script_filename = os.path.basename(script_file)
                #     script_class_name = os.path.splitext(script_filename)[0]
                #     print(f"尝试挂载脚本: {script_class_name} (来自文件: {script_file})")
                #
                #     # 使用脚本管理器为元素添加脚本
                #     script_component = self.script_manager.add_script_to_object(element, script_class_name)
                #
                #     if script_component:
                #         print(f"✓ 脚本 {script_class_name} 已挂载到元素 {element.getName()}")
                #     else:
                #         print(f"⚠️ 脚本 {script_class_name} 挂载失败")
                #         # 列出可用脚本帮助调试
                #         available_scripts = self.script_manager.get_available_scripts()
                #         print(f"当前可用脚本: {available_scripts}")
                # else:
                #     print(f"⚠️ 脚本信息不完整: {script_name}")

        except Exception as e:
            print(f"挂载脚本到元素 {element.getName()} 失败: {str(e)}")
            import traceback
            traceback.print_exc()

    def checkDoubleClick(self, nodePath):
        """检查是否为双击，返回布尔值 - 改进版本"""
        try:
            import time
            current_time = time.time()

            # 必须是同一节点且在时间阈值内
            is_double_click = (self._last_clicked_node is not None and
                               self._last_clicked_node == nodePath and
                               nodePath is not None and
                               current_time - self._last_click_time < self._double_click_threshold)

            if is_double_click:
                # 双击成功，重置状态
                self._last_click_time = 0
                self._last_clicked_node = None
                print(f"✓ 检测到双击: {nodePath.getName()}")
                return True
            else:
                # 单击，更新状态
                self._last_click_time = current_time
                self._last_clicked_node = nodePath
                return False

        except Exception as e:
            print(f"双击检测失败: {e}")
            return False

    def focusCameraOnNode(self, nodePath):
        """带动画效果的聚焦到节点方法"""
        try:
            if not nodePath or nodePath.isEmpty():
                print("无效的节点")
                return

            minPoint = Point3()
            maxPoint = Point3()

            if not nodePath.calcTightBounds(minPoint, maxPoint,self.render):
                print("无法计算选中节点的边界框，使用节点为位置作为替代方案")
                node_pos = nodePath.getPos(self.render)
                optimal_distance = 10.0
                current_cam_pos = self.cam.getPos()
                view_direction = node_pos - current_cam_pos
                if view_direction.length()<0.001:
                    view_direction = Vec3(5,-5,2)
                view_direction.normalize()
                target_cam_pos = node_pos-(view_direction * optimal_distance)

                temp_node = self.render.attachNewNode("temp_lookat_target")
                temp_node.setPos(node_pos)
                dummy_cam = self.render.attachNewNode("dummy_camera")
                dummy_cam.setPos(target_cam_pos)
                dummy_cam.lookAt(temp_node)
                target_cam_hpr = Vec3(dummy_cam.getHpr())

                temp_node.removeNode()
                dummy_cam.removeNode()

                current_cam_pos = Point3(self.cam.getPos())
                current_cam_hpr = Vec3(self.cam.getHpr())
                self._startCameraFocusAnimation(current_cam_pos,target_cam_pos,current_cam_hpr,target_cam_hpr)
                print(f"开始聚焦到节点(使用位置): {nodePath.getName()}")

                return

            center = Point3(
                (minPoint.x + maxPoint.x)*0.5,
                (minPoint.y+maxPoint.y)*0.5,
                (minPoint.z+maxPoint.z)*0.5
            )

            size = (maxPoint - minPoint).length()
            if size < 0.01:
                size = 1.0

            current_cam_pos = Point3(self.cam.getPos())
            current_cam_hpr = Vec3(self.cam.getHpr())

            view_direction = current_cam_pos - center
            if view_direction.length() < 0.001:
                view_direction = Vec3(5,-5,2)

            view_direction.normalize()

            optimal_distance = max(size * 2.5,1.0)

            target_cam_pos = center + (view_direction * optimal_distance)

            temp_node = self.render.attachNewNode("temp_lookat_target")
            temp_node.setPos(center)

            dummy_cam = self.render.attachNewNode("dummy_camera")
            dummy_cam.setPos(target_cam_pos)
            dummy_cam.lookAt(temp_node)
            target_cam_hpr = Vec3(dummy_cam.getHpr())

            temp_node.removeNode()
            dummy_cam.removeNode()

            self._startCameraFocusAnimation(current_cam_pos,target_cam_pos,
                                            current_cam_hpr,target_cam_hpr)

            print(f"开始聚焦到节点: {nodePath.getName()}")
            return True

        except Exception as e:
            print(f"聚焦相机失败: {str(e)}")
            import traceback
            traceback.print_exc()

    def _startCameraFocusAnimation(self, start_pos, end_pos, start_hpr, end_hpr):
        """启动相机聚焦动画 - 改进版本"""
        try:
            class CameraFocusData:
                def __init__(self, start_pos, end_pos, start_hpr, end_hpr):
                    self.start_pos = Point3(start_pos)
                    self.end_pos = Point3(end_pos)
                    self.start_hpr = Vec3(start_hpr)
                    self.end_hpr = Vec3(end_hpr)
                    self.elapsed_time = 0.0
                    self.duration = 0.5  # 增加动画时间使移动更平滑

            self._camera_focus_data = CameraFocusData(start_pos, end_pos, start_hpr, end_hpr)

            from direct.task.TaskManagerGlobal import taskMgr
            taskMgr.remove("cameraFocusTask")

            taskMgr.add(self._cameraFocusTask, "cameraFocusTask")
            print(f"开始相机聚焦动画: {start_pos} -> {end_pos}")

        except Exception as e:
            print(f"相机聚焦动画启动失败: {str(e)}")

    def _normalizeAngle(self, angle):
        """规范化角度到-180到180度之间"""
        while angle > 180:
            angle -= 360
        while angle < -180:
            angle += 360
        return angle

    def _cameraFocusTask(self, task):
        """摄像机聚焦动画任务"""
        try:
            if not hasattr(self, '_camera_focus_data'):
                return task.done

            data = self._camera_focus_data
            from direct.showbase.ShowBaseGlobal import globalClock
            dt = globalClock.getDt()

            if dt <= 0:
                dt = 0.016

            data.elapsed_time += dt
            t = min(1.0, data.elapsed_time / data.duration)

            # 使用平滑插值
            smooth_t = t * t * (3 - 2 * t)

            # 插值位置和朝向
            current_pos = data.start_pos + (data.end_pos - data.start_pos) * smooth_t
            current_hpr = data.start_hpr + (data.end_hpr - data.start_hpr) * smooth_t

            self.cam.setPos(current_pos)
            self.cam.setHpr(current_hpr)

            if t >= 1.0:
                self.cam.setPos(data.end_pos)
                self.cam.setHpr(data.end_hpr)

                print(f"聚焦完成: 位置 {data.end_pos}, 朝向 {data.end_hpr}")

                if hasattr(self, '_camera_focus_data'):
                    delattr(self, '_camera_focus_data')

                return task.done

            return task.cont

        except Exception as e:
            print(f"摄像机聚焦动画任务失败: {e}")
            import traceback
            traceback.print_exc()
            return task.done

    def setupMouseClickHandler(self):
        """设置鼠标点击处理器"""
        try:
            # 设置鼠标监听
            self.accept("mouse1", self.onMouseClick)

            # 启用鼠标观察器
            if not hasattr(self, 'mouseWatcherNode'):
                from panda3d.core import MouseWatcher
                self.mouseWatcherNode = MouseWatcher()

        except Exception as e:
            print(f"设置鼠标点击处理器失败: {e}")

    def onMouseClick(self):
        """处理鼠标左键点击"""
        try:
            if not hasattr(self, 'mouseWatcherNode') or not self.mouseWatcherNode.hasMouse():
                return

            # 获取鼠标位置
            mouse_pos = self.mouseWatcherNode.getMouse()

            # 创建射线进行点击检测
            from panda3d.core import CollisionRay, CollisionNode, CollisionHandlerQueue, BitMask32

            # 创建射线
            ray = CollisionRay()
            ray.setFromLens(self.camNode, mouse_pos.x, mouse_pos.y)

            # 创建碰撞节点
            ray_node = CollisionNode('mouseRay')
            ray_node.addSolid(ray)
            ray_node.setFromCollideMask(BitMask32.allOn())

            # 附加到相机
            ray_np = self.camera.attachNewNode(ray_node)

            # 创建碰撞队列
            handler = CollisionHandlerQueue()

            # 确保碰撞遍历器存在
            if not hasattr(self, 'cTrav'):
                self.cTrav = CollisionTraverser()

            self.cTrav.addCollider(ray_np, handler)
            self.cTrav.traverse(self.render)

            # 检查碰撞结果
            target_node = None
            if handler.getNumEntries() > 0:
                # 按距离排序
                handler.sortEntries()

                # 遍历所有碰撞结果，找到最近的有效节点
                for i in range(handler.getNumEntries()):
                    entry = handler.getEntry(i)
                    clicked_node = entry.getIntoNodePath()

                    # 向上遍历找到可选择的父节点
                    current_node = clicked_node
                    found_valid_node = False

                    while current_node and not current_node.isEmpty():
                        # 检查节点是否应该被选择
                        if (current_node.hasTag("is_scene_element") and
                                not current_node.hasTag("is_gizmo") and
                                current_node.getName() not in ["render", "camera", "ambient_light",
                                                               "directional_light", "mouseRay"] and
                                not current_node.getName().startswith("collision_") and
                                "ground" not in current_node.getName().lower()):
                            target_node = current_node
                            found_valid_node = True
                            break

                        # 如果当前节点是碰撞体，获取其父节点作为目标
                        if current_node.getName().startswith("collision_"):
                            parent = current_node.getParent()
                            if (parent.hasTag("is_scene_element") and
                                    not parent.hasTag("is_gizmo") and
                                    parent.getName() not in ["render", "camera", "ambient_light",
                                                             "directional_light"] and
                                    "ground" not in parent.getName().lower()):
                                target_node = parent
                                found_valid_node = True
                                break

                        current_node = current_node.getParent()
                        if current_node.getName() == "render":
                            break

                    if found_valid_node:
                        break

                # 如果找到了目标节点，则进行双击检测
                if target_node:
                    print(f"找到可选择节点: {target_node.getName()}")
                    print(f"节点位置: {target_node.getPos()}")
                    print(f"节点边界: {target_node.getBounds()}")
                    # 检查是否为双击
                    if self.checkDoubleClick(target_node):
                        print(f"双击节点: {target_node.getName()}")
                        self.focusCameraOnNode(target_node)
                    else:
                        print(f"单击节点: {target_node.getName()}")
                else:
                    print("未找到有效的目标节点")

            # 清理碰撞器
            ray_np.removeNode()

        except Exception as e:
            print(f"处理鼠标点击失败: {e}")
            import traceback
            traceback.print_exc()\

    def createFrameRateDisplay(self):
        from direct.gui.DirectGui import DirectLabel
        from panda3d.core import TextNode

        self.fps_label = DirectLabel(
            text="FPS:0",
            pos=(-1.8,0,0.9),
            scale=0.05,
            text_fg=(1,1,1,1),
            text_align=TextNode.ALeft,
            frameSize=(-0.1,0.3,-0.05,0.05),
            frameColor=(0,0,0,0.5),
            text_font=self.getChineseFont() if self.getChineseFont() else None,
            #parent=self.a2dTopLeft
        )

        taskMgr.add(self.updateFrameRate,"updateFrameRateTask")

    def updateFrameRate(self,task):
        try:
            fps = globalClock.getAverageFrameRate()

            if self.fps_label:
                self.fps_label.setText(f"FPS:{fps:.1f}")
        except Exception as e:
            print(f"更新帧率失败: {e}")
        return task.cont


# 在 main.py 的最后部分，修改为：
if __name__ == "__main__":
    try:
        app = MainApp()
        if hasattr(app, 'run'):
            app.run()
        else:
            print("应用程序初始化失败")
    except Exception as e:
        print(f"应用程序启动失败: {str(e)}")
        import traceback
        traceback.print_exc()


