import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {LineDraw, MeshDraw} from "../../utils/DrawUtil.js"; import math from "../../utils/math.js"; const verticalLine = new THREE.Line3() //控制点和边的合集。具有可以拖拽修改的功能,拖拽时能防止线相交。 export class ctrlPolygon extends THREE.Object3D { constructor (type, prop) { super() this.type = type this.maxMarkers = Number.MAX_SAFE_INTEGER; this.transformData(prop); for(let i in prop){ this[i] = prop[i] } if((this.atPlane || this.showArea) && this.closed && this.dimension == '2d'){ this.areaPlane = this.createAreaPlane(); this.add(this.areaPlane) } //数据--刚开始一定是空的 this.points = []; //mesh 不一定有 this.markers = []; this.edges = []; this.center } initData(prop){ //开始加数据 prop.dataset_points && (this.dataset_points = prop.dataset_points) prop.datasetId && (this.datasetId = prop.datasetId) prop.points_datasets && (this.points_datasets = prop.points_datasets) if(Potree.settings.editType == 'merge' || this.measureType == 'MulDistance Ring'){ //融合页面没有地图,measure的不需要指定datasetId,每个点都有各自的datasetId,跟着各自的模型走 if(this.dataset_points){ this.dataset_points = this.dataset_points.map(e=>{ return e && new THREE.Vector3().copy(e) }) prop.points = this.dataset_points.map((p,i)=>{ return Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:this.points_datasets[i], position: p}) }) if(prop.points.some(e=>e == void 0)){ return false } }else{ this.dataset_points = [] } } if(prop.points){ for(const p of prop.points){ const pos = new THREE.Vector3().copy(p) this.addMarker({point:pos}); } if(Potree.settings.editType != 'merge' && this.measureType != 'MulDistance Ring'){ if(this.datasetId != void 0){//初始化位置 if(this.dataset_points){ this.dataset_points = this.dataset_points.map(e=>{ return e && new THREE.Vector3().copy(e) }) this.transformByPointcloud() //根据dataset_points和this.datasetId生成points } }else{ if(prop.dataset_points && prop.dataset_points.some(e=>e != void 0)){ console.error('存在测量线的datasetId为空而dataset_points有值,请检查并删除:'+this.sid)//存在过的bug,原因未知,可能是后台处理dataset时替换的错误:http://192.168.0.21/index.php?m=bug&f=view&bugID=23601 console.log(this) } } } this.facePlane = this.getFacePlane() this.getPoint2dInfo(this.points) this.update({ifUpdateMarkers:true}) //this.dragChange(new THREE.Vector3().copy(prop.points[prop.points.length-1]), prop.points.length-1); this.setSelected(false ) this.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') }) return true } } addMarker(o={}){ var index = o.index == void 0 ? this.points.length : o.index //要当第几个 this.points = [...this.points.slice(0,index), o.point, ...this.points.slice(index,this.points.length)] //this.points.push(o.point); if(o.dataset_point){ this.dataset_points = [...this.dataset_points.slice(0,index), o.dataset_point, ...this.dataset_points.slice(index,this.dataset_points.length)] } if(o.points_dataset){ this.points_datasets = [...this.points_datasets.slice(0,index), o.points_dataset, ...this.points_datasets.slice(index,this.points_datasets.length)] } if(o.marker){ this.add(o.marker) this.markers = [...this.markers.slice(0,index), o.marker, ...this.markers.slice(index,this.markers.length)] this.updateMarker(o.marker, o.point) o.marker.addEventListener('drag', this.dragMarker.bind(this),{importance:2}); o.marker.addEventListener('drop', this.dropMarker.bind(this),{importance:2}); o.marker.createTime = Date.now() let addHoverEvent = (e)=>{ 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" }) } o.marker.addEventListener('mouseover', mouseover); o.marker.addEventListener('mouseleave', mouseleave); o.marker.addEventListener('startDragging', (e)=>{//for mobile this.setMarkerSelected(o.marker, 'hover', 'single'); this.dispatchEvent('startDragging') }); o.marker.addEventListener('drop', (e)=>{//for mobile this.setMarkerSelected(o.marker, 'unhover', 'single'); }); o.marker.removeEventListener('addHoverEvent',addHoverEvent) } o.marker.addEventListener('addHoverEvent',addHoverEvent)//当非isNew时才添加事件 if(!this.isNew){ o.marker.dispatchEvent('addHoverEvent') } } if(o.edge){ this.add(o.edge) this.edges = [...this.edges.slice(0,index), o.edge, ...this.edges.slice(index,this.edges.length)] } } dragMarker(e){ var I, atMap if(e.hoverViewport != e.drag.dragViewport){//不能使用e.dragViewport,要使用drag中的,因为drag中存储的要一直继承下来,不因mouseup了而改变。 viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"polygon_AtWrongPlace" }) return } if(e.drag.pointerDelta.length() == 0 && !this.isNew){ //部分设备(华为matePad11)在touchstart后立即执行了touchmove,导致marker立即移动,需要屏蔽..(刚创建时也会是0) return } viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"polygon_AtWrongPlace" }) atMap = e.drag.dragViewport.name == 'mapViewport' if(atMap && this.unableDragAtMap){ e.drag.object = null //取消拖拽 return } e.drag.object.isDragging = true I = e.intersect && (e.intersect.adsorption ? e.intersect.location : (e.intersect.orthoIntersect || e.intersect.location)) if(viewer.inputHandler.pressedKeys[18] || Potree.settings.dragPolyBeyondPoint&&!I ){//alt dragPolyBeyondPoint可以平移拖拽到无点的地方---测试用 let i = this.markers.indexOf(e.drag.object); I = this.points[i].clone() const projected = I.clone().project(e.drag.dragViewport.camera); projected.x = e.pointer.x projected.y = e.pointer.y const unprojected = projected.clone().unproject(e.drag.dragViewport.camera); I.copy(unprojected); } if (I) { let i = this.markers.indexOf(e.drag.object); if (i !== -1) { this.dragChange(I.clone(), i, atMap) if(this.points_datasets){ if(e.intersect.pointcloud) this.points_datasets[i] = e.intersect.pointcloud.dataset_id else if(e.intersect.object) this.points_datasets[i] = e.intersect.object.dataset_id else{ if(this.measureType == 'MulDistance Ring'){//因为它的每个point跟着各自的dataset走,而不是整体的dataset let pointcloud = viewer.findClosestDatasetOnMap(I) || viewer.scene.pointclouds[0] this.points_datasets[i] = pointcloud.dataset_id }else this.points_datasets[i] = null } } } this.editStateChange(true) return true } }; dragChange(intersectPos, i, atMap){ let len = this.markers.length let oldPoint = this.points[i]; if(atMap){ intersectPos.setZ(oldPoint.z) //在地图上拖拽,不改变其高度。 } let location = intersectPos.clone() if(this.faceDirection && this.maxMarkers == 2 && len == 2){//add 固定方向的点不直接拖拽 var p1 = this.markers[0].position if(this.faceDirection == 'horizontal'){ var projectPos = location.clone().setZ(p1.z) }else{ var projectPos = p1.clone().setZ(location.z) } //var p2 = p1.clone().add(this.direction) //var projectPos = math.getFootPoint(location, p1, p2) LineDraw.updateLine(this.guideLine, [location, projectPos]) location = projectPos this.guideLine.visible = true }else if( len > 1 ){ var points = this.points.map(e=>e.clone()) points[i].copy(location) //算normal需要提前确认point //若为定义了面朝向的矩形 if(this.faceDirection == 'horizontal'){ if(len == 2){ location.setZ(points[0].z) } if(!this.facePlane){//一个点就能确定面 this.facePlane = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3(0,0,1), this.points[0] ) } }else if(this.faceDirection == 'vertical'){//当有两个点时, 有两个方向的可能 if(len == 2){ if(this.isRect){ let vec = points[0].clone().sub(location) if(Math.sqrt(vec.x*vec.x+vec.y*vec.y) > Math.abs(vec.z) ){//水平(高度差小于水平距离时) location.setZ(points[0].z) //this.cannotConfirmNormal = false;//能确定面为水平方向 }else{//垂直 (当两点一样时也属于这种) location.setX(points[0].x); location.setY(points[0].y); //this.cannotConfirmNormal = true; //不能确定面,因第三点可绕着纵轴线自由移动 } } }else{ {//判断cannotConfirmNormal. 如果前几段都在竖直线上,就不能固定出面方向。 this.cannotConfirmNormal = true let max = this.isRect ? 1 : len-2 for(let i=0;inew THREE.Vector2(e.x,e.y)) var points2 = getDifferentPoint(points_, 2); if(points2){ let normal = math.getNormal2d({p1:points2[0], p2:points2[1]}) normal = new THREE.Vector3(normal.x, normal.y, 0) this.facePlane = new THREE.Plane().setFromNormalAndCoplanarPoint( normal, this.points[0] ) } } } } if(len > 2){ if(!this.faceDirection && this.showArea){ if(len == 3 || this.isRect) this.cannotConfirmNormal = true //当第三个点固定后(有四个点时)才能固定面 if(!this.facePlane || this.cannotConfirmNormal){ var points3 = getDifferentPoint(points, 3);//只有找到三个不同的点算拥有面和area if(points3){ this.facePlane = new THREE.Plane().setFromCoplanarPoints(...points3 ) } } } if(this.atPlane && this.facePlane && !this.cannotConfirmNormal ){//之后加的点一定要在面上 if(atMap){ //地图上用垂直线,得到和面的交点。 verticalLine.set(location.clone().setZ(100000), location.clone().setZ(-100000))//确保长度范围覆盖所有测量面 location = this.facePlane.intersectLine(verticalLine, new THREE.Vector3() ) if(!location) return; }else{ location = this.facePlane.projectPoint(intersectPos, new THREE.Vector3() ) } } points[i].copy(location)//再copy确认一次 if(len == 3 && this.faceDirection == 'horizontal' && this.closed){ //normal方向还不确定 会影响label在里侧还是外侧 let facePlane = new THREE.Plane().setFromCoplanarPoints(...points) if(facePlane.normal.z && facePlane.normal.z * this.facePlane.normal.z < 0){ this.facePlane.normal.z *= -1, this.facePlane.constant *= -1 //console.log(this.facePlane.normal, this.facePlane.constant) } } if(this.isRect){ //是矩形 (即使没有faceDirection也能执行) //根据前两个点计算当前和下一个点 var p1 = points[(i-2+len)%len] var p2 = points[(i-1+len)%len] if(p1.equals(p2)){//意外情况:重复点两次 ( bug点,改了好多遍) if(this.faceDirection == 'vertical'){ p2.add(new THREE.Vector3(0,0,0.0001)) }else{ p2.add(new THREE.Vector3(0,0.0001,0)) } } //p3 : location var foot = math.getFootPoint(location, p1, p2)//p2 修改p2到垂足的位置 var vec = foot.clone().sub(location) var p4 = p1.clone().sub(vec) points[(i-1+len)%len].copy(foot) points[(i+1)%len].copy(p4) this.setPosition((i-1+len)%len, foot);//p2 this.setPosition((i+1)%len, p4); } this.getPoint2dInfo(points) var isIntersectSelf = this.atPlane && this.closed && !this.isRect && this.point2dInfo && this.intersectSelf(this.point2dInfo.points2d)//检测相交 this.isIntersectSelf = isIntersectSelf if(isIntersectSelf){//not-allowed if(!this.isNew && isIntersectSelf == 'lastLine') this.isIntersectSelf = 'all' //已经画好了就不用特别对待最后一条线 if(this.isIntersectSelf == 'lastLine'){ Potree.Utils.updateVisible(this.areaPlane, 'intersectLastLine', false) Potree.Utils.updateVisible(this.areaLabel, 'intersectLastLine', false) }else{ viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"polygon_isIntersectSelf" }) return } } if(!this.isIntersectSelf){ this.areaPlane && Potree.Utils.updateVisible(this.areaPlane, 'intersectLastLine', true) this.areaLabel && Potree.Utils.updateVisible(this.areaLabel, 'intersectLastLine', true) } if(!this.isIntersectSelf || this.isIntersectSelf == 'lastLine' ){ viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"polygon_isIntersectSelf" }) } } var showGuideLine = len>1 && (this.faceDirection || len > 3) if(showGuideLine && this.guideLine){ LineDraw.updateLine(this.guideLine, [intersectPos, location]) this.guideLine.visible = true } //console.log(this.points.map(e=>e.toArray())) } if(this.restrictArea){ let holes = this.restrictArea.holes.concat(this.restrictArea.parentHoles) let holesPoints = holes.filter(e=>e!=this && e.points.length>2).map(e=>e.points) if(!math.isPointInArea(this.restrictArea.points, holesPoints, location)){ viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"polygon_AtWrongPlace" }) this.isAtWrongPlace = true return } //就不处理相交线了。 有个缺点:floor上的hole可以限制room,但hole不受room限制,会导致room的marker被框在hole里而动不了。只能去调整hole了 } viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"polygon_AtWrongPlace" }) this.isAtWrongPlace = false this.setPosition(i, location); this.update({index: this.isRect ? null : i}) this.dispatchEvent({type:'dragChange', index:i}) } dropMarker(e){ //console.log('dropMarker') if (this.isNew && e.pressDistance>Potree.config.clickMaxDragDis){//拖拽的话返回 return this.continueDrag(null,e) } if(e.hoverViewport != e.drag.dragViewport){//copy from dragMarker, for sitemodel, only mapViewport can be dropped return this.continueDrag(null,e) } if(e.isTouch){ if(e.hoverViewport != viewer.mainViewport && this.unableDragAtMap){ viewer.dispatchEvent({type:'reticule_forbit', v:true}) //console.log('reticule_forbit',true) return this.continueDrag(null,e) }else{ viewer.dispatchEvent({type:'reticule_forbit', v:false}) //console.log('reticule_forbit',false) } this.isNew && this.dragMarker(e) //isNew触屏点击时必须先更新下点,因为指尖不在屏幕上时没更新。但对已经创建的marker点击时不应该更新 } if (e.button != THREE.MOUSE.RIGHT && (//右键click的话继续执行,因为会停止 this.isIntersectSelf == 'all' && this.isNew //有线相交了 || this.isAtWrongPlace && this.isNew || !e.isAtDomElement && this.isNew//如果是刚添加时在其他dom点击, 不要响应 || e.hoverViewport != viewer.mainViewport && this.unableDragAtMap //垂直的测量线不允许在地图上放点 || this.isNew && !getDifferentPoint(this.points, this.points.length ) //不允许和之前的点相同, 但这句在点云稀疏时会导致难结束 ) ){ return this.continueDrag(null,e) } //console.log('drop marker' ) let i = this.markers.indexOf(e.drag.object); if (i !== -1) { this.dispatchEvent({ 'type': 'marker_dropped', 'index': i }); if(this.markers.length>2 && this.facePlane)this.cannotConfirmNormal = false this.guideLine &&(this.guideLine.visible = false) } this.setMarkerSelected(e.drag.object, 'unhover', 'single') this.editStateChange(false) e.drag.endDragFun && e.drag.endDragFun(e)// addmarker //if(this.changeCallBack)this.changeCallBack() return true }; getFacePlane(){//最普通一种get方法,根据顶点。且假设所有点已经共面,且不重合 if(/* !this.atPlane || */this.points.length<3) return /* this.facePlane = new THREE.Plane().setFromCoplanarPoints(...this.points.slice(0,3) ) */ let facePlane = this.facePlane if(!this.atPlane || !facePlane){//多折线 没有实时更新facePlane所以重新算 let normal = new THREE.Vector3, len = this.points.length - 2 for(let i=0;ie.clone().applyQuaternion(qua)) this.point2dInfo = { originPoint0 , points2d, quaInverse : qua.clone().invert() } } } setPosition (index, position) {//拖拽后设置位置 let point = this.points[index]; point.copy(position); /* if(this.datasetId){ this.dataset_points[index] = Potree.Utils.datasetPosTransform({toDataset:true, datasetId:this.datasetId, position:point.clone()}) } */ /* if(Potree.settings.editType == 'merge'){ this.dataset_points[index] = Potree.Utils.datasetPosTransform({toDataset:true,this.points_datasets[i], position:point.clone()}) } */ let marker = this.markers[index]; this.updateMarker(marker, point) } updateMarker(marker, pos){ marker.position.copy(pos); marker.waitUpdate(); } intersectSelf(points2d){//add var len = points2d.length for(var i=0;i2){ if(this.point2dInfo){ this.areaPlane.geometry = MeshDraw.getShapeGeo(this.point2dInfo.points2d) let center = math.getCenterOfGravityPoint(this.point2dInfo.points2d) //重心 let firstPos = this.point2dInfo.points2d[0].clone() firstPos.z = 0 //因为shape只读取了xy,所以位移下, 再算出最终位置,得到差距 firstPos.applyQuaternion(this.point2dInfo.quaInverse) let vec = this.point2dInfo.originPoint0.clone().sub(firstPos) center = new THREE.Vector3(center.x, center.y, 0) center.applyQuaternion(this.point2dInfo.quaInverse) this.areaPlane.quaternion.copy(this.point2dInfo.quaInverse) this.areaPlane.position.copy(vec) center.add(vec) this.center = center }else{//prism let z = this.horizonZ || 0 let points2d = this.points.map(e=>e.clone().setZ(z)) this.areaPlane.geometry = MeshDraw.getShapeGeo(points2d) //z=0 let center = math.getCenterOfGravityPoint(points2d) //重心 this.areaPlaneCenter = new THREE.Vector3(center.x, center.y, z) this.areaPlane.position.z = z } }else{ this.areaPlane.geometry = new THREE.Geometry() } } getIndex(index, add){ let lastIndex = this.points.length - 1 if(add == -1) return (index === 0) ? lastIndex : index - 1; else if(add == 1) return (index + 1 > lastIndex) ? 0 : index + 1; } update(options={}){ if(this.points.length === 0){ return; } //performance.mark('measureUpdate-start') let lastIndex = this.points.length - 1 if(options.index != void 0){//更新第几个点 this.updateMarker(this.markers[options.index], this.points[options.index]) let previousIndex = this.getIndex(options.index, -1) let nextIndex = this.getIndex(options.index, +1) if( this.closed || nextIndex != 0 ) LineDraw.updateLine( this.edges[options.index], [this.points[options.index], this.points[nextIndex]]) if( this.closed || previousIndex != lastIndex ) LineDraw.updateLine( this.edges[previousIndex], [this.points[options.index], this.points[previousIndex]]) }else{ for (let index = 0; index <= lastIndex; index++) { let nextIndex = this.getIndex(index, +1) let previousIndex = this.getIndex(index, -1) let point = this.points[index]; let nextPoint = this.points[nextIndex]; let previousPoint = this.points[previousIndex]; if(options.ifUpdateMarkers){ this.updateMarker(this.markers[index], point) } if(!this.closed && nextIndex == 0 )break; //add { let edge = this.edges[index]; if(edge){ LineDraw.updateLine(edge, [point, nextPoint]) } } } } if(this.areaPlane){ this.updateAreaPlane() } //this.dispatchEvent({type:'update'}) viewer.dispatchEvent('content_changed') viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') //暂时先这么都通知 //performance.mark('measureUpdate-end') //let measure = performance.measure('measure-','measureUpdate-start', 'measureUpdate-end' ); //console.log('update-time', measure.duration) } createPrismLines(color){ this.lineMesh = LineDraw.createLine([],{color}) this.lineMesh.name = 'PrismLines' this.add(this.lineMesh) } updatePrismLines(){ if(!this.lineMesh)return let positions = []; let length = this.points.length this.points.forEach((point, index)=>{ //竖线: positions.push(point.clone().setZ(this.zMin), point.clone().setZ(this.zMax)) //横线 let nextPoint = this.points[(index+1)%length]; if(!nextPoint)return;//when length==1 positions.push(point.clone().setZ(this.zMax), nextPoint.clone().setZ(this.zMax))//上横线 positions.push(point.clone().setZ(this.zMin), nextPoint.clone().setZ(this.zMin))//下横线 }) LineDraw.moveLine(this.lineMesh,positions) viewer.dispatchEvent('content_changed') } dispose(){//add this.parent.remove(this) this.markers.concat(this.edges).forEach(e=>e.dispatchEvent({type:'dispose'})) } reDraw(restMarkerCount=0){//重新开始画 let pointCount = this.points.length - restMarkerCount // restMarkerCount为需要留下的marker数量 while(pointCount > 0){ this.removeMarker(--pointCount) } this.point2dInfo = null this.facePlane = null } setMarkerSelected(){} editStateChange(state){ if(!state){ viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"polygon_isIntersectSelf" }) viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"polygon_AtWrongPlace" }) viewer.dispatchEvent({type:'reticule_forbit', v:false}) this.markers.forEach(e=>e.isDragging = false ) } } transformData(prop){ const pick = (defaul, alternative) => { if(defaul != null){ return defaul; }else{ return alternative; } }; prop.showDistances = (prop.showDistances === null) ? true : prop.showDistances; prop.showArea = pick(prop.showArea, false); prop.showAngles = pick(prop.showAngles, false); prop.showCoordinates = pick(prop.showCoordinates, false); prop.showHeight = pick(prop.showHeight, false); prop.showCircle = pick(prop.showCircle, false); prop.showAzimuth = pick(prop.showAzimuth, false); prop.showEdges = pick(prop.showEdges, true); prop.closed = pick(prop.closed, false); prop.maxMarkers = pick(prop.maxMarkers, Infinity); prop.direction = prop.direction//add prop.type = prop.type prop.showGuideLine = pick(prop.showGuideLine, false); prop.isRect = pick(prop.isRect, false); } setSelected(){} continueDrag(marker, e){ let object = marker || e.drag.object object.isDragging = true this.editStateChange(true) var timer = setTimeout(()=>{//等 drag=null之后 //右键拖拽结束后需要重新得到drag if(this.parent && object.parent && object.isDragging){ //console.log('continueDrag') viewer.inputHandler.startDragging( object , {endDragFun: e.drag.endDragFun, notPressMouse:e.drag.notPressMouse, dragViewport:e.drag.dragViewport} ) } },1) return timer } } function getDifferentPoint(points, count){//for facePlane var result = []; for(let i=0;ie.equals(p)))continue; else result.push(p) if(result.length == count)break } if(result.length == count)return result }