# ThreeEngine - 三维水利场景 基于 Three.js 的水利三维可视化场景组件。 ## 嵌入到其他 Vue 项目 ### 1. 安装依赖 ```bash npm install three 3d-tiles-renderer @loaders.gl/3d-tiles @loaders.gl/core @loaders.gl/gltf ``` ### 2. 拷贝项目 将本项目 `src/` 目录下的以下内容拷贝到目标项目中: | 路径 | 说明 | |---|---| | `src/components/` | Vue 组件(Scene3D、WaterLevelLabel 等) | | `src/config/` | 场景配置文件 | | `src/assets/` | 纹理、模型、图标等静态资源 | ### 3. 使用组件 ```vue ``` ## Props 说明 | Prop | 类型 | 默认值 | 说明 | |---|---|---|---| | `labelValues` | `Record` | `{}` | 标签数据值,`{ [标签id]: 数值 }` | | `showDebugTools` | `boolean` | `false` | 是否显示调试面板 | | `tilesetUrl` | `string` | `'/scene/tileset.json'` | 3D Tiles 数据地址 | | `cameraPosition` | `{ x, y, z }` | 默认位置 | 初始相机位置 | | `cameraTarget` | `{ x, y, z }` | 默认 target | 初始相机目标点 | ## Expose API(通过 ref 调用) ### 更新标签值 ```ts // 更新单个标签 sceneRef.value?.setLabelValue('6602380005', 1.58) // 批量更新标签 sceneRef.value?.setLabelValues({ '6602380005': 1.58, 'gate002': 0.5 }) // 直接修改响应式数据 sceneRef.value?.labelValues['6602380005'] = 3.0 ``` ### 相机控制 ```ts // 飞行动画(自由指定位置) sceneRef.value?.flyTo( { x: -900, y: 60, z: -2100 }, // 目标位置 { x: -843, y: 12, z: -2182 }, // 目标看点(可选) 2000 // 动画时长(毫秒) ) // 按预设名称跳转(配置在 sceneConfig.ts 的 cameraPresets 中) sceneRef.value?.flyToPreset('default') // 默认视角,时长 1500ms sceneRef.value?.flyToPreset('gate', 2000) // 闸门视角,时长 2000ms // 查看所有预设 console.log(sceneRef.value?.cameraPresets) ``` ## 镜头预设配置 在 `src/config/sceneConfig.ts` 中配置镜头预设: ```ts export const cameraPresets: CameraPreset[] = [ { id: 'default', name: '默认视角', positionX: -831.56685, positionY: 40.63456, positionZ: -2225.321, targetX: -843.0744, targetY: 12.01539, targetZ: -2182.06814, }, ] ``` ## 标签配置 在 `src/config/sceneConfig.ts` 中配置标签: ```ts export const waterLevelLabels: WaterLevelLabelConfig[] = [ { id: '6602380005', // 唯一标识,外部传值时使用此 id name: '莫勒切河节制分水闸闸后水量监测', type: 'waterLevel', // 标签类型,决定图标和文本 positionX: -830.11, // 场景中的位置(外部无需关心) positionY: 14, positionZ: -2156.83, initialValue: 0.00, // 初始默认值 }, ] ``` ## 扩展标签类型 在 `sceneConfig.ts` 中注册新类型: ```ts export type LabelDataType = 'waterLevel' | 'flowRate' | '你的新类型' export const labelTypeRegistry: Record = { waterLevel: { icon: 'shuiliang.png', unit: 'm³/s', label: '水 量', decimalPlaces: 2, }, flowRate: { icon: 'shuiliang.png', unit: 'm³/s', label: '流 量', decimalPlaces: 2, }, // 新类型: 在这里新增 } ``` 之后在 `WaterLevelLabel.vue` 的 `iconMap` 中映射新的图标文件即可。 --- ## 数据传输与更新机制 ### 数据流总览 ``` 外部系统 │ ├── ① Props 传入初始值 │ :label-values="{ '6602380005': 0.00 }" │ ├── ② 调用 Expose 方法更新 │ sceneRef.setLabelValue(id, value) │ sceneRef.setLabelValues({ id: value, ... }) │ └── ③ 直接修改响应式数据 sceneRef.labelValues[id] = value │ ▼ ┌─────────────────────────┐ │ 场景组件内部 │ │ labelValues (reactive) │ │ ├── Scene3D.vue │ │ └── DucaoScene.vue │ └──────────┬──────────────┘ │ :value prop 绑定 ▼ ┌─────────────────────────┐ │ WaterLevelLabel.vue │ │ watch(value) │ │ → redrawTexture() │ │ → Canvas 重新绘制 │ │ → spriteTexture │ │ .needsUpdate = true │ └─────────────────────────┘ ``` ### 三种更新方式 #### 方式一:Props 传入(初始化 / 响应式更新) ```vue ``` - 父组件通过 prop `labelValues` 传入一个 `Record` - 场景内部 `watch(() => props.labelValues)` 监听变化,同步到本地的 `labelValues` reactive 对象 - **注意**:由于 watch 是 deep 监听,父组件更新 prop 对象的属性值时会自动触发重绘 #### 方式二:调用 Expose 方法 ```ts // 更新单个标签 sceneRef.value?.setLabelValue('6602380005', 1.58) // 批量更新 sceneRef.value?.setLabelValues({ '6602380005': 1.58, '6602380006': 3.20, }) ``` - `setLabelValue(id, value)` — 直接设置 `labelValues[id] = value` - `setLabelValues(levels)` — 遍历设置多个值 - 两种方法都直接操作 reactive 对象,Vue 的响应式系统会自动触发 `WaterLevelLabel` 的 prop 更新 #### 方式三:直接操作响应式数据 ```ts sceneRef.value?.labelValues['6602380005'] = 3.0 ``` - 直接修改 `labelValues` 对象的属性 - 由于 `labelValues` 是 `reactive()` 对象,Vue 会自动追踪变化 ### 内部更新链路(逐层追踪) ``` labelValues[id] 更新 │ ▼ Scene3D.vue / DucaoScene.vue 的模板 │ │ ▼ WaterLevelLabel.vue props.value 更新 │ ▼ watch(() => props.value) 触发 │ ▼ redrawTexture() 被调用 ├── ctx.clearRect() → 清空 Canvas ├── ctx.drawImage(iconImg) → 重绘图标 ├── drawText() → 重绘数值文本 │ ├── 使用 cfg.label (如 "水 量") │ ├── 使用 props.value.toFixed(cfg.decimalPlaces) │ └── 白色文字 + 半透明单位文本 └── spriteTexture.needsUpdate = true → Three.js 更新纹理 │ ▼ 屏幕上的 Sprite 立即显示新数值 ``` ### 初始化流程 ``` 组件挂载 │ ▼ initLabelData() │ ├── 遍历 sceneLabels (从 sceneConfig.ts 读取标签配置) ├── 对每个标签: │ ├── 优先读取 props.labelValues[cfg.id](外部传入值) │ └── 如果外部未传入,则使用 cfg.initialValue(配置默认值) │ ▼ labelDataList 构建完成 │ ▼ 3D 场景初始化 → labelDataList.forEach(init) │ ▼ WaterLevelLabel.init(scene, camera) → ensureSprite() → 创建 CanvasTexture Sprite → createTooltip() → 创建悬停提示 Sprite ``` ### 标签配置数据结构 ```ts interface WaterLevelLabelConfig { id: string // 唯一标识,外部传值时使用 name: string // 名称(悬停 tooltip 显示) type: LabelDataType // 标签类型(waterLevel / flowRate / StressMonitor) scene: SceneType // 所属场景(main / ducao) positionX: number // 3D 场景坐标 positionY: number positionZ: number initialValue: number // 初始默认值 } ``` ### 两个场景的差异 | 场景 | 组件 | 标签来源 | 标签数量 | |---|---|---|---| | 主场景(沉砂池) | Scene3D.vue | `sceneLabels.main` | 6 个 | | 渡槽场景 | DucaoScene.vue | `sceneLabels.ducao` | 8 个 | 两个场景的标签配置统一在 `sceneConfig.ts` 的 `waterLevelLabels` 数组中管理,通过 `scene` 字段区分。但数据更新方式完全一致(Props + Expose + 直接修改)。