import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {TextSprite} from "../TextSprite.js"; import {Utils} from "../../../utils.js"; import Label from "../Label.js"; import {LineDraw} from "../../utils/DrawUtil.js"; import math from "../../utils/math.js"; import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js"; import Sprite from '../Sprite.js' import {config} from '../../settings.js' import browser from "../../utils/browser.js"; import {ctrlPolygon} from './ctrlPolygon.js' let texLoader = new THREE.TextureLoader() let defaultColor = new THREE.Color(config.measure.default.color); let highlightColor = new THREE.Color(config.measure.highlight.color); let color = new THREE.Color(config.measure.color) let textColor = new THREE.Color(config.measure.textColor) var markerMats; var lineMats; var planeMats const textSizeRatio = math.linearClamp(window.outerWidth * window.outerHeight , [360*720, 1920*1080], [0.7, 1]) //pc字显示大一些 用 const lineDepthInfo = { clipDistance : 15,//4,//消失距离 occlusionDistance: 3,//1,//变为backColor距离 } const markerMapShrink = browser.isMobile() ? 0.4 : 0.8 //触屏需要更大的热区 const markerSizeInfo = { width2d : 18 / markerMapShrink , // nearBound : 1.5, farBound : 15, } /* const markerSizeInfo = { minSize : 10 , maxSize : 15 , nearBound : 1.5, farBound : 15, } */ const labelSizeInfo = {width2d:200} const mainLabelProp = { //backgroundColor: {r: defaultColor.r*255, g: defaultColor.g*255, b: defaultColor.b*255, a:config.measure.default.opacity}, backgroundColor: {r: 0, g: 0, b: 0, a:0}, textColor: {r: textColor.r*255, g: textColor.g*255, b: textColor.b*255, a: 1.0}, textBorderColor: {r:255, g: 255, b:255, a: 1.0}, textBorderThick:3 , fontsize: 15 * textSizeRatio, borderRadius : 12, margin:{x:20,y:4}, renderOrder : Potree.config.renderOrders.measureLabel, pickOrder: Potree.config.renderOrders.measureLabel, disToLine:-0.15, useDepth : true , // 2023.10 尽量不让数字被挡住 clipDistance : 10,//消失距离 occlusionDistance: 10,//变为backColor距离 maxOcclusionFactor:0.3, maxClipFactor:0.8 } const subLabelProp = { backgroundColor: {r: 255, g: 255, b: 255, a:0}, textColor: {r: textColor.r*255, g: textColor.g*255, b: textColor.b*255, a: 1.0}, textBorderColor: {r:255, g: 255, b:255, a: 1.0}, textBorderThick:3 , fontsize: 14 * textSizeRatio, renderOrder : Potree.config.renderOrders.measureLabelSub, pickOrder: Potree.config.renderOrders.measureLabelSub, disToLine:-0.13, } const angle = THREE.Math.degToRad(5);//显示水平垂直辅助线的最小角度 const guideShowMinAngle = {min: angle, max: Math.PI/2 - angle} export class Measure extends ctrlPolygon{ constructor (prop) { prop.dimension = '2d' super('measure',prop); this.constructor.counter = (this.constructor.counter === undefined) ? 0 : this.constructor.counter + 1; this.name = this.measureType + this.constructor.counter //'Measure_' + this.constructor.counter; this.markerLabels = []; this.edgeLabels = []; this.angleLabels = []; this.coordinateLabels = []; this.area = {value:0,string:''} if( this.showArea ){ this.areaLabel = this.createAreaLabel(); this.add(this.areaLabel) } //add: if(this.atPlane || this.faceDirection){ //是一个平面上的话 this.createGuideLine(); } if(this.measureType == 'Distance' /* || this.measureType.includes('MulDistance') */){ this.createHorVerGuideLine() } this.selectStates = {} this.setUnitSystem(prop.unit || viewer.unitConvert.UnitService.defaultSystem) Potree.Utils.setObjectLayers(this, 'measure' ) if(this.measureType == 'MulDistance' || this.measureType == 'Hor MulDistance' || this.measureType == 'Ver MulDistance'){ //this.showTotalDis = true this.totalDisLabel = this.createTotalDisLabel() this.add(this.totalDisLabel) } //addMarkers: this.initData(prop) this.pointsPos2d = new Map //屏幕上的二维坐标 this.points_datasets || (this.points_datasets = []) //存每个点是哪个数据集 this.addEventListener('marker_dropped',(e)=>{ this.updateDatasetBelong(e.index) }) this.addEventListener('isVisible', ()=>{ viewer.mapViewer && viewer.mapViewer.dispatchEvent({type:'content_changed'}) }) this.lastDropTime = 0 } initData(prop){ let makeIt = super.initData(prop) if(makeIt){ this.edges.forEach(edge=>{edge.dispatchEvent('addHoverEvent') }) }else{ this.failBuilded = true } } updateDatasetBelong(changeIndex){//更新所属数据集 if(Potree.settings.editType == "merge" || this.measureType == 'MulDistance Ring'){//点直接跟着数据集走,不用找整体的datasetId this.dataset_points[changeIndex] = Potree.Utils.datasetPosTransform({toDataset:true, datasetId:this.points_datasets[changeIndex], position:this.points[changeIndex].clone()}) return } let old = this.datasetId let maxCount = {id:null,count:0} let datasets = {} this.points_datasets.forEach(e=>{ if(e == void 0)return if(datasets[e]){ datasets[e] ++ }else{ datasets[e] = 1 } }) for(let i in datasets) { if(datasets[i]>maxCount.count){ maxCount = {id:i, count:datasets[i]} } } this.datasetId = maxCount.count > 0 ? maxCount.id : null //if(this.datasetId != old){ //this.dispatchEvent({type:'changeDatasetId'}) if(this.datasetId == void 0){ this.dataset_points = null //可能为空或[null,null...] }else{ this.dataset_points = this.points.map(e=>{ return Potree.Utils.datasetPosTransform({toDataset:true,datasetId:this.datasetId, position:e.clone()}) }) } //} } transformByPointcloud(){//每次移动点云 or 加载测量线时要获取一下当前position //有地图时 if(this.datasetId == void 0)return this.points = this.dataset_points.map(e=>{ return Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:this.datasetId, position:e.clone()}) }) this.getPoint2dInfo(this.points) this.update({ifUpdateMarkers:true}) this.setSelected(false)//隐藏edgelabel } update(options={}) { if(options.index == -1)return super.update(options) if(this.showCoordinates && this.points.length>0){ let position = this.points[0]; this.markers[0].position.copy(position); { // coordinate labels let coordinateLabel = this.coordinateLabels[0]; let pos = [ position.toArray() ] if(viewer.transform){ let lonlat = viewer.transform.lonlatToLocal.inverse(position.toArray()) let EPSG4550 = viewer.transform.lonlatTo4550.forward(lonlat) pos.push(lonlat,EPSG4550) } //let msg = position.toArray().map(p => Utils.addCommas(p.toFixed(2))).join(" / "); let msg = pos.map(a=> a.map(p => Utils.addCommas(p.toFixed(10))).join(", ") ).join("
") coordinateLabel.setText(msg); coordinateLabel.setPos(position) coordinateLabel.setVisible(true)//this.showCoordinates; } return } let setEdgeLabel = (label,p1,p2,distance)=>{//设置label位置和字 this.setEdgeLabelPos(label,p1,p2) distance = distance == void 0 ? p1.distanceTo(p2) : distance; //var text = viewer.unitConvert.convert(distance, 'distance', Potree.settings.precision, this.unitSystem, 1 , true)//distance要传0.1 这个factor var text = this.getConvertString(distance, 'distance') label.setText(text) return distance } /* let setEdgeLabel = (label,p1,p2,distance)=>{//设置label位置和字 this.setEdgeLabelPos(label,p1,p2) distance = distance == void 0 ? p1.distanceTo(p2) : distance; var text = this.labelText || viewer.unitConvert.convert(distance, 'distance', Potree.settings.precision , this.unitSystem, 0.001 , true, true)//distance要传0.1 这个factor label.setText(text) } */ let lastIndex = this.points.length - 1 let setLabel = (index)=>{ let previousIndex = this.getIndex(index, -1) let nextIndex = this.getIndex(index, +1) let previousPoint = this.points[previousIndex]; let point = this.points[index]; let nextPoint = this.points[nextIndex]; if(this.showDistances){ // edge labels let edgeLabel = this.edgeLabels[index]; let distance = point.distanceTo(nextPoint) this.edges[index].distance_ = distance edgeLabel.shouldVisi = (index < lastIndex || this.isRect || this.closed && !this.isNew ) && distance>0 //this.closed || edgeLabel.setVisible(edgeLabel.shouldVisi) //closed的在setEdgesDisplay中设置 Utils.updateVisible(edgeLabel, 'shouldVisi', edgeLabel.shouldVisi, 2) if(edgeLabel.shouldVisi){ edgeLabel.lineDir = new THREE.Vector3().subVectors(point,nextPoint).normalize() //[point,nextPoint] setEdgeLabel(edgeLabel,point,nextPoint,distance) } } } if(options.index != void 0){//更新第几个点 setLabel(options.index) let previousIndex = this.getIndex(options.index, -1) setLabel(previousIndex) }else{ for (let index = 0; index <= lastIndex; index++) { setLabel(index) } } if(Potree.config.measure.mulLabelHideFaraway ){ this.measureType == 'MulDistance' && this.clearEdgeLabelVisi() } if(this.measureType == 'Distance' && this.points.length>1){//设置水平垂直辅助线 var pTop, pBtm if(this.points[0].z > this.points[1].z ){ pTop = this.points[0]; pBtm = this.points[1]; }else{ pTop = this.points[1]; pBtm = this.points[0]; } let projectPos = new THREE.Vector3(pTop.x, pTop.y, pBtm.z);//两条guideline的交点 {//倾斜角度太小的时候不显示 let tan = pTop.distanceTo(projectPos) / pBtm.distanceTo(projectPos) let angle = Math.atan(tan); this.shouldShowHorVerGuide = angle > guideShowMinAngle.min && angle < guideShowMinAngle.max } LineDraw.updateLine(this.verGuideEdge, [pTop, projectPos]) LineDraw.updateLine(this.horGuideEdge, [pBtm, projectPos]) setEdgeLabel(this.verEdgeLabel,pTop,projectPos) setEdgeLabel(this.horEdgeLabel,pBtm,projectPos) this.verGuideEdge.visible = this.horGuideEdge.visible = this.shouldShowHorVerGuide this.verEdgeLabel.visible = this.horEdgeLabel.visible = this.shouldShowHorVerGuide } if(this.showArea && this.points.length > 2){ // update area let msg = this.getArea().string this.areaLabel.setPos(this.getCenter('areaPlaneCenter')) this.areaLabel.setText(msg); Utils.updateVisible(this.areaLabel, 'setVisible', true) //this.areaLabel.setVisible(true) } if(this.totalDisLabel){ this.ifShowTotalDis() Utils.updateVisible(this.totalDisLabel,'setVisible', this.showTotalDis) this.edgeLabels.forEach(e=> Utils.updateVisible(e, 'showTotalDis', !this.showTotalDis)) if(this.showTotalDis){ let dis = this.getTotalDistance() let msg = this.getConvertString(dis, 'distance') this.center = null this.center = this.getCenter() this.totalDisLabel.setPos(this.center); this.totalDisLabel.setText(msg); } } }; getArea(){ let area if(this._area != void 0){ area = this._area }else if(this.point2dInfo){ area = Math.abs(math.getArea(this.point2dInfo.points2d))//this.getArea(); }else{//mulDistance Ring 2d面 area = Math.abs(math.getArea(this.points)) } let msg = this.getConvertString(area, 'area') //let msg = viewer.unitConvert.convert(area, 'area', Potree.settings.precision, this.unitSystem/* , 0.1 */ ) this.area = {value:area, string:msg} return this.area } getConvertString(num, type){ return viewer.unitConvert.convert(num, type, Potree.settings.precision, this.unitSystem, true , { 'imperial': {minFactor: 0.01 }, 'metric': {minFactor: 0.01} } ) } ifShowTotalDis(){ let show = this.points.length > 2 if(show){ let maxDis = 0.15 let lastIndex = this.points.length - 1; for(let i=0;i maxDis){ show = false; break; } } } this.showTotalDis = show /* 连续测量: 1. ≥2次测量,单个距离<15cm时,居中显示总长, hover、选中时显示每段长度 2. 若连续测量的线段中,大于等于1段超出15cm,所有线段均显示长度 ------------------- */ } clearEdgeLabelVisi(){//修改点位置后清空,下次render时会自动getEdgeLabelVisi let lastIndex = this.points.length - 1; for (let index = 0; index <= lastIndex; index++) { if(!this.closed && index == lastIndex)continue let edgeLabel = this.edgeLabels[index]; edgeLabel.visiMap.clear() } } getEdgeLabelVisi(viewport){//获取多折线的edgelabel在不同视图里的可见性。要保证任何时候label能出现的线最小二维长度一致 let camera = viewport.camera let lastIndex = this.points.length - 1; /* let pos2ds = this.points.map(point=> point.clone().project(camera) ) //即使只是旋转也会变动,尤其是转到屏幕外后变为显示。所以不用这种 let minDis = 0.01; */ let minDis = 0.02 , minAngleRatio = 0.07, minAngle let vecs let forceShow if(camera.type == 'OrthographicCamera'){ minDis *= Math.pow(camera.top / camera.zoom, 2); //console.log(minDis) }else{ if(Potree.settings.displayMode == 'showPanos' && viewer.images360.zoomLevel == Potree.settings.zoom.max){ forceShow = true //当zoom到最大时强制显示,避免有的线太短永远显示不出长度 }else{ vecs = this.points.map(point=> new THREE.Vector3().subVectors(point, camera.position).normalize()) minAngleRatio /= viewport.resolution.y / 1000 / textSizeRatio //角度占fov最小比率 minAngle = minAngleRatio * THREE.Math.degToRad(camera.fov) } } for (let index = 0; index <= lastIndex; index++) { if(!this.closed && index == lastIndex)continue let edgeLabel = this.edgeLabels[index]; let nextIndex = (index + 1 > lastIndex) ? 0 : index + 1; let previousIndex = (index === 0) ? lastIndex : index - 1; let point = this.points[index]; let nextPoint = this.points[nextIndex]; /* let point2d = pos2ds[index]; let nextPoint2d = pos2ds[nextIndex]; let dis2d = point2d.distanceToSquared(nextPoint2d) let v = dis2d > minDis //可见长度太小,为避免拥挤,不显示 edgeLabel.visiMap.set(camera, v) */ let v if(forceShow){ v = true }else if(camera.type == 'OrthographicCamera'){ let vec = new THREE.Vector3().subVectors(point,nextPoint) let projVec = vec.projectOnPlane(viewport.view.direction) v = projVec.lengthSq() > minDis }else{ let vec0 = vecs[index]; let vec1 = vecs[nextIndex]; v = Math.acos(vec0.dot(vec1)) > minAngle //角度过小代表可见长度太小,为避免拥挤,不显示 } edgeLabel.visiMap.set(camera, v) } } setEdgeLabelPos(label,p1,p2){ //调整label的位置,使倾斜后看起来在线的中心,而不要挡住端点 let center = new THREE.Vector3().addVectors(p1,p2).multiplyScalar(0.5); return label.setPos(center) if(label.lineDir && label.lineDir.length() > 0){ if(viewer.mainViewport.camera.type == 'OrthographicCamera'){ label.setPos(center) }else{ //根据视线和线的夹角(后又加入相机和两个端点距离差)来决定标签偏移位置。+ let eyePos = viewer.mainViewport.camera.position; let dir = viewer.mainViewport.view.direction //new THREE.Vector3().subVectors(center,eyePos).normalize() /*let centerDir = new THREE.Vector3().subVectors(center,eyePos).normalize() if(centerDir.dot(dir)<0){//中点在相机后方,就不设置 label.setPos(center) return } */ let cos = dir.dot(label.lineDir) let nearPoint = cos > 0 ? p2 : p1 //近端点。 let far = cos > 0 ? p1 : p2 //远端点。 let nearPointDir = new THREE.Vector3().subVectors(nearPoint,eyePos)//.normalize() //使label在中点和近端点中变化, 近端点可能到了相机后方,需要投影到相机所在平面上 if(nearPointDir.dot(dir)<0){//近端点到了相机后方,前移。 //let hfov = cameraLight.getHFOVForCamera(viewer.mainViewport.camera , true ); //暂且只看水平fov //if(nearPointDir.dot(dir)0?label.lineDir:label.lineDir.clone().negate()) let camDirPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(dir, eyePos) nearPoint = ray.ray.intersectPlane(camDirPlane, new THREE.Vector3()) if(!nearPoint){//线是垂直的,视线是水平的时候 return label.setPos(center) } } //防止离远了之后也偏移很多,但远了之后相机到端点vec和到中点的vec的夹角接近,不需要怎么偏移的。 let dis1 = nearPoint.distanceToSquared(eyePos) let dis2 = far.distanceToSquared(eyePos) let diff = Math.abs(dis1/dis2) diff<1 && (diff = 1/diff) diff = math.linearClamp(diff,[0, 30], [ 0,1 ]) let efficiency = 0.7; // 0-1 数值越高,r越容易接近1或-1,label越容易在倾斜后靠近近端点。 //let r = 0.5*efficiency*cos + 0.5 let r = 0.5*efficiency*diff*cos + 0.5 r = THREE.Math.clamp(r,0.1,0.9) //视线越接近线的方向,标签应该越往近端点偏移,防止看起来几乎在远端。 if(cos > 0){ center = p1.clone().multiplyScalar(1-r).add(nearPoint.clone().multiplyScalar(r)); //label在线上滑动,使尽量保持在视觉中心 }else{ center = nearPoint.clone().multiplyScalar(1-r).add(p2.clone().multiplyScalar(r)); //label在线上滑动,使尽量保持在视觉中心 } label.setPos(center) } //归零 //this.orient2dInfo = null //this.markers.forEach(e=>e.needsUpdate=true) }else{ label.setPos(center) } } cloneMarker(cloneIndex, index){ return this.addMarker({ index, point: this.points[cloneIndex], dataset_point:this.dataset_points && this.dataset_points[cloneIndex], points_dataset:this.points_datasets[cloneIndex] }) } addMarker (o={}) { var index = o.index == void 0 ? this.points.length : o.index //要当第几个 let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, name:"measure_point"} ) Potree.Utils.setObjectLayers(marker, 'measure' ) marker.pickOrder = marker.renderOrder = Potree.config.renderOrders.measureMarker marker.markerSelectStates = {} marker.addEventListener('startDragging',(e)=>{ /* if(e.drag.dragViewport.name == 'MainView') */viewer.inputHandler.dispatchEvent( {type: 'measuring',v:true, cause:'startDragging', situation:'dragging', object:this}) //add for 调试,方便后期增加点 if(!this.isNew && viewer.inputHandler.pressedKeys['M'.charCodeAt(0)] && this.points.length{ if( e.button != THREE.MOUSE.LEFT )return viewer.inputHandler.dispatchEvent({type: 'measuring', v:false, cause:'stopDragging', situation:'dragging', object:this} ) this.lastDropTime = Date.now() if(Potree.settings.adsorption){ this.isNew || viewer.viewports.forEach((viewport)=>{ this.getPointsPos2d(viewport, true )//forceUpdate }) } this.isNew || viewer.measuringTool.history.afterChange(this) }) marker.addEventListener('click',()=>{ if(viewer.measuringTool.editMode == 'delPoint' ){ /* if(this.points.length == this.minMarkers){//--前端去重绘 viewer.scene.removeMeasurement(this) }else{ */ viewer.measuringTool.history.beforeChange(this) let index = this.markers.indexOf(marker) this.removeMarker(index) viewer.measuringTool.history.afterChange(this) this.dispatchEvent('changed') //} } }) //marker.measure = this let edge { // edges edge = LineDraw.createFatLine( [ ],{mat:this.getLineMat('edgeDefault')} ) edge.pickOrder = 0 Potree.Utils.setObjectLayers(edge, 'measure' ) let addHoverEvent = ()=>{ //当非isNew时才添加事件 let mouseover = (e) => { /* if(this.measureType == 'MulDistance'){ } */ this.setSelected(true, 'edge') }; let mouseleave = (e) => { this.setSelected(false, 'edge') }; edge.addEventListener('mouseover', mouseover); edge.addEventListener('mouseleave', mouseleave); edge.removeEventListener('addHoverEvent', addHoverEvent); edge.addEventListener('click',(e)=>{ let now = Date.now() if(now - this.lastDropTime<100)return ;//防止拖拽marker时误触导致focus, 以及点到marker不focus if(viewer.measuringTool.editMode == 'addPoint' && this.points.length < this.maxMarkers){ viewer.measuringTool.history.beforeChange(this) let index = this.edges.indexOf(edge) + 1 let nextIndex = index % this.edges.length let point = math.getFootPoint(e.hoveredElement.point, this.points[index-1], this.points[nextIndex] ); this.addMarker({ index, point, dataset_point: this.dataset_points && new THREE.Vector3 , //初始化 points_dataset : this.points_datasets[index-1] //使用前一个的 }) this.updateDatasetBelong(index) //获取dataset_point viewer.measuringTool.history.afterChange(this) this.dispatchEvent('changed') //this.update({}) }else{ this.isNew || viewer.measuringTool.isAdding || viewer.focusOnObject(this, 'measure') //正在添加测量线时不要focus其他线(容易误触) } }) } edge.addEventListener('addHoverEvent', addHoverEvent); if(!this.isNew){ edge.dispatchEvent('addHoverEvent') } } super.addMarker(Object.assign(o, {index, marker, edge})) if(this.showEdges){ // edge labels const edgeLabel = this.createEdgeLabel('edgeLabel', !this.closed) this.edgeLabels = [...this.edgeLabels.slice(0,index), edgeLabel, ...this.edgeLabels.slice(index,this.edgeLabels.length)] } if(this.showCoordinates){ // coordinate labels let coordinateLabel = new Label({ className:'measure_pointPos', camera: viewer.scene.getActiveCamera() }) coordinateLabel.setVisible(false) this.coordinateLabels.push(coordinateLabel); } let event = { type: 'marker_added', measurement: this, marker: marker }; this.dispatchEvent(event); //this.setMarker(this.points.length - 1, point); this.update({index})//更新一下倒数第二条线 return marker;//add }; editStateChange(state){ //主要针对edgeLabels显示切换,编辑时显示 super.editStateChange(state) if(!state){ this.editStateTimer = setTimeout(()=>{ if(!this.isEditing){ this.dispatchEvent({type:'editStateChange',state:false}) this.setEdgesDisplay(false) this.areaPlane && Potree.Utils.updateVisible(this.areaPlane, 'intersectLastLine', true) this.areaLabel && Potree.Utils.updateVisible(this.areaLabel, 'intersectLastLine', true) } },100) }else{ if(!this.isEditing){ this.dispatchEvent({type:'editStateChange',state:true}) this.setEdgesDisplay(true) clearTimeout(this.editStateTimer) } } this.isEditing = state } setMarkerSelected(marker, state, hoverObject){ //console.warn(marker.id , state, hoverObject) marker.markerSelectStates[hoverObject] = state let absoluteState = false for(var i in marker.markerSelectStates){ if(marker.markerSelectStates[i] == 'hover'){ absoluteState = true; break; } } if(absoluteState){ marker.material = this.getMarkerMaterial('select') marker.renderOrder = marker.pickOrder = Potree.config.renderOrders.measureMarker+1 }else{ marker.material = this.getMarkerMaterial('default') marker.renderOrder = marker.pickOrder = Potree.config.renderOrders.measureMarker } marker.selected = absoluteState viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') viewer.dispatchEvent('content_changed') } setEdgesDisplay(state, ignoreGuideLine){ this.closed && this.edgeLabels.forEach(e=>Utils.updateVisible(e,'hover',state)) if(this.totalDisLabel && !viewer.screenshoting){ this.edgeLabels.forEach(e=> Utils.updateVisible(e, 'hover', state, 1, state ? 'add' : 'cancel')) Utils.updateVisible(this.totalDisLabel,'hover', !state ) } if(!ignoreGuideLine && this.measureType == 'Distance'){ this.horEdgeLabel.visible = this.verEdgeLabel.visible = this.horGuideEdge.visible = this.verGuideEdge.visible = !!(state && this.shouldShowHorVerGuide) } } setSelected(state, hoverObject){//add //console.log('setSelected',state, hoverObject) let absoluteState = !!state if(hoverObject){//如果没有hoverObject且state为false 就强制取消选中态 this.selectStates[hoverObject] = state for(var i in this.selectStates){ if(this.selectStates[i]){ absoluteState = true; break; } } } if(absoluteState){ this.markers.forEach(e=>this.setMarkerSelected(e, 'hover', 'selectAll' ) ) this.edges.forEach(e=>{ e.renderOrder = Potree.config.renderOrders.lines + 1 e.material = this.getLineMat('edgeSelect') }) this.areaPlane && (this.areaPlane.material = planeMats.selected) //this.areaLabel && this.areaLabel.elem.addClass('highLight') //this.closed || this.edgeLabels.forEach(e=>e.elem.addClass('highLight') ) this.setEdgesDisplay(true, hoverObject=="screenshot") this.areaLabel && setLabelHightState(this.areaLabel, true) this.closed || this.edgeLabels.forEach(e=>setLabelHightState(e, true) ) }else{ this.markers.forEach(e=>this.setMarkerSelected(e, 'unhover', 'selectAll' )) this.edges.forEach(e=>e.material = this.getLineMat('edgeDefault') ) this.areaPlane && (this.areaPlane.material = planeMats.default) this.setEdgesDisplay(false, hoverObject=="screenshot") //this.areaLabel && this.areaLabel.elem.removeClass('highLight') //this.closed || this.edgeLabels.forEach(e=>e.elem.removeClass('highLight') ) this.areaLabel && setLabelHightState(this.areaLabel, false) this.closed || this.edgeLabels.forEach(e=>setLabelHightState(e, false) ) } this.selected = absoluteState if(hoverObject != 'byList'){ //this.bus && this.bus.emit('highlight', this.selected) this.dispatchEvent({type:'highlight',state:this.selected})//列表高亮 } viewer.dispatchEvent('content_changed') viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') } removeMarker(index ){ super.removeMarker(index) this.points_datasets.splice(index, 1); this.dataset_points && this.dataset_points.splice(index, 1) this.coordinateLabels.splice(index, 1); let edgeIndex = index//(index === 0) ? 0 : (index - 1); if(this.edgeLabels[edgeIndex]){ this.edgeLabels[edgeIndex].dispose() this.edgeLabels.splice(edgeIndex, 1); } this.update({index: this.getIndex(index, -1)}); this.dispatchEvent({type: 'marker_removed', measurement: this}); } setPosition(index, position) { super.setPosition(index, position) let event = { type: 'marker_moved', measure: this, index: index, position: position.clone() }; this.dispatchEvent(event); } dispose(){//add var labels = this.edgeLabels.concat(this.coordinateLabels) this.areaLabel && labels.push(this.areaLabel) labels.forEach(e=>e.dispose()) super.dispose() this.dispatchEvent('disposed') } getTotalDistance () { if (this.points.length === 0) { return 0; } let distance = 0; for (let i = 1; i < this.points.length; i++) { let prev = this.points[i - 1]; let curr = this.points[i]; let d = prev.distanceTo(curr); distance += d; } if (this.closed && this.points.length > 1) { let first = this.points[0]; let last = this.points[this.points.length - 1]; let d = last.distanceTo(first); distance += d; } return distance; } getAngleBetweenLines (cornerPoint, point1, point2) { let v1 = new THREE.Vector3().subVectors(point1, cornerPoint); let v2 = new THREE.Vector3().subVectors(point2, cornerPoint); // avoid the error printed by threejs if denominator is 0 const denominator = Math.sqrt( v1.lengthSq() * v2.lengthSq() ); if(denominator === 0){ return 0; }else{ return v1.angleTo(v2); } }; getAngle (index) { if (this.points.length < 3 || index >= this.points.length) { return 0; } let previous = (index === 0) ? this.points[this.points.length - 1] : this.points[index - 1]; let point = this.points[index]; let next = this.points[(index + 1) % (this.points.length)]; return this.getAngleBetweenLines(point, previous, next); } getCenter(type){ if(this.center){ return this.center.clone() }else{ let center = this.points.reduce(function(total, currentValue ){ return total.add(currentValue) }, new THREE.Vector3 ) this.points.length && center.multiplyScalar(1/this.points.length) return center //求不出重心呜呜 } } // updateAzimuth(){ // // if(this.points.length !== 2){ // // return; // // } // // const azimuth = this.azimuth; // // const [p0, p1] = this.points; // // const r = p0.distanceTo(p1); // } createGuideLine(){//add 辅助线 var guideLine = LineDraw.createFatLine([ ],{mat:this.getLineMat('guide')} ) guideLine.visible = false this.guideLine = guideLine this.add(guideLine); } createHorVerGuideLine(){//创建水平与垂直辅助线,仅距离测量有。 var verGuideEdge = LineDraw.createFatLine([ ],{mat:this.getLineMat('guide')} ) verGuideEdge.visible = false this.verGuideEdge = verGuideEdge verGuideEdge.name = 'verGuideEdge' var horGuideEdge = LineDraw.createFatLine([ ],{mat:this.getLineMat('guide')} ) horGuideEdge.visible = false horGuideEdge.name = 'horGuideEdge' this.horGuideEdge = horGuideEdge this.add(this.verGuideEdge); this.add(this.horGuideEdge); //label: this.verEdgeLabel = this.createEdgeLabel('verGuideEdge') this.horEdgeLabel = this.createEdgeLabel('horGuideEdge') } createEdgeLabel(name, hasHoverEvent){ let inf = { sizeInfo: labelSizeInfo, name:name||'edgeLabel', } if(name && name.includes('Guide')){ inf.fontsize = 12 } const edgeLabel = new TextSprite( $.extend({}, hasHoverEvent ? mainLabelProp : subLabelProp, inf) ) if(hasHoverEvent){ edgeLabel.addEventListener('mouseover',()=>{ this.setSelected(true, 'edgeLabel') }) edgeLabel.addEventListener('mouseleave',()=>{ this.setSelected(false, 'edgeLabel') }) edgeLabel.addEventListener('click',()=>{ this.isNew || viewer.measuringTool.isAdding || viewer.focusOnObject(this, 'measure') }) } edgeLabel.visible = false edgeLabel.measure = this edgeLabel.sprite.material.depthTestWhenPick = true Potree.Utils.setObjectLayers(edgeLabel, 'measure' ) this.add(edgeLabel) if(this.measureType == 'MulDistance'){ edgeLabel.visiMap = new Map() } return edgeLabel } createAreaLabel(){ const areaLabel = this.createCenterLabel('areaLabel') return areaLabel; } createTotalDisLabel(){ const totalDisLabel = this.createCenterLabel('totalDisLabel') return totalDisLabel; } createCenterLabel(name){ const centerLabel = new TextSprite( $.extend({},mainLabelProp,{sizeInfo: labelSizeInfo, name, disToLine:0, fontsize:16*textSizeRatio} ) ) centerLabel.addEventListener('mouseover',()=>{ this.isNew || this.setSelected(true, 'centerLabel') }) centerLabel.addEventListener('mouseleave',()=>{ this.isNew || this.setSelected(false, 'centerLabel') }) centerLabel.addEventListener('click',()=>{ this.isNew || viewer.measuringTool.isAdding || viewer.focusOnObject(this, 'measure') }) Potree.Utils.setObjectLayers(centerLabel, 'measure' ) Utils.updateVisible(centerLabel, 'setVisible', false) return centerLabel; } getMarkerMaterial(type) { if(!markerMats){ markerMats = { default: new DepthBasicMaterial($.extend({},lineDepthInfo,{ transparent: !0, opacity: 1, map: texLoader.load(Potree.resourcePath+'/textures/pic_point_s32.png' ), useDepth:true , mapScale: markerMapShrink })), select: new THREE.MeshBasicMaterial({ transparent: !0, opacity: 1, depthTest:false, map: texLoader.load(Potree.resourcePath+'/textures/pic_point32.png'/* , null, null, { antialias: false } */), }), } Measure.markerMats = markerMats markerMats.select.map.repeat.set(1/markerMapShrink,1/markerMapShrink) markerMats.select.map.offset.set((markerMapShrink-1)/2/markerMapShrink, (markerMapShrink-1)/2/markerMapShrink) //markerMats.select.map.offset.set( -1.1 , -1.1 ) } return markerMats[type] } getLineMat(type) { if(!Measure.lineMats){ Measure.lineMats = { edgeDefault: LineDraw.createFatLineMat($.extend({},lineDepthInfo,{ color: config.measure.default.color, lineWidth: config.measure.lineWidth, useDepth :true, dashWithDepth :true, // 只在被遮住的部分显示虚线,因为实线容易挡住label dashed :true, dashSize : 0.04, gapSize: 0.04, transparent: true, opacity: config.measure.default.opacity, depthTestWhenPick:true, })), edgeSelect: LineDraw.createFatLineMat($.extend({},lineDepthInfo,{ color: config.measure.highlight.color,//'#f0ff00', dashSize: 0.5, gapSize: 0.2, lineWidth: config.measure.lineWidth , transparent: true, opacity: config.measure.highlight.opacity })), guide: LineDraw.createFatLineMat($.extend({},lineDepthInfo,{ color:config.measure.guide.color, dashSize: 0.1, gapSize: 0.02, dashed: true, lineWidth: config.measure.lineWidth/2 })), } } return Measure.lineMats[type] } createAreaPlane(){ planeMats || (planeMats = { default: new DepthBasicMaterial( $.extend({},lineDepthInfo,{ color:color, side:THREE.DoubleSide, opacity:0.2, transparent:true, useDepth:true, })), selected: new THREE.MeshBasicMaterial({ color: color , side:THREE.DoubleSide, opacity:0.3, transparent:true, //wireframe:true }) },Measure.planeMats = planeMats) return super.createAreaPlane(planeMats.default) } raycast (raycaster, intersects) { for (let i = 0; i < this.points.length; i++) { let marker = this.markers[i]; marker.raycast(raycaster, intersects); } // recalculate distances because they are not necessarely correct // for scaled objects. // see https://github.com/mrdoob/three.js/issues/5827 // TODO: remove this once the bug has been fixed for (let i = 0; i < intersects.length; i++) { let I = intersects[i]; I.distance = raycaster.ray.origin.distanceTo(I.point); } intersects.sort(function (a, b) { return a.distance - b.distance; }); }; getPointsPos2d(viewport, update){//获取屏幕上的二维坐标 let ps = this.pointsPos2d.get(viewport) if(update || !ps){ let points = this.points.map(e=>{ let p = Potree.Utils.getPos2d(e, viewport, viewer.renderArea ) p.pos3d = e.clone(), p.object = this return p }); this.pointsPos2d.set(viewport, points) console.log('updatePointsPos2d',this.uuid,viewport.name) } return this.pointsPos2d.get(viewport) } transformData(prop){ if(prop.measureType == 'Point'){ prop.showCoordinates = true, prop.closed = true, prop.maxMarkers = 1, prop.minMarkers = 1 }else if(prop.measureType == 'Distance'){ prop.showDistances = true, prop.showEdges = true, prop.maxMarkers = 2, prop.minMarkers = 2 }else if(prop.measureType == 'MulDistance'){ prop.showDistances = true, prop.showEdges = true, prop.minMarkers = 2 }else if(prop.measureType == 'MulDistance Ring'){ prop.showDistances = true, prop.showEdges = true, prop.showArea = true, prop.closed = true, prop.minMarkers = 3 }else if(prop.measureType == 'Ver MulDistance'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.minMarkers = 2 prop.faceDirection = "vertical" prop.unableDragAtMap = true }else if(prop.measureType == 'Hor MulDistance'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.minMarkers = 2 prop.faceDirection = "horizontal" }else if(prop.measureType == 'Ver Distance'){ prop.showDistances = true, prop.showEdges = true, prop.maxMarkers = 2, prop.minMarkers = 2, prop.faceDirection = "vertical" prop.unableDragAtMap = true }else if(prop.measureType == 'Hor Distance'){ prop.showDistances = true, prop.showEdges = true, prop.maxMarkers = 2, prop.minMarkers = 2, prop.faceDirection = "horizontal" }else if(prop.measureType == 'Area'){ prop.showDistances = true, Potree.settings.areaAtNotPlane || (prop.atPlane = true) prop.showEdges = true, prop.closed = true, prop.minMarkers = 3 }else if(prop.measureType == 'Hor Area'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.closed = true, prop.minMarkers = 3 prop.faceDirection = "horizontal" }else if(prop.measureType == 'Ver Area'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.closed = true, prop.minMarkers = 3 prop.faceDirection = "vertical" prop.unableDragAtMap = true }else if(prop.measureType == 'Rect Area'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.closed = true, prop.minMarkers = 4 prop.maxMarkers = 4 }else if(prop.measureType == 'Hor Rect Area'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.closed = true, prop.minMarkers = 4 prop.maxMarkers = 4 prop.isRect = true prop.faceDirection = "horizontal" }else if(prop.measureType == 'Ver Rect Area'){ prop.showDistances = true, prop.atPlane = true, prop.showEdges = true, prop.closed = true, prop.minMarkers = 4 prop.maxMarkers = 4 prop.isRect = true prop.faceDirection = "vertical" prop.unableDragAtMap = true } if(prop.atPlane && prop.closed){ //atPlane在同一平面上 prop.showArea = true } super.transformData(prop) } setUnitSystem(unitSystem){ //console.log(this.name +':' +this.unitSystem) if(unitSystem != this.unitSystem){ if(unitSystem == "metric"){ }else if(unitSystem == 'imperial'){ } this.unitSystem = unitSystem this.update() } } reDraw(restMarkerCount=0){//重新开始画 super.reDraw(restMarkerCount) if(this.measureType == 'Distance'){ this.shouldShowHorVerGuide = false this.setEdgesDisplay(false) } if(this.showArea){ this.area = {value:0}; this.areaLabel && Utils.updateVisible(this.areaLabel, 'setVisible', false ) } if(this.totalDisLabel && this.showTotalDis){ Utils.updateVisible(this.totalDisLabel, 'setVisible', false ) } viewer.inputHandler.dispatchEvent( {type:'measuring', v:true, cause:'reDraw',object:this, situation:'dragging'} ) } } function setLabelHightState(label, state){ if(state){ let color = new THREE.Color(Potree.config.measure.highlight.color) //label.sprite.material.opacity = config.measure.highlight.opacity //label.setBackgroundColor({r:255*color.r, g:255*color.g, b:255*color.b, a:config.measure.highlight.opacity}) label.sprite.material.useDepth = false; //label.textColor = {r: this.color.r*255, g: this.color.g*255, b: this.color.b*255, a: 1} }else{ //label.setBackgroundColor({r: this.color.r*255, g: this.color.g*255, b: this.color.b*255, a:config.measure.default.opacity}) label.sprite.material.useDepth = true //label.sprite.material.opacity = 0.98 //label.textColor = {r: 255, g: 255, b: 255, a: 1} } label.updateTexture() } /* function setLabelHightState(label, state){ if(state){ label.setBackgroundColor({r: highlightColor.r*255, g: highlightColor.g*255, b: highlightColor.b*255, a:config.measure.highlight.labelOpacity}) label.sprite.material.useDepth = false; }else{ label.setBackgroundColor(mainLabelProp.backgroundColor) label.sprite.material.useDepth = true } label.updateTexture() //label.sprite.material.needsUpdate = true } */ function createCircleRadiusLabel(){ const circleRadiusLabel = new TextSprite(""); circleRadiusLabel.setTextColor({r: 140, g: 250, b: 140, a: 1.0}); circleRadiusLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); circleRadiusLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); circleRadiusLabel.fontsize = 16; circleRadiusLabel.material.depthTest = false; circleRadiusLabel.material.opacity = 1; circleRadiusLabel.visible = false; return circleRadiusLabel; } function createCircleRadiusLine(){ /* const lineGeometry = new LineGeometry(); lineGeometry.setPositions([ 0, 0, 0, 0, 0, 0, ]); const lineMaterial = new LineMaterial({ color: 0xff0000, lineWidth: 2, resolution: new THREE.Vector2(1000, 1000), gapSize: 1, dashed: true, }); lineMaterial.depthTest = false; const circleRadiusLine = new Line2(lineGeometry, lineMaterial);*/ var circleRadiusLine = LineDraw.createFatLine([ ],{ color:0xff0000, dashSize: 0.5, gapSize: 0.2, lineWidth: config.measure.lineWidth }) circleRadiusLine.visible = false; return circleRadiusLine; } function createCircleLine(){ const coordinates = []; let n = 128; for(let i = 0; i <= n; i++){ let u0 = 2 * Math.PI * (i / n); let u1 = 2 * Math.PI * (i + 1) / n; let p0 = new THREE.Vector3( Math.cos(u0), Math.sin(u0), 0 ); let p1 = new THREE.Vector3( Math.cos(u1), Math.sin(u1), 0 ); coordinates.push( p0, p1 ); } /* const geometry = new LineGeometry(); geometry.setPositions(coordinates); const material = new LineMaterial({ color: 0xff0000, dashSize: 5, gapSize: 2, lineWidth: 2, resolution: new THREE.Vector2(1000, 1000), }); material.depthTest = false; const circleLine = new Line2(geometry, material); circleLine.visible = false; circleLine.computeLineDistances();*/ var circleLine = LineDraw.createFatLine(coordinates,{ color: 0xff0000, dashSize: 0.5, gapSize: 0.2, lineWidth: config.measure.lineWidth }) return circleLine; } /* function createCircleCenter(){ const sg = new THREE.markerGeometry(1, 32, 32); const sm = new THREE.MeshNormalMaterial(); const circleCenter = new THREE.Mesh(sg, sm); circleCenter.visible = false; return circleCenter; } */ function createLine(){ const line = LineDraw.createFatLine([ ],{ color: 0xff0000, dashSize: 0.5, gapSize: 0.2, lineWidth: config.measure.lineWidth }) return line; } function createCircle(){ const coordinates = []; let n = 128; for(let i = 0; i <= n; i++){ let u0 = 2 * Math.PI * (i / n); let u1 = 2 * Math.PI * (i + 1) / n; let p0 = new THREE.Vector3( Math.cos(u0), Math.sin(u0), 0 ); let p1 = new THREE.Vector3( Math.cos(u1), Math.sin(u1), 0 ); coordinates.push( p0, p1 ); } var line = LineDraw.createFatLine(coordinates,{ color: 0xff0000, dashSize: 0.5, gapSize: 0.2, lineWidth: config.measure.lineWidth }) return line; } /* function createAzimuth(){ const azimuth = { label: null, center: null, target: null, north: null, centerToNorth: null, centerToTarget: null, centerToTargetground: null, targetgroundToTarget: null, circle: null, node: null, }; const sg = new THREE.markerGeometry(1, 32, 32); const sm = new THREE.MeshNormalMaterial(); { const label = new TextSprite(""); label.setTextColor({r: 140, g: 250, b: 140, a: 1.0}); label.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); label.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); label.fontsize = 16; label.material.depthTest = false; label.material.opacity = 1; azimuth.label = label; } azimuth.center = new THREE.Mesh(sg, sm); azimuth.target = new THREE.Mesh(sg, sm); azimuth.north = new THREE.Mesh(sg, sm); azimuth.centerToNorth = createLine(); azimuth.centerToTarget = createLine(); azimuth.centerToTargetground = createLine(); azimuth.targetgroundToTarget = createLine(); azimuth.circle = createCircle(); azimuth.node = new THREE.Object3D(); azimuth.node.add( azimuth.centerToNorth, azimuth.centerToTarget, azimuth.centerToTargetground, azimuth.targetgroundToTarget, azimuth.circle, azimuth.label, azimuth.center, azimuth.target, azimuth.north, ); return azimuth; } */ /* 按alt鼠标滚轮或WS键放慢。 按Alt键可以平行屏幕拖拽点。&dragPolyBeyondPoint 后缀在拖拽到无点云区域也是此效果。 按M键拖拽点可以复制出当前点 */