ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WebGL 3D可视化平台中的平面图导航实现与坐标联动

WebGL 3D可视化平台中的平面图导航实现与坐标联动 上个月项目验收客户站在我们做好的3D园区模型前面转了快十分钟最后冒出一句“模型很漂亮但能不能给我一个平面图”我当时愣了一下后来才反应过来——日常工单派发、安保巡检、设备定位他们的工作流完全是建立在平面图逻辑上的。3D可视化平台做得再炫平面图导航依然是一个绕不开的基础模块。今天这篇就聊聊在基于WebGL架构的3D可视化平台里平面图导航到底怎么落地数据从哪来坐标怎么对齐2D和3D之间怎么做到双向联动以及我在实际开发中踩过的那些坑。这篇内容适合正在做智慧园区、智慧楼宇、机房可视化、仓储物流或者校园导航的同学参考。无论你是自己从零搭平面图导航还是准备在Three.js、Cesium、Unity等WebGL方案里选型这篇文章都会把第一阶段的思路和代码骨架讲清楚。1. 先搞清楚3D场景里为什么还少不了一张平面图1.1 我从需求里总结的三个高频痛点做3D可视化平台的人很容易陷入一个误区觉得3D模型做得越精细用户就越满意。真实情况完全不是这样。我在多个项目里反复听到同样的声音总结下来就是三个痛点。第一个痛点是“找不到”。3D场景有纵深感、有遮挡尤其在一个多层楼宇或者大面积园区里你站在一个视角看过去根本不知道目标设备在哪个房间、哪面墙后面。用户嘴上不说心里想的是你能不能像地图App一样给我一个全局视角让我一眼看到所有楼层和区域。第二个痛点是“接不上”。大部分企业现有的资产台账、工单系统、巡检记录全都是基于CAD平面图或者图纸编号来组织的。3D模型很好看但数据字段对不上业务系统没法复用。平面图导航在这个意义上不是炫技而是要当“翻译层”把已有的2D业务数据和3D空间挂接起来。第三个痛点是“用不惯”。一线运维人员和安保人员常年看平面监控、图纸调度你突然让他们在一个3D漫游场景里找摄像头、找配电箱学习成本太高。平面图是他们熟悉的心智模型3D是辅助理解的工具。两者并存才是真正能被日常使用的产品形态。1.2 平面图在平台里的真实定位索引层、总览层、操作层想明白这三点之后我对平面图导航的定位就不再是“把3D模型压扁”了而是把它拆成三个层级。第一是索引层。用户在平面图上点击一个房间、一台设备3D场景快速飞行过去。这时候平面图承担的是“搜索框”和“快捷入口”的职责核心是定位准确、响应迅速。第二是总览层。当3D视角在园区里穿行时画面右上角悬浮一个平面小地图实时绘制当前相机视野覆盖的区域解决“我在哪”的问题。这一层不需要太花哨但视角同步的实时性必须到位。第三是操作层。工单派发、设备选中、告警弹窗这些操作在平面图上完成比在3D里完成更高效。平面图可以承载批量框选、范围圈选这类2D交互这是3D场景很难做顺手的。明白这三级定位之后后续的功能设计和技术选型就都有了判断依据平面图不是陪衬而是整个平台操作体系的基础设施。1.3 一套方案可以复用的项目类型基于WebGL的3D可视化平台室内外场景差异很大。平面图导航这套方案在以下几类项目里复用价值最高。智慧楼宇和智慧园区是最典型的场景楼层多、设备多、工单流转频繁平面图导航基本是刚需。机房和工厂的可视化项目需要精确到机柜U位、管道阀门平面图要细化到设备级对数据结构的要求更高。医院、校园、会展中心这类以导流和寻址为核心诉求的项目平面图导航的关键在路径规划和跨楼层动线。仓储物流项目则更看重平面图上的区域圈选和批量操作。如果你的项目是类似方向下面这套从数据到联动的实现路径可以直接拿过去用。如果是纯粹的大地形、大场景GIS可视化那平面图导航的定位会不同但坐标对齐和联动思路依然有参考价值。2. 技术底座选型渲染引擎、坐标系统与模块边界2.1 渲染引擎Three.js、Babylon.js、Cesium、Unity WebGL怎么选WebGL架构的3D平台渲染层技术选型是第一道分水岭。我做过几个不同技术路线的项目简单说一下真实体感。Three.js是我目前最推荐的自研行业平台基础。它的优点是生态极其成熟模型加载、拾取、相机控制、后期特效都有现成方案社区资料多团队上手快。缺点是引擎相对底层像楼层剖切、室内导航这类垂直能力需要自己封装但这也意味着可控性最强适合要做成产品而不是一次性Demo的团队。Babylon.js引擎完整度更高内置了UI、物理、粒子、甚至编辑器开箱即用的东西多。但中文资料相对少招聘和团队成长成本需要考虑。如果你团队前端底子弱、又想快速出效果Babylon.js可以认真评估。Cesium强在GIS和全球尺度数据处理高程、地形、影像瓦片非常成熟。但它的强项是室外楼层内部、设备级精细交互做起来并不顺手。有一个技巧是Cesium和Three.js叠加渲染室外用Cesium、室内用Three.js中间做相机同步。这个方案复杂度和坑都不少如果项目室内室外的比例接近7比3我不建议第一版就上这种混合架构。Unity打包WebGL是很多非前端团队的选项。开发效率确实高3D表现力强但包体动辄几十兆上百兆加载慢且浏览器侧需要处理IndexedDB持久化存储问题后面我会专门讲到。微信小游戏容器里对Unity WebGL模板的要求也更苛刻。所以Unity WebGL更适合独立游戏和展示型项目做大型行业可视化平台长期维护成本偏高。我个人的选型标准整理成了一张表供参考。引擎开发语言室内精细模型支持GIS/室外能力包体与加载适合场景Three.jsJavaScript/TS高灵活自主一般可扩展轻量行业可视化平台自研Babylon.jsTypeScript高引擎完整一般中等团队前端弱、快速出效果CesiumJavaScript低偏宏观极强中等室外大场景、地形高程Unity WebGLC#高中等大加载慢短期展示型项目2.2 平面图模块的两种架构姿势独立2D画布还是WebGL内嵌图层引擎定了之后平面图模块放在哪一层渲染直接决定后续所有功能复杂度。我在实际项目中试过两种方案各有明显的优缺点。第一种方案是独立2D画布也就是在3D canvas上一层叠一个普通的canvas或DOM/SVG层。平面图用Leaflet、Canvas 2D或者SVG渲染3D场景用WebGL渲染两者通过鼠标事件和坐标变换做联动。优点非常明显平面图上放几百个标注点毫无压力文字永远清晰锐利缩放平移流畅而且事件系统天然是2D的开发效率很高。很多地图App右下角的鹰眼图就是这个思路。第二种方案是把平面图直接做成3D场景里的一个图层。用PlaneGeometry贴纹理或者用ShapeGeometry画矢量墙体平铺在3D世界里。这样做的优势是坐标系天然统一3D模型和平面图在同一个空间里做“平面图浮起”“雷达扫过平面图”这类特效非常方便还能实现平面图和模型相互穿透时的半透明混合效果。但缺点也明显2D平面图上的小字在3D相机视角下会发虚拾取命中逻辑需要自己写Raycaster大量标注点渲染性能压力大。最后我采用的是混合方案3D场景里放一张半透明的贴地平面图用来做空间参照和特效展示界面右上角再放一个独立的2D Canvas小地图承担总览、框选、点击定位这些高频率操作。这个模式和地图应用保持一致用户几乎不需要学习成本。两种架构的关键对比看下表。对比项独立2D画布方案WebGL内嵌图层方案混合方案推荐文字清晰度极高低受相机角度影响各自场景下最优海量标注性能好需要实例化优化好坐标转换复杂度需要维护两套坐标天然统一需要转换但可控3D特效联动弱强可兼顾开发工作量小中中上2.3 坐标对齐三套坐标系之间的换算链平面图导航技术含量最高的部分就在坐标换算上。这个环节一旦做错后面所有功能都是空中楼阁。实际项目里通常会涉及三套坐标系。第一套是平面图原始坐标系CAD图纸里常用毫米或米原点在图纸左下角可能还有自定义的旋转角度。第二套是3D世界坐标系Three.js里约定为米原点通常在建筑中心或者整个园区的某个锚点。第三套是地理坐标系比如WGS84经纬度主要在与室外GIS系统交互时使用。三套坐标系之间的换算链核心就是一个二维仿射变换平面图坐标先平移到原点附近然后做旋转再乘缩放比例最后平移到3D世界坐标系的指定位置。楼层高度则映射到3D世界坐标的Y轴。我贴一个比较通用的换算函数。实际项目中你只需要在界面里选三个以上一一对应的标定点比如两根柱子、一个墙角、一个大门中心然后用最小二乘法解出缩放、旋转、平移参数。// mapToWorld平面图(x, y) - 3D世界坐标(X, Y, Z) // 约定平面图单位米Y轴朝世界坐标正北 function buildTransform(scale, angle, originMap, originWorld) { const cosA Math.cos(angle); const sinA Math.sin(angle); return (x, y) { const dx x - originMap.x; const dy y - originMap.y; return { x: originWorld.x scale * (dx * cosA - dy * sinA), y: originWorld.y, z: originWorld.z scale * (dx * sinA dy * cosA) }; }; } // 使用示例 const mapToWorld buildTransform(1, 0, { x: 0, y: 0 }, { x: 100, y: 0, z: 200 }); const p mapToWorld(20, 30); console.log(p); // 世界坐标反向换算同理把公式里的矩阵求逆即可。这里有一个非常重要的经验坐标对齐一定要在项目前期、模型还没有最终定稿的时候就完成并且把换算参数存到配置中心。等模型做完再回头对坐标如果发现原点定义错了一个符号所有设备和标注点全部要推倒重来那种返工真的会让人崩溃。3. 平面图数据准备从CAD/图片到可渲染的矢量结构3.1 源数据格式的坑DWG、DXF、GeoJSON、SVG还是自定义JSON平面图数据的源头五花八门最常见的是CAD的DWG文件还有PDF转出的图片、设计院导出的SVG以及少数已经是GeoJSON格式的GIS数据。格式选不对后面全是泪。我最早尝试直接解析DXF结果被各种Block、图层、线型、标注样式折腾得够呛。DXF是文本格式解析起来不算难难的是业务语义重建图纸上画的线哪些是墙体、哪些是门窗示意、哪些是装饰填充、哪些是设备图例程序很难自动区分。如果你在CAD阶段没有约定图层规范解析出来的东西基本不可用。所以我对源数据格式的建议非常明确能用GeoJSON或自定义JSON就不要去解析CAD。GeoJSON是Web端数据交互的事实标准FeatureCollection可以完美表达房间多边形、门点和设备点位而且自带属性机制后续加字段很方便。SVG适合做纯展示但拾取、坐标变换、业务属性绑定都麻烦只适合图纸预览场景。如果必须处理DWG正确姿势是让设计院或者用转换工具先导出DXF再用QGIS、FME、阿里DataV等工具转成GeoJSON。这个转换过程通常需要人的干预把图层按语义归好类。这也是为什么平面图数据准备阶段一定要留出充足的沟通时间数据整理的工时往往比前端开发还多。3.2 一张平面图进入平台的完整流水线我总结的平面图数据处理的流水线是这样的每一步都有明确产物。第一步是源数据收集。拿到CAD原始图纸或者PDF同时要拿到楼层标号、建筑总平面图、指北针方向。没有指北针信息的话坐标对齐会非常痛苦。第二步是图层清理。删掉图框、尺寸标注、填充纹理、文字注释只留下墙体、门窗洞口、房间边界、楼梯电梯等核心结构要素。这一步建议在CAD或者GIS工具里做比写代码自动过滤可靠得多。第三步是坐标校准。确定平面图原点、单位、指北方向生成一个坐标校准配置文件。这个文件就是上一节说的缩放、旋转、平移参数。第四步是要素结构化。把墙线闭合为房间多边形把门窗提取为空间点给每个房间补充房间名、房间号、楼层、面积、所属区域等属性。这一步输出业务所需的空间语义数据。第五步是导出统一JSON。按楼层组织每个楼层里拆出areas、doors、devices、paths这些独立数组供前端直接加载。第六步是前端校验。写一个小的校验工具检查多边形是否自相交、房间是否重叠、设备点是否落在房间内。这些检查可以避免上线后出现“设备点悬浮在墙里”的尴尬局面。我在这一步吃过的亏是有一版项目直接拿设计院导出的填充面当房间边界结果房间多边形之间大量重叠效果看起来和蜘蛛网一样。后来强制加上拓扑校验这类问题才从源头堵住。3.3 多楼层、多区块的数据组织方式平面图导航一旦涉及多楼层数据组织就显得至关重要。我推荐的JSON结构是建筑-楼层-要素三层模型。建筑在最外层每个楼层独立一个节点楼层下再按要素类型分数组。{ buildingId: B001, buildingName: 总部园区A栋, transform: { scale: 1, angle: 0.017, originMap: { x: 0, y: 0 }, originWorld: { x: 100, y: 0, z: 200 } }, floors: [ { floorId: F01, floorName: 一层, level: 1, height: 4.5, mapUrl: /maps/B001_F01.png, boundary: { type: Polygon, coordinates: [] }, areas: [ { id: A1001, name: 前台大厅, type: public, polygon: [] } ], doors: [ { id: D1001, name: 大门, type: entrance, x: 12.3, y: 5.6 } ], devices: [ { id: CAM-1001, name: 摄像头01, type: camera, x: 15.1, y: 8.2 } ] } ] }这个结构的好处是3D场景按floor加载模型平面图模块按floor加载mapUrl和矢量数据两者共用同一份transform配置不会出现模型和平面图数据错位。楼层切换时只需切换当前floorId相关的模型、平面图、设备标注全部联动刷新。多区块的话我建议在楼层下再加一个zone字段比如A区、B区、C区区块内再组织房间和设备。区块之间可以留一条虚拟连接线为后续做跨区块路径导航打基础。4. 核心功能实现渲染、拾取、双向联动一次打通4.1 在3D场景里渲染平面图贴地Plane与矢量Shape的取舍3D场景里的贴地平面图最直接的实现就是把平面图图片贴在一个Plane几何体上平铺在楼层地面高度。这个方案实现快一张图就能出效果适合图纸上有丰富细节、不需要用户逐点交互的场景。我实际项目的做法是贴一张稍大的底图Plane然后用矢量ShapeGeometry叠加画墙体轮廓和房间面。这样远看有图纸细节近看和模型叠加时能半透明融合而且墙体是有深度的实体而不是一张薄片。核心代码思路如下。// 加载平面图纹理并贴到地面 const loader new THREE.TextureLoader(); const mapTexture loader.load(/maps/B001_F01.png); const mapPlane new THREE.Mesh( new THREE.PlaneGeometry(mapWidth, mapHeight), new THREE.MeshBasicMaterial({ map: mapTexture, transparent: true, opacity: 0.6, depthWrite: false }) ); mapPlane.rotation.x -Math.PI / 2; mapPlane.position.set(worldCenter.x, floorHeight 0.02, worldCenter.z); scene.add(mapPlane); // 用房间多边形创建矢量面 function createAreaShape(area) { const shape new THREE.Shape(); area.polygon.forEach(([x, y], index) { const p mapToWorld(x, y); if (index 0) shape.moveTo(p.x, p.z); else shape.lineTo(p.x, p.z); }); return shape; }这里有一个细节很多人会忽略平面图Plane一定要设置depthWrite为false并且稍微抬高一点点比如0.02米否则渲染时会出现地表闪烁z-fighting模型和平面图互相“抖动”。这个坑我第一次做的时候调了半天最后发现是深度写入惹的祸。4.2 2D/3D双向联动相机同步与坐标映射的数学细节平面图导航的核心体验就是2D和3D视角的实时联动。我拆成两个方向3D视角变了平面图上动态更新视野范围框平面图上点击了某个点3D相机移动过去。先说3D到2D的方向。思路是把3D相机视锥体的远平面四个角点沿着视线方向投射到平面图所在的水平面上得到四个交点这四个交点围成的四边形就是当前3D视野在平面图上覆盖的范围。这个四边形投影到2D小地图后用一个半透明矩形框表示。function getCameraFrustumCornersOnMap(camera, mapHeight) { const far camera.far; const tanFov Math.tan(THREE.MathUtils.degToRad(camera.fov * 0.5)); const aspect camera.aspect; const corners [ new THREE.Vector3(-aspect * tanFov * far, tanFov * far, -far), new THREE.Vector3(aspect * tanFov * far, tanFov * far, -far), new THREE.Vector3(-aspect * tanFov * far, -tanFov * far, -far), new THREE.Vector3(aspect * tanFov * far, -tanFov * far, -far) ]; return corners.map((v) { v.applyMatrix4(camera.matrixWorld); const t (mapHeight - camera.position.y) / v.y; return new THREE.Vector3(camera.position.x v.x * t, mapHeight, camera.position.z v.z * t); }); }注意这里的计算忽略了一个细节视线在经过这个角点时如果方向偏离水平面太多t可能变成负数也就是视线反向与水平面相交说明该角点在地平线以下。需要加一个t 0的判断否则视野框会跑到相机后方画面很奇怪。再说2D到3D的方向。用户在小地图或者平面图上点击一个点首先用mapToWorld把平面图坐标换算成3D世界坐标然后做一个相机飞行动画。最简单的方式是保持当前相机高度和朝向不变只平移目标点让相机看向新位置。function flyToMapPoint(mapX, mapY) { const target mapToWorld(mapX, mapY); const currentCamPos camera.position.clone(); const offset currentCamPos.clone().sub(controls.target); const newCamPos new THREE.Vector3(target.x offset.x, currentCamPos.y, target.z offset.z); animateCamera(newCamPos, target); } // animateCamera 可用 TWEEN.js也可以手动插值 function animateCamera(from, to, target) { const tween new TWEEN.Tween({ t: 0 }); tween.to({ t: 1 }, 800); tween.onUpdate(({ t }) { camera.position.lerpVectors(from, to, t); controls.target.lerpVectors(startTarget, target, t); controls.update(); }); tween.start(); }相机飞行时视野框会随着camera的matrixWorld变化自动更新所以2D和3D是天然闭环的不用额外同步。我建议联动更新用requestAnimationFrame驱动而不是监听camera的change事件否则拖动时的性能损耗很大。4.3 点击平面图到3D定位、3D选中回显平面图的完整链路联动不是只有相机移动还要有对象级的双向高亮。我把这条链路整理成标准化事件用发布订阅模式解耦。3D场景选中一台设备时发出select事件事件参数里带设备ID。平面图模块监听这个事件从设备数据里找到对应点位在2D画布上绘制一个闪烁的高亮圆点并把该点在视野框内居中。反向操作同理用户点击平面图上的设备点位发出focus事件3D场景收到后通过Raycaster或直接查找设备组件执行高亮、打开信息面板。const eventBus { handlers: {}, on(type, handler) { (this.handlers[type] || []).push(handler); }, emit(type, payload) { (this.handlers[type] || []).forEach((h) h(payload)); } }; // 3D - 2D eventBus.on(select, (deviceId) { mapModule.highlightDevice(deviceId); }); // 2D - 3D eventBus.on(focus, (deviceId) { sceneModule.focusDevice(deviceId); });这里最关键的问题是ID统一。在数据准备阶段就要保证3D模型的节点ID、设备属性ID、平面图点位ID完全一致。我最开始犯的错是把3D模型里的id叫nodeId平面图里叫deviceId结果事件传参时还要做一层映射排查问题排查到怀疑人生。后来强制规范成全局唯一的deviceId问题迎刃而解。4.4 业务元素挂接设备点位、路径线、告警标记怎么做业务元素是平面图上真正有信息量的内容。设备点位我在2D小地图上用Canvas绘制圆形加图标在3D场景里用Sprite或者CSS2DRenderer标注名称。这里有一个原则凡是高频操作的元素优先放在2D层凡是需要空间位置感知的元素放在3D层。路径线的话平面图上通常需要绘制巡检路线、疏散路线、导航路径。2D Canvas里画线很简单3D场景里我推荐用LineGeometry配合箭头或者光线流动动画视觉效果好很多。告警标记是实际运维里很重要的功能。2D平面图上用红色闪烁圆点表达3D场景里则要让设备材质变红并且加一个垂直的光柱或者环形扩散效果。光柱用MeshBasicMaterial和CylinderGeometry就能做关键是要把opacity设置成半透明并关闭深度写入避免光柱被墙挡住导致告警不可见。5. 性能与兼容性WebGL平台的几个隐藏杀手5.1 大纹理与大量标注点的性能治理平面图通常尺寸很夸张一张A1图纸输出成PNG可能达到几千像素宽。直接整张贴纹理GPU显存和CPU解码都会压力很大移动端甚至可能直接白屏。我用的方案是瓦片化把大图切成256或者512像素的小瓦片按需加载。Three.js里可以用TextureLoader逐个加载或者接入现成的瓦片方案。标注点数量一多DOM做法基本就废了。我见过一个项目在3D场景里用几百个div做设备标签结果用户一旋转模型页面直接卡成PPT。正确的做法是2D平面图用Canvas绘制3D场景用InstancedMesh或Sprite合并渲染几百上千个点位缓存成单次draw call。性能监测这一块我强烈建议从一开始就在页面上挂一个Stats面板实时看FPS、DrawCall和内存占用。不看到具体数字你很难判断优化是不是真的有效。5.2 WebGL上下文丢失避不开的移动端陷阱WebGL上下文丢失是很多人没遇到过、一遇到就发懵的问题。尤其是手机浏览器切后台、锁屏、多标签页切换之后OpenGL上下文被系统回收再切回来画面全黑什么都不动。处理方式是在应用启动时监听webglcontextlost和webglcontextrestored事件。丢失事件触发后阻止默认行为并暂停渲染循环恢复事件触发后需要重新创建所有纹理、Geometry、Shader并恢复相机状态和场景结构。这也是为什么我在项目里把场景资源管理做成可重建结构而不是随手new完就不管了。const canvas renderer.domElement; canvas.addEventListener(webglcontextlost, (event) { event.preventDefault(); cancelAnimationFrame(renderLoopId); console.warn(WebGL context lost); }); canvas.addEventListener(webglcontextrestored, () { rebuildTextures(); rebuildGeometries(); renderLoopId requestAnimationFrame(loop); });不做这个处理上线后大概率会被用户骂“切个后台回来就白屏了”。我自己的教训是桌面端很少触发移动端几乎必现千万别抱着侥幸心理。5.3 持久化存储与容器兼容IDBFS、小游戏模板这些坑要提前知道WebGL应用跑在浏览器里持久化存储并不像文件系统那么可靠。Unity WebGL项目在浏览器里导出后使用IDBFSIndexedDB文件系统写入存档时经常失败尤其在隐私模式、低版本浏览器、或者小游戏容器里IndexedDB的配额和兼容性问题很常见。这个坑我帮别人排查过现象就是存档界面点了保存刷新之后数据全没了。我的建议是存储层一定要做多级降级策略。第一优先级是服务端存储把用户数据和配置放到后端浏览器本地只做缓存第二优先级是IndexedDB第三是localStorage。前端存储失败不能影响核心功能。如果你做的是微信小游戏或者小程序容器里的WebGL项目更要在打包模板阶段就确认好WebGL版本和底层存储能力不要等上线了再补兼容。浏览器WebGL本身也有兼容差异WebGL1和WebGL2在扩展支持、纹理精度上都不一样。建议程序启动时做一个能力探测不支持WebGL2的降级到WebGL1连WebGL1都不支持的给出友好提示而不是白屏。这个探测几行代码就能完成但能省掉后续大量环境报障。6. 第一篇的边界、后续路线与落地建议6.1 这一篇做完平台能跑通什么截止到这篇文章平面图导航的第一阶段我定义的范围是平面图数据接入与校验、2D总览小地图、3D贴地平面图、2D与3D双向定位联动、设备点位渲染与双向高亮。这些功能已经能支撑“从平面图定位到3D从3D选中回显平面图”的核心闭环。在这个阶段路径规划、跨楼层导航、热力图、告警扩散、轨迹回放都还没有做。为什么不做因为如果基础的数据结构和坐标换算没有验证稳定盲目堆功能只会让后面的返工成本翻倍。先把地基夯实再起高楼这个节奏在可视化项目里特别重要。6.2 后面几篇准备写什么这个系列我计划按功能模块往下拆。第二篇重点做路径规划与导航包括平面图上的路网构建、A*寻路、3D场景内的引导线动画。第三篇做设备告警与联动讲告警数据如何与设备点位绑定、光柱特效、声音联动、批量选中。第四篇做多楼层切换与跨层动线重点说楼梯电梯的建模、楼层切换动画、跨层的路径衔接。第五篇如果时间允许写一个性能优化专题把DrawCall合并、纹理压缩、移动端GPU适配这些实战细节完整展开。6.3 如果现在就要动手我建议先做这三件事第一件事整理一套标准的平面图数据JSON结构哪怕很简单也要按建筑-楼层-要素的层次设计好。没有这套结构后面所有功能都会变成临时拼凑的代码。第二件事把坐标对齐工具做成可视化校准用鼠标在平面图上选点在3D场景里选对应点自动计算仿射变换参数并可视化验证。这个工具值得单独花时间做好它会成为整个平台复用的基础设施。第三件事把事件总线和全局ID规范定下来所有模块之间的通信都走事件所有实体都用全局唯一ID。这个约束一开始就严格执行等模块多起来你就知道有多值得。我自己做这套平面图导航最大的体会是技术上的坑大多能填平真正影响项目成败的是对业务工作流的理解。平面图导航做得好的平台不是因为它用了多高深的WebGL技术而是因为它让用户在这个熟悉的视角里能够快速找到信息、完成操作。接下来我也会沿着这个方向把后续的功能模块一个一个拆给大家看。
返回列表