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键拖拽点可以复制出当前点
*/