import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {ctrlPolygon} from '../../objects/tool/ctrlPolygon.js' import {LineDraw, MeshDraw } from "../../utils/DrawUtil.js"; import math from "../../utils/math.js"; import Sprite from '../../objects/Sprite.js' /* import {config} from '../settings' */ import searchRings from "../../utils/searchRings.js"; import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js"; let texLoader = new THREE.TextureLoader() let markerMats let markerSizeInfo = {width2d:35} let color = new THREE.Color('#FFF') let faceMats let getFaceMat = (name)=>{ if(!faceMats){ //navvis材质可以搜gridTexture let gridTex = texLoader.load( Potree.resourcePath+'/textures/gridmap.png' ) gridTex.wrapS = gridTex.wrapT = THREE.RepeatWrapping //gridTex.repeat.set(0.5,0.5)//放大一些 faceMats = { dataset: new THREE.MeshStandardMaterial({ color:812922, side:THREE.DoubleSide, opacity:0.2, transparent:true, depthTest:false, wireframe:true }), building: new THREE.MeshStandardMaterial({ color:812922, metalness: 0.2, roughness:0.8, side:THREE.DoubleSide, opacity:0.1, transparent:true, depthTest:true }), buildingSelect: new THREE.MeshStandardMaterial({ color:36582, metalness: 0, roughness:1, side:THREE.DoubleSide, opacity:0.1, transparent:true, depthTest:true }), floor: new THREE.MeshStandardMaterial({ color:11708469, metalness: 0.1, roughness:1, side:THREE.DoubleSide,//BackSide, opacity:0.05, transparent:true, depthTest:true, }), floorSelect: new DepthBasicMaterial({ map: gridTex, color:16707151, side:THREE.DoubleSide,//BackSide, opacity:1, transparent:true, useDepth : true, clipDistance : 1, occlusionDistance:1, /* occlusionDistance:变为backColor距离, clipDistance:opacity到达0或者1-maxClipFactor时的距离 */ maxClipFactor:0.9, backColor:'#efe' //backColor:"#669988" , }), room: new THREE.MeshStandardMaterial({ color:"#ff44ee", metalness: 0, roughness:1, side:THREE.DoubleSide,//BackSide, opacity:0.08, transparent:true, depthTest:false, }), roomSelect: new DepthBasicMaterial({ map: gridTex, color:"#ff44ee", side:THREE.DoubleSide,//BackSide, opacity:1, transparent:true, useDepth : true, clipDistance : 1, occlusionDistance:0.5, /* occlusionDistance:变为backColor距离, clipDistance:opacity到达0或者1-maxClipFactor时的距离 */ maxClipFactor:0.8, backColor:'#ff88dd'//"#cc99c2" , }) } } return faceMats[name] } export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, floor, room constructor(prop) { prop.dimension = '3d' //prop.name = Potree.config.siteModel.names[prop.buildType] + super('siteModel_'+prop.buildType, prop); this.midMarkers = [] this.buildChildren = []//子实体 this.holes = [] //在这创建的hole this.parentHoles = [];//floor从building那得到的当层holes this.mats = {} //材质 this.panos = this.panos || []; this.center //中心点 if(this.buildType=='floor'){ this.points = prop.points = this.buildParent.points;//完全等于建筑的点 this.buildParent.holes.forEach(hole=>{//从building获取holes let floorHole = new BuildingBox({ buildType : 'hole', buildParent:this, originHole : hole, //整栋大楼在当层的hole ifDraw: this.ifDraw || Potree.settings.drawEntityData }); this.parentHoles.push(floorHole) this.add(floorHole) floorHole.points = hole.points//完全等于建筑的点 }) } if(this.buildType == 'room' || this.buildType == 'hole'){ this.restrictArea = this.buildParent //不能超出的区域 } if(this.buildParent)this.dontDragFloorHeight = this.buildParent.dontDragFloorHeight if(this.buildType != 'hole'){ this.box = this.createBox() //无论是否edit都绘制的原因:为了将在外的点移到在内,需要用mesh来获取intersect this.add(this.box) this.box.visible = !!this.ifDraw } if(this.ifDraw){ //只存储空间模型信息,不绘制 { this.createPrismLines(color) this.lineMesh.visible = false Potree.Utils.setObjectLayers(this.lineMesh, 'bothMapAndScene' ) } this.addEventListener('dragChange',(e)=>{ //修改中点 this.updateTwoMidMarker(e.index) }) } this.initData(prop) } initData(prop){ if(prop.ifDraw){ super.initData(prop) }else{ if(prop.points){ this.points = prop.points } } } intersectPointcloudVolume(pointcloud){//和pointcloud的重叠体积 return this.intersectPointcloudArea(pointcloud) * this.coverPointcloudHeight(pointcloud) } coverPointcloudHeight(pointcloud, getPercent){ let bound2 = pointcloud.bound; let {zMin , zMax} = this.getRealZ() let min = Math.min(zMin, bound2.min.z); let max = Math.max(zMax, bound2.max.z); let height1 = zMax - zMin let height2 = bound2.max.z-bound2.min.z let coverHeight = height1 + height2 - (max-min)//重叠高度 <=0是没重叠 if(getPercent){ return coverHeight / height1 } return coverHeight } intersectPointcloudArea(pointcloud, getPercent){//和pointcloud的重叠面积 var bound = this.getBound() let bound2 = pointcloud.bound; if(!bound.intersectsBox(bound2)) return 0; let boxPoints = pointcloud.getUnrotBoundPoint() //获取tightBound的四个点。 如果是有旋转角度的点云,这个和pointcloud.bound的四个点是不一致的,覆盖面积小于pointcloud.bound let areaWhole = 0 let area1 = this.getArea() let area2 = Math.abs(math.getArea(boxPoints)) {//计算points与点云总面积 (但是把hole也加入了面积)(并集,重叠部分只算一次) let rings = math.getPolygonsMixedRings([this.points, boxPoints] ) rings.forEach(e=>{ areaWhole+=e.area }) } let coverHoleArea = 0 //holes与数据集重叠的部分 let holes = this.holes.concat(this.parentHoles) let holesArea = 0 //所有holes面积相加 let areaHoleWithPointcloud = 0 //hole和点云的面积并集 if(holes.length>0){//还要再扣除holes与数据集重叠的部分。其中holes为mix轮廓 let outHoles = []//没有重合的holes的外轮廓 /* if(holes.length>=2){//合并holes。如果能在绘制时直接合并holes就好啦,这步就转移到那去,但是要删除hole好麻烦 let holes_ = holes.map(e=>e.points) outHoles = math.getPolygonsMixedRings(holes_, true ) outHoles.forEach(e=>{ holesArea+=e.area }) outHoles = outHoles.map(e=>e.points) }else{ outHoles = holes.map(e=>e.points) outHoles.forEach(e=> holesArea += Math.abs(math.getArea(e))) } */ holesArea = this.getHolesArea() //holes与数据集重叠的部分 { let polygons = outHoles.concat([boxPoints]) let rings = math.getPolygonsMixedRings(polygons) rings.forEach(e=>{ areaHoleWithPointcloud+=e.area }) coverHoleArea = holesArea + area2 - areaHoleWithPointcloud//hole和点云的交集 } } let coverArea = area1 + area2 - areaWhole - coverHoleArea; //重叠面积 if(getPercent){ return { toEntity:coverArea / this.getArea(true), //占entity toPointcloud:coverArea / area2 //占点云 } } return coverArea } addHole(points=[]){ let prop = { buildType : 'hole', zMin : this.zMin, zMax : this.zMax, points, buildParent:this, ifDraw: this.ifDraw || Potree.settings.drawEntityData } //hole的zMin zMax跟随buildParent var hole = new BuildingBox(prop); this.holes.push(hole) if(this.buildType == 'building'){//为每一层添加对应的hole this.buildChildren.forEach(floor=>{ let floorHole = new BuildingBox({ buildType : 'hole', zMin : this.zMin, zMax : this.zMax, buildParent:floor, originHole : hole, //整栋大楼在当层的hole ifDraw: this.ifDraw || Potree.settings.drawEntityData }); floor.parentHoles.push(floorHole) floor.add(floorHole) floorHole.points = hole.points//完全等于建筑的点 }) } this.add(hole);//直接加在这,不加meshGroup了 this.update() //update box mesh return hole //hole不创建box,只有它的buildParent需要更新box。 但有线条和marker. hole不在buildChildren里,但有buildParent } removeHole(hole){// 这个hole不会是parentHoles里的。 hole.dispose() if(this.buildType == 'building'){ //若是整栋大楼的hole,在每层去除它的对应hole this.buildChildren.forEach(floor=>{ let holeAtFloor = floor.parentHoles.find(e=>e.originHole == this ) let index = floor.parentHoles.indexOf(holeAtFloor) index > -1 && floor.parentHoles.splice(index, 1) holeAtFloor.dispose() }) } let index = this.holes.indexOf(hole) if(index>-1){ this.holes.splice(index, 1) } this.remove(hole) this.update() } createBox(){ var geometry = new THREE.Geometry(); this.mats.boxDefault = getFaceMat(this.buildType) this.mats.boxSelected = getFaceMat(this.buildType+'Select') var mesh = new THREE.Mesh(geometry, this.mats.boxDefault) mesh.name = 'buildingBox'; if(this.buildType == 'floor'){ Potree.Utils.setObjectLayers(mesh, 'mapUnvisi' ) //楼层默认在地图不显示,为了不会叠加透明度 //mesh.renderOrder = 1 }else{ /* if(this.buildType == 'room'){ mesh.renderOrder = 2 } */ Potree.Utils.setObjectLayers(mesh, 'bothMapAndScene' ) } //mesh.frustumCulled = false; return mesh } addMarker(o={} ){ if(this.buildType=='floor')return; //楼层不需要marker let marker = new Sprite({mat:this.getMarkerMaterial('default'), renderOrder : 3, sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_marker"} ) Potree.Utils.setObjectLayers(marker, 'onlyMapVisi' ) o.marker = marker super.addMarker(o) if(!this.selected)Potree.Utils.updateVisible(marker,'select',false) let addClickEvent = (e)=>{ let click = (e) => { this.dispatchEvent({type:'clickMarker', marker } ) //由entity发送给sitemodel统一处理 }; marker.addEventListener('click', click); marker.addEventListener('clickSelect', (e)=>{ this.setMarkerSelected(marker, e.state ? 'select' : 'unselect' ); }); marker.removeEventListener('addHoverEvent',addClickEvent) } marker.addEventListener('addHoverEvent',addClickEvent)//当非isNew时才添加事件 if(!this.isNew){ marker.dispatchEvent('addHoverEvent') } return marker } removeMarker(index){ super.removeMarker(index); if(!this.isNew){ //重新添加midMarkers this.midMarkers.forEach(e=>this.remove(e)); this.midMarkers = [] this.addMidMarkers() } this.update(); if(this.points.length == 2 && this.box){//清除原先length>=3时候的 this.box.geometry = new THREE.Geometry(); } } addMidMarker(index, point){ if(this.buildType=='floor')return; //楼层不需要marker let marker = new Sprite({mat:this.getMarkerMaterial('midPrepare'), sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_midMarker"} ) this.midMarkers = [...this.midMarkers.slice(0,index), marker, ...this.midMarkers.slice(index,this.midMarkers.length)] marker.renderOrder = 3 Potree.Utils.setObjectLayers(marker, 'onlyMapVisi' ) { // Event Listeners let mouseover = (e) => { this.setMarkerSelected(e.object, 'hover', 'single'); viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"markerMove" }) }; let mouseleave = (e) => { this.setMarkerSelected(e.object, 'unhover', 'single'); viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"markerMove" }) } let drag = (e) => { this.dispatchEvent('startDragging') let index = this.midMarkers.indexOf(marker) let newMarker = this.addMarker({index:(index+1), point:marker.position.clone() }) this.addMidMarker(index+1, new THREE.Vector3 ) this.updateTwoMidMarker(index+1) this.setMarkerSelected(marker, 'unhover') viewer.inputHandler.startDragging(newMarker , {/* dragViewport:viewer.mapViewer.viewports[0], */ } ); //notPressMouse代表不是通过按下鼠标来拖拽. dragViewport指定了只能在地图上拖拽 } marker.addEventListener('drag', drag ); marker.addEventListener('mouseover', mouseover); marker.addEventListener('mouseleave', mouseleave); } this.add(marker) this.updateMarker(marker, point) if(!this.selected)Potree.Utils.updateVisible(marker,'select',false) return marker } addMidMarkers(){//第一次画好所有marker后,一次性为线段增加中点marker let length = this.points.length this.points.forEach((point,index)=>{ let nextPoint = this.points[(index+1)%length] let midPoint = new THREE.Vector3().addVectors(point, nextPoint).multiplyScalar(0.5) this.addMidMarker(index, midPoint ) }) } updateTwoMidMarker(index){//更新第index个marker两边的midMarker if(!this.midMarkers.length)return let length = this.points.length let last = this.points[(index-1+length)%length] //它之前的marker位置 let next = this.points[(index+1)%length];//它之后的marker位置 let current = this.points[index]//当前位置 let lastMid = new THREE.Vector3().addVectors(last, current).multiplyScalar(0.5)//上一个中点 let nextMid = new THREE.Vector3().addVectors(next, current).multiplyScalar(0.5)//下一个中点 let lastMidMarker = this.midMarkers[(index-1+length)%length]; let nextMidMarker = this.midMarkers[index] this.updateMarker(lastMidMarker, lastMid) this.updateMarker(nextMidMarker, nextMid) } dispose(){//销毁geo、remove from parent super.dispose() this.box && this.box.geometry.dispose(); this.lineMesh && this.lineMesh.geometry.dispose(); this.holes.forEach(e=>e.dispose()) this.parentHoles.forEach(e=>e.dispose()) //this.buildChildren.forEach(e=>e.dispose()) this.dispatchEvent('dispose') } updateBox(){ if(!this.box)return this.box.geometry.dispose() var shrink = this.buildType == 'room' ? 0.11 : this.buildType == 'floor' ? 0.082 : 0.2 ;//防止mesh重叠冲突(给一个不寻常的数字) 但离远了还是会有点闪烁 if(this.points.length >= 3){ let holes = this.holes.concat(this.parentHoles) let holesPoints = holes.filter(e=>e.points.length>2).map(e=>e.points) this.box.geometry = MeshDraw.getExtrudeGeo(this.points, holesPoints, { depth:this.zMax-this.zMin-shrink, UVGenerator: new MetricUVGenerator() }) if(this.buildType == 'building' ){ this.box.position.z = this.zMin - shrink / 2 }else{ this.box.position.z = this.zMin + shrink / 2 } } } update(options={}){ super.update(this.buildType != 'floor' && options.ifUpdateMarkers) let length = this.points.length {//确保一下一样 if(this.originHole){ this.points = this.originHole.points //完全等于building的hole } if(this.buildType == 'hole'){ this.zMin = this.buildParent.zMin; this.zMax = this.buildParent.zMax; } } if(!options.dontUpdateBox){ let boxOwner if(this.buildType == 'hole'){ if(this.buildParent.buildType == 'building'){ //若是整栋大楼的hole,在每层都要更新下它的对应hole this.buildParent.buildChildren.forEach(floor=>{ let holeAtFloor = floor.parentHoles.find(e=>e.originHole == this ) holeAtFloor && holeAtFloor.update() //刚开始创建时还没创建对应的 holeAtFloor会为null }) } boxOwner = this.buildParent }else{ boxOwner = this } boxOwner.updateBox() } this.updatePrismLines()//update lines if(!options.dontUpdateChildren){ if(this.buildType == 'building' ){ this.buildChildren.forEach(floor=>{ floor.points = this.points floor.update() }) } { let holes = this.holes.concat(this.parentHoles) holes.forEach(hole=> { hole.update({dontUpdateBox:true})//父级更新了box,hole就不需要更新box了 }) } } } getHolesArea(){ let holes = this.holes.concat(this.parentHoles) let outHoles, holesArea = 0 if(holes.length>=2){//合并holes。如果能在绘制时直接合并holes就好啦,这步就转移到那去,但是要删除hole好麻烦 let holes_ = holes.map(e=>e.points) outHoles = math.getPolygonsMixedRings(holes_, true ) outHoles.forEach(e=>{ holesArea+=e.area }) outHoles = outHoles.map(e=>e.points) }else{ outHoles = holes.map(e=>e.points) outHoles.forEach(e=> holesArea += Math.abs(math.getArea(e))) } return holesArea } getArea(ifRidOfHoles){//面积 //不排除hole return Math.abs(math.getArea(this.points)) - (ifRidOfHoles ? this.getHolesArea() : 0) } getVolume(ifRidOfHoles){//体积 let {zMin , zMax} = this.getRealZ() let height = zMax - zMin; if(isNaN(height))height = 0 return this.getArea(ifRidOfHoles) * height } getRealZ(){//求真实高度时用到的 let zMin , zMax if (this.buildType == 'building') { //因为building只有一个地基平面 所以自身的zMin == 自身的zMax 所以要算 let top = this.buildChildren[this.buildChildren.length - 1] let btm = this.buildChildren[0] zMin = btm ? btm.zMin : 0 //建好的建筑不加楼的话是0。 zMax = top ? top.zMax : 0 }else if(this.buildType == 'hole'){ return this.buildParent.getRealZ() }else{ zMin = this.zMin, zMax = this.zMax } return {zMin,zMax} } //架站式多楼层SG-t-ihjV2cDVFlE 有初始的z, 但是总高度范围小于数据集。 不允许修改高度。 /* getDrawZ(){ //画线和box时用到的z let zMin , zMax if(this.buildType == 'hole'){ if(this.buildParent.buildType == 'building' && atFloor){ zMin = atFloor.zMin, zMax = atFloor.zMax }else{ zMin = this.buildParent.zMin, zMax = this.buildParent.zMax } }else{ zMin = this.zMin, zMax = this.zMax } return {zMin, zMax} } */ getBound(){ let bound = new THREE.Box3 let {zMin , zMax} = this.getRealZ() let points = this.buildType == 'floor' ? this.buildParent.points : this.points points.forEach(p=>{ bound.expandByPoint(p.clone().setZ(zMin)) bound.expandByPoint(p.clone().setZ(zMax)) }) return bound } getMarkerMaterial(type) { if(!markerMats){ markerMats = { default: new THREE.MeshBasicMaterial({ transparent: !0, color, opacity: 0.8, map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), depthTest:false, }), midPrepare: new THREE.MeshBasicMaterial({ //线中心的半透明点 transparent: !0, color, opacity: 0.4, map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), depthTest:false, }), hover: new THREE.MeshBasicMaterial({ transparent: !0, color, opacity: 1, map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), depthTest:false, }), select: new THREE.MeshBasicMaterial({ transparent: !0, color:new THREE.Color('#00C8AF'), opacity: 1, map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), depthTest:false, }), } } return markerMats[type] } setMarkerSelected(marker, state, hoverObject){ //console.warn(marker.id , state, hoverObject) if(state == 'select'){ marker.selected = true marker.material = this.getMarkerMaterial('select') }else if(state == 'unselect'){ marker.selected = false marker.material = this.getMarkerMaterial('default') }else{ if(marker.selected)return //选中时不允许修改为除了'unselect'以外的状态 if(state == 'hover'){ marker.material = this.getMarkerMaterial('hover') }else if(state == 'unhover'){ if(marker.name.includes('mid')){ marker.material = this.getMarkerMaterial('midPrepare') }else{ marker.material = this.getMarkerMaterial('default') } } } } select(){ //最多再显示一层子级的线,如building不会显示room中的hole的线 //box是一直显示的,但会切换材质 /* 选中 box 线 building 自己(底盘)选中 自己, floor不带hole floor 自己选中 自己, room不带hole room 自己选中 自己 */ //注:自己的就代表定包括hole,如果有parentHoles的也(building上的hole的对应) //console.log('select '+this.name, this.selected) if(this.selected)return if(this.box){ this.box.material = this.mats.boxSelected; } if(this.buildType == 'building'|| this.buildType == 'floor'){ this.buildChildren.forEach(e=>{ e.lineMesh.visible = true }) if(this.buildType == 'floor'){ Potree.Utils.setObjectLayers(this.box, 'bothMapAndScene' ) Potree.Utils.setObjectLayers(this.buildParent.box, 'mapUnvisi' ) //当选中floor或room时,building在地图不可见 } }else if(this.buildType == 'room'){ Potree.Utils.setObjectLayers(this.buildParent.box, 'bothMapAndScene' ) Potree.Utils.setObjectLayers(this.buildParent.buildParent.box, 'mapUnvisi' ) } this.lineMesh.visible = true this.markers && this.markers.forEach(e=>Potree.Utils.updateVisible(e,'select',true) ) this.midMarkers && this.midMarkers.forEach(e=>Potree.Utils.updateVisible(e,'select',true) ) let holes = this.holes.concat(this.parentHoles) holes.forEach(e=>e.select()) this.selected = true this.dispatchEvent({type:'select'}) } unselect(){ if(!this.selected)return //console.log('unselect '+this.name ) if(this.box){ this.box.material = this.mats.boxDefault; } if(this.buildType == 'building' || this.buildType == 'floor'){ this.buildChildren.forEach(e=>{ //(这里要保证选中前要先取消选中,否则如选中房间后取消了楼层,房间线就隐藏了) e.lineMesh.visible = false }) if(this.buildType == 'floor'){ Potree.Utils.setObjectLayers(this.box, 'mapUnvisi' ) Potree.Utils.setObjectLayers(this.buildParent.box, 'bothMapAndScene' ) } }else if(this.buildType == 'room'){ Potree.Utils.setObjectLayers(this.buildParent.box, 'mapUnvisi' ) Potree.Utils.setObjectLayers(this.buildParent.buildParent.box, 'bothMapAndScene' ) } this.lineMesh.visible = false this.markers && this.markers.forEach(e=>Potree.Utils.updateVisible(e,'select',false) ) this.midMarkers && this.midMarkers.forEach(e=>Potree.Utils.updateVisible(e,'select',false) ) let holes = this.holes.concat(this.parentHoles) holes.forEach(e=>e.unselect()) this.selected = false this.dispatchEvent({type:'unselect'}) } ifContainsPoint(position){//看它所定义的空间是否包含某个坐标(要排除hole) let {zMin , zMax} = this.getRealZ() if(position.z < zMin || position.z > zMax ) return let holes = this.holes.concat(this.parentHoles) let holesPoints = holes.filter(e=>e!=this && e.points.length>2).map(e=>e.points) let inShape = math.isPointInArea(this.points, holesPoints, position) return !!inShape } } class MetricUVGenerator{ constructor(){ this.a = new THREE.Vector3, this.b = new THREE.Vector3, this.c = new THREE.Vector3, this.d = new THREE.Vector3 } generateTopUV(t, e, n, r, o) { return [new THREE.Vector2(e[3 * n],e[3 * n + 1]), new THREE.Vector2(e[3 * r],e[3 * r + 1]), new THREE.Vector2(e[3 * o],e[3 * o + 1])] } generateSideWallUV(t, e, n, r, o, a) { var s = e; this.a.set(s[3 * n], s[3 * n + 1], s[3 * n + 2]), this.b.set(s[3 * r], s[3 * r + 1], s[3 * r + 2]), this.c.set(s[3 * o], s[3 * o + 1], s[3 * o + 2]), this.d.set(s[3 * a], s[3 * a + 1], s[3 * a + 2]); var c = this.a.x !== this.b.x , l = c ? this.b : this.d , u = this.a.distanceTo(l) , d = l.distanceTo(this.c); return [new THREE.Vector2(this.a.x,0), c ? new THREE.Vector2(this.a.x + u,0) : new THREE.Vector2(this.a.x,d), new THREE.Vector2(this.a.x + u,d), c ? new THREE.Vector2(this.a.x,d) : new THREE.Vector2(this.a.x + u,0)] } }