
import math
import warnings

from direct.actor.Actor import Actor

warnings.filterwarnings("ignore", category=DeprecationWarning)

from QMeta3D.Meta3DWorld import Meta3DWorld
from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight,
                         Point3, WindowProperties,Material,LColor)
from direct.showbase.ShowBaseGlobal import globalClock
from scene.scene_manager import SceneManager

# 尝试导入插件管理器（如果存在）
try:
    from plugins.plugin_manager import PluginManager
    PLUGIN_SUPPORT = True
except ImportError:
    PLUGIN_SUPPORT = False
    print("⚠ 插件管理器未找到，插件功能将不可用")

class CoreWorld(Meta3DWorld):
    """核心世界功能类 - 负责基础的3D世界设置和核心功能"""

    def __init__(self):
        super().__init__()

        # 初始化基础属性
        self.qtWidget = None  # Qt部件引用（用于获取准确的渲染区域尺寸）

        # 设置相机控制参数
        #self.cameraSpeed = 200.0  # 移动速度
        self.cameraSpeed=20.0
        #self.cameraRotateSpeed = 40.0  # 旋转速度
        self.cameraRotateSpeed = 10.0

        # 鼠标控制相关变量
        self.lastMouseX = 0
        self.lastMouseY = 0
        self.mouseRightPressed = False

        # 初始化插件管理器（如果支持）
        if PLUGIN_SUPPORT:
            self.plugin_manager = PluginManager(self)
        else:
            self.plugin_manager = None

        # 初始化世界
        self._setupResourcePaths()
        self._setupCamera()
        self._setupLighting()
        self._setupGround()
        self._loadFont()

    def _setupResourcePaths(self):
        """设置Panda3D资源搜索路径，确保能正确找到Resources文件夹中的模型和贴图"""
        try:
            import os
            from panda3d.core import getModelPath, DSearchPath, Filename
            
            # 获取项目根目录
            project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            resources_dir = os.path.join(project_root, "Resources")
            
            # 确保Resources目录存在
            if not os.path.exists(resources_dir):
                os.makedirs(resources_dir, exist_ok=True)
                print(f"✓ 创建Resources目录: {resources_dir}")
            
            # 添加Resources目录到Panda3D模型搜索路径
            model_path = getModelPath()
            resources_filename = Filename.from_os_specific(resources_dir)
            
            # 检查路径是否已存在，避免重复添加
            if not model_path.findFile(resources_filename):
                model_path.appendDirectory(resources_filename)
                print(f"✓ 添加Resources到模型搜索路径: {resources_dir}")
            
            # 添加core目录到搜索路径
            core_dir = os.path.join(project_root, "core")
            core_filename = Filename.from_os_specific(core_dir)
            if not model_path.findFile(core_filename):
                model_path.appendDirectory(core_filename)
                print(f"✓ 添加core目录到模型搜索路径: {core_dir}")

            # 添加RenderPipeline目录到搜索路径
            rp_dir = os.path.join(project_root, "RenderPipelineFile")
            rp_filename = Filename.from_os_specific(rp_dir)
            if not model_path.findFile(rp_filename):
                model_path.appendDirectory(rp_filename)
                print(f"✓ 添加RenderPipeline目录到模型搜索路径: {rp_dir}")

            # 添加RenderPipeline data目录到搜索路径
            rp_data_dir = os.path.join(rp_dir, "data")
            rp_data_filename = Filename.from_os_specific(rp_data_dir)
            if not model_path.findFile(rp_data_filename):
                model_path.appendDirectory(rp_data_filename)
                print(f"✓ 添加RenderPipeline data目录到模型搜索路径: {rp_data_dir}")

            # 同时添加各个子目录到搜索路径
            subdirs = ['models', 'textures', 'animations', 'icons', 'materials']
            for subdir in subdirs:
                subdir_path = os.path.join(resources_dir, subdir)
                if os.path.exists(subdir_path):
                    subdir_filename = Filename.from_os_specific(subdir_path)
                    if not model_path.findFile(subdir_filename):
                        model_path.appendDirectory(subdir_filename)
                        print(f"✓ 添加子目录到搜索路径: {subdir}")
                else:
                    # 创建不存在的子目录
                    os.makedirs(subdir_path, exist_ok=True)
                    subdir_filename = Filename.from_os_specific(subdir_path)
                    model_path.appendDirectory(subdir_filename)
                    print(f"✓ 创建并添加子目录: {subdir}")
            
            # 设置纹理搜索路径
            from panda3d.core import getTexturePath
            texture_path = getTexturePath()
            if not texture_path.findFile(resources_filename):
                texture_path.appendDirectory(resources_filename)
                
            for subdir in ['textures', 'materials', 'icons']:
                subdir_path = os.path.join(resources_dir, subdir)
                if os.path.exists(subdir_path):
                    subdir_filename = Filename.from_os_specific(subdir_path)
                    if not texture_path.findFile(subdir_filename):
                        texture_path.appendDirectory(subdir_filename)
            
            print(f"✓ 资源路径设置完成")
            print(f"   项目根目录: {project_root}")
            print(f"   Resources目录: {resources_dir}")
            
        except Exception as e:
            print(f"⚠️ 设置资源路径失败: {e}")

    def diagnose_fbx_loading(self, fbx_path):
        """诊断FBX加载状态"""
        print("=== FBX加载诊断 ===")

        # 检查文件存在
        import os
        if not os.path.exists(fbx_path):
            print("❌ FBX文件不存在")
            return None

        try:
            # 尝试加载
            actor = Actor(fbx_path)

            # 检查动画
            anims = actor.getAnimNames()
            print(f"✓ 找到 {len(anims)} 个动画: {anims}")

            # 检查PartBundle
            bundles = actor.getPartBundles()
            print(f"✓ 找到 {len(bundles)} 个PartBundle")

            # 测试动画控制器
            if anims:
                control = actor.getAnimControl(anims[0])
                if control:
                    print(f"✓ 动画控制器创建成功: {anims[0]}")
                    return actor
                else:
                    print(f"❌ 动画控制器创建失败: {anims[0]}")

            return actor

        except Exception as e:

            print(f"❌ FBX加载异常: {e}")

            return None


    def diagnose_actor_animation(self,model_path, anim_path, anim_name="walk"):
        print("=== 开始诊断 Actor 动画问题 ===")
        import os
        from direct.actor.Actor import Actor
        from panda3d.core import Loader
        from panda3d.core import Filename

        # 检查文件存在性
        print(f"1. 文件检查:")
        print(f"   模型文件存在: {os.path.exists(model_path)}")
        print(f"   动画文件存在: {os.path.exists(anim_path)}")

        if not os.path.exists(model_path):
            print("❌ 模型文件不存在，请检查路径")
            return None
        if not os.path.exists(anim_path):
            print("❌ 动画文件不存在，请检查路径")
            return None
        # 2. 检查模型结构
        print(f"2. 模型结构检查:")
        try:
            # 先用普通loader加载检查结构
            temp_model = self.loader.loadModel(model_path)
            if temp_model is None:
                print("❌ 无法加载模型文件")
                return None

                # 检查Character节点
            bundleNP = temp_model.find("**/+Character")
            if bundleNP.isEmpty():
                print("❌ 模型不包含Character节点 - 这是动画模型必需的")
                print("   建议: 确保模型文件是正确的角色模型，包含骨骼结构")
                return None
            else:
                print("✓ 模型包含Character节点")

            temp_model.removeNode()  # 清理临时模型
        except Exception as e:
            print(f"❌ 模型加载失败: {e}")
            return None
            # 3. 检查动画文件结构
        print(f"3. 动画文件检查:")
        try:
            temp_anim = self.loader.loadModel(anim_path)
            if temp_anim is None:
                print("❌ 无法加载动画文件")
                return None

                # 检查AnimBundleNode
            animBundleNP = temp_anim.find('**/+AnimBundleNode')
            if animBundleNP.isEmpty():
                print("❌ 动画文件不包含AnimBundleNode")
                print("   建议: 确保动画文件是正确导出的动画数据")
                return None
            else:
                print("✓ 动画文件包含AnimBundleNode")

            temp_anim.removeNode()  # 清理临时动画
        except Exception as e:
            print(f"❌ 动画文件加载失败: {e}")
        # 4. 尝试创建Actor
        print(f"4. Actor创建测试:")

        # 方法1: 构造函数方式
        try:
            print("   尝试方法1: 构造函数加载")
            actor = Actor(model_path, {anim_name: anim_path})

            print(f"   动画列表: {actor.getAnimNames()}")

            # 强制同步绑定
            print("   尝试强制绑定动画...")
            actor.bindAnim(anim_name, allowAsyncBind=False)

            control = actor.getAnimControl(anim_name)
            if control is not None:
                print("✓ 方法1成功 - 动画控制器创建成功")
                return actor
            else:
                print("❌ 方法1失败 - 动画控制器为None")

        except Exception as e:
            print(f"❌ 方法1异常: {e}")

            # 方法2: 分步加载
        try:
            print("   尝试方法2: 分步加载")
            actor = Actor()
            actor.loadModel(model_path)
            actor.loadAnims({anim_name: anim_path})

            # 强制绑定
            actor.bindAnim(anim_name, allowAsyncBind=False)
            control = actor.getAnimControl(anim_name)

            if control is not None:
                print("✓ 方法2成功 - 动画控制器创建成功")
                return actor
            else:
                print("❌ 方法2失败 - 动画控制器为None")

        except Exception as e:
            print(f"❌ 方法2异常: {e}")
            # 5. 深度诊断
        print(f"5. 深度诊断:")
        try:
            actor = Actor()
            actor.loadModel(model_path)

            # 检查PartBundle
            bundles = actor.getPartBundles()
            print(f"   PartBundle数量: {len(bundles)}")

            if len(bundles) == 0:
                print("❌ 没有找到PartBundle - 模型可能不是正确的角色模型")
                return None

                # 检查动画绑定过程
            print("   尝试手动绑定动画...")
            actor.loadAnims({anim_name: anim_path})

            # 获取详细的绑定信息
            actor_info = actor.getActorInfo()
            print(f"   Actor信息: {actor_info}")

            return None

        except Exception as e:
            print(f"❌ 深度诊断异常: {e}")
            return None

        print("❌ 所有方法都失败了")
        return None

    def _setYCModel(self):
        model = self.loader.loadModel("/home/tiger/文档/Tzjyc_GLTF/tzjyc.gltf")
        model.reparentTo(self.render)
        model.setScale(0.25)
        model.setPos(-8, 42, 0)
        model.setHpr(0, 90, 0)


    def _setupCamera(self):

        """设置相机位置和朝向"""
        self.cam.setPos(0, -50, 20)
        self.cam.lookAt(0, 0, 0)
        self.camLens.setFov(80)
        self.cam.setTag("is_scene_element", "1")
        self.cam.setTag("tree_item_type", "CAMERA_NODE")
        print("✓ 相机设置完成")

    def _setupLighting(self):
        """设置基础光照系统"""
        # 环境光
        alight = AmbientLight('alight')
        alight.setColor((0.2, 0.2, 0.2, 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)

        # 保存光源引用
        self.ambient_light = alnp
        self.directional_light = dlnp

        print("✓ 光照系统设置完成")

    def _setupGround(self):
        """创建地板"""
        cm = CardMaker('ground')
        cm.setFrame(-50, 50, -50, 50)

        # 创建地板节点
        self.ground = self.render.attachNewNode(cm.generate())
        self.ground.setP(-90)
        self.ground.setZ(-1.0)
        self.ground.setColor(0.8, 0.8, 0.8, 1)
        self.ground.setTag("is_scene_element", "1")
        self.ground.setTag("tree_item_type", "SCENE_NODE")

        # 创建支持贴图的材质
        mat = Material()
        mat.setName("GroundMaterial")
        color = LColor(1, 1, 1, 0.8)
        mat.set_base_color(color)
        mat.set_roughness(1)  # 设置合适的初始粗糙度
        mat.set_metallic(0.5)   # 设置较低的初始金属性
        self.ground.set_material(mat)

        # #创建第二个相同的地面，位置稍有偏移
        # self.ground2 = self.render.attachNewNode(cm.generate())
        # self.ground2.setH(-90)
        # self.ground2.setZ(-1.0)
        # self.ground2.setX(50)  # 在X轴方向偏移
        # self.ground2.setZ(49)  # 在X轴方向偏移
        # self.ground2.setColor(0.8, 0.8, 0.8, 1)
        # self.ground2.set_material(mat)
        # self.ground2.setTag("is_scene_element", "1")
        # self.ground2.setTag("tree_item_type", "SCENE_NODE")
        #
        # # 创建第三个相同的地面，位置在另一个方向
        # self.ground3 = self.render.attachNewNode(cm.generate())
        # self.ground3.setH(90)
        # self.ground3.setZ(-1.0)
        # self.ground3.setX(-50)  # 在X轴负方向偏移
        # self.ground3.setZ(49)  # 在X轴负方向偏移
        # self.ground3.setColor(0.8, 0.8, 0.8, 1)
        # self.ground3.set_material(mat)
        # self.ground3.setTag("is_scene_element", "1")
        # self.ground3.setTag("tree_item_type", "SCENE_NODE")
        #
        # self.ground4 = self.render.attachNewNode(cm.generate())
        # # self.ground3.setR(90)
        # self.ground4.setZ(-1.0)
        # self.ground4.setY(50)  # 在X轴负方向偏移
        # self.ground4.setZ(49)  # 在X轴负方向偏移
        # self.ground4.setColor(0.8, 0.8, 0.8, 1)
        # self.ground4.set_material(mat)
        # self.ground4.setTag("is_scene_element", "1")
        # self.ground4.setTag("tree_item_type", "SCENE_NODE")
        #
        # self.ground5 = self.render.attachNewNode(cm.generate())
        # self.ground5.setP(180)
        # self.ground5.setZ(-1)
        # self.ground5.setY(-50)  # 在X轴负方向偏移
        # self.ground5.setZ(49)  # 在X轴负方向偏移
        # self.ground5.setColor(0.8, 0.8, 0.8, 1)
        # self.ground5.set_material(mat)
        # self.ground5.setTag("is_scene_element", "1")
        # self.ground5.setTag("tree_item_type", "SCENE_NODE")
        #
        # self.ground6 = self.render.attachNewNode(cm.generate())
        # self.ground6.setP(90)
        # self.ground6.setZ(-1)
        # self.ground6.setZ(99)  # 在X轴负方向偏移
        # self.ground6.setColor(0.8, 0.8, 0.8, 1)
        # self.ground6.set_material(mat)
        # self.ground6.setTag("is_scene_element", "1")
        # self.ground6.setTag("tree_item_type", "SCENE_NODE")

        # 应用默认PBR效果，确保支持贴图
        try:
            if hasattr(self, 'render_pipeline') and self.render_pipeline:
                self.render_pipeline.set_effect(
                    self.ground,
                    "effects/default.yaml",
                    {
                        "normal_mapping": True,
                        "render_gbuffer": True,
                        "alpha_testing": False,
                        "parallax_mapping": False,
                        "render_shadow": True,
                        "render_envmap": True
                    },
                    50
                )
                # # 为其他两个地面也应用相同的效果
                # self.render_pipeline.set_effect(
                #     self.ground2,
                #     "effects/default.yaml",
                #     {
                #         "normal_mapping": True,
                #         "render_gbuffer": True,
                #         "alpha_testing": False,
                #         "parallax_mapping": False,
                #         "render_shadow": True,
                #         "render_envmap": True
                #     },
                #     50
                # )
                # self.render_pipeline.set_effect(
                #     self.ground3,
                #     "effects/default.yaml",
                #     {
                #         "normal_mapping": True,
                #         "render_gbuffer": True,
                #         "alpha_testing": False,
                #         "parallax_mapping": False,
                #         "render_shadow": True,
                #         "render_envmap": True
                #     },
                #     50
                # )
                # self.render_pipeline.set_effect(
                #     self.ground4,
                #     "effects/default.yaml",
                #     {
                #         "normal_mapping": True,
                #         "render_gbuffer": True,
                #         "alpha_testing": False,
                #         "parallax_mapping": False,
                #         "render_shadow": True,
                #         "render_envmap": True
                #     },
                #     50
                # )
                # self.render_pipeline.set_effect(
                #     self.ground5,
                #     "effects/default.yaml",
                #     {
                #         "normal_mapping": True,
                #         "render_gbuffer": True,
                #         "alpha_testing": False,
                #         "parallax_mapping": False,
                #         "render_shadow": True,
                #         "render_envmap": True
                #     },
                #     50
                # )
                # self.render_pipeline.set_effect(
                #     self.ground6,
                #     "effects/default.yaml",
                #     {
                #         "normal_mapping": True,
                #         "render_gbuffer": True,
                #         "alpha_testing": False,
                #         "parallax_mapping": False,
                #         "render_shadow": True,
                #         "render_envmap": True
                #     },
                #     50
                # )
                # print("✓ 地板PBR效果已应用")
            else:
                print("⚠️ RenderPipeline未初始化，地板将使用基础渲染")
        except Exception as e:
            print(f"⚠️ 地板PBR效果应用失败: {e}")

        print("✓ 地板创建完成（支持材质贴图）")



    # def _loadFont(self):
    #     """加载中文字体"""
    #     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 _loadFont(self):
        """加载中文字体 - 跨平台，Panda3D 友好路径"""
        try:
            import os
            import platform
            from pathlib import Path
            from panda3d.core import Filename

            self.chinese_font = None  # 初始化

            # --- 平台与项目根 ---
            system = platform.system().lower()
            project_root = Path(__file__).resolve().parent.parent  # 上两级

            # --- 候选字体路径（按平台） ---
            if system == "windows":
                win_dir = os.environ.get("WINDIR") or r"C:\Windows"
                win_fonts_dir = Path(win_dir) / "Fonts"
                font_candidates = [
                    project_root / "RenderPipelineFile" / "data" / "font" / "msyh.ttc",
                    win_fonts_dir / "msyh.ttc",
                    win_fonts_dir / "msyh.ttf",
                    win_fonts_dir / "simhei.ttf",
                    win_fonts_dir / "simsun.ttc",
                ]
            elif system == "darwin":  # macOS
                font_candidates = [
                    Path("/System/Library/Fonts/PingFang.ttc"),
                    Path("/System/Library/Fonts/STHeiti.ttc"),
                    Path("/Library/Fonts/Songti.ttc"),
                ]
            else:  # Linux / 其他
                font_candidates = [
                    Path("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"),
                    Path("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"),
                    Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
                ]

            # --- 逐个尝试加载 ---
            for os_path in font_candidates:
                os_path_str = str(os_path)
                if not os.path.exists(os_path_str):
                    # 文件确实不存在就跳过
                    continue

                # 1) 先把 OS 路径转换成 Panda 内部路径（/d/... 或 /usr/...）
                panda_fn = Filename.fromOsSpecific(os_path_str)
                panda_fn.makeTrueCase()  # Windows 上修正大小写（更稳）
                panda_internal = panda_fn.getFullpath()  # 取“内部样式”的字符串

                # 2) 打印便于对照 Panda 的日志
                try:
                    print(f"[FontLoader] 尝试加载: {panda_internal}")
                except Exception:
                    pass

                try:
                    # 3) 传“字符串”给 loader.loadFont（关键修复点）
                    font = self.loader.loadFont(panda_internal)
                    if font:
                        self.chinese_font = font
                        print("✓ 中文字体加载成功:", os_path_str)
                        break
                    else:
                        print("[FontLoader] 返回空字体对象，继续尝试下一个候选...")
                except TypeError as te:
                    # 某些旧版绑定只接受 str，不接受 Filename；此处已传 str，若仍报错再兜底一次
                    try:
                        font = self.loader.loadFont(str(panda_fn))  # 退一步：str(Filename)
                        if font:
                            self.chinese_font = font
                            print("✓ 中文字体加载成功(备用API):", os_path_str)
                            break
                        else:
                            print("[FontLoader] 备用API仍返回空字体对象，继续…")
                    except Exception as te2:
                        print(f"[FontLoader] 加载失败(TypeError)，继续下一个: {os_path}，原因: {te2}")
                except Exception as e:
                    print(f"[FontLoader] 加载失败，继续下一个: {os_path}，原因: {e}")

            # --- 兜底 ---
            if not self.chinese_font:
                print("警告: 无法加载中文字体，将使用默认字体（可能导致中文显示不全）")
                self.chinese_font = None

        except Exception as e:
            print(f"警告: 加载中文字体时发生错误: {e}")
            self.chinese_font = None

    def setQtWidget(self, widget):
        """设置Qt部件引用"""
        self.qtWidget = widget
        print(f"✓ 设置Qt部件引用: {widget}")

    def getWindowSize(self):
        """获取准确的窗口尺寸"""
        if self.qtWidget:
            # 优先使用Qt部件的实际尺寸
            width, height = self.qtWidget.getActualSize()
            if width > 0 and height > 0:
                return width, height

        # 备用方案：使用Panda3D窗口尺寸
        if hasattr(self, 'win') and self.win:
            width = self.win.getXSize()
            height = self.win.getYSize()
            # print(f"从Panda3D窗口获取尺寸!!!!!!!!!!!!!!!!!!!!!: {width} x {height}")
            return width, height

        # 最后的默认值
        print("使用默认窗口尺寸: 800 x 600")
        return 800, 600

    # ==================== 相机控制功能 ====================

    def wheelForward(self, data=None):
        """处理滚轮向前滚动（前进）"""
        # 获取相机的前向向量
        forward = self.cam.getMat().getRow3(1)
        # 计算移动距离
        distance = self.cameraSpeed * globalClock.getDt()
        # 更新相机位置
        currentPos = self.cam.getPos()
        newPos = currentPos + forward * distance
        self.cam.setPos(newPos)

    def wheelBackward(self, data=None):
        """处理滚轮向后滚动（后退）"""
        # 获取相机的前向向量
        forward = self.cam.getMat().getRow3(1)
        # 计算移动距离
        distance = self.cameraSpeed * globalClock.getDt()
        # 更新相机位置
        currentPos = self.cam.getPos()
        newPos = currentPos - forward * distance
        self.cam.setPos(newPos)

    def moveCamera(self, x, y, z):
        """移动相机位置（垂直移动）"""
        # 获取相机的上向量
        upVector = self.cam.getMat().getRow3(2)
        # 计算移动距离
        distance = self.cameraSpeed * globalClock.getDt()
        # 更新相机位置
        currentPos = self.cam.getPos()
        newPos = currentPos + upVector * z * distance
        self.cam.setPos(newPos)

    # ==================== 鼠标事件处理 ====================

    def mousePressEventRight(self, evt):
        """处理鼠标右键按下事件"""
        #print("右键按下")
        self.mouseRightPressed = True
        self.lastMouseX = evt['x']
        self.lastMouseY = evt['y']
        #
        # # 通过 Qt 窗口隐藏光标并捕获鼠标
        # try:
        #     if hasattr(self, 'qtWidget') and self.qtWidget:
        #         from PyQt5.QtCore import Qt
        #         self.qtWidget.setCursor(Qt.BlankCursor)
        #         # 捕获鼠标，使其无法离开窗口
        #         self.qtWidget.grabMouse()
        # except Exception as e:
        #     print(f"通过 Qt 隐藏光标时出错: {e}")

    def mouseReleaseEventRight(self, evt):
        """处理鼠标右键释放事件"""
        #print("右键释放")
        self.mouseRightPressed = False

        # # 恢复 Qt 窗口光标并释放鼠标捕获
        # try:
        #     if hasattr(self, 'qtWidget') and self.qtWidget:
        #         from PyQt5.QtCore import Qt
        #         self.qtWidget.unsetCursor()  # 恢复默认光标
        #         # 释放鼠标捕获
        #         self.qtWidget.releaseMouse()
        # except Exception as e:
        #     print(f"恢复 Qt 光标时出错: {e}")

    def mouseMoveEvent(self, evt):
        """处理鼠标移动事件 - 只处理相机旋转"""
        if not evt:
            return

        if self.mouseRightPressed:
            # 计算鼠标移动距离
            dx = evt.get('x', 0) - self.lastMouseX
            dy = evt.get('y', 0) - self.lastMouseY

            # 计算旋转角度
            rotateSpeed = self.cameraRotateSpeed * globalClock.getDt()

            # 更新相机朝向
            currentH = self.cam.getH()
            currentP = self.cam.getP()

            # 限制俯仰角度在-90到90度之间
            newP = max(-90, min(90, currentP - dy * rotateSpeed))

            self.cam.setH(currentH - dx * rotateSpeed)
            self.cam.setP(newP)

            # 更新鼠标位置
            self.lastMouseX = evt.get('x', 0)
            self.lastMouseY = evt.get('y', 0)

    # ==================== 其他基础功能 ====================

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

    def getGroundNode(self):
        """获取地板节点"""
        return self.ground

    def getAmbientLight(self):
        """获取环境光"""
        return self.ambient_light

    def getDirectionalLight(self):
        """获取定向光"""
        return self.directional_light

    def start_day_night_cycle(self, duration_seconds=10.0):
        """让天空盒在 duration_seconds 秒内从 0:00 过渡到 24:00"""
        self._cycle_start_time = self.taskMgr.globalClock.get_real_time()
        self._cycle_duration = duration_seconds
        self.taskMgr.add(self._day_night_cycle_task, "day_night_cycle_task")

    def _day_night_cycle_task(self, task):
        elapsed = self.taskMgr.globalClock.get_real_time() - self._cycle_start_time
        t = (elapsed % self._cycle_duration) / self._cycle_duration  # 始终在 0~1 循环
        self.render_pipeline.daytime_mgr.time = 24.0 * t
        return task.cont


    def set_daytime(self, time_str):
        """设置时间"""
        try:
            if hasattr(self, 'render_pipeline') and self.render_pipeline:
                self.render_pipeline.daytime_mgr.time = time_str
                print(f"当前时间设置为: {time_str}")
            else:
                print(f"⚠️ RenderPipeline 不可用，无法设置时间: {time_str}")
        except Exception as e:
            print(f"设置时间失败: {e}")

    def get_render_pipeline(self):
        """获取 RenderPipeline 实例"""
        return getattr(self, 'render_pipeline', None)

    def _setupSkybox(self):
        # 加载天空盒模型
        self.skybox = self.loader.loadModel("data/builtin_models/skybox/skybox.bam")
        self.skybox.reparentTo(self.camera)  # 绑定到相机
        self.skybox.setScale(500)
        self.skybox.setBin('background', 0)
        self.skybox.setDepthWrite(False)
        self.skybox.setLightOff()
        self.skybox.setCompass()  # 始终朝向固定

        print("✓ 静态天空盒加载完成")

    def createDirectionalLight(self):
        from RenderPipelineFile.rpcore import light_manager
        from panda3d.core import DirectionalLight, Vec3
        dlight = DirectionalLight("1")

        dlight_np = self.render.attachNewNode(dlight)
        #light_manager.add_light(dlight)

        dlight_np.setHpr(45,-45,0)

        self.render.setLight(dlight_np)

        dlight.setColor((1,1,1,1))
        dlight.setShadowCaster(True,2048,2048)

        print("平行光创建完成")

        # dlight.direction = Vec3(0, 0, -1)  # 光照方向
        # dlight.fov = self.lamp_fov  # 光源角度（类似手电筒）
        # dlight.set_color_from_temperature(1 * 1000.0)  # 色温（K）
        # dlight.energy = self.half_energy  # 光照强度
        # dlight.radius = self.lamp_radius  # 影响范围
        # dlight.casts_shadows = True  # 是否投射阴影
        # dlight.shadow_map_resolution = 256  # 阴影分辨率
        # dlight.setPos(0,0,10)

    def check_material_editor_connection(self):
        """检查材质编辑器连接状态"""
        try:
            # 确保 RenderPipeline 已完全初始化
            if not hasattr(self, 'render_pipeline') or not self.render_pipeline:
                print("RenderPipeline 未初始化")
                return False

                # 检查网络监听器是否存在
            if not hasattr(self.render_pipeline, '_listener'):
                print("NetworkCommunication 监听器未初始化")
                return False

            from RenderPipelineFile.rpcore.util.network_communication import NetworkCommunication
            import tempfile
            import os
            import time

            # 使用唯一的测试文件名
            import uuid
            test_filename = f"test_materials_{uuid.uuid4().hex[:8]}.data"
            temp_path = os.path.join(tempfile.gettempdir(), test_filename)

            print(f"测试材质编辑器连接，文件路径: {temp_path}")

            # 确保测试文件不存在
            if os.path.exists(temp_path):
                os.remove(temp_path)

                # 发送导出命令
            NetworkCommunication.send_async(
                NetworkCommunication.MATERIAL_PORT,
                f"dump_materials {temp_path}"
            )

            # 大幅增加等待时间，因为网络命令处理可能有延迟
            for i in range(60):  # 等待最多30秒
                time.sleep(0.5)
                if os.path.exists(temp_path):
                    # 等待文件写入完成
                    time.sleep(1.0)
                    try:
                        with open(temp_path, 'r') as f:
                            content = f.read().strip()
                            print(f"材质编辑器连接测试成功，文件内容: {len(content)} 字符")
                            os.remove(temp_path)
                            return True
                    except:
                        print("文件读取失败，继续等待...")
                        continue

                if i % 20 == 0 and i > 0:  # 每10秒打印一次状态
                    print(f"等待材质文件创建... ({i // 2}s)")

            print("材质编辑器连接测试超时")
            return False

        except Exception as e:
            print(f"材质编辑器连接测试出错: {e}")
            return False


    def create_material_editor_widget(self):
        """创建材质编辑器组件"""
        try:
            # 确保 RenderPipeline 已完全初始化
            if not hasattr(self, 'render_pipeline') or not self.render_pipeline:
                print("RenderPipeline 未初始化")
                return None

                # 检查网络连接
            if not self.check_material_editor_connection():
                print("无法连接到材质编辑器网络服务")
                return None

                # 创建材质编辑器组件
            from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel

            material_widget = QWidget()
            layout = QVBoxLayout()

            # 添加基本的材质编辑控件
            layout.addWidget(QLabel("材质编辑器"))
            # 这里可以添加更多的材质编辑控件

            material_widget.setLayout(layout)
            return material_widget

        except Exception as e:
            print(f"创建材质编辑器失败: {e}")
            return None

    def _delayed_material_test(self, task):
        """延迟执行的材质编辑器连接测试"""
        print("开始延迟材质编辑器连接测试...")
        success = self.check_material_editor_connection()
        if success:
            print("✓ 材质编辑器连接正常")
        else:
            print("✗ 材质编辑器连接失败")
        return task.done

    def launch_day_time_editor(self):

        # 检查是否已经启动
        if hasattr(self, '_day_time_editor_process') and self._day_time_editor_process:
            if self._day_time_editor_process.poll() is None:  # 进程仍在运行
                print("Day Time Editor 已经在运行")
                return True
        import subprocess
        import os
        import sys

        try:
            if not hasattr(self.world,'render_pipeline') or not self.world.render_pipeline:
                print("错误：renderpipeline未初始化")
                return False

            base_path = self.world.render_pipeline.mount_mgr.base_path
            editor_path = os.path.join(base_path,"toolkit/day_time_editor/main.py")

            if not os.path.exists(editor_path):
                print("错误文件不存在")
                return False

            self._day_time_editor_process = subprocess.Popen([sys.executable, editor_path])
            print("Day Time Editor 已启动")
            return True
        except Exception as e:
            print(f"启动 time editor失败")

    def setup_material_editor_network(self):
        """设置材质编辑器网络通信"""
        from RenderPipelineFile.rpcore.util.network_communication import NetworkCommunication

        def handle_material_requests(message):
            """处理材质编辑器的网络请求"""
            try:
                print(f"收到材质编辑器请求: {message}")
                parts = message.strip().split()
                if not parts:
                    return

                command = parts[0]

                if command == "dump_materials":
                    # 处理导出材质列表请求
                    path = parts[1] if len(parts) > 1 else ""
                    if path:
                        self.export_materials_to_file(path)
                elif command.startswith("update_material"):
                    # 处理更新材质请求
                    data = message[len("update_material "):].strip()
                    self.update_material_from_editor(data.split())

            except Exception as e:
                print(f"处理材质编辑器请求失败: {e}")

        # 注册网络消息处理器
        try:
            NetworkCommunication.listen_threaded(
                NetworkCommunication.MATERIAL_PORT,
                handle_material_requests
            )
            print("✓ 材质编辑器网络通信已设置")
        except Exception as e:
            print(f"设置材质编辑器网络通信失败: {e}")

    def export_materials_to_file(self, path):
        """导出材质列表到文件"""
        try:
            print(f"导出材质列表到: {path}")
            materials = []

            # 收集所有材质
            def collect_materials(node):
                if node.hasMaterial():
                    material = node.getMaterial()
                    if material:
                        materials.append(material)

                for child in node.getChildren():
                    collect_materials(child)

            collect_materials(self.render)

            # 写入文件
            with open(path, "w") as f:
                for i, material in enumerate(materials):
                    name = f"{i}-{material.getName() or 'unnamed'}"

                    # 获取材质属性，使用默认值如果不存在
                    base_color = material.getBaseColor() if hasattr(material, 'getBaseColor') else Vec4(0.6, 0.6, 0.6, 1.0)
                    roughness = material.getRoughness() if hasattr(material, 'getRoughness') else 0.5
                    metallic = material.getMetallic() if hasattr(material, 'getMetallic') else 0.0

                    # 写入材质数据
                    f.write(("{} " * 11).format(
                        name,
                        base_color.x,
                        base_color.y,
                        base_color.z,
                        roughness,
                        0.5,  # refractive_index
                        metallic,
                        0,    # shading_model
                        1.0,  # normal_strength
                        0.0,  # param1
                        0.0   # param2
                    ) + "\n")

            print(f"✓ 成功导出 {len(materials)} 个材质到 {path}")

        except Exception as e:
            print(f"导出材质列表失败: {e}")

    def update_material_from_editor(self, data):
        """从编辑器更新材质"""
        try:
            if len(data) < 11:
                print(f"材质数据不完整: {data}")
                return

            name_parts = data[0].split("-")
            if len(name_parts) < 2:
                print(f"材质名称格式错误: {data[0]}")
                return

            index = int(name_parts[0])
            name = "-".join(name_parts[1:])

            # 收集所有材质
            materials = []
            def collect_materials(node):
                if node.hasMaterial():
                    material = node.getMaterial()
                    if material:
                        materials.append(material)

                for child in node.getChildren():
                    collect_materials(child)

            collect_materials(self.render)

            # 查找匹配的材质
            if index < len(materials):
                material = materials[index]

                # 更新材质属性
                material.setBaseColor(Vec4(float(data[1]), float(data[2]), float(data[3]), 1.0))
                material.setRoughness(float(data[4]))
                material.setMetallic(float(data[6]))

                print(f"✓ 更新材质 {name}: 颜色=({data[1]}, {data[2]}, {data[3]}), 粗糙度={data[4]}, 金属度={data[6]}")
            else:
                print(f"未找到索引为 {index} 的材质")

        except Exception as e:
            print(f"更新材质失败: {e}")

    def launch_material_editor(self):
        """启动材质编辑器"""
        import subprocess
        import os
        import sys

        try:
            if  not self.render_pipeline:
                print("错误：renderpipeline未初始化")
                return False

            base_path = self.render_pipeline.mount_mgr.base_path
            editor_path = os.path.join(base_path,"toolkit/material_editor/main.py")

            if not os.path.exists(editor_path):
                print("错误文件不存在")
                return False

            self._material_editor_process = subprocess.Popen([sys.executable, editor_path])
            print("Material Editor 已启动")
            return True
        except Exception as e:
            print(f"启动 Material editor失败")

    def create_sample_materials(self):
        """创建一些示例材质供编辑器使用"""
        from panda3d.core import Material, Vec4, CardMaker

        # 创建几个测试几何体，每个都有不同的材质
        sample_materials = [
            {"name": "MetalMaterial", "color": Vec4(0.7, 0.7, 0.8, 1.0), "metallic": True, "roughness": 0.2},
            {"name": "PlasticMaterial", "color": Vec4(0.8, 0.2, 0.2, 1.0), "metallic": False, "roughness": 0.8},
            {"name": "GlassMaterial", "color": Vec4(0.9, 0.9, 1.0, 0.3), "metallic": False, "roughness": 0.1},
            {"name": "WoodMaterial", "color": Vec4(0.6, 0.4, 0.2, 1.0), "metallic": False, "roughness": 0.7},
            {"name": "TestPlaneMaterial", "color": Vec4(0.8, 0.8, 0.8, 1.0), "metallic": False, "roughness": 1.0}
        ]

        for i, mat_data in enumerate(sample_materials):
            # 创建几何体
            if mat_data["name"] == "TestPlaneMaterial":
                # 创建一个大的测试平面，类似Blender中的平面
                cm = CardMaker('test_plane')
                cm.setFrame(-5, 5, -5, 5)  # 更大的平面
                geom_node = self.render.attachNewNode(cm.generate())
                geom_node.setPos(0, 15, 0)  # 放在前面显眼位置
                geom_node.setP(-90)  # 水平放置
                print("✓ 创建大型测试平面，适合测试粗糙度贴图")
            else:
                cm = CardMaker(f'sample_geom_{i}')
                cm.setFrame(-1, 1, -1, 1)
                geom_node = self.render.attachNewNode(cm.generate())
                geom_node.setPos(i * 3 - 6, 10, 1)
                geom_node.setP(-90)  # 水平放置

            # 创建材质并确保名称正确设置
            material = Material()
            material.setName(mat_data["name"])  # 明确设置材质名称
            material.setBaseColor(mat_data["color"])
            material.setRoughness(mat_data["roughness"])
            material.setMetallic(1.0 if mat_data["metallic"] else 0.0)

            # 应用材质
            geom_node.setMaterial(material)

            print(f"✓ 创建示例材质: {mat_data['name']}")

        # 延迟一下确保材质完全创建
        import time
        time.sleep(0.1)

    def load_test_models_with_materials(self):
        """加载一些测试模型以提供更多材质供编辑"""
        try:
            # 你可以在这里加载你的模型文件
            # test_model = self.loader.loadModel("models/your_model.gltf")
            # if test_model:
            #     test_model.reparentTo(self.render)
            #     test_model.setPos(5, 0, 0)
            #     test_model.setScale(1.0)
            #     print(f"✓ 测试模型已加载")

            # 创建示例材质
            self.create_sample_materials()

        except Exception as e:
            print(f"加载测试模型失败: {e}")
            # 创建示例材质作为备选
            self.create_sample_materials()

    def launch_plugin_configurator(self):
        """启动材质编辑器"""
        import subprocess
        import os
        import sys

        try:
            if not self.render_pipeline:
                print("错误：renderpipeline未初始化")
                return False

            base_path = self.render_pipeline.mount_mgr.base_path
            editor_path = os.path.join(base_path, "toolkit/plugin_configurator/main.py")

            if not os.path.exists(editor_path):
                print("错误文件不存在")
                return False

            self._material_editor_process = subprocess.Popen([sys.executable, editor_path])
            print("plugin_configurator 已启动")
            return True
        except Exception as e:
            print(f"启动plugin_configurator失败")

