import * as THREE from "../../../libs/three.js/build/three.module.js"; import SplitScreen from "../../utils/SplitScreen" import {BuildingBox} from "./BuildingBox" import Common from "../../utils/Common.js"; import {Images360} from '../Images360/Images360' import {KeyCodes} from '../../KeyCodes' import {config } from "../../settings.js"; import math from "../../utils/math.js"; const minFloorHeight = 0.5 const ifDrawDatasetBound = true //显示一下数据集的tightBound线框 const minMarkers = 3 const Limit = {zMin:-config.map.cameraHeight, zMax:config.map.cameraHeight,} //不能超过camera的高度,为了对称所以也限制了最低 var SiteModel = { bus: new THREE.EventDispatcher(), entities:[], //所有实体 buildings:[], //所有建筑父集 meshGroup: new THREE.Object3D, inEntity : null, lastPos: new THREE.Vector3(Infinity,Infinity,Infinity), init: function(){ viewer.scene.scene.add(this.meshGroup) this.meshGroup.name = 'siteModel' this.SplitScreen = SplitScreen if(Potree.settings.editType == 'pano'){ return } this.createHeightPull(); if(Potree.settings.isTest && ifDrawDatasetBound){ viewer.addEventListener('allLoaded',()=>{ viewer.scene.pointclouds.forEach(pointcloud=>{ let boxPoints = pointcloud.getUnrotBoundPoint(); let boundingBox = new BuildingBox({ name: '数据集tightBound_'+pointcloud.dataset_id, points: boxPoints, buildType : 'dataset', zMax: pointcloud.bound.max.z, zMin: pointcloud.bound.min.z, ifDraw:true }) this.meshGroup.add(boundingBox) //boundingBox.markers.forEach(e=>e.visible = false) }) }) } if(Potree.settings.isOfficial){ viewer.addEventListener('camera_changed', e => { this.updateEntityAt() }) } { let pressDelete = (e)=>{ if(e.keyCode == KeyCodes.BACKSPACE || e.keyCode == KeyCodes.DELETE){ if(this.selectedMarker){ let entity = this.selectedMarker.parent let index = entity.markers.indexOf(this.selectedMarker) entity.removeMarker(index) if(entity.points.length<2){//删到只剩一个点时重新画(如果是hole的点,直接删除hole吧?) this.startInsertion('resume',entity) } } } } viewer.inputHandler.addEventListener('keydown', pressDelete) } }, updateEntityAt(force){ //if(!this.entities.length || this.editing) return //编辑时也要根据位置显示不同楼层的漫游点与cad let fun = ()=>{ //延时update,防止卡顿 let currPos = viewer.mainViewport.view.position if(force || !currPos.equals(this.lastPos)){ //console.log('currPos ', currPos.toArray()) this.lastPos.copy(currPos) let entity; let searchPos = Potree.settings.displayMode == 'showPanos' ? viewer.images360.currentPano : currPos entity = this.pointInWhichEntity(searchPos, 'room'); if(force || this.inEntity != entity ){ let oldEntity = this.inEntity this.inEntity = entity console.log('buildingChange', entity) this.bus.dispatchEvent({type:'buildingChange',entity}) //this.updatePanosVisible(oldEntity, this.inEntity) let lastFloor = this.currentFloor //oldEntity ? oldEntity.buildType == 'floor' ? oldEntity : oldEntity.buildType == 'room' ? oldEntity.buildParent : null : null; //基本只会是floor或room let currentFloor = entity ? entity.buildType == 'floor' ? entity : entity.buildType == 'room' ? entity.buildParent : null : null; //基本只会是floor或room if(currentFloor != lastFloor || force){ console.log('改变了floor',lastFloor,currentFloor) this.currentFloor = currentFloor this.bus.dispatchEvent({type:'FloorChange',currentFloor}) } } force = false return true } } if(force)fun() else Common.intervalTool.isWaiting('sitemodelCameraInterval', fun , 500) }, enter:function(){ Potree.Log('sitemodel enter') this.clear() //确保全部清空 this.editing = true //this.updatePanosVisible(null, null, true)//show all viewer.updateFpVisiDatasets() let mapViewport = viewer.mapViewer.viewports[0] SplitScreen.splitScreen4Views({siteModel:true/* , viewports:[{name:'Top',viewport : mapViewport }] */}) viewer.viewports.forEach(e=>{ if(e.name != 'mapViewport'){ e.layersAdd('siteModelMapUnvisi') } if(e.name == 'right' || e.name == 'back'){ e.layersAdd('siteModeSideVisi') } }) viewer.images360.panos.forEach(pano=>{ viewer.setObjectLayers(pano.marker, 'siteModelMapUnvisi' ) }) mapViewport.layersAdd('siteModeOnlyMapVisi') //只有mapViewport能看到marker }, leave:function(){ Potree.Log('sitemodel leave') let mapViewport = viewer.mapViewer.viewports[0] SplitScreen.recoverFrom4Views() viewer.viewports.forEach(e=>{ if(e.name != 'mapViewport'){ e.layersRemove('siteModelMapUnvisi') } if(e.name == 'right' || e.name == 'back'){ e.layersRemove('siteModeSideVisi') } }) viewer.images360.panos.forEach(pano=>{ viewer.setObjectLayers(pano.marker, 'sceneObjects' ) }) mapViewport.layersRemove('siteModeOnlyMapVisi') this.clear() this.editing = false viewer.updateFpVisiDatasets() } , addFloor:function(parent, dirType, sid, name){//dirType:'top'|'bottom'在上方建还是下方。如果建筑中没有楼层,默认在基底建一个 let buildType = 'floor' let zMin, zMax if(parent.buildChildren.length == 0){ zMin = parent.zMin zMax = zMin + Potree.config.siteModel.floorHeightDefault }else{ if(dirType == 'bottom'){ //var btm = Common.find(parent.buildChildren,null,[e=>e.zMin]) var btm = parent.buildChildren[0] zMax = btm.zMin zMin = zMax - Potree.config.siteModel.floorHeightDefault }else{ //var top = Common.find(parent.buildChildren,null,[e=>e.zMax]) var top = parent.buildChildren[parent.buildChildren.length - 1] zMin = top.zMax zMax = zMin + Potree.config.siteModel.floorHeightDefault } } let prop = { buildType, //name : Potree.config.siteModel.names[buildType], zMin, zMax, buildParent:parent, sid, name, ifDraw:true } var floor = new BuildingBox(prop); /* parent.buildChildren.push(floor) this.meshGroup.add(floor); this.entities.push(floor) */ floor.update() this.addEntity(floor,parent) //this.selectEntity(floor) if(this.selected == parent){//重新选择下,为了显示新楼层线框 parent.unselect() parent.select() } return floor }, startInsertion:function(buildType, parent, sid, name, callback, cancelFun){ let zMin, zMax, entity, resume let mapViewport = viewer.mapViewer.viewports[0] if(buildType == 'resume'){//继续画(使用最后一个点或者新加的点) resume = true entity = parent buildType = parent.buildType //删除原先所有的点,因为它们已经添加了事件,会很麻烦: entity.reDraw(0) entity.isNew = true //当作新的来画 } if(!resume){ if(buildType == 'hole' || buildType == 'room'){ zMin = parent.zMin zMax = parent.zMax }else if(buildType == 'building'){ parent = null zMin = viewer.bound.boundingBox.min.z zMax = viewer.bound.boundingBox.min.z } if(buildType == 'hole'){ entity = parent.addHole() entity.isNew = true this.selectEntity(parent) entity.select() console.log('挖洞 ',entity.uuid) }else{ let prop = { buildType, //name : Potree.config.siteModel.names[buildType],//'building', zMin, zMax, buildParent:parent, sid, name, ifDraw:true } entity = new BuildingBox(prop); entity.isNew = true this.selectEntity(entity) } this.addEntity(entity, parent) } let timer; let endDragFun = (e) => { if (e.button == THREE.MOUSE.LEFT ) { var marker = entity.addMarker({point:entity.points[entity.points.length - 1].clone()}) //entity.editStateChange(true) //重新激活reticule状态 entity.continueDrag(marker, e) } else if (e.button === THREE.MOUSE.RIGHT ) { if(e.pressDistance < Potree.config.clickMaxDragDis )end(e);//非拖拽的话 else entity.continueDrag(null, e/* .drag.object */) } }; let finish = ()=>{//结束绘画 viewer.removeEventListener('cancel_insertions', Exit); entity.removeEventListener('unselect', Exit); clearTimeout(timer) entity.editStateChange(false) //pressExit && viewer.inputHandler.removeEventListener('keydown', pressExit); callback && callback(entity) } let end = (e={}) => {//尝试结束 /* 退出的三种形式: 1 普通:如果大于三个marker,结束且保留;否则重新画。() 2 删除:直接结束且删除。(remove) 3 结束:如果大于三个marker,结束且保留;否则结束且删除。 (finish) 4 保留:无论几个marker,都保留着,结束。(remain) */ if(e.remove){ finish() return this.removeEntity(entity) } if(!e.remain && !e.finish && !e.remove && entity.markers.length<=minMarkers){//右键 当个数不够时取消 //重新开始画 entity.reDraw(1) viewer.updateVisible(entity.markers[0],'unMove',false); var f = ()=>{ viewer.updateVisible(entity.markers[0],'unMove',true); entity.removeEventListener('dragChange',f) } entity.addEventListener('dragChange',f) //console.log('waitcontinue') entity.continueDrag(entity.markers[0], e) return } finish() if (e.remain || !e.remove && entity.markers.length > 3) {//保留 entity.removeMarker(entity.points.length - 1); entity.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') }) if(buildType == 'room'){ this.fitPullBox() } entity.isNew = false entity.addMidMarkers() }else{ this.removeEntity(entity) //直接删除没画好的,比较简单。这样就不用担心旧的continueDrag仍旧触发了 } return entity }; let Exit = (e)=>{ //强制结束 entity.removeEventListener('unselect', Exit); if(viewer.inputHandler.drag){//还未触发drop的话 viewer.inputHandler.drag.object.dispatchEvent({ type: 'drop', drag: viewer.inputHandler.drag, viewer: viewer, pressDistance:0, button : THREE.MOUSE.RIGHT }); viewer.inputHandler.drag = null }else{ end({remain:true}) } viewer.inputHandler.drag = null } viewer.dispatchEvent( 'cancel_insertions' );//取消之前的 viewer.addEventListener('cancel_insertions', Exit); entity.addEventListener('unselect', Exit); var marker = entity.addMarker({point:new THREE.Vector3(0, 0, 0)}) viewer.updateVisible(marker,'unMove',false);//这时候的位置是假的(0,0,0)所以先不可见 var f = ()=>{ viewer.updateVisible(marker,'unMove',true); entity.removeEventListener('dragChange',f) } entity.addEventListener('dragChange',f) marker.isDragging = true viewer.inputHandler.startDragging(marker , {dragViewport:mapViewport, endDragFun, notPressMouse:true} ); //notPressMouse代表不是通过按下鼠标来拖拽. dragViewport指定了只能在地图上拖拽 return entity; }, getPreDealData(points, zMin, zMax, initial, buildType, parent){ /* if( buildType == 'building' ){ zMax = zMin //强制变得一样,作为基底。如果有必要,保存时再算真实的zMax。目前zMin没有保存所以数据是错的,会直接根据floor计算 } */ var bound = viewer.bound.boundingBox if(buildType == 'building' && initial){//初始数据错的,要自己建(只有一个building和floor) 原posIsLonlat console.log('空间模型未编辑过, 初始化了一个') points = [ new THREE.Vector3(bound.min.x, bound.min.y,0), new THREE.Vector3(bound.max.x, bound.min.y,0), new THREE.Vector3(bound.max.x, bound.max.y,0), new THREE.Vector3(bound.min.x, bound.max.y,0), ] zMin = bound.min.z zMax = bound.max.z /* points = points.map(e=>{ return viewer.transform.lonlatToLocal.forward(e) }) */ }else{//相对于初始数据集的模型内坐标 points = points.map(e=> this.transform(e, 'fromDataset')) if(buildType == 'floor' && initial){ zMin = bound.min.z zMax = bound.max.z } } return {points, zMax, zMin } }, resetFromData:function(entity, points=[], holes=[], zMin, zMax ){ var {points, zMax, zMin} = this.getPreDealData(points, zMin, zMax , this.autoBuild , entity.buildType, entity.buildParent ) if(entity.buildType != 'floor' )entity.points = points if(entity.buildType == 'room'){ entity.zMin = zMin entity.zMax = zMax }else if(entity.buildType == 'floor'){//改楼高 let height = zMax - zMin let zMax2 = entity.zMin + height SiteModel.changeZ(entity, 'zMax', zMax2) } { //删除旧的holes重新添加 let holesOld = entity.holes holesOld.forEach(e=>{ entity.removeHole(e) }) holes.forEach(points =>{ let ps = points.map(e=> this.transform(e, 'fromDataset')) let hole = entity.addHole(ps) hole.addMidMarkers() }) } entity.update() return entity } , createFromData:function( buildType, parent ,sid, name, points=[], holes=[], zMin, zMax, initial,panos,flagPano){ if(buildType != 'building' && buildType != 'floor' && buildType != 'room' ) return var {points, zMax, zMin} = this.getPreDealData(points, zMin, zMax , initial, buildType, parent ) { let getPano = (id)=>{ return viewer.images360.panos.find(pano=>pano.id == id) } panos = panos ? panos.map(e=>getPano(e)) : []; flagPano = flagPano != void 0 ? getPano(flagPano) : null ; //最中心的pano 或者 最靠近该实体的pano(当panos为空时) if(!this.editing && buildType == 'floor' && !flagPano){//没有的话可能是自动添加的floor,直接用parent的吧 panos = parent.panos; flagPano = parent.flagPano; } } let prop = { buildType, points, name, sid, zMin, zMax, buildParent:parent, ifDraw:this.editing || Potree.settings.drawEntityData, panos, flagPano, autoBuild : initial } let entity = new BuildingBox(prop) SiteModel.addEntity(entity, parent ) if(this.editing){ if(buildType == 'building'|| buildType == 'room'){ entity.addMidMarkers() } } holes.forEach(points =>{ let ps = points.map(e=> this.transform(e, 'fromDataset')) let hole = entity.addHole(ps) this.editing && hole.addMidMarkers() }) /* if(buildType == 'floor'){ this.updateBuildingZ(parent) } */ return entity }, transform:function(pos, type){ if(Potree.settings.editType == 'pano'){ // 模型不经转换 return new THREE.Vector3().copy(pos).setZ(0); } if(type == 'toDataset'){ let point = Potree.Utils.datasetPosTransform({ toDataset: true, position: pos.clone(), datasetId: Potree.settings.originDatasetId }) return new THREE.Vector2().copy(point) }else{ let position = new THREE.Vector3().copy(pos).setZ(0) return Potree.Utils.datasetPosTransform({ fromDataset: true, position, datasetId: Potree.settings.originDatasetId }) } }, addEntity:function(entity, parent){ this.meshGroup.add(entity); this.entities.push(entity) if(entity.buildType == 'building'){ this.buildings.push(entity) }else{ parent.buildChildren.push(entity) } if(entity.buildType == 'room'){ entity.addEventListener('marker_dropped',()=>{ this.fitPullBox() }) }else if(entity.buildType == 'floor'){ this.updateBuildingZ(parent) parent.dispatchEvent({type:'addFloor'}) } {//仅能存在一个marker被选中。选中的点可以被删除 entity.addEventListener('clickMarker', (e)=>{ if(this.selectedMarker == e.marker){ this.selectedMarker.dispatchEvent({type:'clickSelect',state:false}) this.selectedMarker = null }else{ if(this.selectedMarker){ this.selectedMarker.dispatchEvent({type:'clickSelect',state:false}) } this.selectedMarker = e.marker this.selectedMarker.dispatchEvent({type:'clickSelect',state:true}) } }) entity.addEventListener('removeMarker', (e)=>{ if(this.selectedMarker == e.marker){ this.selectedMarker = null } }) let unselect = (e)=>{//取消选中实体或删除后 if(this.selectedMarker && entity.markers.includes(this.selectedMarker)){ this.selectedMarker.dispatchEvent({type:'clickSelect',state:false}) this.selectedMarker = null } } entity.addEventListener('dispose', unselect) entity.addEventListener('unselect', unselect) } //console.log('添加实体:', entity.buildType, entity.sid, entity.uuid) }, removeEntity : function(entity){ if(!this.entities.includes(entity))return console.log('删除实体:', entity.buildType, entity.sid) if(this.selected == entity){ this.height_pull_box.visible = false this.selectEntity(null) } if(entity.buildType == 'building'){ var index = this.buildings.indexOf(entity); if(index>-1){ this.buildings.splice(index,1) } }else{ var index = entity.buildParent.buildChildren.indexOf(entity); if(index>-1){ entity.buildParent.buildChildren.splice(index,1) } } var index = this.entities.indexOf(entity); if(index>-1){ this.entities.splice(index,1) } entity.dispose() let buildChildren = entity.buildChildren.slice() buildChildren.forEach(e=>this.removeEntity(e)) }, updateBuildingZ:function(building){ building.buildChildren = building.buildChildren.sort((e,a)=>e.zMin-a.zMin)//从低到高排序 building.zMin = building.zMax = building.buildChildren[0].zMin //基底高度 //building.zMax = building.buildChildren[building.buildChildren.length-1].zMax if(this.editing) building.update({dontUpdateChildren:true}) building.dispatchEvent('updateBuildingZ') }, selectEntity : function(entity, state=true){ if(state === false){ entity.unselect() if(this.selected == entity)this.selected = null return } if(this.selected == entity || entity && entity.buildType == 'hole')return //this.buildings.forEach(e=>e.unselect()) this.selected && this.selected.unselect() this.height_pull_box.visible = false if(entity){ entity.select() } this.selected = entity if(entity && (entity.buildType == 'floor' || entity.buildType == 'room' )){ this.height_pull_box.visible = true this.fitPullBox() } if(entity && !entity.isNew && (entity.buildType == 'building' || entity.buildType == 'room' ) && entity.points.length<2){ this.startInsertion('resume',entity) //继续画 } }, fitPullBox: function(){ //自适应拖拽楼层的pullMesh if(!this.selected || this.selected.buildType!= 'floor' && this.selected.buildType!= 'room')return let bound = new THREE.Box3(); bound.expandByObject(this.selected.box) let center = bound.getCenter(new THREE.Vector3() ) let size = bound.getSize(new THREE.Vector3() ) this.height_pull_box.scale.copy(size) this.height_pull_box.position.copy(center) }, changeZ:function(entity, dirType, value){ // floor or room 修改zMin or zMax let max, min //limit if(entity.buildType == 'floor'){//楼层 let index = entity.buildParent.buildChildren.indexOf(entity) if(dirType == 'zMax'){ let upper = entity.buildParent.buildChildren[index+1]; entity.zMax = Math.min(Limit.zMax, value) min = entity.zMin + minFloorHeight if(entity.zMax < min){ entity.zMax = min }else{ if(upper){ max = upper.zMax - minFloorHeight; if(entity.zMax > max){ entity.zMax = max } } } if(upper){ upper.zMin = entity.zMax upper.update() upper.dispatchEvent({type:'changeHeight'}) } }else{ let lower = entity.buildParent.buildChildren[index-1]; entity.zMin = Math.max(Limit.zMin, value) max = entity.zMax - minFloorHeight if(entity.zMin > max){ entity.zMin = max }else{ if(lower){ min = lower.zMin + minFloorHeight; if(entity.zMin < min){ entity.zMin = min } } } if(lower){ lower.zMax = entity.zMin lower.update() lower.dispatchEvent({type:'changeHeight'}) } if(index == 0)this.updateBuildingZ(entity.buildParent) } }else if(entity.buildType == 'room'){//房间 //按照navvis的是不一定限制在当前楼层,只要高度不超过当前楼层即可。 let maxHeight = entity.buildParent.zMax - entity.buildParent.zMin if(dirType == 'zMax'){ min = entity.zMin + minFloorHeight max = entity.zMin + maxHeight entity.zMax = THREE.Math.clamp(value, min, max); }else{ min = entity.zMax - maxHeight max = entity.zMax - minFloorHeight entity.zMin = THREE.Math.clamp(value, min, max); } } entity.update() entity.dispatchEvent({type:'changeHeight'}) //this.selected.emit('update') this.fitPullBox() }, createHeightPull:function(){ //拖拽楼层的bounding box let boxGeo = new THREE.BoxBufferGeometry( 1, 1, 1/4 ) let boxMat = new THREE.MeshBasicMaterial({ color:"#F00", opacity:0, transparent:true, depthTest:false, side:2 }) let height_pull_box_up = new THREE.Mesh(boxGeo,boxMat) let height_pull_box_down = new THREE.Mesh(boxGeo,boxMat) height_pull_box_up.name = 'height_pull_box_up'; height_pull_box_down.name = 'height_pull_box_down'; this.height_pull_box = new THREE.Object3D(); this.height_pull_box.name = 'height_pull_box' this.height_pull_box.add(height_pull_box_up) this.height_pull_box.add(height_pull_box_down) this.height_pull_box.visible = false this.meshGroup.add(this.height_pull_box) height_pull_box_up.position.set(0,0,1/2/* 3/8 */) height_pull_box_down.position.set(0,0,-1/2/* -3/8 */) viewer.setObjectLayers(this.height_pull_box, 'siteModeSideVisi' ) let mouseover = (e)=>{ viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"siteModelFloorDrag" }) } let mouseleave = (e)=>{ viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"siteModelFloorDrag" }) } let firstZ, firstIntersect; let drag = (e)=>{ var intersectPoint = e.intersectPoint.orthoIntersect //不要点云的intersect,只要orthocamera算出的平面intersect if(firstIntersect != void 0){ let moveZ = intersectPoint.z - firstIntersect if(this.selected.buildType == 'floor'){//楼层 //限制高度不能超过上下 if(e.target == height_pull_box_up){ if(firstZ == void 0)firstZ = this.selected.zMax this.changeZ(this.selected, 'zMax', firstZ + moveZ) }else{ if(firstZ == void 0)firstZ = this.selected.zMin this.changeZ(this.selected, 'zMin', firstZ + moveZ) } }else if(this.selected.buildType == 'room'){//房屋 if(e.target == height_pull_box_up){ if(firstZ == void 0)firstZ = this.selected.zMax this.changeZ(this.selected, 'zMax', firstZ + moveZ) }else{ if(firstZ == void 0)firstZ = this.selected.zMin this.changeZ(this.selected, 'zMin', firstZ + moveZ) } } }else{ firstIntersect = intersectPoint.z } } let drop = (e)=>{ firstZ = firstIntersect = null } height_pull_box_up.addEventListener('mousemove',mouseover) height_pull_box_down.addEventListener('mousemove',mouseover) height_pull_box_up.addEventListener('mouseleave',mouseleave) height_pull_box_down.addEventListener('mouseleave',mouseleave) height_pull_box_up.addEventListener('drag',drag) height_pull_box_down.addEventListener('drag',drag) height_pull_box_up.addEventListener('drop',drop) height_pull_box_down.addEventListener('drop',drop) }, pointInWhichEntity(location, buildType, ifIgnoreHole){//buildType是要找的建筑类型 //location 可以是pano或者坐标 //由于房间可能在building外,所以房间要另外单独识别。 let lastResult; //最接近的上一层结果,如果没有result返回这个 let result let level = { building: 0, floor: 1, room: 2 } let traverse = (parent, buildType)=>{//返回第一个符合标准的实体 let contains; if(location instanceof THREE.Vector3){ contains = parent.ifContainsPoint(location) }else{//is pano contains = parent.panos.includes(location) } if(contains){ if(!lastResult || level[lastResult.buildType] < level[parent.buildType] )lastResult = parent if(parent.buildType == buildType){ return parent }else{ for(let i=0,len=parent.buildChildren.length; i{ return traverse(building, 'building') //在building中 }], [(building)=>{ //写法类似pointInWhichPointcloud let boundingBox = building.getBound() let center = boundingBox.getCenter(new THREE.Vector3()) let position = location instanceof THREE.Vector3 ? location : location.position let dis = position.distanceTo(center) let size = boundingBox.getSize(new THREE.Vector3()) let length = size.length() / 2; return length / dis }]); let building = result && result[0] && result[0].score > 1 && result[0].item if(buildType == 'building' || !building)return building result = traverse(building, buildType) /* if(!result && buildType == 'room'){//如果要找的是room, 且按刚才的顺序找不到的话,就单独从所有rooms中找一遍。因为room可能不在floor和building内。 let rooms = this.entities.filter(e=>e.buildType == 'room'); result = rooms.find(e=>e.ifContainsPoint(position)) }*/ //虽然房间可以画到上级之外,但是为了方便起见,假定房间绝对在楼层之内。找不到的话要调整空间模型了。 return result || lastResult } , findPanos: function(){ { this.entities.forEach(entity=>{ //清空: entity.panos = [] entity.flagPano = null viewer.images360.panos.forEach(pano=>{ if(entity.ifContainsPoint(pano.position)){ entity.panos.push(pano) } }) }) } /* viewer.images360.panos.forEach(pano=>{ //一个漫游点只对应一个实体的话 let result = this.pointInWhichEntity(pano.position, 'room'); {//get panos for every entities let entity = result while(entity){ entity.panos.push(pano); entity = entity.buildParent } } }) */ {//search center pano this.entities.forEach(entity=>{ let panos = entity.panos if(panos.length == 0)return let bound = entity.getBound(); let center = bound.getCenter(new THREE.Vector3) let request = [] let rank = [ Images360.scoreFunctions.distanceSquared({position: center}) ] //let panos = entity.panos && entity.panos.length ? entity.panos : viewer.images360.panos //entity没有panos的话,就扩大到所有panos let r = Common.sortByScore(panos, request, rank); if(r && r.length){ entity.flagPano = r[0].item }else{ console.error('no flagPano??') } }) } } , findEntityForDataset:function(){//为每一个数据集寻找它所属的最小实体 /* var entities = this.entities.filter(e=>e.buildType == 'room' || e.buildType == 'floor' && e.buildChildren.length == 0) viewer.scene.pointclouds.forEach(pointcloud=>{ let cloudVolume = pointcloud.getVolume() let scores = [] entities.forEach(entity=>{ let volume = entity.intersectPointcloudVolume(pointcloud) //注:默认已经findPanos过 let panos = entity.panos.filter(e=>pointcloud.panos.includes(e)); let panoCount = panos.length let score = volume / cloudVolume + panoCount / pointcloud.panos.length scores.push({entity, volume, panoCount, score}) }) scores.sort((a,b)=>{ return b.score-a.score }) if(scores.length == 0 || scores[0].volume/cloudVolume < 0.0001 && scores[0].volume < 3 ){//如果约等于0 pointcloud.belongToEntity = null }else{ pointcloud.belongToEntity = scores[0].entity; } }) */ let getScores = (pointcloud, entities, cloudVolume)=>{ let scores = [] entities.forEach(entity=>{ let volume = entity.intersectPointcloudVolume(pointcloud) //注:默认已经findPanos过 let panos = entity.panos.filter(e=>pointcloud.panos.includes(e)); let panoCount = panos.length let score = volume / cloudVolume + panoCount / pointcloud.panos.length scores.push({entity, volume, panoCount, score}) }) scores.sort((a,b)=>{ return b.score-a.score }) return scores } viewer.scene.pointclouds.forEach(pointcloud=>{ //先判断父级,如果父集不通过就不判断子级。 let cloudVolume = pointcloud.getVolume() let entities = this.buildings while(1){ let scores = getScores(pointcloud, entities, cloudVolume) if(scores.length == 0 || scores[0].volume/cloudVolume < 0.0001 && scores[0].volume < 3 ){//如果约等于0 pointcloud.belongToEntity = null break; }else{ entities = scores[0].entity.buildChildren if(entities.length == 0){ pointcloud.belongToEntity = scores[0].entity; break; } } } }) /* 旧版: 只需要考虑 floor 和 room, 因为building的只有一个基底没高度 floor 和 room 在空间中没有完全的从属关系,因为room可以超出floor之外。所以直接混在一起来查找,但要排除有房间的楼层。 (现在改为层层递进查找,否则数据集包含entity多的,会直接挂载到体积最大的房间里,即使看起来主体点云并不在该房间) 有的数据集虽然很高,但只有近地面的部分才是主体,这部分一般含有全部漫游点。为了防止上层的实体因体积较大而分数高,就把包含漫游点的个数也加入考虑。 重叠体积大、且包含漫游点最多的最小实体将会拥有该点云。 期望: 最好不挂载到最小子级,因为现在有房间都到房间里了。 */ } , clear:function(){//清空 /* entities:[], //所有实体 buildings:[], //所有建筑父集 meshGroup: new THREE.Object3D, */ this.selectEntity(null) let length = this.entities.length; for(let i=0;i e.sid == id) let aimPano if (!entity) { return console.error('没找到entity ') } if(Potree.settings.displayMode == 'showPanos'){ if (isNearBy && entity.panos.length) { if(entity.panos.includes(viewer.images360.currentPano)) return 'posNoChange' //已在当前实体中 let position = viewer.scene.getActiveCamera().position let request = [] let rank = [Images360.scoreFunctions.distanceSquared({ position })] let r = Common.sortByScore(entity.panos, request, rank) aimPano = r[0].item } else { if (!entity.flagPano) { return console.log('没有flagPano') } aimPano = entity.flagPano } if(aimPano == viewer.images360.currentPano) return 'posNoChange' viewer.images360.flyToPano(aimPano) }else{ if(isNearBy && entity.ifContainsPoint(viewer.images360.position) ){ return 'posNoChange' //已在当前实体中 } let boundingBox = entity.getBound() let position = boundingBox.getCenter(new THREE.Vector3()) //中心点不一定在entity中,比如半环形建筑(所以要不要改成到漫游点呢) if(viewer.modules.Clip && viewer.modules.Clip.editing){ viewer.modules.Clip.bus.dispatchEvent({type:'flyToPos', position}) }else{ if(math.closeTo(position, viewer.images360.position)) return 'posNoChange' let size = boundingBox.getSize(new THREE.Vector3()) viewer.scene.view.setView({position, duration}) viewer.mapViewer.moveTo(position, size, duration) } } return true }, focusEntity(id){ var entity = this.entities.find(e => e.sid == id) let boundingBox = entity.getBound() let boundSize = boundingBox.getSize(new THREE.Vector3()) let center = boundingBox.getCenter(new THREE.Vector3()) this.SplitScreen.focusOnObject(boundSize, center) this.gotoEntity(id, false, 0) }, removeIlligalArchi(){//删除marker数量小于3个的建筑,当保存时 let needDelete = [] this.entities.forEach(e=>{ if(e.points.length<3){ needDelete.push(e) } }) needDelete.forEach(e=>this.removeEntity(e)) }, } /* 规则 层级: type 中文名 改动范围 其他 BUILDING 建筑 xy mesh由自己的基底以及所有floor的组成。如果删除所有floor,就剩一个平面。故而zMin == zMax FLOOR 楼层 z(xy未解锁) 点击楼层时房间也会显示,而建筑的其他楼层不显示线框,会显示面. 拖拽高度实际是拖拽楼层间的分界线,楼层之间不会有缝隙 ROOM 房间 xyz(xy可加锁) 可能超出楼层外,因为楼层拖拽时房间没变。所以建筑不一定包容房间。 CUSTOM 自定义(现作房间) xyz ( xy范绘制时不能超出父级外轮廓,但父级编辑时可以进入子级轮廓。只要能解锁轮廓的都能切洞) floor输入高度数值的限制和拖拽是一样的,相当于调节其zMax, 且不能超出其上层的zMax。 navvis弊端: 空间模型不会随着数据集移动而移动 (可以做成跟随,但是如果一个建筑对应多个数据集,那只能跟序号在前的数据集走) 建筑点修改后,房间可能飘出建筑外的。 楼层高度修改后也是。 调整高度时,看不到相邻的楼层界限,导致拖不动时像bug。 调整高度时,侧面看有的重叠的部分比较高亮,感觉是冗余信息?有点乱 问题: 磨砂材质 没有阴影,可directionallight 加了呀 暂定一个数据集只属于一个实体(从最小的找起) https://testlaser.4dkankan.com/indoor/t-8KbK1JjubE/api/site_model 删点全删光了要删实体吗 */ export {SiteModel}