THREE.js 3D智慧车站数字孪生项目

169 阅读5分钟

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)

封装了 GLTFLoaderDRACOLoader,提供健壮的异步加载能力,包含 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);
}