3D智慧车站数字孪生项目
版本: 1.2
日期: 2026-01-26
概要: 本文档详细阐述了项目前端 3D 模块的核心架构与技术实现。系统采用 Model-View 分离 的设计模式,将数据逻辑 (model.js) 与视图交互 (view.js) 解耦,结合 Service Worker 离线缓存 与 Three.js 渲染引擎,构建了高性能、可交互的 Web3D 可视化平台。
0. 技术架构概览
系统整体架构分为视图层、逻辑层与数据层,各层职责如下:
graph TD
A[视图层 View.js] -->|用户交互/指令| B(逻辑层 Model.js)
B -->|状态更新/回调| A
B -->|渲染指令| C{Three.js 渲染引擎}
D[后端 API] -->|JSON 数据| B
E[Service Worker] -->|资源拦截/缓存| C
- 视图层 (
view.js): 基于 Vue.js,负责 UI 组件渲染、用户事件捕获(点击、悬停)及业务弹窗管理。 - 逻辑层 (
model.js): 核心 3D 控制器,管理场景图谱 (Scene Graph)、相机系统、光照环境及模型生命周期。 - 数据层 (
models/*.js): 定义业务实体(如点位 Mark、路径 Path),封装数据解析与实例化逻辑。
1. 离线缓存与性能优化 (PWA)
核心文件: public/sw.js
该模块实现了 PWA (Progressive Web App) 的核心离线缓存功能,采用 Cache First (缓存优先) 策略,显著提升大屏 3D 资源的加载速度与稳定性。
1.1 缓存策略与生命周期
- Install: 初始化静态资源缓存 (
STATIC_CACHE)。 - Activate: 自动清理旧版缓存 (
MODEL_CACHE),确保用户访问最新资源。 - Fetch: 拦截网络请求,优先读取本地缓存,未命中则发起请求并自动写入缓存。
// public/sw.js
const CACHE_VERSION = 'v1';
const MODEL_CACHE = 'model-cache-' + CACHE_VERSION;
const STATIC_CACHE = 'static-cache-' + CACHE_VERSION;
self.addEventListener('install', event => {
event.waitUntil(caches.open(STATIC_CACHE));
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.filter(key => key !== MODEL_CACHE && key !== STATIC_CACHE)
.map(key => caches.delete(key))
);
})
);
});
self.addEventListener('fetch', event => {
// 缓存优先策略实现
event.respondWith(
caches.match(event.request).then(response => {
if (response) return response;
return fetch(event.request).then(networkResponse => {
// ... 缓存写入逻辑
return networkResponse;
});
})
);
});
2. 3D 核心引擎与场景管理
核心文件: model.js
作为 3D 场景的“大脑”,负责初始化渲染环境、管理渲染循环及全局光照系统。
2.3 模型异步加载系统 (loadTargetModel)
封装了 GLTFLoader 与 DRACOLoader,提供健壮的异步加载能力,包含 DRACO 压缩支持 和 异步竞态处理 (Token)。
// model.js
async loadTargetModel(targetData) {
const loadStartTime = performance.now();
const loadPath = targetData.objPath;
const loadUrl = targetData.objUrl;
// Token 机制:防止快速切换导致的资源竞争
this._currentLoadToken++;
const loadToken = this._currentLoadToken;
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(globelvar.projectUrl + 'js/draco/');
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
return await new Promise((resolve) => {
gltfLoader.setPath(loadPath).load(loadUrl, (gltf) => {
// 闭包校验:如果 Token 不一致,说明用户已切换场景,丢弃当前结果
if (loadToken !== this._currentLoadToken) return resolve(null);
targetData.modelScene = gltf.scene;
// 材质修正:确保模型在暗场景下可见
gltf.scene.traverse((o) => {
if (o.isMesh) {
o.material.emissive = o.material.color;
o.material.emissiveMap = o.material.map;
}
});
this.scene.add(gltf.scene);
resolve(gltf.scene);
});
});
}
3. 楼层管理与可视化交互
核心文件: model.js
实现了建筑结构的纵向展开与合并功能,提供直观的楼层内部查看体验。
3.1 展开逻辑 (toggleExpand)
基于 TWEEN 库实现平滑的楼层分离动画,并同步切换光照模式以增强内部细节可见性。
// model.js
async toggleExpand(projectName) {
this.isExpanded = !this.isExpanded;
const group = this.modelGroupsMap.get(projectName);
const gap = 600;
if (this.isExpanded) {
// 展开模式:切换光照,计算目标 Y 轴位置
this._applyDirLightMode('alt');
for (let i = 0; i < group.childrenLevel.length; i++) {
const floor = group.childrenLevel[i];
const targetY = (i - (group.childrenLevel.length - 1) / 2) * gap;
let scene = floor.modelScene;
if (!scene) {
await this.loadTargetModel(floor); // 按需加载
scene = floor.modelScene;
}
scene.visible = true;
// TWEEN 动画
const tween = new TWEEN.Tween(scene.position)
.to({ y: targetY }, 1000)
.easing(TWEEN.Easing.Quadratic.Out)
.start();
this._expandTweens.push(tween);
}
} else {
// 合并模式:恢复光照,重置位置
this._applyDirLightMode('main');
this._stopExpandTweensAndResetFloors();
// ...
}
}
4. 视图交互与事件系统
核心文件: view.js & model.js
构建了连接用户操作与 3D 逻辑的桥梁,支持点选与框选交互。
4.1 鼠标交互逻辑
利用 THREE.Raycaster 将屏幕坐标转换为 3D 射线,实现高精度拾取。
// model.js
handleMouseMove(event) {
if (this.currentScene == 'INSIDE') {
if (this.hoverUserEnable) {
// 范围选择模式:更新选框位置
this.setMousePosition(event);
this.raycaster.setFromCamera(this.mouse, this.camera);
const hits = this.raycaster.intersectObject(model, true);
if (hits && hits.length) {
this.hoverCube.position.copy(hits[0].point);
this.hoverCube.visible = true;
}
}
} else {
// 楼层选择模式:高亮悬停楼层
if (!this.isExpanded) return;
this.setMousePosition(event);
this.raycaster.setFromCamera(this.mouse, this.camera);
const intersects = this.raycaster.intersectObjects(floorGroups, true);
if (intersects.length > 0) {
this.applyHoverEffect(intersects[0].object); // 应用高亮材质
}
}
}
5. 动态数据可视化系统
核心文件: model.js & models/mark.js
负责业务数据(设备点位、标签)在 3D 空间中的动态映射与展示,包含性能优化与并发控制。
5.1 精灵模型创建与纹理缓存
通过纹理缓存 (_markTextureCache) 减少显存占用,利用 THREE.Sprite 实现始终面向相机的点位图标。
// models/mark.js
createSprite(option) {
let texture = this.techin._markTextureCache.get(option.info.image);
if (!texture) {
texture = new THREE.TextureLoader().load(option.info.image);
this.techin._markTextureCache.set(option.info.image, texture);
}
const material = new THREE.SpriteMaterial({
map: texture,
transparent: true,
depthTest: false // 确保图标不被模型遮挡
});
const sprite = new THREE.Sprite(material);
sprite.scale.set(option.zoom, option.zoom, 1);
return sprite;
}
5.2 加载并发控制 (Token)
// model.js
async showMarkers(targetData) {
this._currentPointsToken = (this._currentPointsToken || 0) + 1;
const startToken = this._currentPointsToken;
// ... 异步查询接口 ...
if (startToken !== this._currentPointsToken) return; // 校验 Token,防止数据错乱
// 分帧渲染,避免卡顿
for (let i = 0; i < items.length; i++) {
if (i % 20 === 0) await new Promise(r => requestAnimationFrame(r));
// ... 创建点位 ...
}
}
6. 路径规划与导视系统
核心文件: model.js & effect/PathEffect.js
6.1 逃生路线可视化 (showEscapeRoute)
解析路径坐标,生成动态流光效果。
// model.js
async showEscapeRoute(domainUnid) {
const res = await queryEscRouteList({ domainUnid });
const routesArr = JSON.parse(res.data.list[0].routes);
routesArr.forEach(item => {
const pathEffect = new PathEffect(this);
const points = item.points.map(p => ({ x: p.x, y: p.y, z: p.z }));
pathEffect.create({ points }); // 创建几何体
// 添加到动画管理器
this.animationManager.add(pathEffect.animate.bind(pathEffect), "pathEffect" + item.id);
this._escapePathEffects.push({ effect: pathEffect });
});
}
7. 导航与场景跳转逻辑
核心文件: model.js & view.js
7.1 设备定位与跳转 (setMapPosition)
实现从 2D 列表到 3D 场景的精准跳转,包含楼层切换与相机飞行。
// model.js
async setMapPosition(data) {
const domainUnid = data.domainUnid;
const targetData = this.findSceneByDomainUnid(domainUnid);
// 切换楼层
await this.changeFloor(targetData, { skipCamera: true });
// 查找点位并飞行
if (data.unid) {
let marker = this.markerObjects.find(m => m.info.uuid === data.unid);
if (marker) {
const pos = marker.position.clone();
flyToLabel(this, { position: pos, offset: 200 }); // 智能聚焦
}
}
}
8. 视觉特效组件库
核心文件: component/THREE/
- 指南针 (
Compass.js): 实时计算相机方位角。 - 设备报警 (
setDeviceAlarm):
// model.js
async setDeviceAlarm(option) {
// 1. 环境重置
this.clearAlarmIcons();
// 2. 空间定位与飞行
await this.setMapPosition({ domainUnid: option.domainUnid, unid: option.unid });
// 3. 添加报警特效
const marker = this.markerObjects.find(m => m.info.uuid === option.unid);
this._addWarningForMarker(marker, marker.position); // 动态光圈
}
9. 相机控制系统
核心文件: utils/util.js
基于 GSAP 封装的高性能相机运镜工具库。
9.1 通用飞行 (flyTo)
// utils/util.js
export function flyTo(techin, options, fun = function () { }) {
const duration = options.duration || 1.5;
// 禁用控制器,避免动画冲突
techin.controls.enabled = false;
const tl = gsap.timeline({
onUpdate: () => { techin.camera.lookAt(techin.controls.target); }, // 保持注视目标
onComplete: () => {
techin.controls.enabled = true;
fun();
}
});
tl.to(techin.camera.position, options.position, 0);
tl.to(techin.controls.target, options.target, 0);
}