import type { ThemeConfig } from '../../themes/types'; import { IBimComponent } from '../../types/component'; import { themeManager } from '../../services/theme'; import type { EngineOptions, ModelLoadOptions } from './types'; import type { MeasureMode } from '../../types/measure'; import type { SectionBoxRange } from '../section-box-panel/types'; // 导入第三方 SDK 的 createEngine 函数(从 npm 包引入) // import { createEngine as createEngineSDK } from 'iflow-engine-base'; import { createEngine as createEngineSDK } from '../../../../bim_engine_base/dist/bim-engine-sdk.es'; // 重新导出类型,方便外部引用 export type { EngineOptions, ModelLoadOptions }; /** * 创建 Engine 实例的工厂函数 * 兼容旧代码直接 import { createEngine } 的方式 */ export const createEngine = (options: EngineOptions) => { return new Engine(options); }; /** * 3D 引擎组件 * 负责创建和管理第三方 3D 引擎实例 */ export class Engine implements IBimComponent { /** 第三方 3D 引擎实例 */ private engine: any = null; /** 引擎挂载的容器元素 */ private container: HTMLElement; /** 引擎容器 ID(用于传递给 createEngine) */ private containerId: string; /** 引擎配置选项(不包含 container) */ private options: Omit; /** 是否已初始化 */ private _isInitialized = false; /** 是否已销毁 */ private _isDestroyed = false; /** 主题订阅取消函数 */ private unsubscribeTheme: (() => void) | null = null; /** 当前激活的测量类型 */ private currentMeasureType: MeasureMode | null = null; /** 主测量功能是否已激活 */ private isMeasureActive: boolean = false; /** 当前激活的剖切轴向 */ private currentSectionAxis: 'x' | 'y' | 'z' | null = null; /** 剖切盒是否激活 */ private isSectionBoxActive: boolean = false; /** * 构造函数 * @param options 3D 引擎配置选项 */ constructor(options: EngineOptions) { // 解析容器元素 this.container = options.container; // 如果容器没有 id,生成一个唯一的 id if (!this.container.id) { this.containerId = `engine-container-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; this.container.id = this.containerId; } else { this.containerId = this.container.id; } // 保存配置选项(设置默认值) this.options = { backgroundColor: 'linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255))', // 固定背景渐变色 version: options.version ?? 'v2', // 默认使用 v2 版本 showStats: options.showStats ?? false, // 默认不显示统计 showViewCube: options.showViewCube ?? true, // 默认显示视图立方体 }; } /** * 初始化组件 (接口实现) * 创建 div 容器并初始化引擎 */ public init(): void { if (this._isInitialized) { console.warn('[Engine] Engine already initialized.'); return; } if (this._isDestroyed) { console.error('[Engine] Cannot initialize destroyed engine.'); return; } try { // 应用背景色到容器 if (typeof this.options.backgroundColor === 'string') { this.container.style.background = this.options.backgroundColor; } // 创建引擎配置对象 const engineConfig = { containerId: this.containerId, backgroundColor: this.options.backgroundColor, version: this.options.version, showStats: this.options.showStats, showViewCube: this.options.showViewCube, }; // 输出配置信息 console.log('引擎配置信息:', engineConfig); // 调用引擎创建函数创建引擎实例 // 将 options 中的配置复制给 createEngine this.engine = createEngineSDK(engineConfig); if (!this.engine) { throw new Error('Failed to create engine instance'); } // 标记为已初始化 this._isInitialized = true; // 订阅主题变化 this.unsubscribeTheme = themeManager.subscribe((theme) => { this.setTheme(theme); }); // 应用当前主题 this.setTheme(themeManager.getTheme()); } catch (error) { console.error('[Engine] Failed to initialize engine:', error); this._isInitialized = false; throw error; } } /** * 设置主题 (接口实现) * 根据主题调整 3D 引擎的视觉效果(如背景色) * @param theme 全局主题配置 */ public setTheme(_theme: ThemeConfig): void { } /** * 设置语言 (接口实现) */ public setLocales(): void { // 3D 引擎组件暂时不需要本地化 } /** * 检查是否已初始化 */ public isInitialized(): boolean { return this._isInitialized; } /** * 加载 3D 模型 * @param url 模型文件 URL * @param options 加载选项(位置、旋转、缩放) */ public loadModel(url: string, options?: ModelLoadOptions): void { if (!this._isInitialized || !this.engine) { console.error('[Engine] Engine not initialized. Please call init() first.'); return; } if (!url) { console.error('[Engine] Model URL is required.'); return; } this.engine.loaderModule.loadModels([url], options); } /** * 回到主视角 * @returns */ public CameraGoHome() { this.engine.viewCube.CameraGoHome(); } /** * 获取原始 3D 引擎实例 */ public getEngine(): any { return this.engine; } /** * 暂停渲染 */ public pauseRendering(): void { if (!this._isInitialized || !this.engine) { console.warn('[Engine] Engine not initialized.'); return; } this.engine.pauseRendering(); } /** * 恢复渲染 */ public resumeRendering(): void { if (!this._isInitialized || !this.engine) { console.warn('[Engine] Engine not initialized.'); return; } this.engine.resumeRendering(); } /** * 销毁 3D 引擎,释放 GPU 资源 */ public dispose(): void { if (!this._isInitialized || !this.engine) { console.warn('[Engine] Engine not initialized.'); return; } this.engine.dispose(); } // ==================== 测量功能方法 ==================== /** * 激活具体测量类型的统一入口(私有方法) * @param type 测量类型 * @param activateFunc 第三方引擎的激活函数 */ private activateMeasureType(type: MeasureMode, activateFunc: () => void): void { // 1. 检查引擎是否初始化 if (!this._isInitialized || !this.engine) { console.error('Cannot activate measure: engine not initialized.'); return; } // 2. 检查 measure 模块是否存在 if (!this.engine.measure) { console.error('Measure module not available.'); return; } if (!this.isMeasureActive) { console.log('激活测量功能'); this.engine.measure.active(); this.isMeasureActive = true; } // 4. 激活具体的测量类型(直接切换,不需要停用前一个) activateFunc(); this.currentMeasureType = type; } /** * 激活距离测量 */ public activateDistanceMeasure(): void { this.activateMeasureType('distance', () => { console.log(`激活距离测量`); this.engine.measure.distanceMeasure.active(); }); } /** * 激活最小距离测量 */ public activateMinDistanceMeasure(): void { this.activateMeasureType('minDistance', () => { console.log(`激活最小距离测量`); this.engine.measure.clearDistanceMeasure.active(); }); } /** * 激活角度测量 */ public activateAngleMeasure(): void { this.activateMeasureType('angle', () => { console.log(`激活角度测量`); this.engine.measure.angleMeasure.active(); console.log('[Engine] Angle measure activated (placeholder)'); }); } /** * 激活标高测量 */ public activateElevationMeasure(): void { this.activateMeasureType('elevation', () => { console.log(`激活标高测量`); this.engine.measure.elevationMeasure.active(); }); } /** * 激活体积测量 */ public activateVolumeMeasure(): void { this.activateMeasureType('volume', () => { console.log(`激活体积测量`); this.engine.measure.areaMeasure.active(); }); } /** * 激活激光测距 */ public activateLaserDistanceMeasure(): void { this.activateMeasureType('laserDistance', () => { // TODO: 调用第三方引擎方法(当前先空着) // this.engine.measure.laserDistanceMeasure.active(); console.log('[Engine] Laser distance measure activated (placeholder)'); }); } /** * 激活坡度测量 */ public activateSlopeMeasure(): void { this.activateMeasureType('slope', () => { console.log(`激活坡度测量`); this.engine.measure.slopeMeasure.active(); }); } /** * 激活空间体积测量 */ public activateSpaceVolumeMeasure(): void { this.activateMeasureType('spaceVolume', () => { // TODO: 调用第三方引擎方法(当前先空着) // this.engine.measure.spaceVolumeMeasure.active(); console.log('[Engine] Space volume measure activated (placeholder)'); }); } /** * 激活测量功能(根据类型统一入口) * @param mode 测量类型 */ public activateMeasure(mode: MeasureMode): void { switch (mode) { case 'distance': this.activateDistanceMeasure(); break; case 'minDistance': this.activateMinDistanceMeasure(); break; case 'angle': this.activateAngleMeasure(); break; case 'elevation': this.activateElevationMeasure(); break; case 'volume': this.activateVolumeMeasure(); break; case 'laserDistance': this.activateLaserDistanceMeasure(); break; case 'slope': this.activateSlopeMeasure(); break; case 'spaceVolume': this.activateSpaceVolumeMeasure(); break; } } /** * 停用测量功能(关闭测量时调用) */ public deactivateMeasure(): void { if (!this._isInitialized || !this.engine?.measure) { return; } if (!this.isMeasureActive) { return; } console.log('停用测量功能'); this.engine.measure.disActive(); this.isMeasureActive = false; this.currentMeasureType = null; } /** * 获取当前激活的测量类型 * @returns 当前测量类型,如果未激活则返回 null */ public getCurrentMeasureType(): MeasureMode | null { return this.currentMeasureType; } /** * 清除所有测量标注 */ public clearAllMeasures(): void { if (!this._isInitialized || !this.engine?.measure) { return; } console.log('清除所有测量标注'); this.engine.measure.clearAll(); } // ==================== 结束:测量功能方法 ==================== // ==================== 轴向剖切功能 ==================== /** * 激活轴向剖切 * @param axis 要激活的轴向 ('x' | 'y' | 'z') * @remarks * - 如果传入的轴向与当前激活的轴向相同,则静默返回(幂等操作) * - 如果当前有不同的轴向激活,先调用该轴向的 disActive() 再激活新轴向 * - 使用单个 plane 的 disActive() 而非 clipping.disActive(),避免影响其他剖切功能 * - 如果引擎未初始化或 clipping 模块不可用,方法会静默返回并输出错误日志 */ public activateSectionAxis(axis: 'x' | 'y' | 'z'): void { // 1. 检查引擎初始化 if (!this._isInitialized || !this.engine) { console.error('[Engine] Cannot activate section axis: engine not initialized.'); return; } // 2. 检查 clipping 模块 if (!this.engine.clipping) { console.error('[Engine] Clipping module not available.'); return; } // 3. 如果是同一轴向,静默返回(幂等操作) if (this.currentSectionAxis === axis) { console.log(`[Engine] Section axis ${axis} already active, skipping.`); return; } // 4. 如果当前有激活的轴向且不同,先停用当前轴向 if (this.currentSectionAxis) { this.deactivateCurrentSectionAxis(); } // 5. 激活新轴向 const planeMap: Record<'x' | 'y' | 'z', any> = { 'x': this.engine.clipping.sectionPlaneX, 'y': this.engine.clipping.sectionPlaneY, 'z': this.engine.clipping.sectionPlaneZ }; const plane = planeMap[axis]; if (plane && typeof plane.active === 'function') { console.log(`[Engine] Activating section axis: ${axis}`); plane.active(); this.currentSectionAxis = axis; } else { console.error(`[Engine] Section plane ${axis} not available.`); } } /** * 停用当前轴向剖切(内部方法) * @remarks 只停用当前激活的单个轴向,不影响其他剖切功能 */ private deactivateCurrentSectionAxis(): void { if (!this.currentSectionAxis || !this.engine?.clipping) { return; } const planeMap: Record<'x' | 'y' | 'z', any> = { 'x': this.engine.clipping.sectionPlaneX, 'y': this.engine.clipping.sectionPlaneY, 'z': this.engine.clipping.sectionPlaneZ }; const plane = planeMap[this.currentSectionAxis]; if (plane && typeof plane.disActive === 'function') { console.log(`[Engine] Deactivating section axis: ${this.currentSectionAxis}`); plane.disActive(); } } /** * 停用轴向剖切(公共方法,关闭弹窗时调用) * @remarks 使用 clipping.disActive() 停用所有剖切,清理状态 */ public deactivateSectionAxis(): void { if (!this._isInitialized || !this.engine?.clipping) { return; } if (this.currentSectionAxis) { console.log('[Engine] Deactivating all section axis'); this.engine.clipping.disActive(); this.currentSectionAxis = null; } } /** * 获取当前剖切轴向 * @returns 当前激活的轴向,如果未激活则返回 null */ public getCurrentSectionAxis(): 'x' | 'y' | 'z' | null { return this.currentSectionAxis; } // ==================== 结束:轴向剖切功能 ==================== // ==================== 剖切盒功能 ==================== /** 激活剖切盒 */ public activateSectionBox(): void { if (!this._isInitialized || !this.engine) { console.error('[Engine] Cannot activate section box: engine not initialized.'); return; } if (!this.engine.clipping?.sectionBox) { console.error('[Engine] Section box module not available.'); return; } if (this.isSectionBoxActive) { console.log('[Engine] Section box already active, skipping.'); return; } console.log('[Engine] Activating section box'); this.engine.clipping.sectionBox.active(); this.isSectionBoxActive = true; } /** 停用剖切盒 */ public deactivateSectionBox(): void { if (!this._isInitialized || !this.engine?.clipping?.sectionBox) { return; } if (!this.isSectionBoxActive) { return; } console.log('[Engine] Deactivating section box'); this.engine.clipping.sectionBox.disActive(); this.isSectionBoxActive = false; } /** 设置剖切盒范围(百分比 0-100) */ public setSectionBoxRange(range: SectionBoxRange): void { if (!this._isInitialized || !this.engine?.clipping?.sectionBox) { console.error('[Engine] Cannot set section box range: engine not initialized.'); return; } console.log('[Engine] Setting section box range:', range); this.engine.clipping.sectionBox.setboxPercent(range); } /** 适应剖切盒到模型(将范围设置为整个模型包围盒) */ public fitSectionBoxToModel(): void { if (!this._isInitialized || !this.engine?.clipping?.sectionBox) { console.error('[Engine] Cannot fit section box: engine not initialized.'); return; } const box = this.engine.octreeBox?.getBoundingBox(); if (!box) { console.error('[Engine] Cannot fit section box: model bounding box not available.'); return; } console.log('[Engine] Fitting section box to model'); this.engine.clipping.sectionBox.setBox(box); } /** * 重置剖切盒 * @remarks 将剖切盒范围恢复为整个模型的包围盒 */ public resetSectionBox(): void { this.fitSectionBoxToModel(); } // ==================== 结束:剖切盒功能 ==================== // ==================== 漫游功能 ==================== /** 漫游模式是否激活 */ private isWalkModeActive: boolean = false; /** * 激活第一人称漫游模式 * @remarks 切换到第一人称控制器,禁用轨道控制器 */ public activateFirstPersonMode(): void { if (!this._isInitialized || !this.engine) { console.error('[Engine] Cannot activate first person mode: engine not initialized.'); return; } if (!this.engine.controlModule) { console.error('[Engine] Control module not available.'); return; } if (this.isWalkModeActive) { console.log('[Engine] First person mode already active, skipping.'); return; } console.log('[Engine] Activating first person mode'); this.engine.controlModule.switchFirstPersonMode(); this.isWalkModeActive = true; } /** * 停用第一人称漫游模式 * @remarks 切换回轨道控制器 */ public deactivateFirstPersonMode(): void { if (!this._isInitialized || !this.engine?.controlModule) { return; } if (!this.isWalkModeActive) { return; } console.log('[Engine] Deactivating first person mode'); this.engine.controlModule.switchDefaultMode(); this.isWalkModeActive = false; } /** * 设置漫游移动速度 * @param speed 移动速度(默认 0.02) */ public setWalkSpeed(speed: number): void { if (!this._isInitialized || !this.engine?.controlModule?.firstPersonControls) { console.error('[Engine] Cannot set walk speed: engine not initialized.'); return; } console.log('[Engine] Setting walk speed:', speed); this.engine.controlModule.firstPersonControls.moveSpeed = speed; } /** * 设置漫游重力开关 * @param enabled 是否启用重力 */ public setWalkGravity(enabled: boolean): void { if (!this._isInitialized || !this.engine?.controlModule?.firstPersonControls) { console.error('[Engine] Cannot set walk gravity: engine not initialized.'); return; } console.log('[Engine] Setting walk gravity:', enabled); this.engine.controlModule.firstPersonControls.applyGravity = enabled; } /** * 设置漫游碰撞检测开关 * @param enabled 是否启用碰撞检测 */ public setWalkCollision(enabled: boolean): void { if (!this._isInitialized || !this.engine?.controlModule?.firstPersonControls) { console.error('[Engine] Cannot set walk collision: engine not initialized.'); return; } console.log('[Engine] Setting walk collision:', enabled); this.engine.controlModule.firstPersonControls.applyCollision = enabled; } /** * 获取漫游模式是否激活 * @returns 是否处于漫游模式 */ public isFirstPersonModeActive(): boolean { return this.isWalkModeActive; } // ==================== 结束:漫游功能 ==================== /** 激活框选放大功能 */ public activateZoomBox(): void { if (!this._isInitialized || !this.engine) { console.warn('[Engine] Engine not initialized.'); return; } this.engine.rangeScale?.active(); } /** * 销毁组件 (接口实现) * 清理资源、取消订阅、销毁引擎实例 */ public destroy(): void { if (this._isDestroyed) { return; } // 停用测量功能 this.deactivateMeasure(); // 取消主题订阅 if (this.unsubscribeTheme) { this.unsubscribeTheme(); this.unsubscribeTheme = null; } // 销毁 3D 引擎,释放 GPU 资源 if (this.engine) { this.engine.dispose(); this.engine = null; } // 清理容器 this.container.innerHTML = ''; // 更新状态 this.currentMeasureType = null; this.isMeasureActive = false; this._isDestroyed = true; this._isInitialized = false; } }