import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {Utils} from "../../../utils.js"; import Sprite from '../../objects/Sprite.js' import Common from "../../utils/Common.js"; import browser from '../../utils/browser.js' const texLoader = new THREE.TextureLoader() const arrowSpacing = 1 //间隔 const arrowSize = arrowSpacing * 0.5 const planeGeo = new THREE.PlaneBufferGeometry(1,1); const sphereSizeInfo = { nearBound : 0.1, farBound:25, minSize : 50, maxSize : 200 //scale:arrowSize, restricMeshScale : true, } //const arrowsShowingCount = 25; //场景里最多展示多少个箭头 const arrowShowMinDis = 10 export class RouteGuider extends THREE.EventDispatcher{ constructor () { super(); this.route = []; this.curve = [] this.scenePoints = [] this.sceneMeshGroup = new THREE.Object3D; this.mapMeshGroup = new THREE.Object3D; this.generateDeferred; viewer.addEventListener('loadPointCloudDone',this.init.bind(this)) this.lastResult;//保存上一个的结果,以便于反向 this.datasetIds = [];//起始和终点的datasetId } init(){ if(this.inited) return; let zoom, resolution=new THREE.Vector2; viewer.mapViewer.addEventListener('camera_changed', e => { if(!this.routeStart || !this.routeEnd) return var camera = e.viewport.camera Common.intervalTool.isWaiting('routeCameraInterval', ()=>{ //延时update,防止卡顿 if(camera.zoom != zoom || !resolution.equals(e.viewport.resolution)){ //console.log('updateMapArrows') this.updateMapArrows(true) zoom = camera.zoom; resolution.copy(e.viewport.resolution) return true } }, browser.isMobile()?500:200) }) let lastPos = new THREE.Vector3 viewer.addEventListener('camera_changed', e => { if(!this.routeStart || !this.routeEnd || !e.changeInfo.positionChanged) return Common.intervalTool.isWaiting('routeCameraInterval2', ()=>{ //延时update,防止卡顿 let currPos = viewer.scene.getActiveCamera().position if(!currPos.equals(lastPos)){ lastPos.copy(currPos) this.updateArrowDisplay() return true } }, 1000) }) var polesMats = { shadowMat: new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/pano_instruction_bottomMarker.png' ) }), sphereMat : new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ) }), hatMats:{ start: new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/pano_instruction_start_route.png' ) }), end: new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/pano_instruction_target_reached.png' ) }) } } polesMats.shadowMat.map.anisotropy = 4 this.poleStart = this.createPole(polesMats, 'start') this.poleEnd = this.createPole(polesMats, 'end') this.sceneMeshGroup.add(this.poleStart) this.sceneMeshGroup.add(this.poleEnd) let map = texLoader.load(Potree.resourcePath+'/textures/routePoint_panorama.png' ) map.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊 this.arrow = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map })) this.arrow.scale.set(arrowSize,arrowSize,arrowSize) Potree.Utils.setObjectLayers(this.arrow, 'sceneObjects' ) /* this.testArrow = this.arrow.clone(); this.testArrow.material = this.arrow.material.clone() this.testArrow.material.color = 'red' */ this.arrows = new THREE.Object3D; this.sceneMeshGroup.add(this.arrows) Potree.Utils.setObjectLayers(this.sceneMeshGroup, 'sceneObjects' ) //this.sceneMeshGroup.traverse(e=>e.renderOrder = 90) viewer.scene.scene.add(this.sceneMeshGroup); this.sceneMeshGroup.visible = /* this.poleStart.visibile = this.poleEnd.visibile = */ false //-------------map--------------------- /* this.mapMarkStart = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/map_instruction_start_route.png' ) })) this.mapMarkEnd = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/map_instruction_target_reached.png' ) })) this.mapMarkStart.renderOrder = this.mapMarkEnd.renderOrder = 2//在箭头之上 */ let map2 = texLoader.load(Potree.resourcePath+'/textures/routePoint_map_fsna.png' ) this.mapArrowMats = { default: new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map:map2, }), fade: new THREE.MeshBasicMaterial({ transparent:true, depthTest:false, map:map2, opacity:0.4 }), } this.mapArrow = new THREE.Mesh( planeGeo, this.mapArrowMats.default) this.mapArrow.scale.set(arrowSize,arrowSize,arrowSize) this.mapArrows = new THREE.Object3D; this.mapArrows.name = 'mapArrows' this.mapMeshGroup.add(this.mapArrows) this.mapMeshGroup.name = 'mapRouteLayer' this.mapMeshGroup.visible = false viewer.mapViewer.dispatchEvent({type:'add', object:this.mapMeshGroup, name:'route'}) this.mapArrow.layers.mask = this.mapArrows.layers.mask // 修改成和map中的layer一样的 viewer.modules.SiteModel.bus.addEventListener('FloorChange',()=>{ if(this.routeStart && this.routeEnd){ this.updateOpacityAtMap() } }) this.inited = true } updateOpacityAtMap(){//只有当前楼层的透明度为1 var currentFloor = viewer.modules.SiteModel.currentFloor //console.log('updateOpacityAtMap', currentFloor && currentFloor.name) const lift = 0.3 // 因为发送请求时用的是floorPosition的高度,而它可能会到画好的floor之下,所以有误差 this.mapArrows.children.forEach((arrow,index)=>{ let pos = this.mapPoints[index].clone() pos.z += lift let inSide = currentFloor && currentFloor.ifContainsPoint(pos) arrow.material = inSide ? this.mapArrowMats.default : this.mapArrowMats.fade //console.log('arrow',index, arrow.material.opacity) }) viewer.mapViewer.dispatchEvent('content_changed') }//但是如果楼层刚好只框柱相机位置而没框住地面位置就不好了…… createPole(polesMats, name){ const height = 1.5, sphereCount = 6, shadowSize = 0.5 /* sphereSizeInfo.scale */, sphereSize = 0.05 var group = new THREE.Object3D; group.name = 'pole_'+name var shadow = new THREE.Mesh(planeGeo, polesMats.shadowMat) shadow.scale.set(shadowSize,shadowSize,shadowSize) var sliceDis = height / (sphereCount+1); group.add(shadow) for(let i=0;i -(Math.abs(e.position.z - viewer.images360.position.z)) ]) let pano = result && result[0] && result[0].item return pano ? pano.floorPosition.z : viewer.bound.boundingBox.min.z + 1 //若在平面图上画实在得不到当前楼层的,大概率是楼层画得不好,那就只能去获取当前楼层的了 //navvis的高度取的是主视图所在楼层的中心高度(可能再高些) } setEndPole(pos){ this.endPolePos = pos this.bus && this.bus.emit('reposEndMarker', pos) } getSourceProjectionIndex(route) {//真正的起始 var e = route.findIndex(function(t) { return t.instruction && t.instruction.type === 'source_projection_to_navgraph' }); return e < 0 ? 0 : e } getDestinationProjectionIndex(route) {//真正的终点 var e = route.findIndex(function(t) { return t.instruction && t.instruction.type === "destination_projection_to_navgraph" }); return e < 0 ? route.length - 1 : e } generateRoute(){ if(!this.routeStart || !this.routeEnd){ return } //array.reduce(function(total, currentValue, currentIndex, arr), initialValue) let create = ()=>{ this.routeLength = this.route.reduce((total, currentValue, currentIndex, arr)=>{ if(currentIndex == 0)return 0 return total + currentValue.distanceTo(arr[currentIndex-1]); },0) let count = Math.max(2,Math.round(this.routeLength / arrowSpacing))//点数 const curve = new THREE.CatmullRomCurve3( this.route ); curve.curveType = 'chordal'//'centripetal' 'catmullrom'这个可能会超出路径外 this.curve = curve const scenePoints = curve.getSpacedPoints( count );//更平均 //const scenePoints = curve.getPoints( count ); scenePoints.splice(0,1);//去掉首尾 scenePoints.pop() this.scenePoints = scenePoints this.updateMapArrows() this.displayRoute() {//map focus on this area const minBound = new THREE.Vector2(1,1)//针对垂直线,在地图上只有一个点 let bound = new THREE.Box2; this.route.forEach(e=>{ bound.expandByPoint(e) }) let size = bound.getSize(new THREE.Vector2) let markerSize = new THREE.Vector2(115,40) //起始和终点的标识呈长方形 let areaSize = viewer.mapViewer.viewports[0].resolution2 let areaArea = areaSize.x * areaSize.y if(areaArea> 800 * 400){//是放大的 markerSize.multiplyScalar(areaArea / (800 * 400) /* / (size.x * size.y) */) } let margin = size.clone().divide(viewer.mapViewer.viewports[0].resolution2).multiply(markerSize) ///边距 重点是起始和终点的标识占据较大 size.add(margin) let center = bound.getCenter(new THREE.Vector2) size.x = Math.max(size.x, minBound.x ) size.y = Math.max(size.y, minBound.y ) let duration = 1000 viewer.mapViewer.moveTo(center, size, duration) } this.bus.emit('gotResult', {dis:this.routeLength}) /* this.generateDeferred && this.generateDeferred.resolve({dis:this.routeLength}) this.generateDeferred = null */ } if(Potree.fileServer){ let dealData = (data)=>{ if(!data.data){ console.log('没有数据') let result if(data && data.code == 4002){ result = data;//正被修改数据集 }else if(this.routeStart.distanceTo(this.routeEnd) < 1){ result = { code: 500, msg: '距离太短,无法规划路线' } }else{ result = { code: 500, msg: '超出数据集范围,无法规划路线' } } this.clearRoute() this.setStartPole(this.routeStart) this.setEndPole(this.routeEnd) this.displayRoute() //还是要显示一下起始 this.bus && this.bus.emit('gotResult', result ) return //this.generateDeferred && this.generateDeferred.resolve() } data = data.data this.clearRoute() let length = data.length if(length < 2){//可能距离太短 console.log('路径点数为'+length+',直接取起点和终点连线') this.route = [this.routeStart, this.routeEnd]; }else{ let startIndex = this.getSourceProjectionIndex(data) let endIndex = this.getDestinationProjectionIndex(data) let effectiveItems = data.slice(startIndex, endIndex + 1 );//只要点云范围内的点 effectiveItems.forEach((item,i)=>{ let pos = viewer.transform.lonlatToLocal.forward(item.location.slice(0)) pos = new THREE.Vector3().fromArray(pos)//.setZ(item.z) this.route.push(pos) }) console.log(this.route) } this.setStartPole(this.route[0]) this.setEndPole(this.route[this.route.length-1]) create() /* distance: 0.17581000000000116 distance_to_previous: 0.17581000000000116 id: 567 instruction: {type: 'source_projection_to_navgraph'} latitude: 22.366605927999238 location: (3) [113.5957510575092, 22.366605927999238, -1.12419] longitude: 113.5957510575092 z: -1.12419 */ } if(this.lastResult && (this.lastResult.data || this.lastResult.data.code != 4002)){//正被修改数据集的话要重新计算 let data = Common.CloneObject(this.lastResult.data) , use; //直接用上次的结果 if(this.lastResult.routeStart.equals(this.routeStart) && this.lastResult.routeEnd.equals(this.routeEnd)){//和上次请求相同 use = true }else if(this.lastResult.routeStart.equals(this.routeEnd) && this.lastResult.routeEnd.equals(this.routeStart)){//..反向 use = true if(data.data){ data.data = this.lastResult.data.data.slice(0).reverse() } } if(use){ console.log('直接用上次的结果') return setTimeout(()=>{dealData(data)}, 1)//延迟是为了等待获得 RouteGuider.generateDeferred } } let start = this.routeStart.clone(); let end = this.routeEnd.clone(); let startLonlat = viewer.transform.lonlatToLocal.inverse(start) let endLonlat = viewer.transform.lonlatToLocal.inverse(end) var query = { source_longitude: startLonlat.x, source_latitude: startLonlat.y, source_z: start.z, destination_longitude: endLonlat.x, destination_latitude: endLonlat.y, destination_z: end.z }; //let url = `/laser/route/${Potree.settings.number}/getRoute/${this.datasetIds[0]}/${this.datasetIds[1]}?` let url = `/laser/route/${Potree.settings.number}/getRoute/${Potree.settings.originDatasetId}?` for(let i in query){ url+= (i + '='+ query[i] +'&') } Potree.fileServer.get(url).then((data)=>{ console.log(data.data) if(!this.routeStart || !this.routeEnd)return this.lastResult = {//保存数据 routeStart : this.routeStart.clone(), routeEnd: this.routeEnd.clone(), data, } dealData(data) }) }else{ //创个直线 /* const sliceDis = 1 let dis = this.routeStart.distanceTo(this.routeEnd); let count = Math.max(2,Math.round(dis / sliceDis))//点数 let realSlideDis = dis / (count-1); let dir = new THREE.Vector3().subVectors(this.routeEnd, this.routeStart).normalize().multiplyScalar(realSlideDis); this.route = [this.routeStart]; for(let i=0;i 300 let count = Math.max(2,Math.round(this.routeLength * zoom / arrowSpacing / (isBig?35:30)))//点数 if(count == this.mapPoints.length+1)return//没变 const mapPoints = this.curve.getSpacedPoints( count ); mapPoints.splice(0,1);//去掉首尾 mapPoints.pop() this.mapPoints = mapPoints var scale = (isBig ? 26 : 22)/zoom this.mapArrow.scale.set(scale,scale,scale) /* this.mapMarkStart.scale.set(scale,scale,scale) this.mapMarkEnd.scale.set(scale,scale,scale) */ if(ifReset){//因为缩放而重新排布箭头 this.clearRoute({resetMap:true}) this.displayRoute({resetMap:true}) } this.updateOpacityAtMap() } updateArrowDisplay(){//根据当前位置更新显示一定范围内的箭头 if(this.scenePoints.length == 0)return /* var a = Common.sortByScore(this.scenePoints , null, [(point)=>{ //是否还要再requires里限制最远距离? var playerPos = viewer.scene.getActiveCamera().position.clone().setZ(0) var pos = point.clone().setZ(0) return -pos.distanceTo(playerPos); }]); //获得展示的起始点 let start = a[0].item let startIndex = this.scenePoints.indexOf(start) this.arrows.children.forEach((e,i)=>{ if(istartIndex+arrowsShowingCount)e.visible = false else e.visible = true }) */ let cameraPos = viewer.scene.getActiveCamera().position this.arrows.children.forEach((e,i)=>{ if(e.position.distanceTo(cameraPos) < arrowShowMinDis) e.visible = true else e.visible = false }) viewer.dispatchEvent('content_changed') } displayRoute(o={}){ if(!o.resetMap){ this.poleStart.position.copy(this.startPolePos || this.routeStart) this.poleEnd.position.copy(this.endPolePos || this.routeEnd) /* this.mapMarkStart.position.copy(this.routeStart).setZ(0) this.mapMarkEnd.position.copy(this.routeEnd).setZ(0) */ this.scenePoints.forEach(e=>this.addArrow(e)) this.arrows.children.forEach((e,i)=>this.setArrowDir(this.arrows.children,i)); } this.sceneMeshGroup.traverse(e=>e.visible = true) this.mapMeshGroup.visible = true this.mapPoints.forEach(e=>this.addMapArrow(e)) this.mapArrows.children.forEach((e,i)=>this.setArrowDir(this.mapArrows.children,i)); viewer.mapViewer.dispatchEvent({'type':'content_changed'}) this.updateArrowDisplay() } clearRoute(o={}){ if(!o.resetMap){ this.routeLength = 0 this.route = [] this.scenePoints = [] this.mapPoints = [] let arrows = this.arrows.children.slice(0) arrows.forEach(e=>{ this.arrows.remove(e) }) } let mapArrows = this.mapArrows.children.slice(0) mapArrows.forEach(e=>{ this.mapArrows.remove(e) }) this.sceneMeshGroup.traverse(e=>e.visible = false) //包括sprite也要设置,防止update this.mapMeshGroup.visible = false viewer.mapViewer.dispatchEvent({'type':'content_changed'}) viewer.dispatchEvent('content_changed') } clear(){//退出 console.log('导航clear') this.routeStart = null this.routeEnd = null this.clearRoute() } } //大概每十米要花一秒 /* 存在的问题: 路径不准确。起始点和终点偏移。 https://uat-laser.4dkankan.com/routeDebug/ 可查整个map的通路点位图 */