从内置功能到插件:以 LerpBehavior 为例的架构演进与实践

AI 驱动代码审查实战

Claude code-review 插件深度解析,把 AI 智能审查接进 CI/CD 流水线

本文以项目中的「插值行为(LerpBehavior)」模块为案例,说明它如何从系统内置能力演进为可选插件,以及编辑端与运行端各自的加载、启用与数据流转机制。

一、为什么要插件化?

原来系统的目的是开发一个三维组态系统,包含了编辑和运行两部分内容,但是领导要求添加一个爆炸图的编辑和运行的功能(后来演变成了按顺序装备与拆卸功能演示),领导要添加的功能与系统核心功能有一定关系,但关联度又不大,原来和系统的功能完全混在一起,感觉有些问题,后来经过同事提醒,决定做成一个LerpBehavior插件。

LerpBehavior 最初是编辑器与运行时的内置功能:插值位置、旋转、缩放、可视性以及插值控制器,与场景对象强绑定,始终加载、始终可用。

插件化之后,目标变为:

  1. 按需启用:用户可在「插件管理」中开关,关闭后隐藏 UI、停止执行,但不删除场景数据。
  2. 按需打包:支持「瘦包」构建——不含插值代码的编辑器也能运行,打开含插值数据的文件时给出提示。
  3. 运行时按需加载:导出运行数据时,仅当场景实际使用插值行为才写入模块标记;运行时据此决定是否激活 Lerp 子系统。
  4. 可扩展:通过注册表 + 目录(Catalog)模式,第三方可注册新的插值行为类型。

这套设计不是简单的 if/else 开关,而是一套分层、可复用的插件骨架,其他内置功能可以按同样模式迁移。

二、改造前后的架构对比

改造前(内置)

插值行为混在驱动行为体系中,无法独立开关,也无法在 DTO 层表达「是否依赖此模块」。

改造后(插件)

核心变化:

维度内置时代插件时代

行为归属

混在 Drive 体系

独立 LerpBehavior* 子系统

启用控制

LerpModuleGate / LerpModuleRuntimeGate

持久化

仅 lerpBehavInfos

额外写入 enabledEditorPlugins / enabledRuntimeModules

UI

始终显示

v-if="lerpModuleEnabled" 条件渲染

扩展

改核心注册表

registerLerpBehaviorEditor / registerLerpBehaviorCatalogEntry


三、迁移步骤拆解(以 LerpBehavior 为模板)

下面按实际落地顺序说明如何把内置功能改造成插件。其他模块(如未来的 XXXBehavior)可照此 checklist 复用。

步骤 1:定义模块身份(Shared 层)

在 Shared 中放置与端无关的模块元数据,避免编辑端、运行端各写一套。

模块 ID(LerpModuleId.ts):

LerpModuleId.tsLines 1-3

export const LERP_BEHAVIOR_MODULE_ID = 'LerpBehavior' as const;

export type LerpBehaviorModuleId = typeof LERP_BEHAVIOR_MODULE_ID;

行为条目元数据(LerpBehaviorModuleMetadata.ts)——统一描述编辑端类名、运行端类名、中文名、菜单分组、属性面板 key:

LerpBehaviorModuleMetadata.tsLines 18-58

export const BUILTIN_LERP_BEHAVIOR_ENTRIES: readonly LerpBehaviorModuleEntryMeta[] = [
    {
        editorBehaviorName: 'LerpPositionEditor',
        runtimeBehaviorName: 'LerpPosition',
        cnName: '插值位置',
        createMenuGroup: 'behavior',
        trackColorVar: 'var(--track-dark-red)',
        propsKey: 'lerpPos',
    },
    // ... LerpRotation, LerpScaling, LerpVisibility, LerpController
] as const;

要点:Shared 层只放「描述性」信息,不 import 编辑端 Vue 组件或 Babylon 行为实现,保证 Runtime 构建不会拖入编辑器依赖。


步骤 2:从主注册表中拆出独立子系统

运行端 BehaviorRegistry 中明确注释:Lerp 已迁出:

BehaviorRegistry.tsLines 4-6

// 注:Lerp* 已迁出至独立 LerpBehaviorRegistryRuntime,由 LerpBehaviorManager 管理。
// General* 已迁出至独立 GeneralBehaviorRegistryRuntime,由 GeneralBehavManager 管理。

编辑端、运行端各自建立对偶结构:

编辑端运行端

LerpBehaviorRegistry

LerpBehaviorRegistryRuntime

LerpBehaviorManagerEditor

LerpBehaviorManager

LerpBehaviorEditor 基类

LerpBehavior 基类

注册表继承通用基类 BehaviorRegistryBase,支持 createBehavior(name) 工厂创建:

LerpBehaviorRegistry.tsLines 12-24

class LerpBehaviorRegistry extends BehaviorRegistryBase<LerpBehaviorEditor, LerpBehaviorEditorClass> {

	constructor() {
		super([...getBuiltinLerpEditorClasses()]);
	}
	// ...
}

export const lerpBehaviorRegistryEditor = new LerpBehaviorRegistry();
lerpBehaviorRegistryEditor.initialize();

运行端同样从 BUILTIN_LERP_BEHAVIOR_ENTRIES 驱动内置类列表,并暴露 register() 供扩展:

LerpBehaviorRegistryRuntime.tsLines 37-62

export class LerpBehaviorRegistryRuntime {

	private static behaviorClasses: LerpBehaviorClass[] = getBuiltinRuntimeClasses();
	// ...
	public static initialize(): void {
		if (this.initialized) return;
		this.behaviorClasses.forEach(cls => {
			// 按 behaviorName 注册到 Map
		});
		this.initialized = true;
	}

步骤 3:建立 Catalog,绑定「逻辑类 + UI 组件」

编辑端插件不仅要注册 TypeScript 行为类,还要注册属性面板 Vue 组件。LerpBehaviorModuleCatalog 承担这一职责:

LerpBehaviorModuleCatalog.tsLines 27-58

const EDITOR_CLASS_BY_NAME: Record<string, LerpBehaviorEditorClass> = {
    LerpPositionEditor,
    LerpRotationEditor,
    // ...
};

const UI_COMPONENT_BY_NAME: Record<string, Component> = {
    LerpPositionEditor: LerpPositionCom,
    LerpRotationEditor: LerpRotationCom,
    // ...
};

export function getLerpBehaviorCatalogEntries(): LerpBehaviorCatalogEntry[] {
    return [
        ...BUILTIN_LERP_BEHAVIOR_ENTRIES.map(toCatalogEntry),
        ...extraCatalogEntries,  // 插件扩展入口
    ];
}

LerpBehaviorList.vue 启动时读取 Catalog,动态生成 UI 槽位;CreateLerpBehaviorMenu.vue 按 createMenuGroup 分组展示创建菜单。这样新增一种插值行为时,只需:实现 Editor 类 + Vue 组件 + 在 Catalog 注册,无需改列表组件本身。


步骤 4:引入 Gate(开关层)

编辑端 Gate — LerpModuleGate.ts

Gate 是插件的运行时开关,与「代码是否在构建产物中」解耦:

LerpModuleGate.tsLines 5-51

let _enabled = false;

export const onLerpModuleEnabledChanged = new Observable<boolean>();

let _pluginCodeAvailable = true;  // 瘦包构建可设为 false

export function isLerpModuleEnabled(): boolean {
	return _enabled;
}

export function setLerpModuleEnabled(enabled: boolean): void {
	if (_enabled === enabled) return;
	_enabled = enabled;
	if (!enabled) {
		_onDisabledHandler?.();  // 关闭时停止预览/播放
	}
	onLerpModuleEnabledChanged.notifyObservers(_enabled);
}

三个职责:

  1. 会话级开关:isLerpModuleEnabled() 供 Manager、UI 查询。
  2. 事件广播:onLerpModuleEnabledChanged 驱动 Vue 响应式更新。
  3. 关闭副作用:通过 registerLerpModuleDisabledHandler 注册清理逻辑(EditorSystem 构造函数中绑定 pauseAllSessionExecution)。
运行端 Gate — LerpModuleRuntimeGate.ts

LerpModuleRuntimeGate.tsLines 5-16

export function isLerpRuntimeModuleLoaded(): boolean {
    return _loaded;
}

export async function loadLerpRuntimeModule(): Promise<void> {
    LerpBehaviorRegistryRuntime.initialize();
    _loaded = true;
}

export function unloadLerpRuntimeModule(): void {
    _loaded = false;
}

运行端 Gate 更轻:标记模块是否已激活。实际行为创建仍走 applyLerpBehaviorsIfAvailable,在模块未加载时静默跳过,避免场景加载报错。


步骤 5:扩展 DTO,持久化插件状态

编辑文件(DTO_GlobalSettingEditor):

DTO_EditorSystem.tsLines 28-28

public enabledEditorPlugins:string[] | undefined = undefined

运行文件(DTO_GlobalSetting):

DTO_RuntimeSystem.tsLines 39-39

public enabledRuntimeModules:string[] | undefined = undefined //如果要添加插值行为插件,数组中需要添加"LerpBehavior"。

场景数据(每个 Obj3d 节点)——插值行为序列化字段保持不变:

DTO_EditorSystem.tsLines 165-165

public lerpBehavInfos:DTO_CipBehav[] | undefined = undefined,

设计原则:

  • lerpBehavInfos:永远保留插值数据,即使用户关闭插件。
  • enabledEditorPlugins / enabledRuntimeModules:声明「此项目依赖哪些插件」,供加载时自动启用。

自动启用判定(lerpModuleRequirement.ts):

lerpModuleRequirement.tsLines 51-63

export function isLerpEditorPluginMarked(dto: DTO_EditorSystem): boolean {
    return dto.globalSetting.enabledEditorPlugins?.includes(LERP_BEHAVIOR_MODULE_ID) ?? false;
}

export function hasLerpBehavInfosInEditorDto(dto: DTO_EditorSystem): boolean {
    // 扫描 obj3dSetting 中是否存在 lerpBehavInfos
}

export function shouldAutoEnableLerpEditorPlugin(dto: DTO_EditorSystem): boolean {
    return isLerpEditorPluginMarked(dto) || hasLerpBehavInfosInEditorDto(dto);
}

打开文件时,只要 DTO 里有插件标记或有插值数据,就自动 setLerpModuleEnabled(true);若瘦包不含代码则弹窗警告。


步骤 6:在 Manager 入口加 Gate 守卫

所有「创建 / 反序列化」插值行为的入口统一检查 Gate:

LerpBehaviorManagerEditor.tsLines 64-67

public addLerpBehavior(target: TransformNode, behaviorName: string): LerpBehaviorEditor | null {
	if (!isLerpModuleEnabled()) {
		console.warn("LerpBehaviorManagerEditor: Lerp module is disabled");
		return null;
	}

运行端通过包装函数守卫:

LerpBehaviorManager.tsLines 181-189

export function applyLerpBehaviorsIfAvailable(
    manager: LerpBehaviorManager,
    root: TransformNode,
    cipBehavs: DTO_CipBehav[] | undefined
): void {
    if (!isLerpRuntimeModuleLoaded()) return;
    if (!cipBehavs?.length) return;
    manager.setLerpBehavsByInfos(root, cipBehavs);
}

要点:Gate 守卫放在 Manager 层,而不是散落在每个 UI 按钮里,保证反序列化、复制粘贴等路径也不会在插件关闭时偷偷创建行为。


步骤 7:UI 条件渲染 + 插件管理面板

全局设置 — PluginManager.vue:

PluginManager.vueLines 1-6

<template>
<div class="form-item-list">
  	<div class="form-item">
		<label>插值行为</label>
		<t-switch v-model="lerpBehaviorEnabledRef" @change="onLerpBehaviorSwitchChange" />

关闭时有确认对话框,明确告知「数据保留、下次打开含插值数据的文件会自动重新启用」。

属性面板 — ObjectPropertiesPanel.vue:

ObjectPropertiesPanel.vueLines 26-30

<t-collapse-panel header="插值行为列表" v-if="lerpModuleEnabled && (nodeType === 'mesh' || nodeType === 'trNode')">
	<LerpBehaviorList
		v-if="nodeLerpBehavManager"
		:lerp-behav-manager="nodeLerpBehavManager"
	/>

EditorApp.vue — 插值控制器对话框 Portal 同样条件挂载:

EditorApp.vueLines 23-23

<LerpControllerDialogPortal v-if="lerpModuleEnabled" />

所有 UI 监听 onLerpModuleEnabledChanged,与 Gate 保持同步。


四、编辑端:插件如何加载与运行

整体时序如下:

关键代码路径

1. 保存时写入插件标记

EditorSystem.tsLines 360-362

const enabledEditorPlugins = isLerpModuleEnabled()
	? [LERP_BEHAVIOR_MODULE_ID]
	: undefined;

2. 加载时自动启用

EditorSystem.tsLines 420-427

const lerpResolution = resolveLerpPluginOnFileLoad(editorSystemInfo);
if (lerpResolution.needsWarning) {
	ConfirmDialog.ShowConfirmOnlyDialog(
		"检测到插值数据,但当前编辑器不支持插值行为插件。",
		() => {},
	);
}
setLerpModuleEnabled(lerpResolution.shouldEnable);

3. 导出运行数据时写入运行时模块列表

EditorSystem.tsLines 460-463

private _getEnabledRuntimeModules(): string[] | undefined {
	if (!isLerpModuleEnabled()) return undefined;
	if (this.sceneObjManager.lerpBehavManager.lerpBehaviors.length === 0) return undefined;
	return [LERP_BEHAVIOR_MODULE_ID];
}

只有「插件已启用」且「场景中确有插值行为实例」才写入,避免空场景携带无用模块依赖。

4. 反序列化仍走 Manager,不受 UI 可见性影响

SceneObj3dManagerEditor 加载节点时始终调用 lerpBehavManager.setLerpBehaviorsByInfos;若 Gate 为 false,Manager 内部不会创建新行为,但 DTO 中的 lerpBehavInfos 数据仍在文件中,下次启用插件即可恢复。


五、运行端:插件如何加载与运行

运行端链路更短,核心是 JSON 解析 → 模块 Gate → 条件应用行为。

关键代码

1. JSON 加载入口判断模块

RuntimeSystem.tsLines 283-288

const modules = runtimeInfo.globalSetting?.enabledRuntimeModules;
if (modules?.includes(LERP_BEHAVIOR_MODULE_ID)) {
	void loadLerpRuntimeModule();
} else {
	unloadLerpRuntimeModule();
}

2. 场景节点加载时条件应用

SceneObj3dManager 在节点克隆完成后:

SceneObj3dManager.tsLines 235-236

console.log("setLerpBehavsByInfos: obj3dInfo.lerpBehavInfos.length = " + obj3dInfo.lerpBehavInfos?.length);
applyLerpBehaviorsIfAvailable(this.lerpBehavManager, nodeClone, obj3dInfo.lerpBehavInfos || []);

3. Controller 与子行为的加载顺序

与编辑端一致:先加载 Position / Rotation 等子行为,再加载 Controller,最后 initializeFromNode(),确保 Controller 能正确收集子节点上的 Lerp 行为。这是 Lerp 子系统自身的业务约束,与插件机制正交,但插件化后这一顺序更加重要——模块未加载时整条链路被跳过,不会出现「半个 Controller」的中间态。


六、数据流全景

字段语义对照:

字段所在 DTO含义

enabledEditorPlugins

Editor

编辑会话是否启用该插件(保存时写入)

enabledRuntimeModules

Runtime

运行时需要加载哪些模块(导出时按需写入)

lerpBehavInfos

两者均有

具体插值行为实例数据(与插件开关独立)


七、扩展新插值行为(插件 API)

若第三方要扩展新的插值类型,当前代码已预留三条注册路径:

LerpBehaviorRegistry.tsLines 30-32

export const registerLerpBehaviorEditor = (cls: LerpBehaviorEditorClass): void => {
	lerpBehaviorRegistryEditor.register(cls);
};

扩展步骤:

  1. 在 Shared/LerpBehaviorModuleMetadata 增加条目(或运行时动态注册)。
  2. 实现 LerpXxxEditor extends LerpBehaviorEditor + 对应 Vue 属性组件。
  3. 实现 LerpXxx extends LerpBehavior,提供 createFromJson
  4. 调用上述三个 register* API。
  5. 确保 getJsonEditor / getJsonRuntime 序列化格式与 DTO 兼容。

八、迁移其他内置功能的 Checklist

以 LerpBehavior 为范本,迁移任意内置功能 FooFeature 时可按此清单执行:

#任务LerpBehavior 对应文件

1

定义 FOO_MODULE_ID

LerpModuleId.ts

2

Shared 元数据描述表

LerpBehaviorModuleMetadata.ts

3

从主 Registry 拆出独立子系统

LerpBehaviorRegistry / LerpBehaviorRegistryRuntime

4

独立 Manager(编辑 + 运行)

LerpBehaviorManagerEditor / LerpBehaviorManager

5

Gate + Observable

LerpModuleGate / LerpModuleRuntimeGate

6

DTO 增加 enabledEditorPlugins / enabledRuntimeModules 条目

DTO_EditorSystem / DTO_RuntimeSystem

7

自动启用判定(扫描 DTO 数据)

lerpModuleRequirement.ts

8

EditorSystem 保存/加载/导出集成

EditorSystem.ts

9

RuntimeSystem 加载集成

RuntimeSystem.ts

10

UI 条件渲染 + 插件管理开关

PluginManager.vue、各 v-if

11

Manager 入口 Gate 守卫

addLerpBehavior / applyLerpBehaviorsIfAvailable

12

Catalog 绑定逻辑类与 UI(若有面板)

LerpBehaviorModuleCatalog.ts

13

关闭插件时的清理(停预览、停播放)

pauseAllSessionExecution


九、总结

LerpBehavior 的插件化并非简单加一个开关,而是一套分层协作:

  • Shared:模块 ID、元数据、DTO 字段、自动启用规则——编辑端与运行端的「契约」。
  • Gate:会话级启用状态 + 事件广播 + 关闭清理。
  • Registry + Catalog:行为工厂与 UI 绑定的扩展点。
  • Manager:唯一业务入口,统一 Gate 守卫与反序列化顺序。
  • EditorSystem / RuntimeSystem:在数据加载、保存、导出链路中读写插件标记。

对用户而言:关闭插件只是隐藏界面、停止执行;数据始终保留在 lerpBehavInfos 中,下次打开或重新启用即可恢复。对开发者而言:新增插值类型或迁移其他内置功能,都有清晰的模块边界和注册 API 可遵循。

十、附完整代码

LerpModuleId.ts

export const LERP_BEHAVIOR_MODULE_ID = 'LerpBehavior' as const;

export type LerpBehaviorModuleId = typeof LERP_BEHAVIOR_MODULE_ID;

LerpBehaviorModuleMetadata.ts

import { LERP_BEHAVIOR_MODULE_ID } from "./LerpModuleId";

export type LerpCreateMenuGroup = 'behavior' | 'controller';

export type LerpBehaviorModuleEntryMeta = {
    editorBehaviorName: string;
    runtimeBehaviorName: string;
    cnName: string;
    createMenuGroup: LerpCreateMenuGroup;
    trackColorVar?: string;
    propsKey: string;
};

export const LERP_BEHAVIOR_MODULE_DISPLAY_NAME = '插值行为';

export const LERP_CONTROLLER_RUNTIME_CLASS_NAME = 'LerpController';

export const BUILTIN_LERP_BEHAVIOR_ENTRIES: readonly LerpBehaviorModuleEntryMeta[] = [
    {
        editorBehaviorName: 'LerpPositionEditor',
        runtimeBehaviorName: 'LerpPosition',
        cnName: '插值位置',
        createMenuGroup: 'behavior',
        trackColorVar: 'var(--track-dark-red)',
        propsKey: 'lerpPos',
    },
    {
        editorBehaviorName: 'LerpRotationEditor',
        runtimeBehaviorName: 'LerpRotation',
        cnName: '插值旋转',
        createMenuGroup: 'behavior',
        trackColorVar: 'var(--track-dark-green)',
        propsKey: 'lerpRot',
    },
    {
        editorBehaviorName: 'LerpScalingEditor',
        runtimeBehaviorName: 'LerpScaling',
        cnName: '插值缩放',
        createMenuGroup: 'behavior',
        trackColorVar: 'var(--track-dark-blue)',
        propsKey: 'lerpSca',
    },
    {
        editorBehaviorName: 'LerpVisibilityEditor',
        runtimeBehaviorName: 'LerpVisibility',
        cnName: '插值可视性',
        createMenuGroup: 'behavior',
        trackColorVar: 'var(--track-dark-purple)',
        propsKey: 'lerpVisibility',
    },
    {
        editorBehaviorName: 'LerpControllerEditor',
        runtimeBehaviorName: LERP_CONTROLLER_RUNTIME_CLASS_NAME,
        cnName: '插值控制器',
        createMenuGroup: 'controller',
        propsKey: 'controller',
    },
] as const;

export const LERP_BEHAVIOR_MODULE_DESCRIPTOR = {
    moduleId: LERP_BEHAVIOR_MODULE_ID,
    displayName: LERP_BEHAVIOR_MODULE_DISPLAY_NAME,
    entries: BUILTIN_LERP_BEHAVIOR_ENTRIES,
} as const;

export function getLerpEntryByEditorName(editorBehaviorName: string): LerpBehaviorModuleEntryMeta | undefined {
    return BUILTIN_LERP_BEHAVIOR_ENTRIES.find(e => e.editorBehaviorName === editorBehaviorName);
}

export function getLerpEntryByRuntimeName(runtimeBehaviorName: string): LerpBehaviorModuleEntryMeta | undefined {
    return BUILTIN_LERP_BEHAVIOR_ENTRIES.find(e => e.runtimeBehaviorName === runtimeBehaviorName);
}

export function getLerpTrackColorByEditorName(editorBehaviorName: string): string {
    return getLerpEntryByEditorName(editorBehaviorName)?.trackColorVar ?? 'transparent';
}

export function isLerpControllerRuntimeClassName(className: string): boolean {
    return className === LERP_CONTROLLER_RUNTIME_CLASS_NAME
        || className === 'LerpControllerEditor';
}

BehaviorRegistry.ts

import type { TransformNode, Behavior } from "@babylonjs/core";
import { type BehaviorClass, type BehaviorCreationContext } from "./DriveBehavior/DriveBehavior";

// 导入所有 Drive 类行为
// 注:Lerp* 已迁出至独立 LerpBehaviorRegistryRuntime,由 LerpBehaviorManager 管理。
// General* 已迁出至独立 GeneralBehaviorRegistryRuntime,由 GeneralBehavManager 管理。
import DriveAxisRotate from "./DriveBehavior/DriveAxisRotate";
import DriveLerpRotation from "./DriveBehavior/DriveLerpBehavior/DriveLerpRotation";
import DriveLerpPosition from "./DriveBehavior/DriveLerpBehavior/DriveLerpPosition";
import DriveLerpScaling from "./DriveBehavior/DriveLerpBehavior/DriveLerpScaling";
import DriveLerpMatColor from "./DriveBehavior/DriveLerpBehavior/DriveLerpMatColor";
import DriveLerpVisibility from "./DriveBehavior/DriveLerpBehavior/DriveLerpVisibility";
import EnumMatFlash from "./DriveBehavior/EnumMatFlash";
import DriveMatTexFlow from "./DriveBehavior/DriveMatTexFlow";
import DriveMatEnumColor from "./DriveBehavior/DriveMatEnumColor";
import DriveMatEnumTex from "./DriveBehavior/DriveMatEnumTex";
import SetSelNodeValShowName from "./DriveBehavior/SetSelNodeValShowName";
import RangeMaterial from "./DriveBehavior/RangeMaterial";

/**
 * 行为注册表
 * 用于自动注册和管理所有行为类型
 * 支持根据行为名称自动创建行为实例
 */
export class BehaviorRegistry {
    /**
     * 所有可用的行为类列表
     * 要添加新的行为类型,只需在这里添加即可
     */
    private static behaviorClasses: BehaviorClass[] = [
        DriveAxisRotate as any,
        DriveLerpRotation as any,
        DriveLerpPosition as any,
        DriveLerpScaling as any,
        DriveLerpMatColor as any,
        DriveLerpVisibility as any,
        DriveMatEnumColor as any,
        DriveMatEnumTex as any,
        EnumMatFlash as any,
        DriveMatTexFlow as any,
        SetSelNodeValShowName as any,
        RangeMaterial as any,
    ];

    // 行为名称到行为类的映射表
    private static registry = new Map<string, BehaviorClass>();

    // 注册表是否已初始化
    private static initialized = false;

    // 初始化注册表, 自动将所有行为类注册到映射表中
    public static initialize(): void {
        if (this.initialized) return;
        
        this.behaviorClasses.forEach(behaviorClass => {
            const behaviorName = behaviorClass.behaviorName;
            if (!behaviorName) {
                console.warn(`Behavior class missing behaviorName property:`, behaviorClass);
                return;
            }
            
            if (this.registry.has(behaviorName)) {
                console.warn(`Duplicate behavior name: ${behaviorName}`);
                return;
            }
            
            this.registry.set(behaviorName, behaviorClass);
        });
        
        this.initialized = true;
    }

    /**
     * 根据行为名称创建行为实例
     * @param behaviorName 行为类型名称
     * @param trNode 要附加行为的节点
     * @param jsonBehav 行为配置的JSON字符串
     * @param context 可选的创建上下文
     * @returns 创建的行为实例,如果创建失败则返回null
     */
    public static createBehavior(
        behaviorName: string,
        trNode: TransformNode,
        jsonBehav: string,
        context?: BehaviorCreationContext
    ): Behavior<TransformNode> | null {
        // 确保注册表已初始化
        if (!this.initialized) {
            this.initialize();
        }

        const behaviorClass = this.registry.get(behaviorName);
        if (!behaviorClass) {
            return null;
        }

        const behavior = behaviorClass.createFromJson(trNode, jsonBehav, context);
        return behavior;
    }

    /**
     * 获取所有已注册的行为名称
     * @returns 行为名称列表
     */
    public static getRegisteredBehaviors(): string[] {
        if (!this.initialized) {
            this.initialize();
        }
        return Array.from(this.registry.keys());
    }

    /**
     * 检查某个行为类型是否已注册
     * @param behaviorName 行为类型名称
     * @returns 是否已注册
     */
    public static isRegistered(behaviorName: string): boolean {
        if (!this.initialized) {
            this.initialize();
        }
        return this.registry.has(behaviorName);
    }

    /**
     * 手动注册一个行为类(用于扩展)
     * @param behaviorClass 要注册的行为类
     */
    public static register(behaviorClass: BehaviorClass): void {
        if (!this.initialized) {
            this.initialize();
        }
        
        const behaviorName = behaviorClass.behaviorName;
        if (!behaviorName) {
            console.warn(`Behavior class missing behaviorName property:`, behaviorClass);
            return;
        }
        
        if (this.registry.has(behaviorName)) {
            console.warn(`Behavior ${behaviorName} is already registered. Overwriting...`);
        }
        
        this.registry.set(behaviorName, behaviorClass);
        console.log(`Registered behavior: ${behaviorName}`);
    }
}

// 自动初始化注册表
BehaviorRegistry.initialize();

LerpBehaviorRegistry.ts

import LerpBehaviorEditor from "./LerpBehaviorEditor";
import { BehaviorRegistryBase } from "../BehaviorRegistryBase";
import { getBuiltinLerpEditorClasses } from "./LerpBehaviorModuleCatalog";
import type { LerpBehaviorEditorClass } from "./LerpBehaviorEditorClass";

export type { LerpBehaviorEditorClass };

/**
 * 插值行为注册表
 * 管理所有 LerpBehaviorEditor 子类,支持按名称创建实例
 */
class LerpBehaviorRegistry extends BehaviorRegistryBase<LerpBehaviorEditor, LerpBehaviorEditorClass> {

	constructor() {
		super([...getBuiltinLerpEditorClasses()]);
	}

	protected override getRegistryName(): string {
		return "LerpBehaviorRegistry";
	}
}

export const lerpBehaviorRegistryEditor = new LerpBehaviorRegistry();
lerpBehaviorRegistryEditor.initialize();

/**
 * 插件化入口:外部模块可注册自定义 Lerp 行为编辑器。
 * 调用后将立刻生效,下一次 createBehavior(name) 即可命中。
 */
export const registerLerpBehaviorEditor = (cls: LerpBehaviorEditorClass): void => {
	lerpBehaviorRegistryEditor.register(cls);
};

LerpBehaviorRegistryRuntime.ts

import type { TransformNode } from "@babylonjs/core";
import type LerpBehavior from "./LerpBehavior";
import type LerpController from "./LerpController";
import LerpControllerImpl from "./LerpController";
import LerpPosition from "./LerpPosition";
import LerpRotation from "./LerpRotation";
import LerpScaling from "./LerpScaling";
import LerpVisibility from "./LerpVisibility";
import { BUILTIN_LERP_BEHAVIOR_ENTRIES } from "../../../../../Shared/TScripts/LerpBehavior/LerpBehaviorModuleMetadata";

export type LerpRuntimeBehavior = LerpBehavior | LerpController;

export type LerpBehaviorClass = {
	readonly behaviorName: string;
	createFromJson(trNode: TransformNode, jsonBehav: string): LerpRuntimeBehavior | null | undefined;
};

const RUNTIME_CLASS_BY_NAME: Record<string, LerpBehaviorClass> = {
	LerpController: LerpControllerImpl,
	LerpPosition,
	LerpRotation,
	LerpScaling,
	LerpVisibility,
};

function getBuiltinRuntimeClasses(): LerpBehaviorClass[] {
	return BUILTIN_LERP_BEHAVIOR_ENTRIES
		.map(e => RUNTIME_CLASS_BY_NAME[e.runtimeBehaviorName])
		.filter((cls): cls is LerpBehaviorClass => cls != null);
}

/**
 * Lerp 行为注册表(运行时侧)
 * 独立于 DriveBehaviorManager,统一管理场景中的插值行为。
 * 与编辑器侧 LerpBehaviorRegistry 对偶。
 */
export class LerpBehaviorRegistryRuntime {

	private static behaviorClasses: LerpBehaviorClass[] = getBuiltinRuntimeClasses();

	private static registry = new Map<string, LerpBehaviorClass>();

	private static initialized = false;

	public static initialize(): void {
		if (this.initialized) return;

		this.behaviorClasses.forEach(cls => {
			const name = cls.behaviorName;
			if (!name) {
				console.warn(`LerpBehaviorRegistryRuntime: class missing behaviorName`, cls);
				return;
			}
			if (this.registry.has(name)) {
				console.warn(`LerpBehaviorRegistryRuntime: duplicate behavior name: ${name}`);
				return;
			}
			this.registry.set(name, cls);
		});

		this.initialized = true;
	}

	public static createBehavior(
		behaviorName: string,
		trNode: TransformNode,
		jsonBehav: string,
	): LerpRuntimeBehavior | null {
		if (!this.initialized) {
			this.initialize();
		}

		const cls = this.registry.get(behaviorName);
		if (!cls) {
			return null;
		}

		const behavior = cls.createFromJson(trNode, jsonBehav);
		return behavior ?? null;
	}

	public static getRegisteredBehaviors(): string[] {
		if (!this.initialized) {
			this.initialize();
		}
		return Array.from(this.registry.keys());
	}

	public static isRegistered(behaviorName: string): boolean {
		if (!this.initialized) {
			this.initialize();
		}
		return this.registry.has(behaviorName);
	}

	/**
	 * 手动注册一个 Lerp 行为类(用于插件化扩展)
	 */
	public static register(behaviorClass: LerpBehaviorClass): void {
		if (!this.initialized) {
			this.initialize();
		}

		const name = behaviorClass.behaviorName;
		if (!name) {
			console.warn(`LerpBehaviorRegistryRuntime: class missing behaviorName`, behaviorClass);
			return;
		}
		if (this.registry.has(name)) {
			console.warn(`LerpBehaviorRegistryRuntime: behavior ${name} already registered. Overwriting...`);
		}
		this.registry.set(name, behaviorClass);
		if (!this.behaviorClasses.includes(behaviorClass)) {
			this.behaviorClasses.push(behaviorClass);
		}
		console.log(`LerpBehaviorRegistryRuntime: registered ${name}`);
	}
}

// 自动初始化
LerpBehaviorRegistryRuntime.initialize();

LerpBehaviorModuleCatalog.ts

import type { Component } from 'vue';
import type LerpBehaviorEditor from './LerpBehaviorEditor';
import LerpPositionEditor from './LerpPositionEditor';
import LerpRotationEditor from './LerpRotationEditor';
import LerpScalingEditor from './LerpScalingEditor';
import LerpVisibilityEditor from './LerpVisibilityEditor';
import LerpControllerEditor from './LerpControllerEditor';
import LerpPositionCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpPositionCom.vue';
import LerpRotationCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpRotationCom.vue';
import LerpScalingCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpScalingCom.vue';
import LerpVisibilityCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpVisibilityCom.vue';
import LerpControllerCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpControllerCom/LerpControllerCom.vue';
import LerpControllerDialogPortal from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpControllerCom/LerpControllerDialog/LerpControllerDialogPortal.vue';
import {
    BUILTIN_LERP_BEHAVIOR_ENTRIES,
    LERP_BEHAVIOR_MODULE_DESCRIPTOR,
    type LerpBehaviorModuleEntryMeta,
    type LerpCreateMenuGroup,
} from '../../../../../../Shared/TScripts/LerpBehavior/LerpBehaviorModuleMetadata';
import type { LerpBehaviorEditorClass } from './LerpBehaviorEditorClass';

export type LerpBehaviorCatalogEntry = LerpBehaviorModuleEntryMeta & {
    editorClass: LerpBehaviorEditorClass;
    uiComponent: Component;
};

const EDITOR_CLASS_BY_NAME: Record<string, LerpBehaviorEditorClass> = {
    LerpPositionEditor,
    LerpRotationEditor,
    LerpScalingEditor,
    LerpVisibilityEditor,
    LerpControllerEditor,
};

const UI_COMPONENT_BY_NAME: Record<string, Component> = {
    LerpPositionEditor: LerpPositionCom,
    LerpRotationEditor: LerpRotationCom,
    LerpScalingEditor: LerpScalingCom,
    LerpVisibilityEditor: LerpVisibilityCom,
    LerpControllerEditor: LerpControllerCom,
};

const extraCatalogEntries: LerpBehaviorCatalogEntry[] = [];

function toCatalogEntry(meta: LerpBehaviorModuleEntryMeta): LerpBehaviorCatalogEntry {
    const editorClass = EDITOR_CLASS_BY_NAME[meta.editorBehaviorName];
    const uiComponent = UI_COMPONENT_BY_NAME[meta.editorBehaviorName];
    if (!editorClass || !uiComponent) {
        throw new Error(`Missing editor catalog binding for ${meta.editorBehaviorName}`);
    }
    return { ...meta, editorClass, uiComponent };
}

export function getLerpBehaviorCatalogEntries(): LerpBehaviorCatalogEntry[] {
    return [
        ...BUILTIN_LERP_BEHAVIOR_ENTRIES.map(toCatalogEntry),
        ...extraCatalogEntries,
    ];
}

export function getBuiltinLerpEditorClasses(): LerpBehaviorEditorClass[] {
    return getLerpBehaviorCatalogEntries()
        .filter(e => BUILTIN_LERP_BEHAVIOR_ENTRIES.some(b => b.editorBehaviorName === e.editorBehaviorName))
        .map(e => e.editorClass);
}

export function getLerpCatalogEntriesByMenuGroup(group: LerpCreateMenuGroup): LerpBehaviorCatalogEntry[] {
    return getLerpBehaviorCatalogEntries().filter(e => e.createMenuGroup === group);
}

export function registerLerpBehaviorCatalogEntry(entry: LerpBehaviorCatalogEntry): void {
    const idx = extraCatalogEntries.findIndex(e => e.editorBehaviorName === entry.editorBehaviorName);
    if (idx >= 0) {
        extraCatalogEntries[idx] = entry;
    } else {
        extraCatalogEntries.push(entry);
    }
}

export const lerpBehaviorModuleDescriptor = LERP_BEHAVIOR_MODULE_DESCRIPTOR;

// 供将来 EditorApp 按模块注册对话框 Portal
export const lerpControllerDialogPortalComponent = LerpControllerDialogPortal;

export type { LerpBehaviorEditor };

LerpModuleGate.ts

import { Observable } from "@babylonjs/core";
import type { DTO_EditorSystem } from "../../../../../../Shared/TScripts/DTO/DTO_EditorSystem";
import { shouldAutoEnableLerpEditorPlugin } from "../../../../../../Shared/TScripts/LerpBehavior/lerpModuleRequirement";

let _enabled = false;

export const onLerpModuleEnabledChanged = new Observable<boolean>();

let _onDisabledHandler: (() => void) | null = null;

/** 单体构建默认 true;瘦包构建可在启动时通过 setLerpEditorPluginCodeAvailable(false) 覆盖。 */
let _pluginCodeAvailable = true;

export function setLerpEditorPluginCodeAvailable(available: boolean): void {
	_pluginCodeAvailable = available;
}

export function registerLerpModuleDisabledHandler(handler: () => void): void {
	_onDisabledHandler = handler;
}

export function isLerpModuleEnabled(): boolean {
	return _enabled;
}

export function isLerpEditorPluginCodeAvailable(): boolean {
	return _pluginCodeAvailable;
}

export type LerpPluginFileLoadResolution = {
	shouldEnable: boolean;
	needsWarning: boolean;
};

export function resolveLerpPluginOnFileLoad(dto: DTO_EditorSystem): LerpPluginFileLoadResolution {
	const shouldEnable = shouldAutoEnableLerpEditorPlugin(dto);
	const codeAvailable = isLerpEditorPluginCodeAvailable();
	return {
		shouldEnable: shouldEnable && codeAvailable,
		needsWarning: shouldEnable && !codeAvailable,
	};
}

export function setLerpModuleEnabled(enabled: boolean): void {
	if (_enabled === enabled) return;
	_enabled = enabled;
	if (!enabled) {
		_onDisabledHandler?.();
	}
	onLerpModuleEnabledChanged.notifyObservers(_enabled);
}

LerpModuleRuntimeGate.ts

import { LerpBehaviorRegistryRuntime } from "./LerpBehaviorRegistryRuntime";

let _loaded = false;

export function isLerpRuntimeModuleLoaded(): boolean {
    return _loaded;
}

export async function loadLerpRuntimeModule(): Promise<void> {
    LerpBehaviorRegistryRuntime.initialize();
    _loaded = true;
}

export function unloadLerpRuntimeModule(): void {
    _loaded = false;
}

DTO_EditorSystem.ts

import { MatDialogInfo, PreviewSceneInfo } from "../../../Editor/TScripts/EditorSystem/MaterialManager/MaterialManagerEditor";
import type { ViewType } from "../../../Editor/TScripts/EditorSystem/ViewController/ViewControllerEditor";
import type { DTO_ExpressionGraphSnapshot } from "./DTO_ExpressEditor";
import type { BehaviorValueDriveMode } from "../GeneralType";
import { Cip, TranNode, UrlCip, Vec3 } from "./DTO_BaseClass";
import { DTO_DriveAxisRotate, DTO_BehaviorMesh, DTO_BehaviorNode, DTO_Connection, DTO_DriveEnumMatColor, DTO_DriveEnumMatTex, DTO_Environment, DTO_FollowNodeNameGlobalSetting, DTO_FollowNodeNameInfo, DTO_DriveLerpPosition, DTO_DriveLerpRotation, DTO_DriveLerpScale, DTO_DriveLerpVisibility, DTO_Mat, DTO_DriveMatTexFlow, DTO_MirrorReflection, DTO_RoamingPath, DTO_RuntimeOutlineSetting, DTO_ViewBase, DTO_ViewGlobal2D, DTO_ViewGlobal3D, DTO_ViewParticular3D, DTO_CipId, DTO_CipBehav, DTO_CipEnabled } from "./DTO_RuntimeSystem";

// 编辑系统
export class DTO_EditorSystem {
    constructor(
        public globalSetting:DTO_GlobalSettingEditor = new DTO_GlobalSettingEditor(),
        public viewSettingEditor:DTO_ViewSettingEditor = new DTO_ViewSettingEditor(),
        public environment:DTO_Environment = new DTO_Environment(),
		public obj3dSetting:DTO_Obj3dSettingEditor = new DTO_Obj3dSettingEditor()
    ){}
}

// #region 编辑器总设置---------------------------------------------------------------
export class DTO_GlobalSettingEditor{
	constructor(
        public version:string = "1.0.0",
        public editorCamera:DTO_EditorCamera = new DTO_EditorCamera(),
        public undoOperation:DTO_UndoOperation = new DTO_UndoOperation(),
        public gridSetting:DTO_GridSetting = new DTO_GridSetting(),
		public connection:DTO_Connection = new DTO_Connection(),
		public runtimeOutlineSettingEditor:DTO_RuntimeOutlineSettingEditor = new DTO_RuntimeOutlineSettingEditor(),
		public followNodeNameGlobalSettingEditor:DTO_FollowNodeNameGlobalSettingEditor = new DTO_FollowNodeNameGlobalSettingEditor(),
		public enabledEditorPlugins:string[] | undefined = undefined
	){}
}

export class DTO_RuntimeOutlineSettingEditor{
	constructor(
		public useCustom:boolean = false,
		public runtimeOutlineSetting:DTO_RuntimeOutlineSetting = new DTO_RuntimeOutlineSetting()
	){}
}

// 基础跟随UI设定-----------------------------------------------------------------
export class DTO_FollowNodeNameGlobalSettingEditor{
	constructor(
		public useCustom:boolean = true,
		public followNodeNameGlobalSetting:DTO_FollowNodeNameGlobalSetting = new DTO_FollowNodeNameGlobalSetting()
	){}
}

// 编辑器相机-----------------------------------------------------------------
export class DTO_EditorCamera {
    constructor(
        public pos:Vec3 = new Vec3(0,0,0),
        public rotH:number = -Math.PI/4,
        public rotV:number = Math.PI/4,
        public dist:number = 61.8
    ){}
}

// 撤销操作-----------------------------------------------------------------
export class DTO_UndoOperation{
    constructor(
        public undoLimit:number = 30
    ){}
}

// 编辑辅助网格设置-----------------------------------------------------------------
export class DTO_GridSetting{
    constructor(
        public enabled:boolean = true,
        public height:number = 0,
        public size:number = 200,
        public divisions:number = 30,
        public brightness:number = 1,
        public alpha:number = 0.5,
        public fade:boolean = true
    ){}
}
// #endregion

// #region 视角设定-----------------------------------------------------------------
export class DTO_ViewSettingEditor {
	constructor(
		public viewBaseEditor:DTO_ViewBaseEditor = new DTO_ViewBaseEditor(),
		public global2DEditor:DTO_ViewGlobal2DEditor = new DTO_ViewGlobal2DEditor(),
		public global3DEditor:DTO_ViewGlobal3DEditor = new DTO_ViewGlobal3DEditor(), 
		public particular3DEditor:DTO_ViewParticular3DEditor = new DTO_ViewParticular3DEditor(), 
		public roaming3DEditor:DTO_ViewRoaming3DEditor = new DTO_ViewRoaming3DEditor()
	){}
}

export class DTO_ViewBaseEditor{
	constructor(
		public viewBase:DTO_ViewBase = new DTO_ViewBase(),
        public editingType:ViewType = "global2D",
	){}
}

export class DTO_ViewGlobal2DEditor{
	constructor(
        public used:boolean = true,
		public viewGlobal2D:DTO_ViewGlobal2D = new DTO_ViewGlobal2D()
	){}
}

export class DTO_ViewGlobal3DEditor {
	constructor(
        public used:boolean = false,
		public viewGlobal3D:DTO_ViewGlobal3D = new DTO_ViewGlobal3D()
	){}
}

export class DTO_ViewParticular3DEditor {
	constructor(
        public used:boolean = false,
		public viewParticular3D:DTO_ViewParticular3D = new DTO_ViewParticular3D(),
        public editingNodeIndex:number = 0
	){}
}

export class DTO_ViewRoaming3DEditor {
	constructor(
        public used:boolean = false,
		public listPath:DTO_RoamingPathEditor[] = [],
        public editingPathIndex:number = 0
	){}
}

export class DTO_RoamingPathEditor{
	constructor(
		public path:DTO_RoamingPath = new DTO_RoamingPath(),
        public editingNodeIndex:number = 0,
        public isExpand:boolean = true
	){}
}
// #endregion

// #region 场景内物体的设置-----------------------------------------------------------------
export class DTO_Obj3dSettingEditor{
	constructor(
		public matManager:DTO_MatManagerEditor | undefined = undefined,
		public listBehaviorNode:DTO_BehaviorNodeEditor[] | undefined = undefined,
		public listBehaviorMesh:DTO_BehaviorMeshEditor[] | undefined = undefined,
		public listObj3DResources:DTO_Obj3dResourceEditor[] | undefined = undefined
	){}
}

// 对于一个场景中的三维对象来说,它必定来自特定地址的模型文件(暂时支持.babylon和.glb格式),
// 但是具体使用了模型文件的哪些部分以及这些使用的部分的标识名称、变换是需要记录的,
// 记录这些内容的就是DTO_Obj3dInfoEditor。
export class DTO_Obj3dResourceEditor {
	constructor(
		// 这里假定某个模型文件内包含多个根三维对象,
		// UrlCip记录的是模型文件的地址及该文件中被使用的模型的路径cip。
		public urlCip:UrlCip,
		public obj3dInfo:DTO_Obj3dInfoEditor
	){}
}

// 基于外部引入的模型来设置的属性,比如外部引入的.glb文件。
export class DTO_Obj3dInfoEditor{
	constructor(
		public tranNode:TranNode | undefined = undefined,
		public materials:DTO_CipId[] | undefined = undefined,				//依据cip找到模型中的mesh,依据id找到对应的material,运行系统将找到的material赋给找到的mesh
		public selInfos:DTO_CipSelInfoEditor[] | undefined = undefined,		//依据cip找到模型中的node,让node使用SelInfo的设定
		public generalBehavInfos:DTO_CipBehav[] | undefined = undefined,	//依据cip找到模型中的node,让node使用常规Behavior的设定
		public driveBehavInfos:DTO_CipBehav[] | undefined = undefined,		//依据cip找到模型中的node,让node使用驱动Behavior的设定
		public lerpBehavInfos:DTO_CipBehav[] | undefined = undefined,		//依据cip找到模型中的node,让node使用插值Behavior的设定
		public pickables:DTO_CipEnabled[] | undefined = undefined,			//依据cip找到模型中的mesh,将mesh的属性isPickable设置为false
		public castShadows:DTO_CipEnabled[] | undefined = undefined,		//依据cip找到模型中的mesh,将mesh从shadowGenerator的ShadowCaster列表中移除
		public recieveShadows:DTO_CipEnabled[] | undefined = undefined,		//依据cip找到模型中的mesh,将mesh的属性receiveShadows设置为false
		public glowMeshes:Cip[] | undefined = undefined,					//依据cip找到模型中的mesh,将mesh添加到layerController的glow的包含列表中
	){}
}

//可选三维物体组件
export class DTO_CipSelInfoEditor{
	constructor(
		public cip:Cip,
		public selInfo:DTO_SelectableBehaviorEditor
	){}
}

export class DTO_SelectableBehaviorEditor{
	constructor(
		public uuid:string,
		public selectableOnRun:boolean = true,
		public followNodeNameInfoEditor:DTO_FollowNodeNameInfoEditor | undefined = undefined
	){}
}

// #region 节点图标-----------------------------------------------------------------
export class DTO_FollowNodeNameInfoEditor{
	constructor(
		// 如果onRunEnabled为false,则在运行文件中DTO_SelectableBehavior的followedNameInfo为undefined·
		public onRunEnabled:boolean = false,
		public isPreview:boolean = false,
		public followNodeNameInfo:DTO_FollowNodeNameInfo = new DTO_FollowNodeNameInfo()
	){}
}

// #region 材质-----------------------------------------------------------------
// 材质管理器
export class DTO_MatManagerEditor{
    constructor(
        public listMat:DTO_MatEditor[] | undefined = undefined,
        public dialogInfo:MatDialogInfo | undefined = undefined,
        public curMatId:string | undefined = undefined
    ){}
}

// 材质
export class DTO_MatEditor{
	constructor(
		public mat:DTO_Mat = new DTO_Mat(),
		public previewSceneInfo:PreviewSceneInfo | undefined = undefined
	){}
}
// #endregion


// #endregion

// #region 场景内自建的TransformNode和Mesh专属行为--------------------
export class DTO_BehaviorNodeEditor{
    constructor(
        public behaviorNode:DTO_BehaviorNode = new DTO_BehaviorNode()
    ){}
}

// 场景内自建的Mesh专属行为,这种行为生成和改变Mesh
export class DTO_BehaviorMeshEditor{
    constructor(
        public behaviorMesh:DTO_BehaviorMesh = new DTO_BehaviorMesh()
    ){}
}
// #endregion

// #region 外部加载的模型资源=================================================
// export class DTO_Obj3dResourceEditor{
//     constructor(
// 		public urlCip:UrlCip,
// 		public obj3dInfo:DTO_Obj3dInfo = new DTO_Obj3dInfo()
//     ){}
// }

// 基于外部引入的模型来设置的属性,比如外部引入的.glb文件。
// export class DTO_Obj3dInfoEditor{
// 	constructor(
// 		public tranNode:TranNode | undefined = undefined,
// 		public materials:DTO_CipId[] | undefined = undefined,			//依据cip找到模型中的mesh,依据id找到对应的material,运行系统将找到的material赋给找到的mesh
// 		public selInfos:DTO_CipSelInfoEditor[] | undefined = undefined,		//依据cip找到模型中的node,让node使用SelInfo的设定
// 		public generalBehavs:DTO_CipBehav[] | undefined = undefined,	//依据cip找到模型中的node,让node使用常规Behavior的设定
// 		public driveBehavs:DTO_CipBehav[] | undefined = undefined,		//依据cip找到模型中的node,让node使用驱动Behavior的设定
// 		public pickables:DTO_CipEnabled[] | undefined = undefined,		//依据cip找到模型中的mesh,将mesh的属性isPickable设置为false
// 		public castShadows:DTO_CipEnabled[] | undefined = undefined,	//依据cip找到模型中的mesh,将mesh从shadowGenerator的ShadowCaster列表中移除
// 		public recieveShadows:DTO_CipEnabled[] | undefined = undefined,	//依据cip找到模型中的mesh,将mesh的属性receiveShadows设置为false
// 		public glowMeshes:Cip[] | undefined = undefined,				//依据cip找到模型中的mesh,将mesh添加到layerController的glow的包含列表中
// 	){}
// }

// export class DTO_CipSelInfoEditor{
// 	constructor(
// 		public cip:Cip | undefined = undefined,
// 		public selInfo:DTO_SelectableBehavEditor | undefined = undefined
// 	){}
// }

// // #region 可选行为
// export class DTO_SelectableBehavEditor{
//     constructor(
//         public uuid:string = '',
//         public showName:string = '',
//         public deletable:boolean = true
//     ){}
// }

// export class DTO_SelectableMeshEditor extends DTO_SelectableBehavEditor{
//     constructor(
//         public uuid:string = '',
//         public showName:string = ''
//     ){super(uuid, showName, true);}
// }

// export class DTO_SelectableNodeEditor extends DTO_SelectableBehavEditor{
//     constructor(
//         public uuid:string = '',
//         public showName:string = ''
//     ){super(uuid, showName, true);}
// }

// export class DTO_SelectableViewEditor extends DTO_SelectableBehavEditor{
//     constructor(
//         public uuid:string = '',
//         public showName:string = '',
//         public viewType:ViewType = 'global2D',
//         public viewPart:ViewPart = 'node',
//         public props:IndicatorProps | undefined = undefined,
//     ){super(uuid, showName, false);}
// }
// // #endregion

// #region 常规组件===================================================================================
export class DTO_MirrorReflectionEditor{
    constructor(
        public mirrorReflection:DTO_MirrorReflection = new DTO_MirrorReflection(),
		public isPreview:boolean = true
    ){}
}
// #endregion

// #region 驱动组件===================================================================================
export class DTO_DriveLerpPositionEditor{
    constructor(
        public lerpPosition:DTO_DriveLerpPosition = new DTO_DriveLerpPosition(),
		public isPreviewMesh:boolean = false,
		public isPreview:boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
    ){}
}

export class DTO_DriveLerpRotationEditor{
    constructor(
        public lerpRotation:DTO_DriveLerpRotation = new DTO_DriveLerpRotation(),
		public isPreviewMesh:boolean = false,
		public isPreview:boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
    ){}
}

export class DTO_DriveLerpScaleEditor{
    constructor(
        public lerpScale:DTO_DriveLerpScale = new DTO_DriveLerpScale(),
		public isPreviewMesh:boolean = false,
		public isPreview:boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
    ){}
}

export class DTO_DriveLerpVisibilityEditor{
    constructor(
        public lerpVisibility:DTO_DriveLerpVisibility = new DTO_DriveLerpVisibility(),
		public isPreview:boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
    ){}
}

export {
	DTO_LerpControllerEditor,
	DTO_LerpProcessInfoEditor,
	DTO_LerpPositionEditor,
	DTO_LerpRotationEditor,
	DTO_LerpScaleEditor,
	DTO_LerpVisibilityEditor,
} from "./LerpBehavior/DTO_Lerp_Editor";

export class DTO_DriveAxisRotateEditor{
    constructor(
        public axisRotate:DTO_DriveAxisRotate = new DTO_DriveAxisRotate(),
		public isPreview:boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
    ){}
}

export class DTO_DriveEnumMatColorEditor{
	constructor(
		public enumMatColor: DTO_DriveEnumMatColor = new DTO_DriveEnumMatColor(),
		public isPreview: boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
	){}
}

export class DTO_DriveEnumMatTexEditor{
	constructor(
		public enumMatTex: DTO_DriveEnumMatTex = new DTO_DriveEnumMatTex(),
		public isPreview: boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
	){}
}

export class DTO_DriveMatTexFlowEditor{
	constructor(
		public matTexFlow: DTO_DriveMatTexFlow = new DTO_DriveMatTexFlow(),
		public isPreview: boolean = false,
		public expressionGraph?: DTO_ExpressionGraphSnapshot,
		public driveMode?: BehaviorValueDriveMode,
	){}
}
// #endregion

// #endregion

DTO_RuntimeSystem.ts

import {ClampVal, 
	Vec3, 
	Vec2, 
	UrlCip, 
	Cip, 
	ClampVal2D, 
	ClampVal3D, 
	FlashInfo, 
	IdCip as RootIdAndCip, 
	HDRColor,
	Range, 
	Range2D, 
	Range3D, 
	ViewTargetNode, 
	TranNode } from "./DTO_BaseClass";
import type { DicNum } from "../CollectionClass";
import { EnvInfo } from "../../../Editor/TScripts/EditorSystem/MaterialManager/MaterialManagerEditor";
import type { TransitionMode, ViewType } from "../../../Editor/TScripts/EditorSystem/ViewController/ViewControllerEditor";
import { DriveValue } from "./DTO_BaseClass";
import type { AxisType, MatChannelType } from "../GeneralType";

//运行系统总系内容----------------------------------------------------------
export class DTO_RuntimeSystem {
	constructor(
		public globalSetting:DTO_GlobalSetting = new DTO_GlobalSetting(),
		public viewSetting:DTO_ViewSetting = new DTO_ViewSetting(), 
		public environment:DTO_Environment = new DTO_Environment(), 
		public obj3dSetting:DTO_Obj3dSetting | undefined = undefined,
	){}
}

export class DTO_GlobalSetting{
	constructor(
		public version:string = "1.0.0",
		public fineness:number = 1,//编辑器导出时设置的画面精细度
		public outlineSetting:DTO_RuntimeOutlineSetting | undefined = undefined,
		public followNodeNameGlobalSetting:DTO_FollowNodeNameGlobalSetting | undefined = undefined,
		public connection:DTO_Connection | undefined = undefined,
		public enabledRuntimeModules:string[] | undefined = undefined //如果要添加插值行为插件,数组中需要添加"LerpBehavior"。
	){}
}

export class DTO_RuntimeOutlineSetting{
	constructor(
		public colorHover:string = "#cc8800",
		public colorActive:string = "#ffdd00"
	){}
}

// 基础跟随UI设定-----------------------------------------------------------------
export class DTO_FollowNodeNameGlobalSetting{
	constructor(
		public fontSize:number = 14,
		public fontIsBold:boolean = false,
		public colorNormal:string = "#ffeedd",
		public colorHover:string = "#cc8800",
		public colorActive:string = "#ffdd00"
	){}
}

export class DTO_Connection{
	constructor(
		public type:string = "websocket",
		public address:string = ""
	){}
}

//视角设定-----------------------------------------------------------------
export class DTO_ViewSetting {
	constructor(
		public viewBase:DTO_ViewBase = new DTO_ViewBase(),
		public global2D:DTO_ViewGlobal2D | undefined = undefined,
		public global3D:DTO_ViewGlobal3D | undefined = undefined, 
		public particular3D:DTO_ViewParticular3D | undefined = undefined, 
		public roaming3D:DTO_ViewRoaming3D | undefined = undefined, 
	){}
}

export class DTO_ViewBase{
	constructor(
		public transitionMode:TransitionMode = "cut",// "cut"表示直接切换,"lerp"表示在节点间插值,"fade"表示前面画面淡入
		public defaultType:ViewType = "global2D"// "global2D"表示全局二维视角,"global3D"表示全局三维视角,"particular3D"表示特定三维视角,"roaming3D"表示三维漫游视角
	){}
}

export class DTO_ViewGlobal2D{
	constructor(
		public limitPosEnabled:boolean = true,
		public pos:ClampVal2D = new ClampVal2D(new Vec3(0, 0, 0), new Range2D("rectangle", new Vec3(0, 0, 0), 0, new Vec2(1, 1))),
		public size:ClampVal = new ClampVal(5, new Range(1, 15)),//由于摄像机为正交投影,这里size表示正交涵盖的范围。
		public dist:number = 1,	//摄像机距离目标点的距离。由于摄像机为正交类型,且有近、远剪切平面,这个距离设置的太小有可能切掉模型。
		public horizontal:number = 0,
		public vertical:number = Math.PI/4
	){}
}

export class DTO_ViewGlobal3D {
	constructor(
		public limitPosEnabled:boolean = true,
		public pos:ClampVal3D = new ClampVal3D(new Vec3(0, 0, 0), new Range3D('box', new Vec3(0, 0, 0), 0, new Vec3(1, 1, 1))),
		public zoom:ClampVal = new ClampVal(1, new Range(0.1, 10)),
		public horizontal:ClampVal = new ClampVal(0, new Range(-Math.PI, Math.PI)),
		public vertical:ClampVal = new ClampVal(Math.PI/4, new Range(0, Math.PI/2)),
	){}
}

export class DTO_ViewParticular3D {
	constructor(
		public listNode:ViewTargetNode[] = []
	){}
}

export class DTO_ViewRoaming3D {
	constructor(
		public listPath:DTO_RoamingPath[] = []
	){}
}

export class DTO_RoamingPath{
	constructor(
		public name:string = "",
		public speed:number = 1,
		public loop:boolean = true,
		public listNode:ViewTargetNode[] = []
	){}
}

//环境设定-----------------------------------------------------------------
export class DTO_Environment {
	constructor(
		public background:DTO_Background = new DTO_Background(),
		//是否有必要调节环境纹理的照明强度?
		public envLight:DTO_EnvLight = new DTO_EnvLight(),
		public fog:DTO_Fog = new DTO_Fog(false, 0, 100, "#ffffff")
	){}
}

export class DTO_Background{
	constructor(
		public hexColor:string = "#888888",
		public url:string = ""
	){}
}

export class DTO_EnvLight{
	constructor(
		public sun:DTO_Sun = new DTO_Sun(),
		public hem:DTO_Hem = new DTO_Hem()
	){}
}

export class DTO_Sun{
	constructor(
		public enabled:boolean = true,
		public intensity:number = 1,
		public color:string = "#ffffff",
		public angleH:number = 0,
		public angleV:number = Math.PI/4,
		public shadowEnabled:boolean = true
	){}
}

export class DTO_Hem{
	constructor(
		public enabled:boolean = false, 
		public intensity:number = 0.3, 
		public groundColor:string = "#664422"
	){}
}

export class DTO_Fog{
	constructor(
		public enabled:boolean = false, 
		public start:number = 0, 
		public end:number = 1000, 
		public color:string = "#ffffff"
	){}
}

/** 
 * 场景内物体的设置
 * listMat 材质列表,这里指的是在场景中创建的材质,而不是模型导入时自带的材质
 * listBehaviorNode 在场景中创建的TransformNode节点,这些节点一般需要添加特定的行为才有意义,如果图标在三维空间中的基准点等。
 * listBehaviorMesh 在场景中创建的Mesh节点,这些Mesh的形状一般是有行为创建的,比如带有宽度的线条等。
 * listObj3DRes 资源列表,这里指的从外部加载到场中的模型资源。
*/
export class DTO_Obj3dSetting{
	constructor(
		public materials:DTO_Mat[] | undefined = undefined,
		public behavNodes:DTO_BehaviorNode[] | undefined = undefined,
		public behavMeshes:DTO_BehaviorMesh[] | undefined = undefined,
		public obj3DResources:DTO_Obj3dResource[] | undefined = undefined
	){}
}

/** 材质参数
 * uuid 材质自身的标识符。
 * name 材质名称,用于通过列表选择材质。
 * baseColor 漫反射颜色,是一个htmlColor。
 * baseTex 漫反射纹理
 * metallic 金属性,值范围:0-1
 * roughness 粗糙度,值范围:0-1
 * emissive 自发光,值范围:非负数
 * emissiveTex 自发光纹理
 * enTexUrl 环境纹理,url地址,当该值为空时,使用DTO_Environment中的skyUrl对应的纹理
 * transparencyMode 透明模式,0表示不透明,1表示ALPHATEST,2表示ALPHABLEND
 * cull 裁剪面,可用值为"back"、"front"、"none"
*/
export class DTO_Mat{
	constructor(
		public uuid:string = '',
		public name:string = '',
		public baseColor:HDRColor | undefined = undefined,
		public baseTex:DTO_Texture | undefined = undefined,
		public metallic:number = 0,
		public roughness:number = 0,
		// 法线纹理暂不启用,一旦启用会大大增加开发难度和用户使用难度
		// public normalTex:DTO_Texture | undefined = undefined,
		// public normalIntensity: number = 1,
		public emissiveColor:HDRColor | undefined = undefined,
		public emissiveTex:DTO_Texture | undefined = undefined,
		public envInfo:EnvInfo | undefined = undefined,
		//添加环境强度参数吗??
		public transparencyMode:number = 0,
		public alpha : number = 1,
		public cull:string = "back",
		public wire:boolean = false
	){}
}

//纹理参数
export class DTO_Texture{
	constructor(
		public url:string,
		public isTransparent:boolean = false,
		public scale:Vec2 = new Vec2(1, 1),
		public offset:Vec2 = new Vec2(0, 0),
		public inverY:boolean = true
	){}
}

//可选三维物体组件
export class DTO_CipSelInfo{
	constructor(
		public cip:Cip,
		public selInfo:DTO_SelectableBehavior
	){}
}

export class DTO_SelectableBehavior{
	constructor(
		public uuid:string,
		public selectableOnRun:boolean = true,
		public followNodeNameInfo:DTO_FollowNodeNameInfo | undefined = undefined
	){}
}

export class DTO_FollowNodeNameInfo{
	constructor(
		public content:string = '',
		public offset:Vec3 = new Vec3(0,0,0)
	){}
}

export class DTO_CipEnabled{
	constructor(
		public cip:Cip,
		public enabled:boolean
	){}
}
//
export class DTO_CipBehav{
	constructor(
		public cip:Cip,
		public behaviorInfo:DTO_BehaviorInfo
	){}
}

export class DTO_BehaviorInfo{
	constructor(
		public className: string,
		public data: Record<string, unknown>
	){}
}

// 将 data 对象序列化为 JSON 字符串,供各行为类的 createFromJson 使用
export function getBehaviorJson(behavInfo: DTO_BehaviorInfo): string {
	if (!behavInfo.data) return '';
	return JSON.stringify(behavInfo.data);
}

// 基础组件===========================================================================

/**
 * uuid 行为的唯一标识符
 * 一般应该给行为添加一个name属性,但是暂时看来name属性对于运行系统应该没有用,
 * 只能用于在编辑器中显示,编辑器中可以添加有名字的行为字典,字典的key是行为的id,value是行为的name。
 */
export class DTO_BaseBehavior{
	constructor( 
		public uuid:string = ''
	){}
}

// 普通组件===========================================================================
export class DTO_GeneralBehavior extends DTO_BaseBehavior{
	constructor( 
		uuid:string = ''
	){super(uuid);}
}

export class DTO_MirrorReflection extends DTO_GeneralBehavior{
	constructor(
		uuid:string = '',
		public flatnessThreshold:number = 1.0,
		public textureSize:number = 512,
		public blurKernel:number = 0
	){super(uuid);}
}

// 注视组件
export class DTO_LookAt extends DTO_GeneralBehavior{
	constructor(
		uuid:string = '',
		public target:RootIdAndCip,
		public update:boolean = true,
		public yaw:number = 0,
		public pitch:number = 0,
		public roll:number = 0
	){super(uuid);}
}

export class DTO_YawFaceCam extends DTO_GeneralBehavior{
	constructor(
		uuid:string = '',
		public type:string
	){super(uuid);}
}

export class DTO_OcclusionFader extends DTO_GeneralBehavior{
	constructor(
		uuid:string = '',
		public targets:RootIdAndCip[],
		public visibility:number = 0.1
	){super(uuid);}
}

export class DTO_LineRenderer extends DTO_GeneralBehavior{
	constructor(
		uuid:string = '',
		public positions:Vec3[],
		public width:number = 0.01,
		public matInfo:DTO_LineMat | null = null
	){super(uuid);}
}

// 驱动组件==============================================================================

// 驱动组件的基类,包括驱动值的记录、驱动器的记录、驱动器的执行方法。
class DTO_DriveBehavior extends DTO_BaseBehavior{
	constructor(
		uuid:string = "",
		public driveValue:DriveValue | undefined = undefined
	){super(uuid);}
}

class DTO_DriveLerpBehaviour extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		process:DriveValue | undefined = undefined
	){super(uuid, process);}
}

// 驱动3D模型进行插值移动,
// start是起始位置,end是结束位置。
// prcess表示插值进度,值位于0-1之间。
export class DTO_DriveLerpPosition extends DTO_DriveLerpBehaviour{
	constructor(
		uuid:string = '',
		process:DriveValue = new DriveValue(),
		public start: Vec3 = new Vec3(),
		public end: Vec3 = new Vec3()
	){super(uuid, process);}
}

// 驱动3D模型进行插值旋转,
// start表示其实朝向的欧拉角,end表示结束朝向的欧拉角。
// prcess表示插值进度,值位于0-1之间。
export class DTO_DriveLerpRotation extends DTO_DriveLerpBehaviour{
	constructor(
		uuid:string = '',
		process:DriveValue = new DriveValue(),  //插值进度
		public start:Vec3 = new Vec3(),
		public end:Vec3 = new Vec3()
	){super(uuid, process);}
}

export class DTO_DriveLerpScale extends DTO_DriveLerpBehaviour{
	constructor(
		uuid:string = '',
		process:DriveValue = new DriveValue(),
		public start:Vec3 = new Vec3(),
		public end:Vec3 = new Vec3()
	){super(uuid, process);}
}

// 驱动材质中的颜色进行插值变化,
// texName是材质中需要被驱动的纹理的名称,如"albedo",
// start是开始颜色值,如"#FFFFFF"
// end是结束颜色值,如"#000000"
// process插值进度
export class DTO_DriveLerpMatColor extends DTO_DriveLerpBehaviour{
	constructor(
		uuid:string = '',
		process:DriveValue = new DriveValue(),
		public colorType:MatChannelType = 'base',
		public start:string = '#000000',
		public end:string = '#FFFFFF'
	){super(uuid, process)}
}

export class DTO_DriveLerpVisibility extends DTO_DriveLerpBehaviour{
	constructor(
		uuid:string = '',
		process:DriveValue = new DriveValue(),
		public start:number = 0,
		public end:number = 1
	){super(uuid, process);}
}

// 驱动3D模型沿着特定轴旋转,
// axis表示旋转的轴向,其值为"x"、"y"、"z"中的一个。
// speed表示旋转速度。
export class DTO_DriveAxisRotate extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		speed:DriveValue | undefined = undefined,
		public axis:AxisType = 'x',
	){super(uuid, speed);}
}

// 驱动材质中的贴图进行偏移,
// texType是材质中需要被驱动的纹理的名称,如"base","emissive",
// direction是纹理偏移方向,speed是纹理偏移速度
export class DTO_DriveMatTexFlow extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		speed:DriveValue | undefined = undefined,
		public direction:Vec2 = new Vec2(0, 0),
		public texType:MatChannelType = 'base'
	){super(uuid, speed)}
}

// 运行过程中,当传来特定整数时,模型特定类型(目前就是'base'和'emissive',暂不考虑其它)的颜色值将被设定在对应该整数颜色.
// 典型的情况是阀门的状态会以整数表示,比如0表示关闭、1表示开启,2表示关闭中,3表示开启中,4表示关闭错误,5表示开启错误等。
// colorType是颜色类型,目前就是'base'和'emissive',暂不考虑其它。
// colors是颜色值的数组,数组中的每个元素是一个颜色值,颜色值是一个htmlColor。
export class DTO_DriveEnumMatColor extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		process:DriveValue | undefined = undefined,
		public colorType:MatChannelType = 'base',
		public colors:string[] = []
	){super(uuid, process);}
}

// 运行过程中,当传来特定整数时,模型特定类型(目前就是'base'和'emissive',暂不考虑其它)的纹理将被设定在对应该整数纹理.
// 典型的情况是表示皮带运输煤量的贴图会根据煤量的多少而变化。
// texType是纹理类型,目前就是'base'和'emissive',暂不考虑其它。
// texUrls是纹理url的数组,数组中的每个元素是一个纹理url。
export class DTO_DriveEnumMatTex extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		process:DriveValue | undefined = undefined,
		public texType:MatChannelType = 'base',
		public texUrls:string[] = []
	){super(uuid, process);}
}

// 这个类通过整数驱动三维物体材质颜色的变化,这里假定三维物体的材质与特定整数有对应关系。
// 运行过程中,当传来特定整数时,模型特定名称的颜色值将被设定在对应该整数颜色.
// 典型的情况是阀门的状态会以整数表示,比如0表示关闭、1表示开启,2表示关闭中,3表示开启中,4表示关闭错误,5表示开启错误等。
export class DTO_DriveEnumMatFlash extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		process:DriveValue | undefined = undefined,
		public numMapColor:DicNum<FlashInfo>
	){super(uuid, process);}
}

export class DTO_DriveSetSelNodeValShowName extends DTO_DriveBehavior{
	constructor(
		uuid:string,
		process:DriveValue | undefined = undefined,
		public prefix:string = "", 
		public suffix:string = "", 
		public fix:number = -1
	){super(uuid, process);}
}

export class DTO_DriveRangeMaterial extends DTO_DriveBehavior{
	constructor(
		uuid:string = '',
		process:DriveValue | undefined = undefined,
		public splitNum:number[],
		public matIds:string[]
	){super(uuid, process);}	
}

export class DTO_LineMat{
	constructor(
		public color:string,
		public texture:DTO_Texture | null = null,
		public transparencyMode:number = 0,
		public alpha:number = 1,
		public sideOrientation:number = 0
	){}
}

// 基于TransformNode的对象属性,比如作为一个可选的图标的三维定位。
export class DTO_BehaviorNode{
	constructor(
		public tranNode:TranNode | undefined = undefined,
		public selNode:DTO_SelectableBehavior | undefined = undefined,
		public generalBehavInfos:DTO_BehaviorInfo[] | undefined = undefined,
		public driveBehavInfos:DTO_BehaviorInfo[] | undefined = undefined
	){}
}

// 基于Mesh的对象属性,由于有些组件依赖于Mesh对象,而TransformNode对于于Mesh对象无法互相转化,
// 所以只能给某个组件使用Mesh对象来挂载,比如场景中手动创建的三维线条等。
export class DTO_BehaviorMesh extends DTO_BehaviorNode{
	constructor(
		tranNode:TranNode | undefined = undefined,
		selNode:DTO_SelectableBehavior | undefined = undefined,
		generalBehavInfos:DTO_BehaviorInfo[] | undefined = undefined,
		driveBehavInfos:DTO_BehaviorInfo[] | undefined = undefined,
		public recieveShadow:boolean = false,
		public castShadow:boolean = false
	){super(tranNode, selNode, generalBehavInfos, driveBehavInfos);}
}

// 对于一个场景中的三维对象来说,它必定来自特定地址的模型文件(暂时支持.babylon和.glb格式),
// 但是具体使用了模型文件的哪些部分以及这些使用的部分的标识名称、变换是需要记录的,
// 记录这些内容的就是DTO_Obj3DInfo。
export class DTO_Obj3dResource {
	constructor(
		// 这里假定某个模型文件内包含多个根三维对象,
		// UrlCip记录的是模型文件的地址及该文件中被使用的模型的路径cip。
		public urlCip:UrlCip,
		public obj3dInfo:DTO_Obj3dInfo  //info是该资源的一些信息
	){}
}

// 基于外部引入的模型来设置的属性,比如外部引入的.glb文件。
export class DTO_Obj3dInfo{
	constructor(
		public tranNode:TranNode | undefined = undefined,
		public materials:DTO_CipId[] | undefined = undefined,				//依据cip找到模型中的mesh,依据id找到对应的material,运行系统将找到的material赋给找到的mesh
		public selInfos:DTO_CipSelInfo[] | undefined = undefined,			//依据cip找到模型中的node,让node使用SelInfo的设定
		public generalBehavInfos:DTO_CipBehav[] | undefined = undefined,	//依据cip找到模型中的node,让node使用常规Behavior的设定
		public driveBehavInfos:DTO_CipBehav[] | undefined = undefined,		//依据cip找到模型中的node,让node使用驱动Behavior的设定
		public lerpBehavInfos:DTO_CipBehav[] | undefined = undefined,		//依据cip找到模型中的node,让node使用插值Behavior的设定
		public pickables:DTO_CipEnabled[] | undefined = undefined,			//依据cip找到模型中的mesh,将mesh的属性isPickable设置为false
		public castShadows:DTO_CipEnabled[] | undefined = undefined,		//依据cip找到模型中的mesh,将mesh从shadowGenerator的ShadowCaster列表中移除
		public recieveShadows:DTO_CipEnabled[] | undefined = undefined,		//依据cip找到模型中的mesh,将mesh的属性receiveShadows设置为false
		public glowMeshes:Cip[] | undefined = undefined,					//依据cip找到模型中的mesh,将mesh添加到layerController的glow的包含列表中
	){}
}

export class DTO_CipId{
	constructor(
		public cip:Cip,	
		public uuid:string
	){}
}

export {
	DTO_LerpController,
	DTO_LerpProcessInfo,
	DTO_LerpPosition,
	DTO_LerpRotation,
	DTO_LerpScale,
	DTO_LerpVisibility,
} from "./LerpBehavior/DTO_Lerp_Runtime";

lerpModuleRequirement.ts

import type { DTO_RuntimeSystem, DTO_Obj3dSetting, DTO_Obj3dInfo, DTO_CipBehav } from "../DTO/DTO_RuntimeSystem";
import type { DTO_EditorSystem, DTO_Obj3dInfoEditor } from "../DTO/DTO_EditorSystem";
import { BUILTIN_LERP_BEHAVIOR_ENTRIES } from "./LerpBehaviorModuleMetadata";
import { LERP_BEHAVIOR_MODULE_ID } from "./LerpModuleId";

function collectFromObj3dInfo(info: DTO_Obj3dInfo | undefined, classNames: Set<string>): void {
    if (!info?.lerpBehavInfos?.length) return;
    for (const cipBehav of info.lerpBehavInfos) {
        const name = cipBehav?.behaviorInfo?.className;
        if (name) classNames.add(name);
    }
}

function collectFromObj3dSetting(setting: DTO_Obj3dSetting | undefined, classNames: Set<string>): void {
    if (!setting) return;
    setting.obj3DResources?.forEach((res) => collectFromObj3dInfo(res.obj3dInfo, classNames));
}

export function collectLerpBehaviorClassNamesFromCipBehavs(cipBehavs: DTO_CipBehav[] | undefined): string[] {
    if (!cipBehavs?.length) return [];
    const names = new Set<string>();
    for (const cipBehav of cipBehavs) {
        const name = cipBehav?.behaviorInfo?.className;
        if (name) names.add(name);
    }
    return Array.from(names);
}

export function collectLerpBehaviorClassNames(dto: DTO_RuntimeSystem): string[] {
    const names = new Set<string>();
    collectFromObj3dSetting(dto.obj3dSetting, names);
    return Array.from(names);
}

export function isLerpModuleRequiredByDto(dto: DTO_RuntimeSystem): boolean {
    return collectLerpBehaviorClassNames(dto).length > 0;
}

export function getKnownLerpRuntimeBehaviorNames(): string[] {
    return BUILTIN_LERP_BEHAVIOR_ENTRIES.map(e => e.runtimeBehaviorName);
}

export function getKnownLerpEditorBehaviorNames(): string[] {
    return BUILTIN_LERP_BEHAVIOR_ENTRIES.map(e => e.editorBehaviorName);
}

function collectFromEditorObj3dInfo(info: DTO_Obj3dInfoEditor | undefined): boolean {
    return (info?.lerpBehavInfos?.length ?? 0) > 0;
}

export function isLerpEditorPluginMarked(dto: DTO_EditorSystem): boolean {
    return dto.globalSetting.enabledEditorPlugins?.includes(LERP_BEHAVIOR_MODULE_ID) ?? false;
}

export function hasLerpBehavInfosInEditorDto(dto: DTO_EditorSystem): boolean {
    const resources = dto.obj3dSetting?.listObj3DResources;
    if (!resources?.length) return false;
    return resources.some(res => collectFromEditorObj3dInfo(res.obj3dInfo));
}

export function shouldAutoEnableLerpEditorPlugin(dto: DTO_EditorSystem): boolean {
    return isLerpEditorPluginMarked(dto) || hasLerpBehavInfosInEditorDto(dto);
}

LerpBehaviorManagerEditor.ts

import { TransformNode, type Node, Observable, type Observer } from "@babylonjs/core";
import LerpBehaviorEditor from "./LerpBehaviorEditor";
import LerpControllerEditor from "./LerpControllerEditor";
import { lerpBehaviorRegistryEditor } from "./LerpBehaviorRegistry";
import { isLerpModuleEnabled } from "./LerpModuleGate";
import { isLerpControllerRuntimeClassName } from "../../../../../../Shared/TScripts/LerpBehavior/LerpBehaviorModuleMetadata";
import { DTO_CipBehav, DTO_BehaviorInfo, getBehaviorJson } from "../../../../../../Shared/TScripts/DTO/DTO_RuntimeSystem";
import { ChildIndexPath } from "../../../../../../Shared/TScripts/GeneralClass";
import { Cip } from "../../../../../../Shared/TScripts/DTO/DTO_BaseClass";
import type { RunMode } from "../../../../../../Shared/TScripts/GeneralType";
import type { ProcessInfoCopyData } from "./LerpControllerEditor";

export type { ProcessInfoCopyData };

type PausedLerpPreview = {
	behavior: LerpBehaviorEditor;
	wasPreview: boolean;
};

/**
 * 编辑器场景 Lerp 行为管理器
 *
 * 唯一入口 + 协调者。所有 LerpBehaviorEditor 的添加路径:
 *   - UI 添加     → addLerpBehavior()
 *   - 反序列化     → setLerpBehaviorsByInfos()
 *   - 复制粘贴     → registerExistingBehavior()
 *
 * 协调职责:
 *   - 持有所有 onBehaviorsAddedObservable 监听器,统一注册/清理
 *   - 持有所有 Controller 的引用,添加新 LerpBehavior 时通知它们
 */
export default class LerpBehaviorManagerEditor {

	private _lerpBehaviors: LerpBehaviorEditor[] = [];
	public get lerpBehaviors(): LerpBehaviorEditor[] {
		return [...this._lerpBehaviors];
	}

	public readonly onLerpBehaviorsChanged = new Observable<LerpBehaviorEditor[]>();

	private _lerpControllers: LerpControllerEditor[] = [];
	public get lerpControllers(): LerpControllerEditor[] {
		return [...this._lerpControllers];
	}

	private _lerpControllersVersion: number = 0;
	public get lerpControllersVersion(): number {
		return this._lerpControllersVersion;
	}

	public readonly onLerpControllersChanged = new Observable<void>();

	// 每个 Node 上可为多个 Controller 各持有一条 onBehaviorsAddedObservable 监听(若该 API 存在)。
	private _nodeObservers: Map<Node, Map<LerpControllerEditor, Observer<unknown>>> = new Map();

	private _registeredBehaviors: Set<LerpBehaviorEditor> = new Set();

	// #region registration entry points

	/**
	 * 工厂方式创建并注册 LerpBehaviorEditor。
	 * 典型场景:UI 添加行为(LerpBehaviorList.vue)
	 */
	public addLerpBehavior(target: TransformNode, behaviorName: string): LerpBehaviorEditor | null {
		if (!isLerpModuleEnabled()) {
			console.warn("LerpBehaviorManagerEditor: Lerp module is disabled");
			return null;
		}
		if (!target) {
			console.warn("LerpBehaviorManagerEditor: target is null");
			return null;
		}

		const behavior = lerpBehaviorRegistryEditor.createBehavior(behaviorName);
		if (!behavior) {
			console.warn(`Failed to create lerp behavior: ${behaviorName}`);
			return null;
		}

		target.addBehavior(behavior);
		// attach 先调用以设置 behavior.target,使后续 registerExistingBehavior 中的
		// _notifyControllersOfNewBehavior 能正确判断后代关系。
		behavior.attach(target);
		this.registerExistingBehavior(behavior, target);
		return behavior;
	}

	/**
	 * 将已在外部通过 target.addBehavior() 挂载的 LerpBehaviorEditor 纳入 Manager 管理。
	 * 典型场景:复制粘贴、外部直接挂载等绕过 addLerpBehavior 的路径。
	 */
	public registerExistingBehavior(behavior: LerpBehaviorEditor, target: TransformNode): void {
		if (!behavior || !target) return;
		if (!(behavior instanceof LerpBehaviorEditor)) return;

		if (!this._lerpBehaviors.includes(behavior)) {
			this._lerpBehaviors.push(behavior);
			this.onLerpBehaviorsChanged.notifyObservers(this._lerpBehaviors);
		}

		if (!this._registeredBehaviors.has(behavior)) {
			this._registeredBehaviors.add(behavior);
			behavior.onDetachObservable.add(() => {
				this._registeredBehaviors.delete(behavior);
				this._removeBehavior(behavior);
			});
		}

		if (behavior instanceof LerpControllerEditor) {
			if (!this._lerpControllers.includes(behavior)) {
				this._lerpControllers.push(behavior);
				this._lerpControllersVersion++;
				this.onLerpControllersChanged.notifyObservers();
			}
			this._setupNodeObserversForController(behavior);
			this._backfillProcessInfosForController(behavior);
		}

		this._notifyControllersOfNewBehavior(behavior);
	}
	// #endregion

	// #region node observers (moved from LerpControllerEditor)

	/**
	 * 为指定 Controller 注册其所有目标子节点上的 onBehaviorsAddedObservable 监听。
	 * 由 Manager 统一持有监听器引用,在 Controller detach 或 Manager dispose 时统一清理。
	 */
	private _setupNodeObserversForController(controller: LerpControllerEditor): void {
		const nodes = this._collectAllNodesFrom(controller.target);
		nodes.forEach(node => {
			const observable = (node as any).onBehaviorsAddedObservable;
			if (!observable) return;

			let perNode = this._nodeObservers.get(node as unknown as Node);
			if (!perNode) {
				perNode = new Map();
				this._nodeObservers.set(node as unknown as Node, perNode);
			}
			if (perNode.has(controller)) return;

			const observer = observable.add((newBehavior: any) => {
				if (
					newBehavior instanceof LerpBehaviorEditor
					&& !(newBehavior instanceof LerpControllerEditor)
					&& newBehavior !== controller
				) {
					controller.addProcessInfo(newBehavior);
				}
			});
			perNode.set(controller, observer);
		});
	}

	private _collectAllNodesFrom(root: TransformNode | null): TransformNode[] {
		const nodes: TransformNode[] = [];
		if (!root) return nodes;
		const collect = (node: TransformNode): void => {
			nodes.push(node);
			node.getChildren().forEach(child => {
				if (child instanceof TransformNode) collect(child);
			});
		};
		collect(root);
		return nodes;
	}
	// #endregion

	// #region internal helpers

	private _removeBehavior(behavior: LerpBehaviorEditor): void {
		const index = this._lerpBehaviors.indexOf(behavior);
		if (index > -1) {
			this._lerpBehaviors.splice(index, 1);
			this.onLerpBehaviorsChanged.notifyObservers(this._lerpBehaviors);
		}

		if (behavior instanceof LerpControllerEditor) {
			const cIdx = this._lerpControllers.indexOf(behavior);
			if (cIdx > -1) this._lerpControllers.splice(cIdx, 1);
			this._lerpControllersVersion++;
			this.onLerpControllersChanged.notifyObservers();
			// 清理该 Controller 对应的所有 node observers
			this._cleanupObserversForController(behavior);
		}
	}

	/** 清理与指定 Controller 关联的所有 node observers。 */
	private _cleanupObserversForController(controller: LerpControllerEditor): void {
		const nodes = this._collectAllNodesFrom(controller.target);
		nodes.forEach(node => {
			const perNode = this._nodeObservers.get(node as unknown as Node);
			if (!perNode) return;

			const observer = perNode.get(controller);
			if (!observer) return;

			const observable = (node as any).onBehaviorsAddedObservable;
			if (observable) observable.remove(observer);
			perNode.delete(controller);
			if (perNode.size === 0) {
				this._nodeObservers.delete(node as unknown as Node);
			}
		});
	}

	/** Controller 注册时回溯已有子 Lerp 行为(反序列化 / 复制时子行为先于 Controller 加载)。 */
	private _backfillProcessInfosForController(controller: LerpControllerEditor): void {
		if (!controller.target) return;

		for (const existing of this._lerpBehaviors) {
			if (existing === controller) continue;
			if (existing instanceof LerpControllerEditor) continue;
			if (existing.target && this._isNodeDescendantOf(existing.target, controller.target)) {
				controller.addProcessInfo(existing);
			}
		}

		controller.consumePendingCopyData();
	}

	private _notifyControllersOfNewBehavior(behavior: LerpBehaviorEditor): void {
		if (behavior instanceof LerpControllerEditor) return;

		this._lerpControllers.forEach(controller => {
			if (behavior.target && controller.target && this._isNodeDescendantOf(behavior.target, controller.target)) {
				controller.addProcessInfo(behavior);
			}
		});
	}

	private _isNodeDescendantOf(node: TransformNode | null, ancestor: TransformNode | null): boolean {
		if (!node || !ancestor) return false;
		let current: TransformNode | null = node;
		while (current) {
			if (current === ancestor) return true;
			current = current.parent as TransformNode | null;
		}
		return false;
	}
	// #endregion

	// #region set / get behaviors by infos

	/**
	 * 从 DTO 反序列化所有 Lerp behaviors。
	 * 加载顺序:先 others(Position/Rotation/...),再 Controllers。
	 * 这样 Controller attach 时所有子节点的 UUID 均已就绪。
	 */
	public setLerpBehaviorsByInfos(root: TransformNode, cipBehavs: DTO_CipBehav[]): void {
		if (!cipBehavs || cipBehavs.length === 0) return;

		const controllers: DTO_CipBehav[] = [];
		const others: DTO_CipBehav[] = [];

		cipBehavs.forEach(cipBehav => {
			if (!cipBehav || !cipBehav.behaviorInfo) return;
			if (isLerpControllerRuntimeClassName(cipBehav.behaviorInfo.className)) {
				controllers.push(cipBehav);
			} else {
				others.push(cipBehav);
			}
		});

		// Step 1: Load all non-Controller Lerp behaviors first (their UUIDs will be restored from DTO)
		others.forEach(cipBehav => this._setLerpBehavByCipBehav(root, cipBehav));

		// Step 2: Load Controllers — they can now scan subtree and resolve child UUIDs
		controllers.forEach(cipBehav => this._setLerpBehavByCipBehav(root, cipBehav));

		// Step 3: Controllers' setByJsonEditor() stored _pendingProcessInfosData;
		// now that all child behaviors have their UUIDs restored, consume them.
		this._lerpControllers.forEach(controller => {
			controller._applyPendingProcessInfosData();
		});

		this.onLerpBehaviorsChanged.notifyObservers(this._lerpBehaviors);
	}

	private _setLerpBehavByCipBehav(root: Node, cipBehav: DTO_CipBehav): void {
		if (!cipBehav || !cipBehav.cip) return;

		const cip: ChildIndexPath = new ChildIndexPath(cipBehav.cip.ids);
		const curNode = cip.ids.length === 0 ? root : cip.getNodeFromeChildren(root.getChildren());
		const curTrNode = curNode as TransformNode;
		if (!curTrNode) return;

		this._setLerpBehav(curTrNode, cipBehav.behaviorInfo);
	}

	private _setLerpBehav(trNode: TransformNode, behavInfo: DTO_BehaviorInfo): LerpBehaviorEditor | null {
		if (!behavInfo || !behavInfo.className) return null;

		const behavior = lerpBehaviorRegistryEditor.createBehavior(behavInfo.className);
		if (!behavior) {
			console.warn(`Failed to create lerp behavior: ${behavInfo.className}`);
			return null;
		}

		const jsonEditor = getBehaviorJson(behavInfo);
		if (jsonEditor && jsonEditor.trim() !== "") {
			try {
				behavior.setByJsonEditor(jsonEditor);
			} catch (error) {
				console.error(`Failed to apply JSON config for lerp behavior ${behavInfo.className}:`, error);
			}
		}

		trNode.addBehavior(behavior);
		behavior.attach(trNode);
		this.registerExistingBehavior(behavior, trNode);
		return behavior;
	}

	public getLerpBehaviorInfosFromNode(rootNode: TransformNode, runMode: RunMode = "Editor"): DTO_CipBehav[] {
		const lerpBehavs: DTO_CipBehav[] = [];

		const collectBehavs = (node: TransformNode, currentPath: number[]): void => {
			for (const behavior of node.behaviors) {
				if (behavior instanceof LerpBehaviorEditor) {
					const cip = new Cip(currentPath.length > 0 ? [...currentPath] : []);
					const name = runMode === "Editor" ? behavior.name : behavior.name.replace("Editor", "");
					const json = runMode === "Editor" ? behavior.getJsonEditor() : behavior.getJsonRuntime();
					try {
						const behaviorInfo = new DTO_BehaviorInfo(name, JSON.parse(json) as Record<string, unknown>);
						lerpBehavs.push(new DTO_CipBehav(cip, behaviorInfo));
					} catch (error) {
						console.error(`Failed to serialize lerp behavior ${behavior.name}:`, error);
					}
				}
			}

			const children = node.getChildren();
			children.forEach((child, index) => {
				if (child instanceof TransformNode) {
					collectBehavs(child, [...currentPath, index]);
				}
			});
		};

		collectBehavs(rootNode, []);
		return lerpBehavs;
	}
	// #endregion

	// #region export preview pause

	public pauseAllExportUnsafePreviews(): PausedLerpPreview[] {
		const paused: PausedLerpPreview[] = [];

		for (const behavior of this._lerpBehaviors) {
			if (!behavior || typeof behavior !== "object") continue;
			if (typeof (behavior as any).setPreview !== "function") continue;
			if (!(behavior as any).isPreview) continue;

			try {
				behavior.savePreviewStateForSerialization();
				paused.push({ behavior, wasPreview: true });
				behavior.setPreview(false);
			} catch (e) {
				console.error("暂停 lerp behavior 预览时出错:", (behavior as any).name, e);
				paused.pop();
			}
		}

		return paused;
	}

	public restoreExportUnsafePreviews(paused: PausedLerpPreview[] | null | undefined): void {
		if (!paused) return;

		for (const item of paused) {
			if (!item?.behavior) continue;
			if (typeof item.behavior !== "object") continue;
			if (typeof item.behavior.setPreview !== "function") continue;

			if (item.wasPreview) {
				item.behavior.setPreview(true);
			}
			item.behavior.clearPreviewStateForSerialization();
		}
	}

	/** 插件关闭时停止所有预览与控制器播放,不删除场景数据。 */
	public pauseAllSessionExecution(): void {
		for (const behavior of this._lerpBehaviors) {
			if (!behavior || typeof behavior !== "object") continue;
			if (typeof (behavior as any).setPreview !== "function") continue;
			if (!(behavior as any).isPreview) continue;
			try {
				(behavior as any).setPreview(false);
			} catch (e) {
				console.error("停止 lerp behavior 预览时出错:", (behavior as any).name, e);
			}
		}

		for (const controller of this._lerpControllers) {
			try {
				controller.setPlayState("stop");
			} catch (e) {
				console.error("停止 lerp controller 播放时出错:", e);
			}
		}
	}
	// #endregion

	public dispose(): void {
		this._lerpBehaviors.forEach(behavior => {
			behavior.detach();
		});
		this._lerpBehaviors = [];
		this._lerpControllers = [];

		this._nodeObservers.forEach((perNode, node) => {
			const observable = (node as any).onBehaviorsAddedObservable;
			if (!observable) return;
			perNode.forEach(observer => observable.remove(observer));
		});
		this._nodeObservers.clear();
		this._registeredBehaviors.clear();

		this.onLerpBehaviorsChanged.clear();
		this.onLerpControllersChanged.clear();
	}
}

LerpBehaviorManager.ts

import { TransformNode, type Node, Observable } from "@babylonjs/core";
import LerpBehavior from "./LerpBehavior";
import LerpController from "./LerpController";
import { LerpBehaviorRegistryRuntime, type LerpRuntimeBehavior } from "./LerpBehaviorRegistryRuntime";
import { isLerpRuntimeModuleLoaded } from "./LerpModuleRuntimeGate";
import { DTO_CipBehav, DTO_BehaviorInfo, getBehaviorJson } from "../../../../../Shared/TScripts/DTO/DTO_RuntimeSystem";
import { isLerpControllerRuntimeClassName } from "../../../../../Shared/TScripts/LerpBehavior/LerpBehaviorModuleMetadata";
import { ChildIndexPath } from "../../../../../Shared/TScripts/GeneralClass";
import { SceneObj3dManager } from "../../SceneObj3dManager";

/**
 * 运行时 Lerp 行为管理器
 * 独立于 DriveBehaviorManager,专门管理场景中的插值行为。
 * 仿编辑器侧 LerpBehaviorManagerEditor,但适配运行时(createFromJson + addBehavior 路径)。
 */
export class LerpBehaviorManager {

    private _lerpBehaviors: LerpRuntimeBehavior[] = [];
    public get lerpBehaviors(): LerpRuntimeBehavior[] {
        return [...this._lerpBehaviors];
    }

    public readonly onLerpBehaviorsChanged = new Observable<LerpRuntimeBehavior[]>();

    public readonly onAddLerpBehaviorObservable = new Observable<LerpRuntimeBehavior>();

    private _lerpControllers: LerpController[] = [];
    public get lerpControllers(): LerpController[] {
        return [...this._lerpControllers];
    }

    private _obj3dManager: SceneObj3dManager;

    constructor(obj3dManager: SceneObj3dManager) {
        this._obj3dManager = obj3dManager;
    }

    // #region set behaviors by infos
    /**
     * 从 DTO 列表反序列化 Lerp 行为。
     * 关键设计:先把所有非 Controller 的 Lerp 子行为加载完,再加载 Controller,
     * 避免 Controller 在 attach 时通过 node.behaviors 找不到已加载的子行为。
     */
    public setLerpBehavsByInfos(root: TransformNode, cipBehavs: DTO_CipBehav[]): void {
        if (!cipBehavs || cipBehavs.length === 0) return;

        const controllers: DTO_CipBehav[] = [];
        const others: DTO_CipBehav[] = [];

        cipBehavs.forEach(cipBehav => {
            if (!cipBehav || !cipBehav.behaviorInfo) return;
            if (isLerpControllerRuntimeClassName(cipBehav.behaviorInfo.className)) {
                controllers.push(cipBehav);
            } else {
                others.push(cipBehav);
            }
        });

        others.forEach(cipBehav => {
            this._setLerpBehavByCipBehav(root, cipBehav);
        });

        controllers.forEach(cipBehav => {
            this._setLerpBehavByCipBehav(root, cipBehav);
        });

        this._lerpControllers.forEach(controller => {
            controller.initializeFromNode();
        });

        this.onLerpBehaviorsChanged.notifyObservers(this._lerpBehaviors);
    }

    private _setLerpBehavByCipBehav(root: Node, cipBehav: DTO_CipBehav): void {
        if (!cipBehav || !cipBehav.cip) return;

        const cip: ChildIndexPath = new ChildIndexPath(cipBehav.cip.ids);
        const curNode = cip.ids.length === 0 ? root : cip.getNodeFromeChildren(root.getChildren());
        const curTrNode = curNode as TransformNode;
        if (!curTrNode) return;

        this.setLerpBehavByInfo(curTrNode, cipBehav.behaviorInfo);
    }

    public setLerpBehavByInfo(trNode: TransformNode, behavInfo: DTO_BehaviorInfo): LerpRuntimeBehavior | null {
        if (!behavInfo || !behavInfo.className) return null;

        const jsonBehav: string = getBehaviorJson(behavInfo);
        if (!jsonBehav || jsonBehav.length === 0) return null;

        const behavior = LerpBehaviorRegistryRuntime.createBehavior(behavInfo.className, trNode, jsonBehav);
        if (!behavior) {
            console.warn(`Failed to create lerp behavior: ${behavInfo.className}`);
            return null;
        }

        if (behavior instanceof LerpController) {
            this._trackBehavior(behavior);
            this._lerpControllers.push(behavior);
            this.onAddLerpBehaviorObservable.notifyObservers(behavior);
        } else {
            behavior.attach(trNode);
            this._trackBehavior(behavior);
            this._notifyControllersOfNewBehavior(behavior);
            this.onAddLerpBehaviorObservable.notifyObservers(behavior);
        }

        return behavior;
    }
    // #endregion

    // #region tracking
    private _trackBehavior(behavior: LerpRuntimeBehavior): void {
        if (this._lerpBehaviors.includes(behavior)) return;
        this._lerpBehaviors.push(behavior);
        this.onLerpBehaviorsChanged.notifyObservers(this._lerpBehaviors);

        behavior.onDetachObservable.add(() => {
            this._removeBehavior(behavior);
        });
    }

    private _removeBehavior(behavior: LerpRuntimeBehavior): void {
        const index = this._lerpBehaviors.indexOf(behavior);
        if (index > -1) {
            this._lerpBehaviors.splice(index, 1);
            this.onLerpBehaviorsChanged.notifyObservers(this._lerpBehaviors);
        }

        if (behavior instanceof LerpController) {
            const cIdx = this._lerpControllers.indexOf(behavior);
            if (cIdx > -1) this._lerpControllers.splice(cIdx, 1);
        }
    }

    private _notifyControllersOfNewBehavior(behavior: LerpBehavior): void {
        const behaviorTarget = behavior.target;
        if (!behaviorTarget) return;
        this._lerpControllers.forEach(controller => {
            const controllerTarget = controller.target;
            if (!controllerTarget) return;
            if (this._isNodeDescendantOf(behaviorTarget, controllerTarget)) {
                controller.addProcessInfo(behavior);
            }
        });
    }

    private _isNodeDescendantOf(node: TransformNode, ancestor: TransformNode): boolean {
        let current: TransformNode | null = node;
        while (current) {
            if (current === ancestor) return true;
            current = current.parent as TransformNode | null;
        }
        return false;
    }
    // #endregion

    // #region attach coupling
    public bindController(controller: LerpController): void {
        if (controller && !this._lerpControllers.includes(controller)) {
            this._lerpControllers.push(controller);
        }
    }

    public get obj3dManager(): SceneObj3dManager {
        return this._obj3dManager;
    }
    // #endregion

    public dispose(): void {
        this._lerpBehaviors.forEach(behavior => {
            behavior.detach();
        });
        this._lerpBehaviors = [];
        this._lerpControllers = [];
        this.onLerpBehaviorsChanged.clear();
        this.onAddLerpBehaviorObservable.clear();
    }
}

export function applyLerpBehaviorsIfAvailable(
    manager: LerpBehaviorManager,
    root: TransformNode,
    cipBehavs: DTO_CipBehav[] | undefined
): void {
    if (!isLerpRuntimeModuleLoaded()) return;
    if (!cipBehavs?.length) return;
    manager.setLerpBehavsByInfos(root, cipBehavs);
}

EditorSystem.ts

import { AbstractEngine, Engine, Observable, Observer, PointerEventTypes, Scene, UniversalCamera, Vector3 } from "@babylonjs/core";
import { DTO_EditorSystem, DTO_GlobalSettingEditor } from "../../../Shared/TScripts/DTO/DTO_EditorSystem";
import { Tool } from "../../../Shared/TScripts/Tool";
import EditorBaseScene from "./EditorBaseScene/EditorBaseScene";
import EnvControllerEditor from "./EnvControllerEditor/EnvControllerEditor";
import CommandManager from "../Command/CommandManager";
import CommandDispatcher from "../Command/CommandDispatcher";
import KeyboardControllerEditor from "./InputManage/KeyboardControllerEditor";
import SceneObj3dManagerEditor from "./ObjManager/SceneObj3dManagerEditor";
import MouseControllerEditor from "./InputManage/MouseControllerEditor";
import LayerControllerEditor from "./LayerControllerEditor";
import ScenePickerEditor from "./InputManage/ScenePickerEditor";
import GizmoController from "./GizmoController";
import type SelectableBehaviorEditor from "./ObjManager/Behaviours/SelectableBehavior/SelectableBehaviorEditor";
import ShortCutsManagerEditor from "./InputManage/ShortCutsManagerEditor";
import CameraControllerEditor from "./EditorBaseScene/CameraControllerEditor";
import ShadowManagerEditor from "./ShadowManagerEditor";
import ViewControllerEditor from "./ViewController/ViewControllerEditor";
import { DTO_GlobalSetting, DTO_RuntimeSystem } from "../../../Shared/TScripts/DTO/DTO_RuntimeSystem";
import CmdProperty from "../Command/CmdProperty";
import { cmdEmitter, emitter } from "../utils/EventBus";
import { AssetContainerManager } from "../../../Runtime/TScripts/Obj3dManage/AssetContainerManager";
import { RuntimeOutlineSetting } from "./RuntimeOutlineSetting";
import FollowedNodeNameGlobalSetting from "./ObjManager/FollowNodeUI/FollowNodeNameGlobalSetting";
import WebConnectionEditor from "./WebConnectionEditor";
import type { DriveData } from "../../../Shared/TScripts/DTO/DTO_BaseClass";
import { LERP_BEHAVIOR_MODULE_ID } from "../../../Shared/TScripts/LerpBehavior/LerpModuleId";
import {
	isLerpModuleEnabled,
	setLerpModuleEnabled,
	resolveLerpPluginOnFileLoad,
	registerLerpModuleDisabledHandler,
} from "./ObjManager/Behaviours/LerpBehavior/LerpModuleGate";
import ConfirmDialog from "../utils/ConfirmDialog";

export default class EditorSystem {
	private static _instance: EditorSystem | null = null;

	// 记录最近编辑文件的保存路径,用于快速保存
	private _lastEditingFileSavePath: string | null = null;
	public getLastEditingFileSavePath(): string | null {
		return this._lastEditingFileSavePath;
	}
	public setLastEditingFileSavePath(path: string | null): void {
		this._lastEditingFileSavePath = path;
		console.log("setLastEditingFileSavePath:", this._lastEditingFileSavePath);
	}
	public hasLastEditingFileSavePath(): boolean {
		return this._lastEditingFileSavePath !== null && this._lastEditingFileSavePath !== undefined;
	}

	// 场景快照,用于判断场景是否被编辑
	private _originalSceneJson: string | null = null;
	public saveOriginalSnapshot(): void {
		this._originalSceneJson = JSON.stringify(this.getDataEditor());
	}
	public isSceneModified(): boolean {
		if (this._originalSceneJson === null) return false;
		const currentJson = JSON.stringify(this.getDataEditor());
		return currentJson !== this._originalSceneJson;
	}

	public readonly canvas: HTMLCanvasElement;
	public readonly engine: AbstractEngine;
	public readonly scene: Scene;
	public readonly camera:UniversalCamera;
	//-----------------------------------------------------------------------------------------
	// 统一的 AssetContainerManager,供整个编辑器系统使用
	// 重要:集中管理模型资源的加载和复用,避免重复加载
	public readonly assetContainerManager: AssetContainerManager;
	//-----------------------------------------------------------------------------------------
	public readonly commandManager:CommandManager;
	public readonly commandDispatcher:CommandDispatcher;
	public readonly shadowManager:ShadowManagerEditor;
	public readonly scenePicker:ScenePickerEditor;
	public readonly mouseController:MouseControllerEditor;
	public readonly keyboardController:KeyboardControllerEditor;
	public readonly shortcutsManager:ShortCutsManagerEditor;
	public readonly layerController:LayerControllerEditor;
	public readonly runtimeOutlineSetting:RuntimeOutlineSetting = new RuntimeOutlineSetting();
	public readonly followNodeNameGlobalSetting:FollowedNodeNameGlobalSetting = new FollowedNodeNameGlobalSetting();
	public readonly envController:EnvControllerEditor;
	public readonly sceneObjManager:SceneObj3dManagerEditor;
	public readonly gizmoController:GizmoController;
	public readonly cameraController:CameraControllerEditor;
	public readonly viewController:ViewControllerEditor;
	public readonly webConnection:WebConnectionEditor;
	public readonly baseScene: EditorBaseScene;
	
	// Observer 引用,用于 dispose 时取消订阅
	private _onPickSelBehavObserver: Observer<SelectableBehaviorEditor[]> | null = null;
	private _onAddSelBehavObserver: Observer<SelectableBehaviorEditor[]> | null = null;
	private _onRemoveSelBehavObserver: Observer<SelectableBehaviorEditor[]> | null = null;
	private _onHoverSelBehavObserver: Observer<SelectableBehaviorEditor | null> | null = null;
	private _onSetSelectedObserver: Observer<SelectableBehaviorEditor[]> | null = null;
	private _onEditorDriveDataObserver: Observer<DriveData[]> | null = null;
	private _onDriveIdsChangedObserver: Observer<string[]> | null = null;
	private _onSceneLoadedDriveIdsObserver: Observer<void> | null = null;
	
	// EventBus 事件处理器引用,用于 dispose 时取消订阅
	private _onSaveFileSuccessHandler: ((payload: any) => void) | null = null;

	public readonly version:string = "1.0.0";

	// 私有构造函数
	private constructor(canvas: HTMLCanvasElement) {
		//
		this.canvas = canvas;
		this.engine = new Engine(this.canvas, true);
		this.scene = new Scene(this.engine);
		this.camera = new UniversalCamera("EditorCamera", Vector3.Zero(), this.scene);
		//
		// 创建统一的 AssetContainerManager,供整个编辑器系统使用
		this.assetContainerManager = new AssetContainerManager();
		//
		this.commandManager = new CommandManager(30, true);
		this.commandDispatcher = new CommandDispatcher(this.commandManager);
		this.shadowManager = new ShadowManagerEditor(this.scene);
		this.scenePicker = new ScenePickerEditor(this.scene);
		this.mouseController = new MouseControllerEditor(this.scene);
		this.keyboardController = new KeyboardControllerEditor(this.scene);
		this.layerController = new LayerControllerEditor(this.scene);
		this.gizmoController = new GizmoController(this.scene);
		this.sceneObjManager = new SceneObj3dManagerEditor(this);
		this.viewController = new ViewControllerEditor(this.scene);
		this.envController = new EnvControllerEditor(this.shadowManager);
		this.shortcutsManager = new ShortCutsManagerEditor(this.keyboardController);
		this.cameraController = new CameraControllerEditor(this.camera, this.mouseController, this.sceneObjManager);
		this.webConnection = new WebConnectionEditor();
		this.baseScene = new EditorBaseScene(this.scene);
		//
		registerLerpModuleDisabledHandler(() => {
			this.sceneObjManager.lerpBehavManager.pauseAllSessionExecution();
		});
		this.init();
	}

	// 初始化单例(创建 EditorSystem)
	public static initialize(canvas: HTMLCanvasElement): EditorSystem {
		if (!EditorSystem._instance) {
			EditorSystem._instance = new EditorSystem(canvas);
		}
		return EditorSystem._instance;
	}

	// 获取当前实例(可能尚未初始化)
	public static get instance(): EditorSystem | null {
		return EditorSystem._instance;
	}

	// 销毁单例并释放资源
	public static destroy() {
		EditorSystem._instance?.dispose();
		EditorSystem._instance = null;
	}

	public init() {
		// MouseController 内部监听会在 MouseController.dispose() 中清理
		this.mouseController.addListener(PointerEventTypes.POINTERMOVE, this.scenePicker.setHover.bind(this.scenePicker));
		this.mouseController.addListener(PointerEventTypes.POINTERTAP, this.scenePicker.setPick.bind(this.scenePicker));

		// scenePicker 和 sceneObjManager.selectableManager 是两个独立的系统,需要通过 Observable 进行通信。
		// 保存 Observer 引用,便于后续移除
		this._onPickSelBehavObserver = this.scenePicker.onPickSelBehavObservable.add(
			this.sceneObjManager.selectableManager.cmdToggleSelectedBehavs.bind(this.sceneObjManager.selectableManager)
		);
		this._onAddSelBehavObserver = this.scenePicker.onAddSelBehavObservable.add(
			this.sceneObjManager.selectableManager.cmdAddSelectedBehavs.bind(this.sceneObjManager.selectableManager)
		);
		this._onRemoveSelBehavObserver = this.scenePicker.onRemoveSelBehavObservable.add(
			this.sceneObjManager.selectableManager.cmdRemoveSelectedBehavs.bind(this.sceneObjManager.selectableManager)
		);
		this._onHoverSelBehavObserver = this.scenePicker.onHoverSelBehavObservable.add(
			this.sceneObjManager.selectableManager.setHoveredSelBehav.bind(this.sceneObjManager.selectableManager)
		);
		
		// sceneObjManager.selectableManager 和 gizmoController 是两个独立的系统,需要通过 Observable 进行通信。
		// 保存这个 Observer 引用
		this._onSetSelectedObserver = this.sceneObjManager.selectableManager.onSetSelectedBehavsObservable.add((selBehavs: SelectableBehaviorEditor[]) => {
			const nodeSet = this.sceneObjManager.selectableManager.getSelectableBehavRoots(selBehavs);
			this.gizmoController.setOperateNodes([...nodeSet]);
		});

		// webConnection 和 sceneObjManager.driveBehavManager 是两个独立的系统,需要通过 Observable 进行通信。
		this._onEditorDriveDataObserver = this.webConnection.onDriveDataObservable.add((driveDataItems: DriveData[]) => {
			driveDataItems.forEach((item) => this.sceneObjManager.driveBehavManager.driveDriveBehavior(item));
		});
		this._onDriveIdsChangedObserver = this.sceneObjManager.driveBehavManager.onDriveIdsChangedObservable.add((driveIds: string[]) => {
			this.webConnection.setDriveIds(driveIds);
		});
		this._onSceneLoadedDriveIdsObserver = this.sceneObjManager.onSceneLoadedObj3dObservable.add(() => {
			this.webConnection.setDriveIds(this.sceneObjManager.driveBehavManager.driveIds);
		});
		
		// 监听保存文件成功事件
		this._onSaveFileSuccessHandler = (payload: any) => {
			const { fullPath, extension } = payload;
			// 只有文件扩展名为 .e3d 时,才记录编辑文件路径
			// .c3d 是导出的运行文件,不需要记录编辑文件路径
			if (extension === '.e3d') {
				this.setLastEditingFileSavePath(fullPath);
				// 保存成功后更新快照,这样下次新建时才不会提示未保存
				this.saveOriginalSnapshot();
			}
		};
		emitter.on('SaveFileSuccess', this._onSaveFileSuccessHandler);
	}

	private _runFineness:number = 1;
	public get runFineness():number{
		return this._runFineness;
	}

	public cmdSetRunFineness(value:number){
		const cmd = new CmdProperty<number>(this._setRunFineness.bind(this), value, this._runFineness);
		cmdEmitter.emit("execute", cmd);
	}

	public readonly onSetRunFinenessObservable:Observable<number> = new Observable<number>();
	private _setRunFineness(value:number){
		this._runFineness = value;
		this.onSetRunFinenessObservable.notifyObservers(value);
	}

	public async setByFile(url:string): Promise<void>{
		return new Promise((resolve) => {
			// 监听场景对象加载完成事件
			const onLoadedObserver = this.sceneObjManager.onSceneLoadedObj3dObservable.add(() => {
				this.sceneObjManager.onSceneLoadedObj3dObservable.remove(onLoadedObserver);
				// 加载完成后保存快照
				this.setLastEditingFileSavePath(url);
				
				// 使用 setTimeout 延迟保存快照,确保所有异步操作完成
				// 这是因为 notifyObservers 可能触发链式反应,需要等待所有 Promise 完成
				setTimeout(() => {
					this.saveOriginalSnapshot();
					resolve();
				}, 100);
			});

			Tool.loadText(url)
				.then((json:string | null) => {
					this.setByJson(json);
					// 如果没有异步加载,onSceneLoadedObj3dObservable 不会触发
					// 所以需要检查 loudingCount,如果已经是 0 则直接保存
					if (this.sceneObjManager.loudingCount === 0) {
						this.sceneObjManager.onSceneLoadedObj3dObservable.remove(onLoadedObserver);
						this.setLastEditingFileSavePath(url);
						setTimeout(() => {
							this.saveOriginalSnapshot();
							resolve();
						}, 100);
					}
				})
				.catch((error) => {
					console.error("Failed to load editor system from file:", url, error);
					this.sceneObjManager.onSceneLoadedObj3dObservable.remove(onLoadedObserver);
					resolve();
				});
		});
	}

	public setByJson(json:string | null):void{
		if(!json){
			console.error("EditorSystem json is null or empty");
			return;
		}

		const editorSystem:DTO_EditorSystem = JSON.parse(json);
		if(editorSystem){
			this.setByDataEditor(editorSystem);
		}
	}

	public async dispose() {
		// 移除 Observable 订阅
		if (this._onPickSelBehavObserver) {
			this.scenePicker.onPickSelBehavObservable.remove(this._onPickSelBehavObserver);
			this._onPickSelBehavObserver = null;
		}
		if (this._onAddSelBehavObserver) {
			this.scenePicker.onAddSelBehavObservable.remove(this._onAddSelBehavObserver);
			this._onAddSelBehavObserver = null;
		}
		if (this._onRemoveSelBehavObserver) {
			this.scenePicker.onRemoveSelBehavObservable.remove(this._onRemoveSelBehavObserver);
			this._onRemoveSelBehavObserver = null;
		}
		if (this._onHoverSelBehavObserver) {
			this.scenePicker.onHoverSelBehavObservable.remove(this._onHoverSelBehavObserver);
			this._onHoverSelBehavObserver = null;
		}
		if (this._onSetSelectedObserver) {
			this.sceneObjManager.selectableManager.onSetSelectedBehavsObservable.remove(this._onSetSelectedObserver);
			this._onSetSelectedObserver = null;
		}
		if (this._onEditorDriveDataObserver) {
			this.webConnection.onDriveDataObservable.remove(this._onEditorDriveDataObserver);
			this._onEditorDriveDataObserver = null;
		}
		if (this._onDriveIdsChangedObserver) {
			this.sceneObjManager.driveBehavManager.onDriveIdsChangedObservable.remove(this._onDriveIdsChangedObserver);
			this._onDriveIdsChangedObserver = null;
		}
		if (this._onSceneLoadedDriveIdsObserver) {
			this.sceneObjManager.onSceneLoadedObj3dObservable.remove(this._onSceneLoadedDriveIdsObserver);
			this._onSceneLoadedDriveIdsObserver = null;
		}
		
		// 移除 EventBus 订阅
		if (this._onSaveFileSuccessHandler) {
			emitter.off('SaveFileSuccess', this._onSaveFileSuccessHandler);
			this._onSaveFileSuccessHandler = null;
		}

		// 清空 Observable 事件
		this.onSetEditorSystem.clear();
		this.onGetEditorSystem.clear();

		// 按依赖顺序销毁各个组件
		await this.baseScene.dispose(); // baseScene.dispose 是异步的
		this.viewController.dispose();
		this.cameraController.dispose();
		this.gizmoController.dispose();
		this.scenePicker.dispose();
		this.sceneObjManager.dispose();
		this.envController.dispose();
		this.shadowManager.dispose();
		this.layerController.dispose();
		this.commandDispatcher.dispose();
		this.commandManager.dispose();
		this.shortcutsManager.dispose();
		this.keyboardController.dispose();
		this.mouseController.dispose();
		this.webConnection.dispose();

		// 清理 AssetContainerManager:只清空内部 Map,不 dispose container
		this.assetContainerManager.dispose();
		
		this.scene.dispose();
		this.engine.dispose();
	}
	

	// #region 数据=====================================================================================
	private _editorSystemInfo :DTO_EditorSystem | undefined = undefined;
	public get editorSystemInfo():DTO_EditorSystem | undefined{
		return this._editorSystemInfo;
	}

	public readonly onSetEditorSystem:Observable<DTO_EditorSystem> = new Observable<DTO_EditorSystem>();
	public readonly onGetEditorSystem:Observable<DTO_EditorSystem> = new Observable<DTO_EditorSystem>();

	// 获取编辑器数据,用于保存编辑文件
	public getDataEditor():DTO_EditorSystem{
		// 保存前暂停所有驱动预览,避免把预览态写入场景数据
		const pausedPreviews = this.sceneObjManager.driveBehavManager.pauseAllExportUnsafePreviews();
		
		try {
			const enabledEditorPlugins = isLerpModuleEnabled()
				? [LERP_BEHAVIOR_MODULE_ID]
				: undefined;

			this._editorSystemInfo = new DTO_EditorSystem(
				new DTO_GlobalSettingEditor(
					this.version,
					this.cameraController.getDataEditor(),
					this.commandManager.getDataEditor(),
					this.baseScene.sceneGrid.getDataEditor(),
					this.webConnection.getDataEditor(),
					this.runtimeOutlineSetting.getDataEditor(),
					this.followNodeNameGlobalSetting.GetGlobalSettingEditor(),
					enabledEditorPlugins,
				),
				this.viewController.getDataEditor(),
				this.envController.getDataEditor(),
				this.sceneObjManager.getDataEditor()
			);

			this.onGetEditorSystem.notifyObservers(this._editorSystemInfo);

			return this._editorSystemInfo;
		} finally {
			// 保存结束后恢复预览状态
			this.sceneObjManager.driveBehavManager.restoreExportUnsafePreviews(pausedPreviews);
		}
	}

	// 获取运行时数据
	public getDataRuntime():DTO_RuntimeSystem{
		// 导出前暂停所有驱动预览,避免把预览态写入运行数据
		const pausedPreviews = this.sceneObjManager.driveBehavManager.pauseAllExportUnsafePreviews();
		
		try {
			const enabledRuntimeModules = this._getEnabledRuntimeModules();

			return new DTO_RuntimeSystem(
				new DTO_GlobalSetting(
					this.version,
					this._runFineness,
					this.runtimeOutlineSetting.getDtataRuntime(),
					this.followNodeNameGlobalSetting.GetGlobalSetting(),
					this.webConnection.getDataRuntime(),
					enabledRuntimeModules,
				),
				this.viewController.getDataRuntime(),
				this.envController.getDataRuntime(),
				this.sceneObjManager.getDataRuntime(),
			);
		} finally {
			// 导出结束后恢复预览状态
			this.sceneObjManager.driveBehavManager.restoreExportUnsafePreviews(pausedPreviews);
		}
	}

	// 设置编辑器数据
	public setByDataEditor(editorSystemInfo :DTO_EditorSystem):void{
		this._editorSystemInfo = editorSystemInfo;

		const lerpResolution = resolveLerpPluginOnFileLoad(editorSystemInfo);
		if (lerpResolution.needsWarning) {
			ConfirmDialog.ShowConfirmOnlyDialog(
				"检测到插值数据,但当前编辑器不支持插值行为插件。",
				() => {},
			);
		}
		setLerpModuleEnabled(lerpResolution.shouldEnable);

		// 重要:资源清理流程已移至 SceneObj3dManagerEditor.setByDataEditor 内部
		// 正确的顺序是:
		// 1. clearAllLoadedNodes() - 清理已加载节点
		// 2. assetContainerManager.dispose() - 清空资源
		// 3. _loadAndSetObj3d() - 加载新数据
		// 这样可以确保 dispose 和异步加载的顺序正确

		this.cameraController.setByDataEditor(editorSystemInfo.globalSetting.editorCamera);
		this.baseScene.sceneGrid.setByDataEditor(editorSystemInfo.globalSetting.gridSetting);
		this.commandManager.setByDataEditor(editorSystemInfo.globalSetting.undoOperation);
		//
		this.runtimeOutlineSetting.setByDataEditor(editorSystemInfo.globalSetting.runtimeOutlineSettingEditor);
		this.followNodeNameGlobalSetting.SetGlobalSettingEditor(editorSystemInfo.globalSetting.followNodeNameGlobalSettingEditor);
		this.webConnection.setByDataEditor(editorSystemInfo.globalSetting.connection);

		this.viewController.setByDataEditor(editorSystemInfo.viewSettingEditor);
		this.envController.setByDataEditor(editorSystemInfo.environment);
		
		// 注意:sceneObjManager.setByDataEditor 内部会处理资源清理和重新加载流程
		this.sceneObjManager.setByDataEditor(editorSystemInfo.obj3dSetting);

		this.onSetEditorSystem.notifyObservers(editorSystemInfo);
	}

	public reset():void{
		setLerpModuleEnabled(false);
		this.setByDataEditor(new DTO_EditorSystem());
		// reset 为空白场景后,更新快照,这样 isSceneModified() 才能正确判断
		this.saveOriginalSnapshot();
	}

	private _getEnabledRuntimeModules(): string[] | undefined {
		if (!isLerpModuleEnabled()) return undefined;
		if (this.sceneObjManager.lerpBehavManager.lerpBehaviors.length === 0) return undefined;
		return [LERP_BEHAVIOR_MODULE_ID];
	}
	// #endregion
}

RuntimeSystem.ts

import { Mesh, Observable, PointerEventTypes, Scalar, Scene, ShadowGenerator, TransformNode} from "@babylonjs/core";
import type { Observer } from "@babylonjs/core";
import { DTO_RuntimeSystem } from "../../Shared/TScripts/DTO/DTO_RuntimeSystem";
import { LERP_BEHAVIOR_MODULE_ID } from "../../Shared/TScripts/LerpBehavior/LerpModuleId";
import { loadLerpRuntimeModule, unloadLerpRuntimeModule } from "./Obj3dManage/Behaviors/LerpBehavior/LerpModuleRuntimeGate";
import { ViewController } from "./ViewCtrl/ViewController";
import { SceneObj3dManager } from "./Obj3dManage/SceneObj3dManager";
import LayerController from "./Controllers/LayerController";
import { MouseController } from "./Controllers/MouseController";
import { PostProcessController } from "./Controllers/PostProcessController";
import { EnvController } from "./EnvCtrl/EnvController";
import type SelectableBehavior from "./Obj3dManage/Behaviors/SelectableBehavior";
import type { DriveBehavior } from "./Obj3dManage/Behaviors/DriveBehavior/DriveBehavior";
import { Tool } from "../../Shared/TScripts/Tool";
import type NodeBaseInfo from "./Obj3dManage/Behaviors/NodeBaseInfo";
import { FollowNodeName } from "./Obj3dManage/FollowNodeUI/FollowNodeName";
import RuntimeDriveConnection, { type RuntimeConnectionInfo } from "./Connection/RuntimeDriveConnection";

/**
 * RuntimeSystem 是运行时页面的核心协调器。
 *
 * 它负责创建视角、输入、环境、三维对象、图层、后处理和驱动联网等子系统,
 * 并把这些子系统之间的事件关系串联起来。外部调用方只需要通过 setByFile/setByJson
 * 提供运行时数据。
 */
export class RuntimeSystem{
	private _scene:Scene;
	public get scene():Scene{return this._scene;}
	//
	private _mouseController:MouseController;
	public readonly layerController:LayerController;
	//
	private _viewController:ViewController;
	private _envController:EnvController;
	public readonly sceneObj3dManager:SceneObj3dManager;
	//
	private _postProcessController:PostProcessController;
	//
	public readonly driveConnection: RuntimeDriveConnection;
	private _driveBehaviorsChangedObserver: Observer<DriveBehavior[]> | null = null;
	//
	public onRunSystemInitObservable:Observable<void> = new Observable<void>();

	constructor(scene:Scene){
		this._scene = scene;
		this._mouseController = new MouseController(this._scene);
		this.layerController = new LayerController(this._scene);

		this._viewController = new ViewController(this._scene, this._mouseController);
		this._envController = new EnvController(this._scene);
		this.sceneObj3dManager = new SceneObj3dManager(this);
		this.driveConnection = new RuntimeDriveConnection();
		this._connectDriveConnection();

		this._postProcessController = new PostProcessController(this._scene, this._viewController.camera);
	}

	public init(){
		// 在所有控制器创建完成后,建立各子系统之间的事件关系。
		this._connectViewControllerAndMouseController(this._mouseController, this._viewController);
		this._connectEnvControllerAndObj3dManager(this.sceneObj3dManager, this._envController);
		this._connectObj3dManagerAndMouseController(this.sceneObj3dManager, this._mouseController);
		this._connectObj3dManagerAndLayerController(this.sceneObj3dManager, this.layerController);

		this.onLoadedFileFailedObservable.addOnce(()=>{
			window.dispatchEvent(new CustomEvent('onLoadedFileFailed'));
		});
		this.onParseFileFailedObservable.addOnce(()=>{
			window.dispatchEvent(new CustomEvent('onParseFileFailed'));
		});
		this.sceneObj3dManager.onSceneLoadedObj3dObservable.addOnce(()=>{
			// 所有场景对象加载完成后通知宿主页面。
			window.dispatchEvent(new CustomEvent('onSceneLoaded'));
		});

		this.onRunSystemInitObservable.notifyObservers();
	}

	//#region 连接控制器
	private _connectViewControllerAndMouseController(mouseController:MouseController, viewController:ViewController):void{
		// 右键切换当前视角模式,具体切换策略由 ViewController 负责。
		mouseController.addListener(PointerEventTypes.POINTERTAP, viewController.setNextView);
	}

	private _connectEnvControllerAndObj3dManager(obj3dManager:SceneObj3dManager, envController:EnvController):void{
		const sun = envController.envLitController?.sunLight;

		if(sun){
			// 三维对象需要使用这个阴影生成器参与太阳光阴影。
			const sunShadowGenerator = new ShadowGenerator(2048, sun);
			sunShadowGenerator.bias = 0.0005;
			sunShadowGenerator.usePercentageCloserFiltering = true;
			obj3dManager.setShadowGenerator(sunShadowGenerator);
		}

		obj3dManager.onAddNodeInfoObservable.add((obj3dID:NodeBaseInfo)=>{
			if(!obj3dID)return;
			const node:TransformNode | null = obj3dID.owner;
			if(node){
				// 新增对象的 mesh 范围用于辅助更新太阳光照范围。
				const meshes:Mesh[] = node.getChildMeshes();
				envController.envLitController?.UpdateSunPositionByMeshes(meshes);
			}
		});
	}
	
	private _connectObj3dManagerAndMouseController(obj3DManager:SceneObj3dManager, mouseController:MouseController){
	
		const selNodeManager = obj3DManager.selNodeManager;
		if(!selNodeManager)return;

		mouseController.addListener(
			PointerEventTypes.POINTERTAP,
			(pointerInfo) => {
				if(!pointerInfo)return;
				const pickInfo = pointerInfo.pickInfo;
				if(!pickInfo)return;
				
				if(!pickInfo.hit){
					selNodeManager.setSelected(null);
					return;
				}

				const mesh = pickInfo.pickedMesh;
				if(!mesh){
					selNodeManager.setSelected(null);
					return;
				}

				// 选择状态由命中 mesh 上挂载的 SelectableBehavior 驱动。
				const selNode:SelectableBehavior | null = mesh.getBehaviorByName("SelectableBehavior") as SelectableBehavior;
				selNodeManager.setSelected(selNode);
			}
		);

		mouseController.addListener(
			PointerEventTypes.POINTERMOVE,
			(pointerInfo) => {
				if(!pointerInfo)return;

				const pickInfo = pointerInfo.pickInfo;
				if(!pickInfo)return;

				if(!pickInfo.hit){
					selNodeManager.setHovered(null);
					return;
				}

				const mesh = pickInfo.pickedMesh;
				if(!mesh){
					selNodeManager.setHovered(null);
					return;
				}

				// 悬停状态与选择状态使用相同的行为查找方式。
				const selNode:SelectableBehavior | null = mesh.getBehaviorByName("SelectableBehavior") as SelectableBehavior;
				selNodeManager.setHovered(selNode);
			}
		);
	}

	private _connectObj3dManagerAndLayerController(obj3DManager:SceneObj3dManager, layerController:LayerController){

		const selNodeManager = obj3DManager.selNodeManager;
		if(!selNodeManager)return;

		selNodeManager.onSelectedObservable.add((selNode:SelectableBehavior)=>{
			if(!selNode)return;

			// 通知宿主页面,并显示选中描边。
			window.dispatchEvent(new CustomEvent("nodeSelected", { detail: { id: selNode.uuid, name: selNode.followNodeNameInfo, state: true } }));

			const meshes:Mesh[] = selNode.getChildrenMeshes();
			meshes.forEach((mesh)=>{
				if(mesh){
					layerController.outlineController.addMeshSelected(mesh);
				}
			});
		});

		selNodeManager.onUnselectedObservable.add((selNode:SelectableBehavior)=>{
			if(!selNode)return;

			// 通知宿主页面,并移除选中描边。
			window.dispatchEvent(new CustomEvent("nodeSelected", { detail: { id: selNode.uuid, name: selNode.followNodeNameInfo, state: false } }));
			
			const meshes:Mesh[] = selNode.getChildrenMeshes();
			meshes.forEach((mesh)=>{
				if(mesh){
					layerController.outlineController.removeMeshSelected(mesh);
				}
			});
		});

		selNodeManager.onHoveredObservable.add((selNode:SelectableBehavior)=>{
			if(!selNode)return;
			if(selNode.isSelected())return;

			// 已选中的节点不再同时显示悬停描边。
			window.dispatchEvent(new CustomEvent("nodeHovered", { detail: { id: selNode.uuid, name: selNode.followNodeNameInfo, state: true } }));
			
			const meshes:Mesh[] = selNode.getChildrenMeshes();
			meshes.forEach((mesh)=>{
				if(mesh){
					layerController.outlineController.addMeshHovered(mesh);
				}
			});
		});

		selNodeManager.onUnhoveredObservable.add((selNode)=>{
			if(!selNode)return;
			if(selNode.isSelected())return;

			// 通知宿主页面,并移除悬停描边。
			window.dispatchEvent(new CustomEvent("nodeHovered", { detail: { id: selNode.uuid, name: selNode.followNodeNameInfo, state: false } }));
			
			const meshes:Mesh[] = selNode.getChildrenMeshes();
			meshes.forEach((mesh)=>{
				if(mesh){
					layerController.outlineController.removeMeshHovered(mesh);
				}
			});
		});
	}

	//#endregion

	/**
	 * 将运行时联网接入场景驱动。
	 *
	 * RuntimeDriveConnection 只负责联网和消息解析。
	 * SceneObj3dManager 和 DriveBehaviorManager 仍然负责实际的对象更新。
	 */
	private _connectDriveConnection(): void {
		this.driveConnection.onDriveDataObservable.add((driveDataItems) => {
			driveDataItems.forEach((item) => this.sceneObj3dManager.driveDriveBehavior(item));
		});

		this.driveConnection.onConnectionInfoChangedObservable.add((connectionInfo) => {
			this._notifyRuntimeConnectionChanged(connectionInfo);
		});

		// 服务器不支持在线修改 fieldnames,驱动 id 变化时可能需要重连。
		this._driveBehaviorsChangedObserver = this.sceneObj3dManager.driveBehavManager.onDriveBehaviorsChanged.add(() => {
			this.driveConnection.setDriveIds(this.sceneObj3dManager.driveBehavManager.driveIds);
		});
	}

	/** 将连接状态广播到当前窗口和父 frame。 */
	private _notifyRuntimeConnectionChanged(connectionInfo: RuntimeConnectionInfo): void {
		window.dispatchEvent(new CustomEvent("runtimeConnectionChanged", { detail: connectionInfo }));

		if(window.parent && window.parent !== window){
			window.parent.postMessage({
				type: "runtimeConnectionChanged",
				payload: connectionInfo,
			}, "*");
		}
	}

	//#region 运行时数据加载
	public onLoadedFileFailedObservable = new Observable<void>();

	/** 从外部 JSON 文件加载运行时配置。 */
	public setByFile(url:string):void{
		Tool.loadText(url)
			.then((json:string | null)=>this.setByJson(json))
			.catch((error:Error)=>{
				console.error("Failed to load runtime system from file:", url, error);
				this.onLoadedFileFailedObservable.notifyObservers();
			});
	}

	public onParseFileFailedObservable = new Observable<void>();

	/** 从 JSON 字符串应用运行时配置,常用于编辑器预览或 iframe 调用。 */
	public setByJson(json:string | null):void{
		if(!json)return;

		try{
			const runtimeInfo = JSON.parse(json) as DTO_RuntimeSystem;
			if (runtimeInfo) {
				const modules = runtimeInfo.globalSetting?.enabledRuntimeModules;
				if (modules?.includes(LERP_BEHAVIOR_MODULE_ID)) {
					void loadLerpRuntimeModule();
				} else {
					unloadLerpRuntimeModule();
				}
				this._set(runtimeInfo);
			}
		}
		catch(error){
			console.error("Failed to parse runtime system.", error);
			this.onParseFileFailedObservable.notifyObservers();
		}
	}

	private _set(runtimeSystem:DTO_RuntimeSystem | null){
		if(!runtimeSystem)return;

		// TODO: 后续支持重复加载时,需要在这里清理上一次的场景内容。
		const globalSetting = runtimeSystem.globalSetting;

		// fineness 越高,硬件缩放级别越低,画面越清晰但渲染开销越大。
		const hardwareScalingLevel = 1 / (1 + Scalar.Clamp((globalSetting.fineness - 1 ) * 0.25, 0, 5));
		const engine = this._scene.getEngine();
		engine.setHardwareScalingLevel(hardwareScalingLevel);

		const outlineSetting = globalSetting.outlineSetting;
		if(outlineSetting){
			this.layerController.outlineController.setColorActive(outlineSetting.colorActive);
			this.layerController.outlineController.setColorHover(outlineSetting.colorHover);
		}

		FollowNodeName.setByData(globalSetting.followNodeNameGlobalSetting);

		this._viewController.SetByData(runtimeSystem.viewSetting);

		this._envController.SetByData(runtimeSystem.environment);

		this.layerController.outlineController.setThicknessSelected(2 / hardwareScalingLevel);
		this.layerController.outlineController.setThicknessHovered(2 / hardwareScalingLevel);

		if(runtimeSystem.obj3dSetting){
			this.sceneObj3dManager.setByData(runtimeSystem.obj3dSetting);
			this.sceneObj3dManager.onSceneLoadedObj3dObservable.addOnce(()=>{
				if(globalSetting.connection){
					// 需要等对象和驱动行为都创建完成后,才能准确收集 driveIds。
					this.driveConnection.connect(
						globalSetting.connection.address, 
						this.sceneObj3dManager.driveBehavManager.driveIds
					);
				}
			});

			console.log("runSystem.obj3dSetting.listBehaviorNode = " + runtimeSystem.obj3dSetting.behavNodes);
		}
		else{
			console.log("runSystem.obj3dSetting = null");
		}
	}
	//#endregion

	//#region 运行时联网接口
	public connectRuntimeConnection(address?: string, driveIds?: string[]): void {
		this.driveConnection.connect(address, driveIds);
	}

	public disconnectRuntimeConnection(): void {
		this.driveConnection.disconnect();
	}

	public reconnectRuntimeConnection(): void {
		this.driveConnection.reconnect();
	}

	public setRuntimeConnectionAddress(address: string): void {
		this.driveConnection.setAddress(address);
	}

	public setRuntimeConnectionDriveIds(driveIds: string[]): void {
		this.driveConnection.setDriveIds(driveIds);
	}

	public getRuntimeConnectionInfo(): RuntimeConnectionInfo {
		return this.driveConnection.getConnectionInfo();
	}
	//#endregion

	public dispose():void{
		if(this._driveBehaviorsChangedObserver){
			this.sceneObj3dManager.driveBehavManager.onDriveBehaviorsChanged.remove(this._driveBehaviorsChangedObserver);
			this._driveBehaviorsChangedObserver = null;
		}
		this.driveConnection.dispose();

		this._mouseController.dispose();
		this.layerController.dispose();

		this._viewController.dispose();
		this._envController.dispose();
		this.sceneObj3dManager.dispose();

		this._postProcessController.dispose();
	}
}

PluginManager.vue

<template>
<div class="form-item-list">
  	<div class="form-item">
		<label>插值行为</label>
		<t-switch v-model="lerpBehaviorEnabledRef" @change="onLerpBehaviorSwitchChange" />
  	</div>
</div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { Observer } from '@babylonjs/core'
import {
	isLerpModuleEnabled,
	setLerpModuleEnabled,
	onLerpModuleEnabledChanged,
} from '../../TScripts/EditorSystem/ObjManager/Behaviours/LerpBehavior/LerpModuleGate'
import ConfirmDialog from '../../TScripts/utils/ConfirmDialog'

const lerpBehaviorEnabledRef = ref(false)

let enabledObserver: Observer<boolean> | null = null

const syncFromGate = () => {
	lerpBehaviorEnabledRef.value = isLerpModuleEnabled()
}

const onLerpBehaviorSwitchChange = (enabled: boolean) => {
	if (enabled) {
		setLerpModuleEnabled(true)
		return
	}

	lerpBehaviorEnabledRef.value = true

	ConfirmDialog.ShowConfirmDialog(
		'关闭插值行为',
		'关闭插值行为将隐藏相关界面与执行,但场景数据仍保留。下次打开包含插值数据的文件时将自动重新启用。',
		() => {
			setLerpModuleEnabled(false)
		},
	)
}

onMounted(() => {
	syncFromGate()
	enabledObserver = onLerpModuleEnabledChanged.add((enabled) => {
		lerpBehaviorEnabledRef.value = enabled
	})
})

onUnmounted(() => {
	if (enabledObserver) {
		onLerpModuleEnabledChanged.remove(enabledObserver)
		enabledObserver = null
	}
})
</script>

ObjectPropertiesPanel.vue

<template>
	<div class="object-properties-panel">
	  	<t-collapse :default-expand-all="true" :borderless="true">
			<t-collapse-panel header="基础属性">
				<ObjectBaseProperties class="properties-panel"/>
			</t-collapse-panel>

			<t-collapse-panel header="材质设置" v-if="nodeType === 'mesh'">
				<ObjectMatProperties class="properties-panel"/>
			</t-collapse-panel>
			
			<t-collapse-panel header="通用行为列表" v-if="nodeType === 'mesh' || nodeType === 'trNode'">
				<GeneralBehaviorList 
					v-if="nodeGeneralBehavManager"
					:behav-manager="nodeGeneralBehavManager"
				/>
			</t-collapse-panel>
			
			<t-collapse-panel header="实时数据驱动行为列表" v-if="nodeType === 'mesh' || nodeType === 'trNode'">
				<DriveBehaviorList
					v-if="nodeDriveBehavManager"
					:behav-manager="nodeDriveBehavManager"
				/>
			</t-collapse-panel>

			<t-collapse-panel header="插值行为列表" v-if="lerpModuleEnabled && (nodeType === 'mesh' || nodeType === 'trNode')">
				<LerpBehaviorList
					v-if="nodeLerpBehavManager"
					:lerp-behav-manager="nodeLerpBehavManager"
				/>
			</t-collapse-panel>
    	</t-collapse>
  	</div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, provide, ref } from 'vue';
import ObjectBaseProperties from './ObjectBaseProperties.vue';
import ObjectMatProperties from './ObjectMatProperties.vue';
import DriveBehaviorList from './DriveBehaviorProperties/DriveBehaviorList.vue';
import LerpBehaviorList from './LerpBehaviorProperties/LerpBehaviorList.vue';
import GeneralBehaviorList from './GeneralBehaviorProperties/GeneralBehaviorList.vue';
import EditorSystem from '../../TScripts/EditorSystem/EditorSystem';
import SelectableBehaviorEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/SelectableBehavior/SelectableBehaviorEditor';
import SelectableMeshEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/SelectableBehavior/SelectableMeshEditor';
import SelectableNodeEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/SelectableBehavior/SelectableNodeEditor';
import SelectableViewEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/SelectableBehavior/SelectableViewEditor';
import NodeGeneralBehavManagerEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/GeneralBehaviors/NodeGeneralBehavManagerEditor';
import NodeDriveBehavManagerEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/DriveBehaviors/NodeDriveBehavManagerEditor';
import LerpBehaviorManagerEditor from '../../TScripts/EditorSystem/ObjManager/Behaviours/LerpBehavior/LerpBehaviorManagerEditor';
import {
	isLerpModuleEnabled,
	onLerpModuleEnabledChanged,
} from '../../TScripts/EditorSystem/ObjManager/Behaviours/LerpBehavior/LerpModuleGate';
import { Observer } from '@babylonjs/core';

const objManager = EditorSystem.instance!.sceneObjManager;
provide("objManager", objManager);

type NodeType = 'trNode' | 'mesh' | 'view' | null;

const nodeType = ref<NodeType>(null);

// 通用的行为管理器获取函数
const getBehaviorManager = <T>(behaviorName: string) => {
	if (objManager.selectableManager.selectedSelBehavs.length !== 1) return null;
	const trNode = objManager.selectableManager.selectedSelBehavs[0].target;
	if (!trNode) {
		console.log("trNode is null");
		return null;
	}
	const behavManager = trNode.getBehaviorByName(behaviorName);
	if (!behavManager) {
		console.log(`${behaviorName} is null`);
		return null;
	}
	return behavManager as T;
};

let nodeGeneralBehavManager = computed<NodeGeneralBehavManagerEditor | null>(() =>
	getBehaviorManager<NodeGeneralBehavManagerEditor>('NodeGeneralBehavManagerEditor')
);

let nodeDriveBehavManager = computed<NodeDriveBehavManagerEditor | null>(() =>
	getBehaviorManager<NodeDriveBehavManagerEditor>('NodeDriveBehavManagerEditor')
);

// LerpBehaviorManagerEditor 是 scene-level 的,通过 sceneObjManager 直接访问
const lerpModuleEnabled = ref(isLerpModuleEnabled());
let nodeLerpBehavManager = computed<LerpBehaviorManagerEditor | null>(() =>
	lerpModuleEnabled.value ? objManager.lerpBehavManager : null
);

let lerpModuleEnabledObserver: Observer<boolean> | null = null;

/* ===== 局部变量 ===== */
let selectableBehav: SelectableBehaviorEditor;

const setRefBySelBehav = () => {
	if (!selectableBehav) return;
	
	if(selectableBehav instanceof SelectableMeshEditor){
		nodeType.value = 'mesh';
	}
	else if(selectableBehav instanceof SelectableNodeEditor){
		nodeType.value = 'trNode'
	}
	else if(selectableBehav instanceof SelectableViewEditor){
		nodeType.value = 'view'
	}
	else{
		nodeType.value = null;
	}
}

let setRefBySelBehavObserver:any = null;

/* ===== 生命周期 ===== */
onMounted(() => {
	lerpModuleEnabledObserver = onLerpModuleEnabledChanged.add((enabled) => {
		lerpModuleEnabled.value = enabled;
	});

	if (objManager.selectableManager.selectedSelBehavs.length !== 1) return;
	selectableBehav = objManager.selectableManager.selectedSelBehavs[0];
	setRefBySelBehav();
	setRefBySelBehavObserver = objManager.selectableManager.onSetSelectedBehavsObservable.add(setRefBySelBehav);
})

onUnmounted(() => {
	if (lerpModuleEnabledObserver) {
		onLerpModuleEnabledChanged.remove(lerpModuleEnabledObserver);
		lerpModuleEnabledObserver = null;
	}
	objManager.selectableManager.onSetSelectedBehavsObservable.remove(setRefBySelBehavObserver);
	setRefBySelBehavObserver = null;
})
</script>

<style scoped>
.object-properties-panel{
	overflow-y: auto;
}

.properties-panel {
	background-color: var(--td-base-color-bg03);
	padding: 16px 16px 16px 26px ;
}

.drive-behavior-list, .lerp-behavior-list{
	padding: 0 !important;
}
</style>

EditorApp.vue

<template>
<div id="app" 
	class="app-container dark-theme"
>
	<!-- Portal 目标元素,必须放在最前面 -->
	<div id="app-portal"></div>
	
	<LeftPanel ref="leftPanelRef" :visible="leftPanelVisible" @toggleLeftPanel="onToggleLeftPanel"	/>
	
	<div class="main-content">
		<TopToolbar ref="topToolbarRef" />
		<SceneOperation 
			ref="sceneOperationRef" 
			@onInitScene="onSceneOperationInitialized" 
		/>
		<BottomInfoBar ref="bottomInfoBarRef"/>
	</div>
	
	<RightPanel ref="rightPanelRef" :visible="rightPanelVisible" @toggleRightPanel="onToggleRightPanel" />

	<!-- Portal 组件 -->
    <MatEditorPortal />
	<LerpControllerDialogPortal v-if="lerpModuleEnabled" />
	<SaveFileDialogPortal />
	<ExpressionEditorPortal />
	<SceneSaveConfirmDialogPortal ref="saveConfirmDialogRef" />

</div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { MessagePlugin } from 'tdesign-vue-next'
import { Observer } from '@babylonjs/core'
import SceneOperation from './components/SceneOperation.vue'
import TopToolbar from './components/TopToolbar.vue'
import BottomInfoBar from './components/BottomInfoBar.vue'
import LeftPanel from './components/LeftPanel.vue'
import RightPanel from './components/RightPanel.vue'
import MatEditorPortal from './components/MaterialEditorDialog/MatEditorPortal.vue'
import LerpControllerDialogPortal from './components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpControllerCom/LerpControllerDialog/LerpControllerDialogPortal.vue'
import SaveFileDialogPortal from './components/SaveFileDialog/SaveFileDialogPortal.vue'
import ExpressionEditorPortal from './components/ExpressionEditorDialog/ExpressionEditorPortal.vue'
import SceneSaveConfirmDialogPortal from './components/Dialog/SceneSaveConfirmDialogPortal.vue'
import { emitter, sceneOperationEmitter } from './TScripts/utils/EventBus'
import EditorSystem from './TScripts/EditorSystem/EditorSystem'
import {
	isLerpModuleEnabled,
	onLerpModuleEnabledChanged,
} from './TScripts/EditorSystem/ObjManager/Behaviours/LerpBehavior/LerpModuleGate'

const lerpModuleEnabled = ref(isLerpModuleEnabled())
let lerpModuleEnabledObserver: Observer<boolean> | null = null

const topToolbarRef = ref();
const bottomInfoBarRef = ref();
const leftPanelRef = ref();
const rightPanelRef = ref();
const saveConfirmDialogRef = ref<InstanceType<typeof SceneSaveConfirmDialogPortal> | null>(null);
const leftPanelVisible = ref(true)
const rightPanelVisible = ref(true)
const sceneOperationRef = ref<InstanceType<typeof SceneOperation> | null>(null)

// 处理保存编辑文件
const handleSaveEditingFile = () => {
	const editorSystem = EditorSystem.instance;
	if (!editorSystem) {
		MessagePlugin.error('编辑器系统未初始化');
		return;
	}

	// 检查是否有上次保存的路径
	if (editorSystem.hasLastEditingFileSavePath()) {
		// 直接使用上次的路径保存
		const lastPath = editorSystem.getLastEditingFileSavePath()!;
		// 统一将反斜杠替换为正斜杠,然后处理
		const normalizedPath = lastPath.replace(/\\/g, '/').replace(/^\.\//, '');
		const pathParts = normalizedPath.split('/');
		const fileName = pathParts.pop()!;
		const dirPath = '/' + pathParts.join('/');
		
		// 直接调用 API 保存
		quickSave(dirPath, fileName);
	} else {
		// 打开保存对话框
		emitter.emit('openSaveDialog');
	}
};

// 快速保存(使用已有路径)
const quickSave = async (dirPath: string, fileName: string) => {
	const editorSystem = EditorSystem.instance;
	if (!editorSystem) return;

	try {
		const content = JSON.stringify(editorSystem.getDataEditor());
		
		const response = await fetch('file_manager.php', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				action: 'save-text-file',
				path: dirPath,
				fileName: fileName,
				content: content
			})
		});

		const result = await response.json();
		
		if (result.status === 'success') {
			MessagePlugin.success('文件保存成功');
		} else {
			MessagePlugin.error(result.message || '文件保存失败');
		}
	} catch (error) {
		MessagePlugin.error('文件保存失败');
		console.error(error);
	}
};

// 处理另存编辑文件
const handleSaveEditingFileAs = () => {
	emitter.emit('openSaveAsDialog');
};

// 处理导出运行文件
const handleExportRuntimeFile = () => {
	emitter.emit('openExportDialog');
};

// 处理新建文件
const handleNewFile = () => {
	const editorSystem = EditorSystem.instance;
	if (!editorSystem) {
		MessagePlugin.error('编辑器系统未初始化');
		return;
	}

	// 判断场景是否被编辑
	if (!editorSystem.isSceneModified()) {
		// 未编辑:直接 reset 为空白场景
		editorSystem.reset();
		return;
	}

	// 已编辑:弹出三按钮确认对话框
	if (saveConfirmDialogRef.value) {
		saveConfirmDialogRef.value.openDialog(
			'场景已修改',
			'是否保存对场景的修改?',
			// 保存按钮回调
			() => {
				if (editorSystem.hasLastEditingFileSavePath()) {
					// 有保存路径:直接覆盖保存
					const lastPath = editorSystem.getLastEditingFileSavePath()!;
					// 统一将反斜杠替换为正斜杠,然后处理
					const normalizedPath = lastPath.replace(/\\/g, '/').replace(/^\.\//, '');
					const pathParts = normalizedPath.split('/');
					const fileName = pathParts.pop()!;
					const dirPath = '/' + pathParts.join('/');
					quickSaveAndReset(dirPath, fileName);
				} else {
					// 无保存路径:弹出保存对话框
					emitter.emit('openSaveDialog', {
						onConfirm: () => {
							// 保存对话框确认后 reset 为空白场景
							editorSystem.reset();
						},
						onCancel: () => {
							// 取消保存,当作无事发生
						}
					});
				}
			},
			// 不保存按钮回调
			() => {
				editorSystem.reset();
			},
			// 取消按钮回调
			() => {
				// 无事发生
			}
		);
	}
};

// 处理拖拽加载 .e3d 前的保存确认
const handleAskLoadSceneWithSaveConfirm = (data: { filePath: string; onConfirm: () => void }) => {
	const editorSystem = EditorSystem.instance;
	if (!editorSystem) {
		MessagePlugin.error('编辑器系统未初始化');
		return;
	}

	// 判断场景是否被编辑
	if (!editorSystem.isSceneModified()) {
		// 未编辑:直接确认加载
		data.onConfirm();
		return;
	}

	// 已编辑:弹出三按钮确认对话框
	if (saveConfirmDialogRef.value) {
		saveConfirmDialogRef.value.openDialog(
			'场景已修改',
			'是否保存对场景的修改?',
			// 保存按钮回调
			() => {
				if (editorSystem.hasLastEditingFileSavePath()) {
					// 有保存路径:直接覆盖保存,然后加载新场景
					const lastPath = editorSystem.getLastEditingFileSavePath()!;
					const normalizedPath = lastPath.replace(/\\/g, '/').replace(/^\.\//, '');
					const pathParts = normalizedPath.split('/');
					const fileName = pathParts.pop()!;
					const dirPath = '/' + pathParts.join('/');
					quickSaveAndLoadScene(dirPath, fileName, data.filePath);
				} else {
					// 无保存路径:弹出保存对话框
					emitter.emit('openSaveDialog', {
						onConfirm: () => {
							// 保存对话框确认后加载新场景
							data.onConfirm();
						},
						onCancel: () => {
							// 取消保存,当作无事发生
						}
					});
				}
			},
			// 不保存按钮回调
			() => {
				// 不保存,直接加载新场景
				data.onConfirm();
			},
			// 取消按钮回调
			() => {
				// 无事发生
			}
		);
	}
};

// 快速保存并加载新场景(不刷新页面)
const quickSaveAndLoadScene = async (dirPath: string, fileName: string, loadFilePath: string) => {
	const editorSystem = EditorSystem.instance;
	if (!editorSystem) {
		MessagePlugin.error('编辑器系统未初始化');
		return;
	}

	try {
		let content: string;
		try {
			content = JSON.stringify(editorSystem.getDataEditor());
		} catch (e) {
			console.error('获取场景数据失败:', e);
			MessagePlugin.error('获取场景数据失败');
			return;
		}

		// 使用 PHP API 保存文件
		const response = await fetch('file_manager.php', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				action: 'save-text-file',
				path: dirPath,
				fileName: fileName,
				content: content
			})
		});

		const result = await response.json();

		if (result.status === 'success') {
			// 保存成功后,加载新场景
			editorSystem.setByFile(loadFilePath).then(() => {
				// 更新场景后通知 UI
				emitter.emit('sceneLoaded');
			});
		} else {
			console.warn('文件保存失败:', result.message);
		}
	} catch (error) {
		console.error('保存过程中出错:', error);
		MessagePlugin.error('保存失败');
	}
};

// 快速保存并 reset 为空白场景
const quickSaveAndReset = async (dirPath: string, fileName: string) => {
	const editorSystem = EditorSystem.instance;
	if (!editorSystem) {
		MessagePlugin.error('编辑器系统未初始化');
		return;
	}

	try {
		let content: string;
		try {
			content = JSON.stringify(editorSystem.getDataEditor());
		} catch (e) {
			console.error('获取场景数据失败:', e);
			MessagePlugin.error('获取场景数据失败');
			return;
		}

		const response = await fetch('file_manager.php', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				action: 'save-text-file',
				path: dirPath,
				fileName: fileName,
				content: content
			})
		});

		const result = await response.json();

		if (result.status === 'success') {
			MessagePlugin.success('文件保存成功');
			// 保存成功后 reset 为空白场景
			editorSystem.reset();
		} else {
			MessagePlugin.error(result.message || '文件保存失败');
		}
	} catch (error) {
		console.error('保存失败:', error);
		MessagePlugin.error('文件保存失败');
	}
};

// 激活运行页:打开 cc_runtime.html,收到其 "init" 消息后向其发送带字符串的方法调用
// Ctrl 按下时必定新开窗口;未按 Ctrl 时若已打开则刷新并重发数据,否则新开
let runtimeWindow: Window | null = null;
const handleRuntimePreview = () => {
	const runtimeUrl = new URL('cc_runtime.html', window.location.href).href;

	const setupInitListener = (win: Window) => {
		const onMessage = (event: MessageEvent) => {
			if (event.source !== win || event.data?.type !== 'runtimeInitialized') return;
			window.removeEventListener('message', onMessage);
			const editorSystem = EditorSystem.instance;
			if (!editorSystem) {
				MessagePlugin.error('编辑器系统未完成初始化,无法发送数据');
				return;
			}
			const jsonString = JSON.stringify(editorSystem.getDataRuntime());
			win.postMessage(
				{ type: 'callMethod', methodName: 'setRuntimeByJson', args: [jsonString] },
				'*'
			);
		};
		window.addEventListener('message', onMessage);
	};

	if (!isCtrlPressed && runtimeWindow && !runtimeWindow.closed) {
		setupInitListener(runtimeWindow);
		runtimeWindow.location.reload();
		runtimeWindow.focus();
		return;
	}

	runtimeWindow = window.open(runtimeUrl);
	if (!runtimeWindow) {
		MessagePlugin.warning('请允许弹窗以打开运行页');
		return;
	}

	setupInitListener(runtimeWindow);

	runtimeWindow.focus();
};

let contextMenuEnabled = false
onMounted(() => {
	lerpModuleEnabledObserver = onLerpModuleEnabledChanged.add((enabled) => {
		lerpModuleEnabled.value = enabled
	})

	addFunctionKeyStateListener()

	if(contextMenuEnabled){
		document.addEventListener('contextmenu', (e) => {
			e.preventDefault();
			return false;
		});
	}

	// 监听保存/另存/导出/新建事件
	emitter.on('saveEditingFile', handleSaveEditingFile);
	emitter.on('saveEditingFileAs', handleSaveEditingFileAs);
	emitter.on('exportRuntimeFile', handleExportRuntimeFile);
	emitter.on('runtimePreview', handleRuntimePreview);
	emitter.on('newFile', handleNewFile);

	// 监听拖拽加载 .e3d 前的保存确认
	sceneOperationEmitter.on('askLoadSceneWithSaveConfirm', handleAskLoadSceneWithSaveConfirm);
})

onUnmounted(() => {
	if (lerpModuleEnabledObserver) {
		onLerpModuleEnabledChanged.remove(lerpModuleEnabledObserver)
		lerpModuleEnabledObserver = null
	}
	removeFunctionKeyStateListener();

	// 清理事件监听
	emitter.off('saveEditingFile', handleSaveEditingFile);
	emitter.off('saveEditingFileAs', handleSaveEditingFileAs);
	emitter.off('exportRuntimeFile', handleExportRuntimeFile);
	emitter.off('runtimePreview', handleRuntimePreview);
	emitter.off('newFile', handleNewFile);
	sceneOperationEmitter.off('askLoadSceneWithSaveConfirm', handleAskLoadSceneWithSaveConfirm);
})

// 功能键状态
let isCtrlPressed = false;
const addFunctionKeyStateListener = () => {
	document.addEventListener('keydown', (e) => {
		if(e.key === 'Control') {
			isCtrlPressed = true;
		}
	});
	document.addEventListener('keyup', (e) => {
		if(e.key === 'Control') {
			isCtrlPressed = false;
		}
	});
};

const removeFunctionKeyStateListener = () => {
	document.removeEventListener('keydown', (e) => {
		if(e.key === 'Control') {
			isCtrlPressed = false;
		}
	});
	document.removeEventListener('keyup', (e) => {
		if(e.key === 'Control') {
			isCtrlPressed = false;
		}
	});
};

const onSceneOperationInitialized = () => {
	topToolbarRef.value.init()
	bottomInfoBarRef.value.init()
	leftPanelRef.value.init()
	rightPanelRef.value.init()
}


const onToggleLeftPanel = () => {
	leftPanelVisible.value = !leftPanelVisible.value
	updateCanvasSize()
}

const onToggleRightPanel = () => {
	rightPanelVisible.value = !rightPanelVisible.value
	updateCanvasSize()
}

const updateCanvasSize = () => {
	nextTick(() => {
		sceneOperationRef.value?.ResizeEngine()
	})
}

</script>

<style scoped>
.app-container {
	display: flex;
	height: 100vh;
	background-color: var(--td-bg-color-container);
	color: #e0e0e0;
}

.main-content {
	display: flex;
	flex-direction: column;
	flex: 1;
	overflow: hidden;
	margin: 0 16px;
}

:deep(.t-collapse-panel__content){
	padding: 0 !important;
}

:deep(.sub-form) {
	padding-left: 20px;
	border-left: 1px solid var(--td-base-color-border01);
}

:deep(.form-item-list-padding-r0){
	display: flex;
	flex-direction: column;
	padding: 12px 0px 16px 22px;
	gap: 16px;
}

:deep(.form-item-list){
	display: flex;
	flex-direction: column;
	gap: 16px;
}

:deep(.form-item) {
	display: flex;
	align-items: center;
	justify-content: left;
	padding: 0;
	gap: 12px;
}

:deep(.form-item span),
:deep(.form-item label) {
	color:var(--td-text-color-primary);
	flex: 0 0 auto !important; /* 不放大、不缩小、自动宽度 */
  	white-space: nowrap;       /* 强制不换行 */
}

:deep(.form-item-space-between) {
	display: flex;
	align-items: center;
	justify-content: space-between;
}

/* 能产生垂直滚动的面板 */
:deep(.vertical-scroll-panel-container) {
	height: 100%;
	display: flex;
	flex-direction: column;
}

:deep(.vertical-scroll-panel) {
	height: 100%;
	overflow-y: auto;
}

:deep(.vertical-scroll-panel::-webkit-scrollbar) {
	width: 8px;
}

:deep(.vertical-scroll-panel::-webkit-scrollbar-track) {
	background: var(--td-base-color-bg01);
	border-radius: 4px;
}

:deep(.vertical-scroll-panel::-webkit-scrollbar-thumb) {
	background: var(--td-base-color-bg08);
	border-radius: 4px;
}

:deep(.vertical-scroll-panel::-webkit-scrollbar-thumb:hover) {
	background: var(--td-base-color-bg10);
}

/* tdesign-color-picker样式 */
:deep(.t-color-picker__trigger .t-input__wrap){
	background-color: transparent !important;
	border: none;
}

:deep(.t-color-picker__trigger .t-input__wrap .t-input){
	background-color: transparent !important;
	border: none;
	padding: 0;
}

/* tdesign-input样式 */
:deep(.t-input){
	height: 26px !important;
	color: white !important;
}

:deep(.t-input.t-is-disabled){
	color: var(--td-base-color-bg10) !important;
	background-color: var(--td-base-color-bg08) !important;
}

:deep(.t-input-number__decrease),
:deep(.t-input-number__increase){
	display: none !important;
}

:deep(.t-input-number){
	padding: 0 !important;
}

:deep(.t-input-number .t-input__wrap){
	margin: 0 !important;
}

:deep(.t-input-number .t-input__wrap .t-input .t-input__inner){
	text-align: left !important;
}

/* 暗色主题样式 */
.dark-theme {
	--td-brand-color01: #2e5cd8;
	--td-brand-color02: #0a773b;
	--td-bg-color-container: #2f2f2f;
	--td-bg-color-component: #444444;
	--td-text-color-primary: #e0e0e0;
	--td-text-color-secondary: #b0b0b0;
	/* 新定义组件变量 */
	--td-bg-color-input: #222222;
	--td-base-color-hover: #3f3f3f;
	--td-base-color-active: #eeffff;
	--td-base-color-border01: #dddddd;
	--td-base-color-border02: #bbbbbb;
	--td-base-color-border03: #999999;
	--td-base-color-border04: #777777;
	--td-base-color-border05: #555555;
	--td-base-color-border06: #333333;
	/**/
	--td-base-color-bg01: #000000;
	--td-base-color-bg02: #111111;
	--td-base-color-bg03: #222222;
	--td-base-color-bg04: #333333;
	--td-base-color-bg05: #444444;
	--td-base-color-bg06: #555555;
	--td-base-color-bg07: #666666;
	--td-base-color-bg08: #777777;
	--td-base-color-bg09: #888888;
	--td-base-color-bg10: #999999;
	--td-base-color-bg11: #aaaaaa;
	--td-base-color-bg12: #bbbbbb;
	--td-base-color-bg13: #cccccc;
	--td-base-color-bg14: #dddddd;
	--td-base-color-bg15: #eeeeee;
	--td-base-color-bg16: #ffffff;
	/* track color */
	--track-dark-red: #332222;
	--track-dark-green: #1a1f1a;
	--track-dark-blue: #222233;
	--track-dark-yellow: #333322;
	--track-dark-purple: #332233;
	--track-dark-lightblue: #223333;
	--track-dark-gray: #2a2a2a;
}
</style>

<style>
div,
span,
label{
	font-size: 15px;
	color: var(--td-text-color-primary);
}
*,
*::before,
*::after {
    transition: none !important;
    transition-duration: 0s !important;
    transition-delay: 0s !important;
}
</style>

<!-- 在TDesign中,一般弹出类型的组件往往无法穿透,使用不带scope的样式 -->
<style>
/* 可拖拽的非模态对话框t-dialog样式 -------------------------------------------------------------------------------------------*/
.t-dialog__wrap{
	z-index: 1000 !important;
	pointer-events: auto !important;
}

.drag-modeles-dialog{
	z-index: 2000;
}

.drag-modeles-dialog .t-dialog__wrap .t-dialog__position .t-dialog--default{
	display: flex;
	flex-direction: column;
	padding: 2px;
}

.drag-modeles-dialog .t-dialog__wrap .t-dialog__position .t-dialog--default .t-dialog__body{
	padding: 0px;
}
.drag-modeles-dialog .t-dialog__wrap .t-dialog__position .t-dialog--default .t-dialog__header{
	padding: 8px;
	pointer-events: none;	/* 避免header拦截拖拽对话框 */
}

.drag-modeles-dialog .t-dialog__wrap .t-dialog__position .t-dialog--default .t-dialog__header .t-dialog__close{
	pointer-events: auto;	/* 保证关闭按钮可用 */
}

.drag-modeles-dialog .t-dialog__wrap .t-dialog__position .t-dialog--default .t-dialog__footer{
	padding: 0px;
}

.drag-modeles-dialog .t-dialog__wrap .t-dialog__position .t-dialog--default .t-dialog__footer div{
	display: flex;
	padding: 8px 18px;
	justify-content: right;
	gap:8px
}


/* 颜色拾取器t-color-picker的弹出面板popup样式----------------------------------------------------------------------------------- */
.t-color-picker__format--item .input-group .input-group__item .t-input__wrap .t-input{
	background-color:#222 !important;
}

.t-color-picker__format-mode-select .t-select-input .t-input__wrap .t-input{
	background-color: #222  !important;
}
</style>

<!-- input number样式 -->
<style scoped>
:deep(.t-input__inner){
	color: rgb(255, 255, 255);
}
	
:deep(.t-input__wrap){
	background-color: var(--td-base-color-bg02) !important;
	border: 1px solid grey;
	border-radius: 6px;
}

:deep(.t-input__wrap .t-input){
	height: 24px !important;
	background: transparent !important;
	border: none;
}

:deep(.t-input__wrap .t-input--focused){
	background: transparent !important;
	border: 1px solid var(--td-base-color-border03) !important;
	box-shadow: none !important;
}
</style>

<!-- 自定义对话框样式 -->
<style>
	/* 遮罩层样式 */
	.custom-dialog-mask {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background-color: transparent;
		z-index: 10;
		display: flex;
		justify-content: center;
		align-items: center;
		pointer-events: none;
	}

	/* 对话框容器样式 */
	.custom-dialog-container {
		position: absolute;
		background-color: #1a1a1a;
		border: 1px solid var(--td-base-color-border04);
		border-radius: 4px;
		box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
		overflow: hidden;
		display: flex;
		flex-direction: column;
		pointer-events: auto;
	}

	.custom-dialog-body {
		flex: 1;
		overflow: hidden;
	}

	/* 对话框头部样式 */
	.custom-dialog-header {
		height: 40px;
		background-color: #2d2d2d;
		border-bottom: 1px solid #444;
		color: #e0e0e0;
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 0 6px;
		cursor: move;
		user-select: none;
		flex-shrink: 0;
	}

	.custom-dialog-title {
		padding-left: 6px;
		font-size: 16px;
		font-weight: 500;
	}

	.custom-dialog-target-name {
		margin-left: 3px;
		font-size: 14px;
		color: #b0b0b0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.custom-dialog-header .show-content-btns {
		display: flex;
		align-items: center;
		gap: 8px;
	}

	.custom-dialog-header .close-btn {
		background: none;
		border: none;
		color: #e0e0e0;
		font-size: 20px;
		width: 28px;
		height: 28px;
		border-radius: 2px;
		cursor: pointer;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.custom-dialog-header .close-btn:hover {
		background-color: #444;
	}

	.custom-dialog-header .show-content-btns .show-area-btn {
		width: 26px;
		height: 26px;
		padding: 0;
		display: flex;
		align-items: center;
		justify-content: center;
		border: none;
		background-color: transparent;
		color: #e0e0e0;
	}

	.custom-dialog-header .show-content-btns .show-area-btn:hover {
		background-color: #3a3a3a;
		border-color: #666;
	}

	.custom-dialog-header .show-content-btns .show-area-btn.active {
		background-color: #1890ff;
		color: white;
	}

	.custom-dialog-content-container {
		display: flex;
		flex-direction: column;
		height: 100%;
		background-color: #1a1a1a;
		color: #e0e0e0;
	}

	/* tdesign-tree样式 对话框中的目录树设置,没有使用!important,允许特例覆盖 */
	.t-tree__item{
		height: 22px;
	}
</style>

<!-- tdesign-select下拉菜单样式 ----->
<style>
	/* 悬停样式 */
	.t-select-option.t-select-option__hover:not(.t-is-disabled).t-select-option.t-select-option__hover:not(.t-is-selected){
		background-color: #535353 !important;
	}
	/* 选中样式 */
	.t-select-option.t-is-selected:not(.t-is-disabled){
		color: #fff !important;
		background-color: #1890ff !important;
	}
</style>

SceneObj3dManager.ts

import { DTO_Obj3dInfo, DTO_Obj3dSetting, DTO_Obj3dResource, DTO_CipSelInfo, DTO_BehaviorNode, DTO_BehaviorMesh, DTO_CipEnabled } from "../../../Shared/TScripts/DTO/DTO_RuntimeSystem";
import { Scene, ShadowGenerator, AssetContainer, TransformNode, Vector3, Mesh, Observable} from "@babylonjs/core";
import { AssetContainerManager } from "./AssetContainerManager";
import { RootNodeInfo } from "./Behaviors/RootNodeInfo";
import { ChildIndexPath } from "../../../Shared/TScripts/GeneralClass";
import { DriveBehaviorManager } from "./DriveBehaviorManager";
import { MaterialManager } from "./MaterialManager";
import { ShadowManager } from "./ShadowManager";
import { SelectableBehaviorManager } from "./SelectableBahaviorManager";
import { GeneralBehavManager } from "./GeneralBehavManager";
import { LerpBehaviorManager, applyLerpBehaviorsIfAvailable } from "./LerpBehaviorManager";
import SelectableBehavior from "./Behaviors/SelectableBehavior";
import { FollowNodeUIManager } from "./FollowNodeUI/FollowNodeUIManager";
import { DriveData, Tran, type Cip } from "../../../Shared/TScripts/DTO/DTO_BaseClass";
import NodeBaseInfo from "./Behaviors/NodeBaseInfo";
import type { RuntimeSystem } from "../RuntimeSystem";
import { MatConverter } from "../../../Shared/TScripts/MatConverter";
import type { DriveBehavior } from "./Behaviors/DriveBehavior/DriveBehavior";

/**
 * 3D对象管理者
 * 
 * 该类是运行时系统3D对象管理的核心类,负责统一管理场景中所3D对象的生命周期和行为。
 * 
 * 主要职责:
 * 1. 3D资源加载:从配置数据异步加载3D模型资源,支持多个资源并发加载
 * 2. 对象实例化:根据配置信息克隆和实例化3D对象节点
 * 3. 节点配置:设置节点的变换(位置、旋转、缩放)、材质、可拾取性等属性
 * 4. 行为管理:管理节点的驱动行为(DriveBehavior)和通用行为(GeneralBehavior)
 * 5. 选择管理:管理场景中可被选择的节点(SelNode)
 * 6. 视觉效果:管理材质、阴影、发光、透明度等视觉效果
 * 7. UI管理:管理节点关联的UI图标显示
 * 8. 事件通知:通过Observable提供场景加载、对象添加、行为变化等事件通知
 * 
 * 集成的子管理器:
 * - MaterialManager: 材质管理,处理PBR材质的创建和应用
 * - SelNodeManager: 可选择节点管理,维护所有可被用户选择的节点
 * - NodeUIManager: 节点UI管理,管理节点关联的图标显示
 * - DriveBehavManager: 驱动行为管理,处理节点的动态驱动行为
 * - GeneralBehavManager: 通用行为管理,处理节点的各种通用行为组件
 * - ShadowManager: 阴影管理,配置节点的投影和接收阴影
 * - AssetContainerManager: 资源容器管理,负责异步加载3D模型文件
 * 
 * 关键流程:
 * 1. setByData() 接收配置数据,初始化图标、材质、行为节点等
 * 2. _loadAndSetObj3d() 异步加载所有3D资源文件
 * 3. _processByObj3dInfo() 处理每个加载完成的资源,进行克隆和配置
 * 4. 各种set方法配置节点的材质、行为、选择性、阴影等属性
 * 5. onSceneLoaded 在所有资源加载完成后触发
 * 
 * 使用示例:
 * ```typescript
 * const obj3dManager = new Obj3dManager(runtimeSystem);
 * obj3dManager.setByData(obj3dSettingData);
 * obj3dManager.onSceneLoaded.add(() => {
 *   console.log("所有3D对象加载完成");
 * });
 * ```
 */
export class SceneObj3dManager {

	//#region 构造函数----------------------------------------------------------------------------------------------------
	private readonly runtimeSystem:RuntimeSystem;

	public readonly scene:Scene;

	public readonly materialManager:MaterialManager;

	public readonly selNodeManager:SelectableBehaviorManager = new SelectableBehaviorManager();

	public readonly nodeUIManager:FollowNodeUIManager;

	public readonly driveBehavManager:DriveBehaviorManager;

	public readonly generalBehavManager:GeneralBehavManager;

	public readonly lerpBehavManager:LerpBehaviorManager;

	public onDriveBehavsChanged:Observable<DriveBehavior[]> = new Observable<DriveBehavior[]>();

	constructor(RuntimeSystem:RuntimeSystem) {
		this.runtimeSystem = RuntimeSystem;
		this.scene = this.runtimeSystem.scene;

		this.materialManager = new MaterialManager(this.scene);
		//
		this.nodeUIManager = new FollowNodeUIManager(this.scene, this.selNodeManager);

		this.driveBehavManager = new DriveBehaviorManager(this);
		this.driveBehavManager.onDriveBehavsChanged.add(
			(driveBehavs:DriveBehavior[]) => {
				this.onDriveBehavsChanged.notifyObservers(driveBehavs);
			} 
		);

		this.generalBehavManager = new GeneralBehavManager(this);

		this.lerpBehavManager = new LerpBehaviorManager(this);
	}
	//#endregion

	//#region RootObj3D列表
	private _nodeBaseInfos:NodeBaseInfo[] = [];
	public getNodeBaseInfo(id:string):NodeBaseInfo | null{
		this._nodeBaseInfos.forEach((obj3dID)=>{
			if(obj3dID.uuid == id)return obj3dID;
		});
		return null;
	}

	public includeNodeBaseInfo(id:string):boolean{
		return this.getNodeBaseInfo(id)!= null;
	}
	//#endregion

	private _shadowManager:ShadowManager | undefined = undefined;
	public setShadowGenerator(shadowGenerator:ShadowGenerator | undefined):void{
		if(shadowGenerator){
			this._shadowManager = new ShadowManager(shadowGenerator);
		}
	}

	public setByData(obj3dSetting:DTO_Obj3dSetting):void{
		//		
		this.materialManager.setPBRMetallicRoughnessMaterials(obj3dSetting.materials || []);

		this._setBehaviorNodes(obj3dSetting.behavNodes || []);

		this._setBehaviorMeshes(obj3dSetting.behavMeshes || []);

		this._loadAndSetObj3d(obj3dSetting.obj3DResources || []);
	}

	private loudingCount:number = 0;
	private failedCount:number = 0;
	public readonly onSceneLoadedObj3dObservable = new Observable<void>();
	private _loadAndSetObj3d(obj3dResources:DTO_Obj3dResource[]):void{
		if(obj3dResources){
			const assetContainerManager = new AssetContainerManager();
			obj3dResources.forEach((obj3DResource) => {
				if(obj3DResource){
					if(obj3DResource.urlCip){

						this.loudingCount++;
						console.log("loudingCount++ = " + this.loudingCount);

						const url = obj3DResource.urlCip.url;
						assetContainerManager.loadAssetContainer(this.scene, url)
						.then((container) => {
							//首先将contianer内所有的材质转换成PBRMetallicRoughnessMaterial,这是有必要的吗??
							MatConverter.ConvertAssetContainerMaterial(container, this.scene);
							//根据obj3DResource信息对container的内容进行克隆和设置
							this._processByObj3dInfo(container, obj3DResource);
						})
						.catch((error) => {
							//捕获加载或处理过程中的错误,避免影响其他资源的加载
							console.error(`Failed to load 3D resource from ${url}:`, error);
							this.failedCount++;
							console.log("failed loaded count = " + this.failedCount);
						})
						.finally(() => {
							//无论成功还是失败,都要递减计数
							this.loudingCount--;
							console.log("loudingCount-- = " + this.loudingCount);
							if(this.loudingCount == 0){
								//所有资源加载完成(无论成功或失败)
								assetContainerManager.dispose();
								this.onSceneLoadedObj3dObservable.notifyObservers();
							}
						});
					}
				}
			});
		}
	}

	public readonly onAddNodeInfoObservable = new Observable<NodeBaseInfo>();
 
	private _processByObj3dInfo(container:AssetContainer, obj3DResource:DTO_Obj3dResource):void
	{
		const urlCip = obj3DResource.urlCip;
		if(!urlCip){ 
			console.log("obj3DResource.urlCip does not exist."); 
			return; 
		}

		const cip = urlCip.cip;
		if(!cip){ 
			console.log("urlCip.cip does not exist."); 
			return; 
		}

		const childIndexPath:ChildIndexPath = new ChildIndexPath(cip.ids);
		if(!childIndexPath){ 
			console.log("childIndexPath does not exist.."); 
			return; 
		}

		const rootNodes = container.rootNodes;
		console.log("rootNodes.length = " + rootNodes.length + "; rootNodes[0].name = " + rootNodes[0].name);
		const trNode:TransformNode = childIndexPath.getNodeFromeChildren(rootNodes) as TransformNode;
		
		if(!trNode){ 
			console.log("trNode does not exist."); 
			return; 
		}

		const obj3dInfo:DTO_Obj3dInfo = obj3DResource.obj3dInfo;

		const nodeClone = trNode.clone(obj3dInfo.tranNode?.name || '', null, false);
		if(!nodeClone){
			console.log("nodeClone does not exist.");
			return;
		}

		// 添加RootNodeInfo行为,用于记录该对象的urlCip和id
		const rootObj3d = new RootNodeInfo(urlCip, obj3dInfo.tranNode?.uuid || '');
		nodeClone.addBehavior(rootObj3d);
		rootObj3d.attach(nodeClone);
		this._nodeBaseInfos.push(rootObj3d);
		this.onAddNodeInfoObservable.notifyObservers(rootObj3d);
		//
		this._setNodeTran(nodeClone, obj3dInfo.tranNode?.tran || new Tran());
		//
		this.materialManager.setObj3dMats(nodeClone, obj3dInfo.materials || []);
	
		this._addSelNodes(nodeClone, obj3dInfo.selInfos || []);

		console.log("setGeneralBehavsByInfos: obj3dInfo.generalBehavInfos.length = " + obj3dInfo.generalBehavInfos?.length);
		this.generalBehavManager.setGeneralBehavsByInfos(nodeClone, obj3dInfo.generalBehavInfos || []);

		console.log("setDriveBehavsByInfos: obj3dInfo.DriveBehavInfos.length = " + obj3dInfo.driveBehavInfos?.length);
		this.driveBehavManager.setDriveBehavsByInfos(nodeClone, obj3dInfo.driveBehavInfos || []);

		console.log("setLerpBehavsByInfos: obj3dInfo.lerpBehavInfos.length = " + obj3dInfo.lerpBehavInfos?.length);
		applyLerpBehaviorsIfAvailable(this.lerpBehavManager, nodeClone, obj3dInfo.lerpBehavInfos || []);

		this._setLoadedObj3dPickable(nodeClone, obj3dInfo.pickables || []);

		this._setMeshesGlow(nodeClone, obj3dInfo.glowMeshes || []);

		this._shadowManager?.InitShadow(nodeClone, obj3dInfo.castShadows || [], obj3dInfo.recieveShadows || []);
		
		if(!this.scene.transformNodes.includes(nodeClone)){
			this.scene.addTransformNode(nodeClone);
		}
	}

	private _addSelNodes(root:TransformNode, listDTO_SelNode:DTO_CipSelInfo[]){
		if(!root)return;
		if(!listDTO_SelNode || listDTO_SelNode.length == 0)return;

		const childNodes = root.getChildren();

		listDTO_SelNode.forEach((selNode)=>{
			if(selNode){
				const selInfo = selNode.selInfo;
				if(!selInfo.selectableOnRun){
					return;
				}

				if(selNode.cip){
					let node:TransformNode | null = root;
					if(selNode.cip.ids.length > 0){
						const cip = new ChildIndexPath(selNode.cip.ids);
						node = cip.getNodeFromeChildren(childNodes) as TransformNode | null;
					}

					if(node){
						const selectableBehavior = new SelectableBehavior(selInfo.uuid);
						selectableBehavior.setByData(selInfo);

						node.addBehavior(selectableBehavior);
						selectableBehavior.attach(node as TransformNode);
						this.selNodeManager.addSelectableBehavior(selectableBehavior);
					}
				}
			}
		});
	}

	private _setBehaviorNodes(listBehaviorNode:DTO_BehaviorNode[]){
		listBehaviorNode.forEach((behaviorNode)=>{
			this._setBehaviorNode(behaviorNode);
		});
	}

	private _setBehaviorNode(behaviorNode:DTO_BehaviorNode){
		if(!behaviorNode)return;
		
		if(this.includeNodeBaseInfo(behaviorNode.tranNode?.uuid || '')){
			console.log("Obj3dManager has this id:" + behaviorNode.tranNode?.uuid || '');
			return;
		}

		const node = new TransformNode(behaviorNode.tranNode?.name || '', this.scene);

		this._setNodeTran(node,behaviorNode.tranNode?.tran || new Tran())

		const obj3dID = new NodeBaseInfo(behaviorNode.tranNode?.uuid || '');
		node.addBehavior(obj3dID);
		this._nodeBaseInfos.push(obj3dID);

		if(behaviorNode.selNode){
			const selInfo = behaviorNode.selNode;
			if(selInfo.selectableOnRun){
				const selectableBehavior = new SelectableBehavior(selInfo.uuid);
				selectableBehavior.setByData(selInfo);

				node.addBehavior(selectableBehavior);
				selectableBehavior.attach(node);
				this.selNodeManager.addSelectableBehavior(selectableBehavior);
			}
		}

		if(behaviorNode.generalBehavInfos && behaviorNode.generalBehavInfos.length > 0){
			behaviorNode.generalBehavInfos.forEach((comInfo)=>{
				this.generalBehavManager.setGeneralBehavByInfo(node, comInfo);
			});
		}

		if(behaviorNode.driveBehavInfos && behaviorNode.driveBehavInfos.length > 0){
			behaviorNode.driveBehavInfos.forEach((driveInfo)=>{
				this.driveBehavManager.setDriveBehavior(node, driveInfo);
			});
		}

		// 注:BehaviorNode 仅包含 drive/general 两类,不含 lerp。
		// lerp 行为归属于被加载到 obj3DResources 中的外部模型,因此走 _processByObj3dInfo 路径。
	}

	private _setBehaviorMeshes(listBehaviorMesh:DTO_BehaviorMesh[]){
		listBehaviorMesh.forEach((behaviorMesh)=>{
			this._setBehaviorMesh(behaviorMesh);
		});
	}

	private _setBehaviorMesh(behaviorMesh:DTO_BehaviorMesh){
		if(!behaviorMesh)return;
		
		if(this.includeNodeBaseInfo(behaviorMesh.tranNode?.uuid || '')){
			console.log("Obj3dManager has this id:" + behaviorMesh.tranNode?.uuid || '');
			return;
		}

		const mesh = new Mesh(behaviorMesh.tranNode?.name || '', this.scene);

		this._setNodeTran(mesh, behaviorMesh.tranNode?.tran || new Tran())

		const obj3dID = new NodeBaseInfo(behaviorMesh.tranNode?.uuid || '');
		mesh.addBehavior(obj3dID);
		this._nodeBaseInfos.push(obj3dID);

		if(behaviorMesh.selNode){
			const selInfo = behaviorMesh.selNode;
			if(selInfo.selectableOnRun){
				const selectableBehavior = new SelectableBehavior(selInfo.uuid);
				selectableBehavior.setByData(selInfo);

				mesh.addBehavior(selectableBehavior);
				selectableBehavior.attach(mesh);
				this.selNodeManager.addSelectableBehavior(selectableBehavior);
			}
		}

		if(behaviorMesh.generalBehavInfos && behaviorMesh.generalBehavInfos.length > 0){
			behaviorMesh.generalBehavInfos.forEach((comInfo)=>{
				this.generalBehavManager.setGeneralBehavByInfo(mesh, comInfo);
			});
		}

		if(behaviorMesh.driveBehavInfos && behaviorMesh.driveBehavInfos.length > 0){
			behaviorMesh.driveBehavInfos.forEach((driveInfo)=>{
				this.driveBehavManager.setDriveBehavior(mesh, driveInfo);
			});
		}

		// 注:BehaviorMesh 不含 lerp,详见 _setBehaviorNode 的注释。
	}

	private _setNodeTran(node:TransformNode, tran:Tran){
		const p = tran.pos;
		const r = tran.rot;
		const s = tran.sca;
		node.setAbsolutePosition(new Vector3(p.x, p.y, p.z));
		node.rotation = new Vector3(r.x, r.y, r.z);
		node.scaling = new Vector3(s.x, s.y, s.z);
	}

	private _setLoadedObj3dPickable(node:TransformNode, cipEnalbeds:DTO_CipEnabled[]):void{
		cipEnalbeds.forEach((cipEnabled)=>{
			let mesh;
			if(cipEnabled.cip.ids.length > 0){
				const childrenIndexPath = new ChildIndexPath(cipEnabled.cip.ids);
				mesh = childrenIndexPath.getNodeFromeChildren(node.getChildMeshes()) as Mesh;
			}
			if(mesh){
				mesh.isPickable = cipEnabled.enabled;
			}
		});
	}

	private _setMeshesGlow(node:TransformNode, listCip:Cip[]){
		listCip.forEach((cip)=>{
			let mesh = node as Mesh;
			if(cip.ids.length > 0){
				const childrenIndexPath = new ChildIndexPath(cip.ids);
				mesh = childrenIndexPath.getNodeFromeChildren(node.getChildMeshes()) as Mesh;
			}
			if(mesh){
				this.runtimeSystem.layerController.glowLayer.addIncludedOnlyMesh(mesh);
			}
		});
	}

	//#region 驱动行为------------------------------------------
	public driveDriveBehavior(driveData:DriveData){
		this.driveBehavManager.driveDriveBehavior(driveData);
	}
	//#endregion

	//#region onDispose------------------------------------------------
	public dispose():void{
		this.materialManager.dispose();
		this.selNodeManager.dispose();
		this.nodeUIManager.dispose();
		this._shadowManager?.dispose();
		this.generalBehavManager?.dispose();
		this.lerpBehavManager?.dispose();
		this.driveBehavManager.dispose();
	}
	//#endregion
}

LerpBehaviorRegistry.ts

import LerpBehaviorEditor from "./LerpBehaviorEditor";
import { BehaviorRegistryBase } from "../BehaviorRegistryBase";
import { getBuiltinLerpEditorClasses } from "./LerpBehaviorModuleCatalog";
import type { LerpBehaviorEditorClass } from "./LerpBehaviorEditorClass";

export type { LerpBehaviorEditorClass };

/**
 * 插值行为注册表
 * 管理所有 LerpBehaviorEditor 子类,支持按名称创建实例
 */
class LerpBehaviorRegistry extends BehaviorRegistryBase<LerpBehaviorEditor, LerpBehaviorEditorClass> {

	constructor() {
		super([...getBuiltinLerpEditorClasses()]);
	}

	protected override getRegistryName(): string {
		return "LerpBehaviorRegistry";
	}
}

export const lerpBehaviorRegistryEditor = new LerpBehaviorRegistry();
lerpBehaviorRegistryEditor.initialize();

/**
 * 插件化入口:外部模块可注册自定义 Lerp 行为编辑器。
 * 调用后将立刻生效,下一次 createBehavior(name) 即可命中。
 */
export const registerLerpBehaviorEditor = (cls: LerpBehaviorEditorClass): void => {
	lerpBehaviorRegistryEditor.register(cls);
};

LerpBehaviorModuleCatalog.ts

import type { Component } from 'vue';
import type LerpBehaviorEditor from './LerpBehaviorEditor';
import LerpPositionEditor from './LerpPositionEditor';
import LerpRotationEditor from './LerpRotationEditor';
import LerpScalingEditor from './LerpScalingEditor';
import LerpVisibilityEditor from './LerpVisibilityEditor';
import LerpControllerEditor from './LerpControllerEditor';
import LerpPositionCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpPositionCom.vue';
import LerpRotationCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpRotationCom.vue';
import LerpScalingCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpScalingCom.vue';
import LerpVisibilityCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpVisibilityCom.vue';
import LerpControllerCom from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpControllerCom/LerpControllerCom.vue';
import LerpControllerDialogPortal from '../../../../../components/ObjectPropertiesPanel/LerpBehaviorProperties/LerpBehaviorComs/LerpControllerCom/LerpControllerDialog/LerpControllerDialogPortal.vue';
import {
    BUILTIN_LERP_BEHAVIOR_ENTRIES,
    LERP_BEHAVIOR_MODULE_DESCRIPTOR,
    type LerpBehaviorModuleEntryMeta,
    type LerpCreateMenuGroup,
} from '../../../../../../Shared/TScripts/LerpBehavior/LerpBehaviorModuleMetadata';
import type { LerpBehaviorEditorClass } from './LerpBehaviorEditorClass';

export type LerpBehaviorCatalogEntry = LerpBehaviorModuleEntryMeta & {
    editorClass: LerpBehaviorEditorClass;
    uiComponent: Component;
};

const EDITOR_CLASS_BY_NAME: Record<string, LerpBehaviorEditorClass> = {
    LerpPositionEditor,
    LerpRotationEditor,
    LerpScalingEditor,
    LerpVisibilityEditor,
    LerpControllerEditor,
};

const UI_COMPONENT_BY_NAME: Record<string, Component> = {
    LerpPositionEditor: LerpPositionCom,
    LerpRotationEditor: LerpRotationCom,
    LerpScalingEditor: LerpScalingCom,
    LerpVisibilityEditor: LerpVisibilityCom,
    LerpControllerEditor: LerpControllerCom,
};

const extraCatalogEntries: LerpBehaviorCatalogEntry[] = [];

function toCatalogEntry(meta: LerpBehaviorModuleEntryMeta): LerpBehaviorCatalogEntry {
    const editorClass = EDITOR_CLASS_BY_NAME[meta.editorBehaviorName];
    const uiComponent = UI_COMPONENT_BY_NAME[meta.editorBehaviorName];
    if (!editorClass || !uiComponent) {
        throw new Error(`Missing editor catalog binding for ${meta.editorBehaviorName}`);
    }
    return { ...meta, editorClass, uiComponent };
}

export function getLerpBehaviorCatalogEntries(): LerpBehaviorCatalogEntry[] {
    return [
        ...BUILTIN_LERP_BEHAVIOR_ENTRIES.map(toCatalogEntry),
        ...extraCatalogEntries,
    ];
}

export function getBuiltinLerpEditorClasses(): LerpBehaviorEditorClass[] {
    return getLerpBehaviorCatalogEntries()
        .filter(e => BUILTIN_LERP_BEHAVIOR_ENTRIES.some(b => b.editorBehaviorName === e.editorBehaviorName))
        .map(e => e.editorClass);
}

export function getLerpCatalogEntriesByMenuGroup(group: LerpCreateMenuGroup): LerpBehaviorCatalogEntry[] {
    return getLerpBehaviorCatalogEntries().filter(e => e.createMenuGroup === group);
}

export function registerLerpBehaviorCatalogEntry(entry: LerpBehaviorCatalogEntry): void {
    const idx = extraCatalogEntries.findIndex(e => e.editorBehaviorName === entry.editorBehaviorName);
    if (idx >= 0) {
        extraCatalogEntries[idx] = entry;
    } else {
        extraCatalogEntries.push(entry);
    }
}

export const lerpBehaviorModuleDescriptor = LERP_BEHAVIOR_MODULE_DESCRIPTOR;

// 供将来 EditorApp 按模块注册对话框 Portal
export const lerpControllerDialogPortalComponent = LerpControllerDialogPortal;

export type { LerpBehaviorEditor };

LerpBehaviorRegistryRuntime.ts

import type { TransformNode } from "@babylonjs/core";
import type LerpBehavior from "./LerpBehavior";
import type LerpController from "./LerpController";
import LerpControllerImpl from "./LerpController";
import LerpPosition from "./LerpPosition";
import LerpRotation from "./LerpRotation";
import LerpScaling from "./LerpScaling";
import LerpVisibility from "./LerpVisibility";
import { BUILTIN_LERP_BEHAVIOR_ENTRIES } from "../../../../../Shared/TScripts/LerpBehavior/LerpBehaviorModuleMetadata";

export type LerpRuntimeBehavior = LerpBehavior | LerpController;

export type LerpBehaviorClass = {
	readonly behaviorName: string;
	createFromJson(trNode: TransformNode, jsonBehav: string): LerpRuntimeBehavior | null | undefined;
};

const RUNTIME_CLASS_BY_NAME: Record<string, LerpBehaviorClass> = {
	LerpController: LerpControllerImpl,
	LerpPosition,
	LerpRotation,
	LerpScaling,
	LerpVisibility,
};

function getBuiltinRuntimeClasses(): LerpBehaviorClass[] {
	return BUILTIN_LERP_BEHAVIOR_ENTRIES
		.map(e => RUNTIME_CLASS_BY_NAME[e.runtimeBehaviorName])
		.filter((cls): cls is LerpBehaviorClass => cls != null);
}

/**
 * Lerp 行为注册表(运行时侧)
 * 独立于 DriveBehaviorManager,统一管理场景中的插值行为。
 * 与编辑器侧 LerpBehaviorRegistry 对偶。
 */
export class LerpBehaviorRegistryRuntime {

	private static behaviorClasses: LerpBehaviorClass[] = getBuiltinRuntimeClasses();

	private static registry = new Map<string, LerpBehaviorClass>();

	private static initialized = false;

	public static initialize(): void {
		if (this.initialized) return;

		this.behaviorClasses.forEach(cls => {
			const name = cls.behaviorName;
			if (!name) {
				console.warn(`LerpBehaviorRegistryRuntime: class missing behaviorName`, cls);
				return;
			}
			if (this.registry.has(name)) {
				console.warn(`LerpBehaviorRegistryRuntime: duplicate behavior name: ${name}`);
				return;
			}
			this.registry.set(name, cls);
		});

		this.initialized = true;
	}

	public static createBehavior(
		behaviorName: string,
		trNode: TransformNode,
		jsonBehav: string,
	): LerpRuntimeBehavior | null {
		if (!this.initialized) {
			this.initialize();
		}

		const cls = this.registry.get(behaviorName);
		if (!cls) {
			return null;
		}

		const behavior = cls.createFromJson(trNode, jsonBehav);
		return behavior ?? null;
	}

	public static getRegisteredBehaviors(): string[] {
		if (!this.initialized) {
			this.initialize();
		}
		return Array.from(this.registry.keys());
	}

	public static isRegistered(behaviorName: string): boolean {
		if (!this.initialized) {
			this.initialize();
		}
		return this.registry.has(behaviorName);
	}

	/**
	 * 手动注册一个 Lerp 行为类(用于插件化扩展)
	 */
	public static register(behaviorClass: LerpBehaviorClass): void {
		if (!this.initialized) {
			this.initialize();
		}

		const name = behaviorClass.behaviorName;
		if (!name) {
			console.warn(`LerpBehaviorRegistryRuntime: class missing behaviorName`, behaviorClass);
			return;
		}
		if (this.registry.has(name)) {
			console.warn(`LerpBehaviorRegistryRuntime: behavior ${name} already registered. Overwriting...`);
		}
		this.registry.set(name, behaviorClass);
		if (!this.behaviorClasses.includes(behaviorClass)) {
			this.behaviorClasses.push(behaviorClass);
		}
		console.log(`LerpBehaviorRegistryRuntime: registered ${name}`);
	}
}

// 自动初始化
LerpBehaviorRegistryRuntime.initialize();

AI 驱动代码审查实战

Claude code-review 插件深度解析,把 AI 智能审查接进 CI/CD 流水线

打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 HFSS,其全称为High Frequency Structure Simulator,是由Ansys公司研发的一款高级三维电磁场仿真软件,主要应用于射频、微波以及光学领域内的设计工作性能分析。当前压缩包内提供的是一个基于HFSS软件构建的偶极子天线模型,并且包含了该模型的仿真数据,我们将对这一模型及其关联的学术知识进行细致的探讨。偶极子天线属于天线设计中最基础的类型之一,其结构由两个大小相等且布局对称的导体单元构成,整体形状类似于汉字“工”。在2.4GHz的频率条件下,此类天线被广泛部署于Wi-Fi、蓝牙等无线通信系统的构建中。HFSS软件能够对偶极子天线的电气特性进行高精度模拟,涵盖辐射模式、增益水平、方向图形态、输入阻抗以及S参数等多个核心指标。 S参数(即Scattering Parameters),是用于评估天线或微波器件输入端输出端之间相互影响程度的关键参数。S参数详细刻画了信号流经网络设备时的反射传输状态,其中S11(输入反射系数)和S21(传输系数)是最为常用的两种表征方式。借助HFSS软件执行S参数仿真,可以获取天线在多种频率下的反射传输特性表现,从而协助设计人员对天线的阻抗匹配程度和运行效率进行有效评估。在此模型中,S参数仿真工作业已完成,因此我们可以直接审视2.4GHz频率下的阻抗匹配状况,以验证天线在该工作频段内能否展现出理想的性能。 在"Project1_1.aedt""Project1.aedt"这两个提供的文件中,储存了HFSS项目的完整信息。这些文件内含了天线的几何构造细节、材料物理属性、边界约束条件、求解器配置参数以及仿真获取的结果...
打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 在鸿蒙OS(HarmonyOS)的系统构建过程中,SQLite扮演着关键的角色,它作为一个轻量级的数据管理工具,为各类应用程序提供本地化数据存储的支持。本实将详细剖析如何在鸿蒙OS平台上运用SQLite进行数据管理操作。 SQLite作为一个开源的、自给自足的、无需运行服务的、支持事务的SQL数据库管理系统,非常适合于嵌入式系统以及移动设备的应用。在鸿蒙OS系统中,SQLite作为数据持久化的关键技术,能够协助开发人员储存和处理应用中的结构化信息。接下来我们将具体研究以下几个核心要点: 1. **SQLite API鸿蒙OS的融合**: 鸿蒙OS系统提供了SQLite进行交互的API接口,开发者可以利用这些接口来建立数据库、设计数据表,执行SQL指令,以及进行数据的读取和写入。在将SQLite集成到系统中时,开发者需要明确如何在HarmonyOS项目中导入SQLite库,并精确配置相关依赖。 2. **数据库的建立**: 在鸿蒙OS应用程序中,首要任务是创建一个SQLite数据库。这一步骤通常在应用启动阶段完成,通过调用`sqlite3_open()`函数来指定数据库文件的存储路径。 3. **数据表的构建**: 数据表的建立是通过执行SQL的`CREATE TABLE`指令来实现的。如,为了创建一个用户数据表,可以编写如下的SQL指令: ``` CREATE TABLE Users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER); ``` 4. **数据的添加**: 使用`sqlite3_exec()`函数来执行SQ...
内容概要:本文围绕“考虑N-1故障集的电力系统安全约束经济调度(SCED)”展开研究,提出了一种在N-1故障条件下保障电力系统安全运行的经济调度模型。通过构建包含线路、发电机等关键元件故障场景的安全约束优化模型,综合考虑系统潮流约束、机组出力范围、备用容量需求及支路传输能力等多重技术约束,采用Matlab平台实现高效的优化求解算法,实现了系统运行经济性安全性的协调统一。文中详细阐述了模型的构建逻辑、约束条件的数学表达、求解流程的设计,并通过标准算系统进行了仿真验证,结果表明所提方法能够在确保电网在单一元件故障下仍满足安全运行要求的同时,有效降低系统总体运行成本,具有良好的工程应用前景。; 适合人群:具备电力系统分析优化理论基础,从事电力系统调度、运行规划、安全评估等相关领域的科研人员、工程技术人员及高校研究生,尤其适用于关注电力系统可靠性经济性协同优化的专业人士。; 使用场景及目标:①应用于电力系统日常运行中的安全约束经济调度计算,实现预防性安全校核;②为电网调度机构提供应对N-1故障的决策支持工具,辅助制定预防控制策略;③作为高等院校和研究机构在电力系统优化、安全分析等课程中的教学案或科研参考; 阅读建议:建议读者结合提供的Matlab代码深入理解模型的具体实现过程,重点掌握安全约束的建模技巧优化求解器的配置方法,可通过修改系统参数或扩展至N-k故障场景以进一步探究模型的鲁棒性适用边界。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值