暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

商场楼宇室内导航系统

wei_shuo 2024-10-22
101

商场楼宇室内导航系统

本文所涉及所有资源均在传知代码平台可获取

Table of Contents

效果图

导航效果图

在这里插入图片描述

查看信息

在这里插入图片描述

数据加载

因为是一个本地项目,所以我把所有的数据都存放到对应的json文件中,再自定义一个fetch方法,用来请求数据,以下是楼层文件信息,店铺信息就不展示了,结构都是差不多的。这里简单陈述一下,方便之后文章的理解

[{ "url": "./static/model/F1.gltf", "name": "F1", "id": 1 }, { "url": "./static/model/F1.gltf", "name": "F2", "id": 2 }, { "url": "./static/model/F1.gltf", "name": "F3", "id": 3 } ]

异步请求数据代码

export function GetFloor(url:string) { return new Promise( function (resolve, reject) { window.fetch(url).then(function (data) { resolve(data.json()) }) } ) }

通过异步请求json文件,得到json文件内部数据

await GetFloor('./static/json/floor.json').then((res: any) => { this.floorData = res })

加载模型

楼层文件是我用c4d制作的一个 obj文件,由于c4d目前对gltf输入和输出并不友好,所以作为前端,有前端的解决方式,用Obj2gltf的转换库git地址,开箱即用,当然如果你身边有3d设计师,可以让设计师用Blender将obj转成gltf的格式

模型选型

这里稍微讲一下文件格式选型,文中用到的是gltf格式,gltf可以以最小的体积表现更多的内容,包括贴图,着色器等等,而且传输性能相对高效。作为通用模型格式,设计师也能更好的交付自己的模型,或者与设计师之间的协作也更方便。个人作为一个野生前端开发,没有任何支援,只能自己瞎搞搞

处理楼层模型

加载到楼层模型后 需要将楼层展示到场景中,但是具体展示多少,怎么才能全部展示出来,这就需要计算相机的距离,我这里偷懒了,所有的楼层都是用的同一个模型,所以计算一个楼层的尺寸就知道其他的尺寸了,如果不是相同的楼层就需要计算所有楼层的尺寸并找到一个最大值

if (this.loadFloorIndex === 0) { // 根据第一层计算相机位置,将所有楼层适配显示到屏幕 const ground = scene.getObjectByName('floor') const size = new THREE.Vector3() this.$getBox.getbox(ground).getSize(size) // 两条直角边 const a = size.x const b = size.z // 求斜边 const c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)) this.camera.position.set(c + Math.min(datalength, 4) * 50, Math.min(helfLength, 4) * this.floorHeight, c + Math.min(datalength, 4) * 50) }

上述代码中 helfLength是楼层数量/2,作为一个根据楼层数量改变的变量,将控制相机的位置,从而让所有的楼层都展示在场景中

3层效果图

在这里插入图片描述

5层效果图

在这里插入图片描述

绑定店铺

店铺数据

{ "id": 11, "floorid": 1, "name": "兰巴赫", "coordinates": "80,37", "doorcoordinates": "", "floorname": "F1", "format": "餐饮" },

coordinates字段作为模型和数据之间的标识,是通过box3.getCenter获取

const cp = Math.floor(v3.x) + ',' + Math.floor(v3.z)

通过判断店铺所在楼层和楼层的center数据将模型和数据绑定起来

member.coordinates === cp && member.floorid === floorId

创建店铺名称

export function createText(name, vector) { var text = document.createElement("div"); text.className = "member-name"; text.textContent = name let css2d: any = new CSS2DObject(text); css2d.position.copy(vector); css2d.name = name css2d.isType = '2' return css2d }

动态显示隐藏2d元素

在这里插入图片描述

基于射线的原理做一个点到点的射线检测

export function pointRay(star, end, children) { // createLine(star, end); let nstar = star.clone(); // 克隆一个新的位置信息,这样不会影响传入的三维向量的值 let nend = end.clone().sub(nstar).normalize(); // 克隆一个新的位置信息,这样不会影响传入的三维向量的值 var raycaster = new THREE.Raycaster(nstar, nend); // 创建一个正向射线 var intersects = raycaster.intersectObjects( children, true ); // console.log(intersects[0].point.length()) // console.log(end.length()) let jclang = 0 let textlang = 0 if (intersects.length != 0) { jclang = star.distanceTo(intersects[0].point) textlang = star.distanceTo(end) } return jclang < textlang; }

从相机发射一条射线到2d元素,将楼层信息作为检测目标,如果在这条射线上检测到楼层的存在了,说明当前楼层挡住了目标2d元素,这里用到的检测是每个楼层的底板部分,忽略店铺模块,这是出于性能考虑的

空气墙

射线检测会检测每一个目标模型的顶点信息,当顶点信息过多,性能肯定会有损耗,比如在熊猫基地这篇文章中用到的地球模型,就是特别复杂的模型,在不得已的情况,2d的射线检测,目标模型就不能使用这个模型,在代码中,使用了一个类似大小的球体作为检测目标,像这种球体啊,立方体啊,都比较好找替代的,如果是非常规模型,就需要设计师提供对应的空气墙,毕竟性能提升是3d开发绕不过的话题

查看信息

在页面右上角有一个操作模板,是区分功能的,可以查看店铺信息、楼层信息、室内导航等等。
同样是使用射线检测,点击时根据选择的的右上角功能进行不同的操作

var handleClick = function (event, _this) { if ((window as any).unRay) { return } _this.mouse.x = (event.clientX / document.body.offsetWidth) * 2 - 1; _this.mouse.y = -(event.clientY / document.body.offsetHeight) * 2 + 1; if (event.touches) { _this.mouse.x = (event.touches[0].pageX / window.innerWidth) * 2 - 1; _this.mouse.y = -(event.touches[0].pageY / window.innerHeight) * 2 + 1; } else { _this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; _this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; } var raycaster = new THREE.Raycaster(); raycaster.setFromCamera(_this.mouse, _this.camera); var raylist = raycaster.intersectObjects(_this.floorGroup.children, true); if (raylist[0]) { let obj = raylist[0].object let handleState = (window as any).$handleState // 处理轨迹 if (handleState === 'trail') { handleTrail(obj, _this) } else if (handleState === 'floor') { let obj = raylist[0].object.parent handleFloor(_this, obj) } else if (handleState === 'member') { let obj = raylist[0].object handleMember(_this, obj) } } }

楼梯

电梯数据

{ “id”: 101, “floorid”: 1, “name”: “麦当劳扶梯”, “coordinates”: “-1,0”, “doorcoordinates”: “”, “floorname”: “F1”, “type”: 0 },

导航

室内导航的流程相当复杂而又精密。首先,系统需要准确识别用户当前的位置,并确定用户所要前往的目的地是否与其在同一楼层。如果目的地位于同一楼层,那么导航系统将直接指引用户前往目的地;但如果目的地位于不同楼层,系统将面临更复杂的挑战。

在这种情况下,系统首先会检索并选取离用户当前位置最近的电梯,以便跨越楼层。选择电梯时,需要考虑多种因素,如电梯当前位置、运行状态以及用户所需楼层等。一旦电梯选定,系统将指导用户前往该电梯并搭乘。

接下来是电梯内的移动过程。系统需要准确控制电梯的运行方向,确保用户能够顺利抵达目的楼层。在到达目的楼层后,用户需要再次确定自己的位置并根据导航系统的指引,前往具体的目的地。

因此,整个导航过程可划分为三个关键阶段:跨楼层定位、电梯选择与移动、最终目的地导航。每个阶段都需要系统精确的定位和智能的决策,以确保用户能够高效、准确地到达目的地

if (mySelfFloorId === memberFloorId) { trail = gettrail(mySelfPosition, obj) if (trail.length === 0) { // 需要记录进店前的终点,作为下一次出发的起点, // 代码需要重新整理 alert('你无路可逃') return } let memberCp = v2tov3(obj.cp, obj.parent.getWorldPosition().y) let endToPcTrail = getEndToPc(trail[trail.length - 1], memberCp) let allTrail = trail.concat(endToPcTrail) Drawline(_this, allTrail) // var geometry = new THREE.BufferGeometry().setFromPoints(allTrail); // var material = new THREE.LineBasicMaterial({ // color: '#f14f54' // }); // var line = new THREE.Line(geometry, material); // _this.scene.add(line) headerMove(allTrail, _this.mySelf) } else { // 找到最近的电梯 // 获取头像所在楼层 const headerFloor = _this.scene.getObjectByProperty('floorId', _this.mySelf.mySelfFloorId) let minEle = null let minLen = 9999 let eleCp = new Vector3() let eleVector3 = new Vector3() let floorPosition = new THREE.Vector3() as any headerFloor.children.forEach((mesh, index) => { // 找到所有电梯 if (mesh.elevatorId) { // 获取电梯中心点 eleCp = v2tov3(mesh.cp, mesh.getWorldPosition(new Vector3()).y) let len = eleCp.distanceTo(mySelfPosition) if (minLen > len) { minLen = len minEle = mesh mesh.getWorldPosition(floorPosition) eleVector3 = eleCp } } }) if (minEle) { // 第一阶段轨迹 let trailFirst = gettrail(mySelfPosition, minEle) // 获取第二段轨迹(上电梯) let firstEleFloor = eleVector3 let endEleFloor = new THREE.Vector3() // 获取重点楼层的Y obj.parent.getWorldPosition(endEleFloor) _this.mySelf.mySelfFloorId = obj.parent.floorId // 获取到第二段结束的点 let endPoint = firstEleFloor.clone().setY(endEleFloor.y) let trailSecond = getEndToPc(firstEleFloor, endPoint) let points = obj.parent.userData.trailPoint // 寻找距离电梯上来后的最近可以行走的点 let minPoint = null let minLen = 99999 for (let i = 0; i < points.length; i++) { let p = points[i] let len = endPoint.distanceTo(p) if (len < minLen) { minLen = len minPoint = p } } // 获取电梯到店铺最近点的 let trailThird = gettrail(minPoint, obj) // let lastTrail // 获取店铺最近点到店铺中心的路径 let lastTrail = getEndToPc(trailThird[trailThird.length - 1], v2tov3(obj.cp, obj.getWorldPosition(new THREE.Vector3()).y)) if (trailFirst.length === 0 || trailSecond === 0 || trailThird.length === 0 || lastTrail.length === 0) { alert('你无路可逃') return } let allTrail = trailFirst.concat(trailSecond).concat(trailThird).concat(lastTrail) headerMove(allTrail, _this.mySelf) Drawline(_this, allTrail) // var geometry = new THREE.BufferGeometry().setFromPoints(allTrail); // var material = new THREE.LineBasicMaterial({ // color: '#f14f54' // }); // var line = new THREE.Line(geometry, material); // _this.scene.add(line) } }

文章代码资源点击附件获取

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论