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 , MeshDraw} 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' import {Measure} from './Measure.js' import CursorDeal from "../../utils/CursorDeal.js"; let texLoader = new THREE.TextureLoader() const labelSizeInfo = {width2d:180} //稍微小点防止字体模糊 const titleLineHeight = 2 const arrowCountMax = 1000; //箭头总数不能超过这个值。 The count value passed into the constructor represents the maximum number of instances of this mesh. You can change the number of instances at runtime to an integer value in the range [0, count].If you need more instances than the original count value, you have to create a new InstancedMesh. let lastArrowCamPos, lastArrowCount = 0 const depthProps = { useDepth : true , //startClipDis : 0.5, clipDistance : 2,//消失距离 //startOcclusDis: 0.5, occlusionDistance: 0.7,//变为backColor距离 maxOcclusionFactor:0.9, maxClipFactor:1 } const planeGeo = new THREE.PlaneBufferGeometry(1,1) const voidGeometry = new THREE.BufferGeometry() let markerMats const getMarkerMat = function (name) { if(!markerMats){ markerMats = { default: new DepthBasicMaterial( $.extend({}, depthProps,{ transparent: true, map: texLoader.load( Potree.resourcePath+'/textures/dot_n.png'), opacity:0.9 })), drag : new DepthBasicMaterial({ transparent: true, useDepth:false, map: texLoader.load( Potree.resourcePath+'/textures/dot_s.png'), }), delete : new DepthBasicMaterial({ transparent: true, useDepth:false, map: texLoader.load( Potree.resourcePath+'/textures/dot_r.png'), }), adding : new DepthBasicMaterial({ transparent: true, opacity:0.3, useDepth:false, map: texLoader.load( Potree.resourcePath+'/textures/dot_n.png'), }), } for(let i in markerMats){markerMats[i].map.anisotropy = 4} } return markerMats[name] } const getMeshQuaInPath = (lineDir)=>{ const quaBase = new THREE.Quaternion().setFromEuler(new THREE.Euler(-Math.PI/2,0, Math.PI/2)) return math.getQuaFromPosAim( new THREE.Vector3, lineDir ).multiply(quaBase) } const getEndCaps = (function () { let endCap, map return function(path){ if(!endCap){ map = texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png') map.anisotropy = 3 map.repeat.set(0.5, 1); //map.magFilter = THREE.NearestFilter let mesh = new THREE.Mesh(planeGeo ) mesh.scale.x = 0.5; mesh.position.x = -0.25 //Potree.Utils.setObjectLayers(mesh,'measure') endCap = new THREE.Object3D() endCap.add(mesh) } let endCaps = [] let material = path.edge.material.clone() material.map = map delete material.defines.mapOverlay for(let i=0;i<2;i++){ let cap = endCap.clone() cap.children[0].material = material path.add(cap) endCaps.push(cap) } return endCaps } })() const setMarkerScale = (marker, halfPathWidth)=>{ let s = halfPathWidth * 2.9 marker.scale.set(s,s,s) } let fakeMarker let lastFadeTime let showFakeMarker = (path, position)=>{//添加marker时指示位置 if(!fakeMarker){ fakeMarker = new THREE.Mesh(planeGeo, getMarkerMat('adding')) fakeMarker.name = 'fakeMarker' fakeMarker.renderOrder = Potree.config.renderOrders.path.marker } setMarkerScale(fakeMarker, path.halfPathWidth) path.add(fakeMarker) fakeMarker.position.copy(position) Potree.Utils.updateVisible(fakeMarker,'add',true) viewer.dispatchEvent('content_changed') return fakeMarker } let hideFakeMarker = ()=>{ fakeMarker && Potree.Utils.updateVisible(fakeMarker,'add',false) lastFadeTime = Date.now() } export class Path extends ctrlPolygon{ constructor (prop) { super('Path', prop); this.zPlaneWhenNoIntersect = 0 //允许不在model上,直接绘制此高度的水平面上 this.markerLabels = []; this.points_datasets || (this.points_datasets = []) //存每个点是哪个数据集 this.hoverStates = {} this.selectStates = {} this.setFadeFar(null) this.geoPoints = [] { let group = new THREE.Object3D; group.name = 'titleGroup' this.titleLabel = new TextSprite(Object.assign({}, depthProps,{ text: '', sizeInfo:{width2d:200}, textColor:{r:255,g:255,b:255,a:1.0}, backgroundColor:{r:0,g:0,b:0,a:0.5}, borderRadius: 6, fontsize: this.fontsize || 13, renderOrder : Potree.config.renderOrders.path.label, pickOrder: Potree.config.renderOrders.path.label, clipDistance : 10, fadeFar: this.fadeFar, transform2Dpercent:{x:0,y:0.5}, //向上移动一半 maxLineWidth: 300, textAlign: Potree.settings.isOfficial && 'left' })) this.titleLabel.sprite.material.depthTest = false let line = LineDraw.createFatLine([new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,titleLineHeight)], Object.assign({},depthProps,{color: '#ffffff', lineWidth: 1, transparent:true, fadeFar: this.fadeFar})) line.renderOrder = Potree.config.renderOrders.line group.add(line) group.add(this.titleLabel) this.titleLabel.position.z = titleLineHeight this.add(group); this.setTitleVisi(this.titleLabel.parent, false, 'noPoint') this.setTitle(Potree.settings.isOfficial ? '' : 'title' ) line.addEventListener('mouseover',(e)=>{ this.editEnable && CursorDeal.add('hoverGrab') }); line.addEventListener('startDragging',(e)=>{ this.editEnable && CursorDeal.add('grabbing') }); line.addEventListener('drop',(e)=>{ this.editEnable && CursorDeal.remove('grabbing') }); line.addEventListener('mouseleave',(e)=>{ this.editEnable && CursorDeal.remove('hoverGrab') }); line.addEventListener('drag',(e)=>{ if(this.editEnable){//一旦用户拖动了title,title就固定了,不再随着path居中 let position if(e.intersect?.location){ position = e.intersect?.location }else{ let {x,y} = Potree.Utils.getPointerPosAtHeight(0,e.pointer) position = new THREE.Vector3(x,y,0) } this.updateTitlePos(position) this.dispatchEvent({type:'titlePosChanged', position , root:e.intersect?.pointcloud || e.intersect?.object}) } }); } {//和measure不同的是它的边是连在一起的一整条 this.edge = new THREE.Mesh(voidGeometry, new DepthBasicMaterial( $.extend({}, depthProps,{color: this.color , /* opacity: 0.6, */side:2,/* transparent:true, */fadeFar: this.fadeFar}))) //new THREE.MeshBasicMaterial({depthWrite:false, transparent:true, color:this.color || '#fff', opacity:0.5, side:2})) this.edge.material.defines.mapOverlay = true this.add(this.edge) this.edge.renderOrder = Potree.config.renderOrders.path.edge, //和tag的一样,但为何遮不住它?好在一般底下有一层地面能遮住 this.edge.name = 'pathEdge' let addHoverEvent = ()=>{ let mouseover = (e) => { if(this.isNew)return this.setSelected('hover') this.hoverStates.edge = true if(this.addOrRemovePoint && !this.isNew){ CursorDeal.add('pen_addPoint') } }; let mouseleave = (e) => { if(this.isNew)return this.setSelected('unhover') this.hoverStates.edge = false CursorDeal.remove('pen_addPoint') hideFakeMarker() }; this.edge.addEventListener('mouseover', mouseover); this.edge.addEventListener('mouseleave', mouseleave); this.edge.addEventListener('mousemove', (e)=>{ if(this.addOrRemovePoint && !this.isNew && !this.hoverStates.marker){ let { point } = this.getPosByIntersect(e, 'onlyPoint') showFakeMarker(this, point) } }); this.edge.addEventListener('click',(e)=>{ let now = Date.now() if(now - this.lastDropTime<100 || this.isNew || e.button !== THREE.MOUSE.LEFT)return ; if(this.addOrRemovePoint ){ let {index, prevIndex, point} = this.getPosByIntersect(e) viewer.measuringTool.history.beforeChange(this) this.addMarker({ index, point, dataset_point: this.dataset_points && new THREE.Vector3 , //初始化 points_dataset : this.points_datasets[prevIndex] //使用前一个的 }) this.updateDatasetBelong(index) //获取dataset_point viewer.measuringTool.history.afterChange(this) this.updateEdge() this.hideArrowUntilUpdate() this.dispatchEvent('changed') }else{ this.isNew || viewer.measuringTool.isAdding || this.setSelected('click') //viewer.focusOnObject(this, 'measure') //正在添加测量线时不要focus其他线(容易误触) } e.consume() //防止后续双击 }) } this.edge.addEventListener('addHoverEvent', addHoverEvent, {once:true}); if(!this.isNew){ this.edge.dispatchEvent('addHoverEvent') } } { this.endCaps = getEndCaps(this) //端点处的半圆 } this.addEventListener('marker_dropped',(e)=>{ this.updateDatasetBelong(e.index) }) this.setPathWidth(prop.width || 0.2) this.setColor(prop.color || '#fff') this.setEditEnable(true) this.lastDropTime = 0 //Potree.Utils.setObjectLayers(this, 'measure' ) if(!Potree.settings.isOfficial){ this.setAddOrRemPoint(true) this.addEventListener('createDone',()=>{ this.setAddOrRemPoint(false) }) this.setArrowDisplay(true) } this.initData(prop) } updateTitlePos(pos){ if(pos){ this.fixedTitlePos = pos }else{ if(!this.fixedTitlePos){ //居中 pos = this.getCenter() } } if(pos){ this.titleLabel.parent.position.copy(pos) this.titleLabel.updatePose() } } getPosByIntersect(e, type){//intersect落在线上的位置,以及在哪两个点之间 if( !Potree.settings.pathSmooth ){ let prevIndex = Math.floor(e.hoveredElement.faceIndex / 2) //端点1(可能是最后一个) let nextIndex = this.getIndex(prevIndex, 1) //端点2(可能是第一个) let index = prevIndex + 1 //新点在端点1后 let point = math.getFootPoint(e.hoveredElement.point, this.points[prevIndex], this.points[nextIndex] ); return {index, prevIndex, point } }else{ let prevIndex0 = Math.floor(e.hoveredElement.faceIndex / 2) //所在的mesh片段的端点1 let nextIndex0 = prevIndex0 + 1 //所在的mesh片段的端点2 let point = math.getFootPoint(e.hoveredElement.point, this.geoPoints[prevIndex0], this.geoPoints[nextIndex0] ); //新点位置 if(type == 'onlyPoint')return {point} let prevIndex,nextIndex let count = this.points.length - 1 for(let i=0;i this.UtoTMapArr[prevIndex0]){ prevIndex = i //该片段端点1在原先points中哪个点之后(可包含) } if(nextIndex == void 0 && i / count <= this.UtoTMapArr[nextIndex0] && (i+1) / count > this.UtoTMapArr[nextIndex0]){ nextIndex = i //该片段端点2在原先points中哪个点之后(可包含) } } if(nextIndex == void 0){//最后一个点 nextIndex = count-1 } if(prevIndex != nextIndex){//端点在不同points区间, 需要判断intersect的究竟在哪个区间 //console.log('跨点', prevIndex, nextIndex) this.geoPoints[prevIndex] let lengths = [] //geo端点间包含points哪些点 let j=prevIndex; let A = this.geoPoints[prevIndex0] let B = this.geoPoints[nextIndex0] let APlen = A.distanceTo(point); // 端点->点击点 let AB = new THREE.Vector3().subVectors(B,A) let searchIndex while(j <= nextIndex){ //根据APlen长度算区间 let len = AB.clone().normalize().dot(new THREE.Vector3().subVectors(this.points[j+1], A)) //在AB的投影长度 if(len > APlen){ searchIndex = j break } j++ } searchIndex == void 0 && (searchIndex = nextIndex) //最后一个点之后 prevIndex = searchIndex } nextIndex = prevIndex + 1 //console.log(prevIndex, nextIndex) let index = prevIndex + 1 //新点在端点1后 return {index, prevIndex, point } } }//如果后续还出现index错误的问题,可以改为绘制时用折线,完成后用曲线。 createMarkerLabel(text, hasHoverEvent){ const label = new TextSprite( $.extend( {}, depthProps, { sizeInfo: labelSizeInfo, name:'markerTitle', text : "" , fontsize: this.fontsize || 12, renderOrder : Potree.config.renderOrders.path.label, pickOrder: Potree.config.renderOrders.path.label, fontWeight:'',//thick backgroundColor:{r:255,g:255,b:255,a:0.5}, textColor:{r:0,g:0,b:0,a:1}, fadeFar: this.fadeFar, maxLineWidth: 300, transform2D: {x:0,y:0.2}, //朝上偏移一些 配合最外层z混合增高 textAlign: Potree.settings.isOfficial && 'left' }) ) if(hasHoverEvent){ label.addEventListener('mouseover',()=>{ this.setSelected(true, 'label') }) label.addEventListener('mouseleave',()=>{ this.setSelected(false, 'label') }) label.addEventListener('click',()=>{ this.isNew || viewer.measuringTool.isAdding || viewer.focusOnObject(this, 'measure') }) } //label.measure = this //label.sprite.material.depthTestWhenPick = true //Potree.Utils.setObjectLayers(label, 'measure' ) this.add(label) return label } updateMarker(marker, pos){ marker.position.copy(pos); marker.position.z += 0.01 let index = this.markers.indexOf(marker) if(this.markerLabels[index]){ this.markerLabels[index].position.copy(pos) this.markerLabels[index].position.z += 0.3 //混合增高 this.markerLabels[index].updatePose() } } setPathWidth(w){ let v = w / 2 if(this.halfPathWidth == v)return this.halfPathWidth = v this.markers.forEach(e=>setMarkerScale(e, this.halfPathWidth)) this.updateEdge() this.editEnable || this.updateEndCaps() this.updateArrowRepeat() viewer.dispatchEvent('content_changed') this.hideArrowUntilUpdate() } setColor(color){ if(this.color == color)return this.color = color let c = new THREE.Color().set(this.color) this.titleLabel.setTextColor({r: c.r*255, g:c.g*255, b:c.b*255, a:0.5}) this.updateSelectStyle() //apply color viewer.dispatchEvent('content_changed') } updateEdge(){ if(this.lastUpdatePoints_ && Potree.Common.ifSame(this.lastUpdatePoints_,this.points) && this.halfPathWidth == this.lastHalfPathWidth) return //没变 不更新 //this.edge.geometry = MeshDraw.getExtrudeGeo(edgeExtrudePoints, null, {extrudePath: this.points, openEnded:true, shapeDontClose:true/* , dontSmooth:true, steps: this.points.length-1 */}) //getExtrudeGeo是平滑过的曲线,和设计不一样,且容易翻转,转角有时候细分过少 //只允许path水平放置 let geo let points = this.getDifferentPoint(this.points) let count = points.length this.edge.geometry.dispose() if(count <= 1){ geo = voidGeometry this.geoPoints = [] this.curve = null }else{ if(Potree.settings.pathSmooth){//使用曲线 let curve = this.curve = new THREE.CatmullRomCurve3(points, false ) curve.UtoTMapArr = [] //用于存储 getSpacedPoints得到的点对应points的百分比对应 let oldCount = count count = Math.max(2, Math.round(this.getTotalDistance() * 200)), points = curve.getSpacedPoints(count-1) //拆分为更密集的点 /* //window.arcLengthDivisions && (curve.arcLengthDivisions = arcLengthDivisions) //默认200,但改为1也没变化呀 let oldCount = count //减少点数(拐弯的部分紧凑些,直线部分宽松些): let lastVec let newPoints = [] this.UtoTMapArr = [] let pointIndexs = []//找出新点中对应原先控制点的index,这些点必须加入拐点,否则会出现控制点偏移path(当它所在部分接近直线时) for(let n=1;ne>= n / (oldCount-1) ) ) } for(let i=0;i1){// 和上一个加入点的vec之间的夹角如果过大就加入 if(pointIndexs.includes(i) || curVec.angleTo(lastVec) > 0.05){//最小角度 newPoints.push(point) this.UtoTMapArr.push(curve.UtoTMapArr[i]) lastVec = curVec } } } } points = newPoints */ let result = MeshDraw.lessCurvePoints(points, oldCount, 0.05, curve.UtoTMapArr) this.UtoTMapArr = result.newUtoTMapArr points = result.newPoints count = points.length //delete curve.UtoTMapArr } let lastSideVec let posArr = [], faceArr = [], uvArr = [] let gatherLen = 0 //累加长度 this.geoPoints = points //根据点序列计算geometry for(let i=0; ie.clone()) }//不使用curve的曲线是因为那种geo点数较多,且改的话不知道updateArrows怎么改,除非变为贴图,但就和需求中需要调节间距违背。 update(options={}) { if(options.index == -1)return super.update(options) //updateEdge marker this.points.length<=1 && this.updateEdge() //补偿上一句 this.editEnable || this.updateEndCaps() { let oldVisi = this.titleLabel.parent.visible this.setTitleVisi(this.titleLabel.parent, this.markers.length>0, 'noPoint') if(this.titleLabel.parent.visible && !oldVisi)this.titleLabel.updatePose() } this.updateTitlePos() }; addMarker(o={}) { var index = o.index == void 0 ? this.points.length : o.index //要当第几个 let marker = new THREE.Mesh(planeGeo, getMarkerMat('default')) marker.name = 'marker' setMarkerScale(marker,this.halfPathWidth) marker.markerSelectStates = {} //Potree.Utils.setObjectLayers(marker, 'measure' ) marker.renderOrder = Potree.config.renderOrders.path.marker marker.addEventListener('mouseover',(e)=>{ if(this.addOrRemovePoint && !this.isNew){ CursorDeal.add('pen_delPoint') //hideFakeMarker() } this.hoverStates.marker = marker }/* ,{importance:1} */) marker.addEventListener('mouseleave',(e)=>{ CursorDeal.remove('pen_delPoint') marker == this.hoverStates.marker && (this.hoverStates.marker = null) }) marker.addEventListener('click',(e)=>{ if(this.isNew || e.button !== THREE.MOUSE.LEFT )return if(this.addOrRemovePoint ){//点击删除点 this.removePoint(this.markers.indexOf(marker)) }else{//点击选中点 this.dispatchEvent({type:'markerSelect', marker}) this.setMarkerSelected(marker, 'click' ); setTimeout(()=>{ viewer.addEventListener('global_click', (e)=>{ //点击空白处取消全部 if(e.clickElement?.oriObject == marker)return this.dispatchEvent({type:'markerSelect', marker, cancel:true}) this.setMarkerSelected(marker, 'unclick' ); }, {once:true} ) },10) } e.consume() }) marker.addEventListener('startDragging',(e)=>{ this.isNew || this.setMarkerSelected(marker, 'click' ); //选中 this.isNew || viewer.measuringTool.history.beforeChange(this) this.arrows && Potree.Utils.updateVisible(this.arrows, 'dragging', false) //Potree.Common.waitTool.cancel('pathUpdateArrowDelay') }) marker.addEventListener('drop',(e)=>{ if(!this.isNew && this.arrows){ Potree.Utils.updateVisible(this.arrows, 'dragging', true) Path.updateArrows(true) } //this.hideArrowUntilUpdate() if( e.button != THREE.MOUSE.LEFT )return viewer.inputHandler.dispatchEvent({type: 'measuring', v:false, cause:'stopDragging', situation:'dragging', object:this} ) this.lastDropTime = Date.now() this.isNew || viewer.measuringTool.history.afterChange(this) this.setMarkerSelected(marker, 'unclick' ); }) let label = this.createMarkerLabel('') this.markerLabels = [...this.markerLabels.slice(0,index), label, ...this.markerLabels.slice(index, this.points.length)] this.setMarkerTitle(index, Potree.settings.isOfficial ? '' : 'point') super.addMarker(Object.assign(o, {index, marker })) //this.updateEdge() return marker }; updateEndCaps(){ let len = this.points.length let pts = this.geoPoints.length>0 ? this.geoPoints : this.points let len2 = pts.length this.endCaps.forEach((e,i)=>{ Potree.Utils.updateVisible(e, 'hasPoints', len>0) if(len){ if(len>1){ let dir = i==0 ? new THREE.Vector3().subVectors(pts[1], pts[0]) : new THREE.Vector3().subVectors(pts[len2-2], pts[len2-1]) e.quaternion.copy(getMeshQuaInPath(dir)) }else{ i==0 ? e.quaternion.set(0,0,0,1) : e.quaternion.set(0,0,1,0) //两个半圆拼成一个圆点 } e.position.copy(this.points[i==0 ? 0 : len-1]) let s = this.halfPathWidth * 2.15 e.scale.set(s,s,s) } }) } setEditEnable(state){//是否显示可修改控件 this.editEnable = !!state this.markers.forEach(e=>Potree.Utils.updateVisible(e,'editEnable', this.editEnable)) this.endCaps.forEach((e,i)=>Potree.Utils.updateVisible(e,'editEnable', !this.editEnable)) if(markerMats){//因为marker材质每条path都共用,所以isOfficial时请保证只有一条在编辑 for(let i in markerMats){ markerMats[i].fadeFar = this.fadeFar } } if(!state){ this.updateEndCaps() } viewer.dispatchEvent('content_changed') } setAddOrRemPoint(state){//是否可以加减点, 此时不能拖拽marker this.addOrRemovePoint = !!state this.updateCursorState() } updateCursorState(){//可能在hover时修改addOrRemovePoint,也可能从marker和edge之间切换, 主要是addOrRemovePoint开关时要记得之前的hover状态 CursorDeal[this.addOrRemovePoint ? 'add':'remove']('pen') if(this.addOrRemovePoint && !this.isNew){ CursorDeal[this.hoverStates.edge ? 'add' : 'remove']('pen_addPoint') CursorDeal[this.hoverStates.marker ? 'add' : 'remove']('pen_delPoint') }else{ CursorDeal.remove('pen_addPoint') CursorDeal.remove('pen_delPoint') } } setTitleVisi(label, v, reason=''){ Potree.Utils.updateVisible(label, 'hideTitle-'+reason, v) viewer.dispatchEvent('content_changed') } setTitle(title=''){ this.title = title this.titleLabel.setText(title) this.setTitleVisi(this.titleLabel.parent, title instanceof Array || title.trim() != '', 'noText') viewer.dispatchEvent('content_changed') } setMarkerTitle(index, title){ if(!this.markerLabels[index])return this.markerLabels[index].originText = title this.markerLabels[index].setText(title) this.setTitleVisi(this.markerLabels[index], title instanceof Array || title.trim() != '', 'noText') viewer.dispatchEvent('content_changed') } editStateChange(state){ //拖动时被调用 super.editStateChange(state) if(!state){ this.editStateTimer = setTimeout(()=>{ if(!this.isEditing){ this.dispatchEvent({type:'editStateChange',state:false}) } },100) }else{ if(!this.isEditing){ this.dispatchEvent({type:'editStateChange',state:true}) clearTimeout(this.editStateTimer) } } this.isEditing = state } removePoint(index){ if(index == -1){ return //双击会这样,加了迅速删除, 可能因为没来得及删 } viewer.measuringTool.history.beforeChange(this) this.removeMarker(index) this.hideArrowUntilUpdate() viewer.measuringTool.history.afterChange(this) this.dispatchEvent('changed') if(this.points.length == 0){ //viewer.measuringTool.changeToAddState(this) viewer.measuringTool.startInsertion({resume:true, measure:this}) } } setMarkerSelected(marker, state ){ state == 'hover' && (marker.markerSelectStates.hover = true ) state == 'unhover' && (marker.markerSelectStates.hover = false ) state == 'click' && (marker.markerSelectStates.click = true ) //click or drag state == 'unclick' && (marker.markerSelectStates.click = false ) if(marker.markerSelectStates.click){ marker.material = getMarkerMat('drag') marker.renderOrder = marker.pickOrder = Potree.config.renderOrders.path.marker +1 }else if(marker.markerSelectStates.hover){ marker.material = getMarkerMat(this.addOrRemovePoint ? 'delete' : 'drag') marker.renderOrder = marker.pickOrder = Potree.config.renderOrders.path.marker +1 }else{ marker.material = getMarkerMat('default') marker.renderOrder = marker.pickOrder = Potree.config.renderOrders.path.marker } //marker.selected = absoluteState viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') viewer.dispatchEvent('content_changed') } setSelected( state, byList){ if(state == 'click' && this.selectStates.click) return this.setSelected('unclick') //重复点击了 state == 'hover' && (this.selectStates.hover = true, byList||this.dispatchEvent({type:'highlight', state:true}), CursorDeal.add('pointer')) state == 'unhover' && (this.selectStates.hover = false, byList||this.dispatchEvent({type:'highlight', state:false}), CursorDeal.remove('pointer')) state == 'click' && (this.selectStates.click = true, byList||this.dispatchEvent({type:'chose', state:true}) ) state == 'unclick' && (this.selectStates.click = false, byList||this.dispatchEvent({type:'chose', state:false}) ) /* this.arrows?.material.color.set(this.selectStates.click ? '#ffffff' : this.color); ([this.edge, this.endCaps[0].children[0]].forEach(e=>{ e.material.opacity = this.selectStates.click ? 1 : this.selectStates.hover ? 0.8 : 0.6 e.material.color.set(this.selectStates.click ? '#00C8AF' : this.color) })) */ this.updateSelectStyle() state == 'click' && setTimeout(()=>{ viewer.addEventListener('global_click', (e)=>{ //再点击取消 this.setSelected('unclick') }, {once:true} ) },10) viewer.dispatchEvent('content_changed') viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') } updateSelectStyle(){ let c = new THREE.Color().set(this.color).getHSL({ h: 0, s: 0, l: 0 }) let color, arrowColor if(this.selectStates.click){ color = '#00C8AF' arrowColor = '#ffffff' }else if(this.selectStates.hover){ color = new THREE.Color().setHSL(c.h, c.s, c.l - 0.1 ) arrowColor = new THREE.Color().setHSL(c.h, c.s, c.l >= 0.4 ? c.l - 0.3 : c.l + 0.3 ) }else{ arrowColor = new THREE.Color().setHSL(c.h, c.s, c.l >= 0.4 ? c.l - 0.3 : c.l + 0.3 ) color = this.color } if(this.arrows){ this.arrows.material.color.set(arrowColor); }else{ this.edge.material.uniforms.mapColor.value.set(arrowColor) } ([this.edge, this.endCaps[0].children[0]].forEach(e=>{ e.material.color.set(color) })) } removeMarker(index ){ super.removeMarker(index) this.points_datasets.splice(index, 1); this.dataset_points && this.dataset_points.splice(index, 1) let labelIndex = index if(this.markerLabels[labelIndex]){ this.markerLabels[labelIndex].dispose() this.markerLabels.splice(labelIndex, 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); } setFontSize(fontsize){ this.fontsize = fontsize this.markerLabels.concat(this.titleLabel).forEach(e=>{ e.fontsize = fontsize e.updateTexture(); }) viewer.dispatchEvent('content_changed') } setFadeFar(far){//消失距离 if(far == void 0) far = 0 //不设置 this.traverse((e)=>{ //if(e.name == 'marker')return //因为marker材质共用的所以不改。因此正式编辑时(有marker时)别设置消失距离。 if(e.material?.uniforms?.fadeFar){ e.material.fadeFar = far } }) this.fadeFar = far if(markerMats){//因为marker材质每条path都共用,所以isOfficial时请保证只有一条在编辑 for(let i in markerMats){ markerMats[i].fadeFar = this.fadeFar } } viewer.dispatchEvent('content_changed') } dispose(){ super.dispose() this.titleLabel.dispose() this.markerLabels.forEach(e=>e.dispose()) this.arrows?.dispose() this.arrows?.material.dispose() this.edge.geometry.dispose() this.edge.material.dispose() this.setAddOrRemPoint(false) //cursor recover this.dispatchEvent('disposed') } updateArrowRepeat(){ if(!this.edge.material.map)return this.edge.material.map.repeat.x = Math.round(this.totalLength / this.halfPathWidth * 0.5) * (this.reverse ? -1 : 1) this.edge.material.map.needsUpdate = true this.edge.material.setUV() } setArrowDisplay(show){ if(Potree.settings.pathSmooth){ if(show){ let map = texLoader.load( Potree.resourcePath+'/textures/arrow.png',()=>{ viewer.dispatchEvent('content_changed') }) map.anisotropy = 2 map.wrapS = THREE.RepeatWrapping map.repeat.set(10,1.3) map.offset.set(0,-0.15) this.edge.material.map = map this.updateArrowRepeat() }else{ this.edge.material.map?.dispose() this.edge.material.map = null } }else{ if(show){ if(!this.arrows){ let map = texLoader.load( Potree.resourcePath+'/textures/arrow.png') //map.anisotropy = 2 map.generateMipmaps = false; map.minFilter = THREE.LinearFilter //防止边缘黑边, 但会造成锯齿 let material = new DepthBasicMaterial(Object.assign({}, depthProps, { map, transparent:true, side:2, fadeFar:this.fadeFar , color:this.selectStates.click ? '#ffffff' : this.color })) //let material = new THREE.MeshBasicMaterial({map )}) this.arrows = new THREE.InstancedMesh(planeGeo, material, arrowCountMax); //会自动向shader添加define和instanceMatrix this.arrows.renderOrder = Potree.config.renderOrders.path.edge this.add(this.arrows) //Potree.Utils.setObjectLayers(this.arrows, 'measure' ) } this.updateSelectStyle() } if(this.arrows){ Potree.Utils.updateVisible(this.arrows, 'show', show) show && Path.waitUpdateArrows() } } viewer.dispatchEvent('content_changed') } setReverse(reverse){ if(this.reverse != reverse){ this.reverse = reverse if(Potree.settings.pathSmooth){ this.updateArrowRepeat() }else{ Path.updateArrows(true) } } } hideArrowUntilUpdate(){ this.arrows && Potree.Utils.updateVisible(this.arrows, 'changing', false) Path.waitUpdateArrows() } static waitUpdateArrows(){ if(Potree.settings.pathSmooth)return Potree.Common.waitTool.wait('pathUpdateArrowDelay',()=>{ viewer.scene.measurements.forEach(e=>e instanceof Path && e.arrows && Potree.Utils.updateVisible(e.arrows, 'changing', true) ) Path.updateArrows(true) viewer.dispatchEvent('content_changed') },300) //为了防止不停点击不停更新,所以隐藏一下不变了再更新 } static updateArrows(force){ //console.error('updateArrows?') const far = math.linearClamp(Potree.fpsRendered2, [10, 60], [200, 300]); //几乎看不见了 let paths = viewer.scene.measurements.filter(e=>e instanceof Path && e.arrows?.visible) let waitAdd = [] let minDisSq = Infinity let startTime = performance.now() let lines = [] paths.forEach(path=>{ let len = path.points.length let far_ = path.halfPathWidth * far far_ = path.fadeFar ? Math.min(far_, path.fadeFar * 1.2) : far_ let farSquared_ = far_ * far_ for(let i=0; i farSquared_ && lastArrowCount > 0)continue minDisSq = Math.min(minDisSq, disSq) lines.push({line, disSq, path}) } }) let spaceDis = THREE.Math.clamp( math.toPrecision(Math.pow(minDisSq, 0.1) * 5, 1), 5, 15) //箭头之间的间距,适当调节稀疏 spaceDis *= math.linearClamp(Potree.fpsRendered2, [10, 60], [3, 1]) if((lastArrowCamPos && math.closeTo(lastArrowCamPos, viewer.mainViewport.view.position, 2) || lines.length == 0) && !force)return //很远的时候lines空的不更新(不清空) lastArrowCamPos = viewer.mainViewport.view.position.clone() viewer.scene.measurements.forEach(e=>e instanceof Path && e.arrows && (e.arrows.count = 0)) //归零 lines.sort((a,b)=>{return a.disSq - b.disSq}) //尽快收集好就近的,使后加的都比waitAdd最后一个大,减少比较 lines.forEach(info=>{ if(waitAdd[arrowCountMax-1] && info.disSq > waitAdd[arrowCountMax-1].disSq)return //线段最近点已经超过当前list中最远的那个 info.dir = new THREE.Vector3().subVectors(info.line.end, info.line.start).normalize() let lineLen = info.line.end.distanceTo(info.line.start) let spaceDis_ = info.path.halfPathWidth * spaceDis //跟随箭头缩放 let sliceCount = Math.round( lineLen / spaceDis_) //分段 if(sliceCount < 1) return; if(sliceCount == 1) sliceCount = 2//至少为2 let arrowCount = sliceCount - 1 let sliceLen = lineLen / sliceCount let j = arrowCount while(j > 0){ let pos = info.line.start.clone().add(info.dir.clone().multiplyScalar(j*sliceLen)) let disSq = pos.distanceToSquared(viewer.mainViewport.view.position) j--; if(waitAdd[arrowCountMax-1] && disSq > waitAdd[arrowCountMax-1].disSq)continue //按从小到大排好。(不知道这种事先排好序的方法 和全部加入最后排序选前面的 相比哪个耗时。 因为冒泡排序更快,但如果总数很多会不会慢?) let newItem = {pos, disSq, lineInfo:info} let index = waitAdd.findIndex(e=>e.disSq>disSq) if(index == -1){ waitAdd.push(newItem) }else{ waitAdd = [...waitAdd.slice(0,index), newItem, ...waitAdd.slice(index, waitAdd.length)] } waitAdd.length>arrowCountMax && waitAdd.pop() } }) let scaleMap = new Map waitAdd.forEach((e,i)=>{ let scaleQuaMatrix = e.lineInfo.scaleQuaMatrix if(!scaleQuaMatrix){ let scaleMatrix = scaleMap.get(e.lineInfo.path) if(!scaleMatrix){ let s = e.lineInfo.path.halfPathWidth * 1.6 scaleMatrix = new THREE.Matrix4().scale(new THREE.Vector3((e.lineInfo.path.reverse ? -1 : 1 ) *s,s,s)) scaleMap.set(e.lineInfo.path, scaleMatrix) } let qua = getMeshQuaInPath(e.lineInfo.dir)//math.getQuaFromPosAim( new THREE.Vector3, e.lineInfo.dir ).multiply(quaBase) let quaMatrix = new THREE.Matrix4().makeRotationFromQuaternion(qua) scaleQuaMatrix = e.lineInfo.scaleQuaMatrix = new THREE.Matrix4().multiplyMatrices(quaMatrix, scaleMatrix) } let matrix = scaleQuaMatrix.clone().setPosition(e.pos) e.lineInfo.path.arrows.count ++ e.lineInfo.path.arrows.setMatrixAt(e.lineInfo.path.arrows.count-1, matrix) e.lineInfo.path.arrows.instanceMatrix.needsUpdate = true; }) lastArrowCount = waitAdd.length //console.log('updateArrows spaceDis', spaceDis, 'count', waitAdd.length, 'cost', performance.now() - startTime) } } Path.prototype.cloneMarker = Measure.prototype.cloneMarker Path.prototype.updateDatasetBelong = Measure.prototype.updateDatasetBelong Path.prototype.reDraw = Measure.prototype.reDraw /* 没有intersect的点的dataset_point怎么赋值 1 lonlat : 因为说是加在地图上。 但万一用户在添加完path之后才设置经纬度,这个点可能就飘到很远的地方? 由于模型当前的位置都是本地坐标而不是经纬度,所以模型不会跟着一起飘远。 2 就近的datasetId : 但如果path全部点都在地图上, 现场只有一个离他远的模型,但它仍被判断在该模型上。之后在路径边加了其他模型。 当它的所属模型被删除后它就像意外消失了…… 3 本地坐标 : 当模型移动后该点不会跟着移动。 写起来比上一条麻烦点,但逻辑不容易误解。 目前用的3 */ /* const plane = new THREE.Plane(normal, dis); viewer.renderer.localClippingEnabled = true; material.clippingPlanes = [ localPlane ] #include #include */