import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {BoxVolume} from '../../../utils/VolumeNew.js' import { ClipTask, ClipMethod} from "../../../defines.js" import {mapClipBox} from '../../objects/tool/mapClipBox.js' import Common from '../../utils/Common.js' import math from '../../utils/math.js' import {Images360} from '../panos/Images360.js' import SplitScreen from "../../utils/SplitScreen.js" import {ExtendView} from "../../../viewer/ExtendView.js"; import Viewport from "../../viewer/Viewport.js"; import {TextSprite} from "../../objects/TextSprite.js"; import {ExtendPointCloudMaterial} from "../../../materials/ExtendPointCloudMaterial.js"; import AxisViewer from "../../objects/tool/AxisViewer.js"; const defaultBoxWidth = 16; //navvis: 10 //navvis position: si {x: 0, y: 0, z: 0} const cameraProps = [ { name : 'top', axis:["x","y"], direction : new THREE.Vector3(0,0,-1), //镜头朝向 openCount:0, }, { name : 'front', axis:["x","z"], direction : new THREE.Vector3(0,1,0), openCount:0, }, { name : 'mainView', openCount:0, } ] var Clip = { bus : new THREE.EventDispatcher, selectedDatasets : [], changeCallback(force){ if(this.activeViewName != 'mainView'){ this.splitScreenTool.updateCameraOutOfModel() //更新相机位置 } if(Potree.settings.isOfficial && this.showMap){ let fun = ()=>{ let pointclouds = this.getIntersectPointcloud() if( Common.getDifferenceSet(pointclouds,this.selectedDatasets).length){ this.selectedDatasets = pointclouds //console.error('clipSelectedDatasets',selectedDatasets) this.bus.dispatchEvent({type:'updateSelectedDatasets', selectedDatasets:pointclouds.map(e=>e.dataset_id) }) } } if(force)fun() else Common.intervalTool.isWaiting('clipSelectedDatasets', fun , 300) } }, enter:function(){ this.initViews() this.previousView = { position: viewer.images360.position, target: viewer.scene.view.getPivot(), displayMode : Potree.settings.displayMode, //--- ifShowMarker : Potree.settings.ifShowMarker, } let pointcloud = this.getPointcloud() let bound = pointcloud.bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。 let boundSize = bound.getSize(new THREE.Vector3()) let target = this.getTarget(bound.getCenter(new THREE.Vector3())); //navvis的位置xy是用相机位置 this.ViewService.mainView.getCamera().position 我觉得也可以用第一个漫游点的,或者最接近bound中心的漫游点 let scale = new THREE.Vector3(defaultBoxWidth,defaultBoxWidth, boundSize.z)//z和navvis一样 let eyeDir = viewer.scene.view.direction.clone().setZ(0/* -boundSize.z/3 */).multiplyScalar(-defaultBoxWidth) //为了使所在楼层不变,不修改z //let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3) let position = new THREE.Vector3().addVectors(target, eyeDir) Potree.settings.displayMode = 'showPointCloud' viewer.setView({ position , target, duration:300, callback:function(){ } }) //viewer.setControls(viewer.orbitControls); viewer.setLimitFar(false) //viewer.setClipState(false) //暂时关闭旧的clipping /* new AxisViewer(viewer.mainViewport, viewer.renderArea,{domStyle:{ bottom: '2px',right: '20px', width:'80px',height:'80px'} }) */ { this.box = new BoxVolume({ clip:true }) this.box.clipTask = ClipTask['SHOW_INSIDE_Big' /* "SHOW_INSIDE" */] this.box.showBox = false this.box.name = "ClipBox"; this.box.position.copy(target) this.box.scale.copy(scale) viewer.controls.setTarget(this.box.position)//绕其旋转 this.splitScreenTool.focusCenter = this.box.position //带动mapBox this.box.addEventListener('position_changed',e=>{ if(this.showMap){ this.mapBox.center.setX(this.box.position.x) this.mapBox.center.setY(this.box.position.y) this.mapBox.updatePoints() } this.changeCallback() }) this.box.addEventListener('scale_changed',e=>{ if(this.showMap){ var scale = this.box.scale this.mapBox.updatePoints(scale) } this.changeCallback() }) this.box.addEventListener('orientation_changed',e=>{ if(this.showMap){ this.mapBox.angle = this.box.rotation.z this.mapBox.rotateBar.rotation.z = this.mapBox.angle this.mapBox.updatePoints() } this.changeCallback() }) viewer.scene.addVolume(this.box); } if(this.showMap){//map let boxRotateBack = ()=>{//不知道是不是这么写。 因为可能z的旋转不一定都在z this.box.rotation.x = 0; this.box.rotation.y = 0; } this.mapBox = new mapClipBox(target, scale) viewer.mapViewer.scene.add(this.mapBox) //带动box this.mapBox.addEventListener('repos',e=>{ this.box.position.setX(this.mapBox.center.x) this.box.position.setY(this.mapBox.center.y) boxRotateBack() this.changeCallback() }) this.mapBox.addEventListener('dragChange',e=>{ var scale = this.mapBox.getScale() this.box.scale.setX(scale.x) this.box.scale.setY(scale.y) this.box.position.setX(this.mapBox.center.x) this.box.position.setY(this.mapBox.center.y) boxRotateBack() this.changeCallback() }) this.mapBox.addEventListener('rotate',e=>{ this.box.rotation.z = this.mapBox.angle boxRotateBack() this.changeCallback() }) } this.switchView('mainView') { //viewer.setClipTask(ClipTask["SHOW_INSIDE"]) } Potree.settings.unableNavigate = true Potree.settings.ifShowMarker = false Potree.Utils.updateVisible(viewer.measuringTool.scene, 'clipModel', false) //Potree.Utils.updateVisible(viewer.mapViewer.cursor, 'clipModel', false)//隐藏地图游标 viewer.inputHandler.toggleSelection(this.box); viewer.inputHandler.fixSelection = true viewer.transformationTool.setModeEnable(['scale', 'translation', 'rotation'] ) viewer.transformationTool.frame.material.color.set(Potree.config.clip.color)//navvis 15899953 viewer.setPointStandardMat(true) if(this.showMap){ let mapVisi = false this.events = { flyToPos : (e)=>{ let dis = 2 let target = e.position target = this.box.position position = e.position //为了方便缩放操作,直接使用box中心作为target let duration = e.duration == void 0 ? 1000 : e.duration viewer.scene.view.setView({position, duration, target}) }, mapVisiChange(e){ mapVisi = e.visible let delay = 100 //因resize了camera需要时间更新projectionMatrix setTimeout(()=>{ let boundingBox = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld) if(mapVisi){//切换地图 if(viewer.fpVisiDatasets.length == 0){//不会显示任何一个floorplan图,就要就近移动到一个可见数据集里 let clouds = viewer.scene.pointclouds.filter(e=>e.visible) if(clouds.length == 0)clouds = viewer.scene.pointclouds let scores = clouds.map((e,i)=>{ return [ -viewer.scene.view.position.distanceToSquared(e.bound2) , i] }) scores = scores.sort((a,b)=> a[0] - b[0]) let neareast = clouds[scores[0][1]] viewer.flyToDataset({ pointcloud : neareast, duration:0}) } if(Clip.switchMapCount == 0 || !Potree.Utils.getPos2d(viewer.scene.view.position, viewer.mapViewer.viewports[0], viewer.mapViewer.renderArea).inSight || !Potree.Utils.isInsideFrustum(boundingBox, viewer.mapViewer.camera)){ //要使box框和游标都在屏幕内。因为游标是3d的当前位置,很可能是准备去框住的位置 let bound = boundingBox.clone() bound.expandByPoint(viewer.scene.view.position) let size = bound.getSize(new THREE.Vector3) let center = bound.getCenter(new THREE.Vector3) let margin = viewer.mainViewport.resolution.clone().multiplyScalar(0.3) viewer.mapViewer.moveTo(center, size, 100, margin) } Clip.switchMapCount++ //关于究竟是focus box还是dataset有点纠结,又或是两个的union。box和数据集可能离得很远,且无法确定当前想选择的数据集,且数据集可能无floorplan, 即使有可能也不展示…… }else{//切换3d if(!Potree.Utils.isInsideFrustum(boundingBox, viewer.scene.getActiveCamera())){//屏幕上没有box的话 viewer.focusOnObject({boundingBox}, 'boundingBox', 100 ) } } },delay) } } this.switchMapCount = 0 this.bus.addEventListener('flyToPos',this.events.flyToPos) viewer.mapViewer.addEventListener('forceVisible',this.events.mapVisiChange) } this.editing = true setTimeout(()=>{this.changeCallback(true)},1) }, leave:function(){ viewer.inputHandler.fixSelection = false viewer.scene.removeVolume(this.box); this.showMap && this.mapBox.dispose() //viewer.setControls(viewer.fpControls); this.switchView('mainView') Potree.settings.unableNavigate = false Potree.settings.ifShowMarker = this.previousView.ifShowMarker Potree.Utils.updateVisible(viewer.measuringTool.scene, 'clipModel', true) //Potree.Utils.updateVisible(viewer.mapViewer.cursor, 'clipModel', true) viewer.setView(this.previousView) viewer.setLimitFar(true) viewer.setPointStandardMat(false) //viewer.setClipState(true) viewer.controls.setTarget(null) this.splitScreenTool.focusCenter = null //viewer.mainViewport.axis.dispose() cameraProps.forEach(e=>e.openCount = 0) if(this.showMap){ this.bus.removeEventListener('flyToPos',this.events.flyToPos) viewer.mapViewer.removeEventListener('forceVisible',this.events.mapVisiChange) this.events = null } this.editing = false }, initViews(){ if(this.views){ this.views.top.pitch = -Math.PI this.views.top.yaw = 0 this.views.front.pitch = 0 this.views.front.yaw = 0 //--还原for bugID 44757 return } this.views = {} this.cameras = {} this.orthoCamera = new THREE.OrthographicCamera(-100, 100, 100, 100, 0.01, 10000) this.orthoCamera.up.set(0,0,1) this.splitScreenTool = new SplitScreen this.targetPlane = viewer.mainViewport.targetPlane = new THREE.Plane() this.shiftTarget = viewer.mainViewport.shiftTarget = new THREE.Vector3 //project在targetPlane上的位置 for(let i=0;i<2;i++){ let prop = cameraProps[i]; let view = new ExtendView() this.views[prop.name] = view this.cameras[prop.name] = this.orthoCamera view.name = prop.name view.direction = prop.direction } this.views.mainView = viewer.mainViewport.view this.cameras.mainView = viewer.mainViewport.camera }, switchView(name ){//替换view和camera到mainViewport if(this.activeViewName == name)return let view = this.views[name] let camera = this.cameras[name] let prop = cameraProps.find(e=>e.name == name) let {boundSize, center, boundingBox} = viewer.bound this.lastViewName = this.activeViewName this.activeViewName = name let lastView = this.views[this.lastViewName] let lastCamera = this.cameras[this.lastViewName] viewer.mainViewport.view = view viewer.mainViewport.camera = camera if(lastCamera)lastView.zoom = lastCamera.zoom view.zoom && (camera.zoom = view.zoom) viewer.updateScreenSize({forceUpdateSize:true})//更新camera aspect left等 view.applyToCamera(camera) let ifFocus let boxBound = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld) if(!Potree.Utils.isInsideFrustum(boxBound, camera)){//屏幕上没有box的话 ifFocus = true } if(name == 'mainView'){ if(lastView){//2d->3d } if(prop.openCount == 0) ifFocus = false //viewer.fpControls.lockKey = false }else{ this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center ) this.targetPlane.projectPoint(view.position, this.shiftTarget ) //target转换到过模型中心的平面,以保证镜头一定在模型外 //view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport)) //if(view.zoom)camera.zoom = view.zoom//恢复上次的zoom if(prop.openCount == 0){//至多执行一次 ifFocus = true } if(this.lastViewName == 'mainView'){//3d->2d }else{ } ifFocus || this.splitScreenTool.updateCameraOutOfModel() //更新相机位置 //if(name == 'top') viewer.mainViewport.alignment = {rotate:true,translate:true}; if(name == 'front'){ //viewer.mainViewport.alignment = {translate:true, rotateSide:true, translateVec:new THREE.Vector3(0,0,1)}; //只能上下移动 viewer.mainViewport.rotateSide = true }else{ viewer.mainViewport.rotateSide = false } //viewer.fpControls.lockKey = true } ifFocus && this.focusOnObject(this.box) //首次到front最好能自动对准box的长边的角度上 prop.openCount ++; }, focusOnObject(box, duration=0){ if(this.activeViewName == 'mainView'){ viewer.focusOnObject({boundingBox:box.boundingBox.clone().applyMatrix4(box.matrixWorld)}, 'boundingBox', duration) }else{ this.orthoMoveFit(box.position, {bound:box.boundingBox.clone().applyMatrix4(box.matrixWorld)}, duration) } }, transform(type, axis, value ){//使用按键微调 this.box[type][axis] += value; }, setModeEnable(type, enable){ let modes = [] ;['scale', 'translation', 'rotation'].forEach(e=>{ if(e == type){ enable && modes.push(e) }else{ viewer.transformationTool.modesEnabled[e] && modes.push(e) } }) viewer.transformationTool.setModeEnable(modes) viewer.dispatchEvent('content_changed') }, rotateSideCamera(angle){//侧视图绕模型中心水平旋转到的角度 angle: -180 ~ 180 let diff = THREE.Math.degToRad(angle) - viewer.mainViewport.view.yaw this.splitScreenTool.rotateSideCamera(viewer.mainViewport, diff) }, orthoMoveFit(pos, info, duration){ var margin = {x:viewer.mainViewport.resolution.x*0.4, y:viewer.mainViewport.resolution.y*0.4} this.splitScreenTool.viewportFitBound(viewer.mainViewport, info.bound, pos, duration, margin ) }, getPointcloud:function(){ //找一个离当前最近的点云,且最好有漫游点 let pointclouds = viewer.scene.pointclouds.filter(e=>e.panos.length>0) if(pointclouds.length == 0)pointclouds = viewer.scene.pointclouds; let result = Common.sortByScore(pointclouds,[],[e=>{ let center = e.bound.getCenter(new THREE.Vector3) let size = e.bound.getSize(new THREE.Vector3).length() / 2 let posToCenter = viewer.images360.position.distanceTo(center) return size / posToCenter }]) return result[0].item }, getTarget:function(boundCenter){//box位置。要找一个有点云的地方。方案1相机位置, 方案2接近相机的漫游点, 方案3接近中心的漫游点。选择方案2,因最大概率有点云 var target = new THREE.Vector3() var cameraPos = viewer.images360.position; var pano = Common.find(viewer.images360.panos , [], [Images360.sortFunctions.floorDisSquaredToPoint(cameraPos)]); if(pano){ target.copy(pano.position) target.setZ(boundCenter.z) }else{ target.copy(boundCenter) } return target }, /* switchMap:function(state){ }, */ download:function( ){ if(this.getIntersectPointcloud().length == 0){ return null } var visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection')) visiPointclouds.sort((a,b)=>{return a.dataset_id-b.dataset_id})//缓存需要固定排序好比较 let data = { transformation_matrix: visiPointclouds.map((cloud)=>{ let data = { id: cloud.dataset_id, matrix : this.getTransformationMatrix(cloud).elements, //剪裁大框 visiMatrixes: cloud.material.clipBoxes_in.map(e=>this.getTransformationMatrix(cloud, e.inverse).elements), //若干个可见型小框(虽然现在用不到了,因为普通界面不展示这些剪裁区域) unVisiMatrixes: cloud.material.clipBoxes_out.map(e=>this.getTransformationMatrix(cloud, e.inverse).elements), //若干个不可见型小框 modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements } return data }) , aabb: "b-0.5 -0.5 -0.5 0.5 0.5 0.5" //剪裁空间( 所有点在乘上这个矩阵后, 还能落在 1 * 1 * 1的box内的点就是所裁剪的 } return data }, downloadNoCrop(){//不剪裁 下载整个点云 var visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection')) visiPointclouds.sort((a,b)=>{return a.dataset_id-b.dataset_id})//缓存需要固定排序好比较 let data = { transformation_matrix: visiPointclouds.map((cloud)=>{ let data = { id: cloud.dataset_id, matrix : new THREE.Matrix4().elements, //固定值 modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements } return data }) , aabb: "b-12742000 -12742000 -12742000 12742000 12742000 12742000" //固定剪裁空间 } console.log(data) return data }, getTransformationMatrix:function(pointcloud, invMatrix) {//剪裁矩阵 var invMatrix = invMatrix || this.box.matrixWorld.clone().invert() return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose() }, getIntersectPointcloud(){ var intersect = (pointcloud)=>{ if(pointcloud.intersectBox(this.box.matrixWorld))return true } return viewer.scene.pointclouds.filter(e=>intersect(e)) }, getRulerBound(){//坐标尺边界 let camera = viewer.mainViewport.camera if(!camera.isOrthographicCamera)return let w = camera.right / camera.zoom //half let h = camera.top / camera.zoom let points = [] var boundAtCamera = new THREE.Box3() Clip.box.children[0].geometry.vertices.forEach(e=>{ //模仿getPosWithFullBound let p = e.clone().applyMatrix4(Clip.box.matrixWorld) points.push(p) let p1 = p.clone().applyMatrix4(camera.matrixWorldInverse); boundAtCamera.expandByPoint(p1) }) //需要找出clipbox的bound的左上角,它在标尺中是原点 let ClipBoxLeftTop = new THREE.Vector2(boundAtCamera.min.x, boundAtCamera.max.y) //相对于相机的位置 let camPos = new THREE.Vector2(-ClipBoxLeftTop.x, ClipBoxLeftTop.y)//由于ClipBoxLeftTop要变换到原点,所以相机位置就成了ClipBoxLeftTop的相反数, 但因是从上到下所以y再乘-1 let bound_ = { left: camPos.x - w, right: camPos.x + w, bottom: camPos.y + h, //注意从上到下增大 top: camPos.y - h, } //console.log(bound) return bound_ }, screenshot: async (rulerBound, rulerMargin, unitText='像素 : 米' )=>{ //测绘图下载。顶视图|侧视图 return new Promise((resolve,reject)=>{ if(Clip.screenshoting )return reject() let visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection')) let camera = viewer.mainViewport.camera //if(Clip.activeViewName == 'mainView')return reject() const maxWidth = 8192, minWidth = Potree.browser.urlHasValue('clipMinWidth',true) || 1024 //图片尺寸最大最小值。 //Potree.settings.pointDensity = 'ultraHigh' viewer.inputHandler.deselectAll() Clip.box.visible = false Clip.screenshoting = true let material = new ExtendPointCloudMaterial material.pointSizeType = Potree.PointSizeType.FIXED //Potree.PointSizeType material.size = Potree.browser.urlHasValue('clipPointSize',true) || 1 material.uniforms.minSize.value = 0.1 material.activeAttributeName = 'rgba' //material.classification = pointcloud.material.classification; material.clipBoxBig_in = visiPointclouds[0].material.clipBoxBig_in material.shaderNeedsUpdate = true material.bigClipInBox = visiPointclouds[0].material.bigClipInBox let materials = visiPointclouds.map(e=>{ let mat = e.material e.material = material return mat }) //根据boundingBox尺寸来定图片尺寸 let boundingBox = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld) let points = [] var bound = new THREE.Box3() Clip.box.children[0].geometry.vertices.forEach(e=>{ //模仿getPosWithFullBound let p = e.clone().applyMatrix4(Clip.box.matrixWorld) points.push(p) let p1 = p.clone().applyMatrix4(camera.matrixWorldInverse); bound.expandByPoint(p1) }) let boundSize = bound.getSize(new THREE.Vector3) let minZoom = Potree.browser.urlHasValue('clipZoom',true) || 80 //1px = 1个点 = 1cm , 如果一个点都是1cm的话 //zoom不宜过大或过小,过小点云重叠、画面模糊;过大点云有间隙,纹理就看不清 //中间部分75-80为佳,文字100,边缘50. 最好支持调节 . 且当下载尺寸变大往往点云也多,可能冲破pointbudget限制 //let text = `1 : ${(1 / camera.zoom).toFixed(4)}(像素 : 米)` let w = boundSize.x * minZoom let h = boundSize.y * minZoom let max = Math.max(w,h) let s = 1 if(max > maxWidth){ s = maxWidth/max }else if(max < minWidth){ s = minWidth/max } w *= s, h *= s material.size *= s s != 1 && console.log('宽度缩放倍数',s) //虽然size>1就是浪费像素,但是太小了视觉效果有点差……可能因为fov不真实。 点云大就像是老式电视机虽然显示像素量少但感觉不出画面模糊,且边缘锐利。 w = Math.round(w) h = Math.round(h) let focusObjectInfo = [{boundingBox, points}, 'boundingBox', 0, { dontChangeCamDir:true, dontMoveMap:true, boundScale:1 }] //分块渲染截图,最后拼合图片。需要使每一块的点数不超过pointBudget, 且尺寸不超过4096(会崩溃),过小的话一般不会超过pointBudget,除非是深度较大后排点云多。 let r = THREE.Math.clamp(Math.sqrt(3.5e7 / boundSize.z) * s, 1024, 4096); //每多少米分一块。 推理 wc*hc = w / r * h / r = zoom * w * s * zoom * h * s / r^2 = xyz / V(常数,每多少立方米会超出pointBudget,就是括号里的数字,给大概值) //let r = THREE.Math.clamp(1024 / boundSize.z, 256, 4096); let wc = w / r //横向块数 let hc = h / r //纵向块数 let splitRenderInfo = (wc > 1 || hc > 1) && {wc: Math.ceil(wc), hc:Math.ceil(hc)} //因为结果需要化为整数所以可能不太理想, 分割数过多。 另外如果面积小但是因z过长而超出pointBudget没关系,因后排点云无需绘制 console.log({wc,hc,w,h}) let meterPerPixel = boundSize.x / w let text = `1 : ${(meterPerPixel).toFixed(4)}(${unitText})` let beforeScreenshot = ()=>{ if(rulerBound){ let ruler = Clip.getRulerBound() Object.assign(rulerBound,ruler) } } let {getImagePromise, finishPromise} = viewer.startScreenshot({ type: 'default', bgOpacity:0, focusObjectInfo, maxTimeForPointLoad : 3e5, pointDensity:'screenshot2', splitRenderInfo, beforeScreenshot }, w, h, 1 ) finishPromise.done(({dataUrl}) => { viewer.inputHandler.toggleSelection(Clip.box); Clip.box.visible = true let img = new Image img.src = dataUrl img.onload = async ()=>{//加上标尺比例水印 //固定文字大小和边距像素 //如果有一边过小,可能超出画面外,暂时不管这种可能 const fontsize = math.linearClamp(w, [100, 700, 8000], [12, 20, 30]) const marginSelf = {//img外的margin(标尺内) left : rulerBound ? 76 : 40, right : rulerBound ? 30 : 40, bottom : rulerBound ? 20 + fontsize*2 : 40 + fontsize*2, top : rulerBound ? 60 : 40, } //const Margin = rulerMargin + marginSelf //文字的边距 let bottomRatioToImg = - Math.min(fontsize*2.5, rulerBound ? marginSelf.bottom : marginSelf.bottom*0.9 ) / h//- (marginSelf.bottom - fontsize)/ h //在img之下. text底部到img底部的距离 //let leftRatioToImg = Margin / w let labelInfo = { bottomRatioToImg, horizonCenter:true,//leftRatioToImg, textColor: rulerBound ? {r: 0, g: 0, b: 0, a: 1} : { r: 255, g: 255, b: 255, a: 1 }, textBorderColor : { r: 30, g: 30, b: 30, a: 1 }, textBorderThick : rulerBound ? 0 : 1 , fontsize, fontWeight: rulerBound ? 'normal':'Bold', outputCanvas : !!rulerBound, bgColor: rulerBound ? {r:255,g:255,b:255,a:255}:null } labelInfo.bgMargin = {//img外需要多少margin left: marginSelf.left + rulerMargin, //rulerMargin是在标尺外的margin bottom : marginSelf.bottom + rulerMargin, right: marginSelf.right + rulerMargin, top: marginSelf.top + rulerMargin } if(rulerBound){//因为margin 扩大bound rulerBound.left -= marginSelf.left * meterPerPixel rulerBound.right += marginSelf.right * meterPerPixel rulerBound.top -= marginSelf.top * meterPerPixel rulerBound.bottom += marginSelf.bottom * meterPerPixel } //console.log('topRatioToImg',topRatioToImg,'leftRatioToImg',leftRatioToImg,'fontsize', labelInfo.fontsize) let result = await Potree.Utils.imgAddText(img, text , labelInfo ) //Common.downloadFile(finalDataUrl, 'screenshot11.png') visiPointclouds.forEach((e,i)=>e.material = materials[i]) Clip.screenshoting = false viewer.viewports = [viewer.mainViewport] resolve(result) } }) }) /* 隐患:无法获知最大可加载的点的数量,也未知需要加载的点的数量。需要掌控好分块数量,避免因pointBudget限制造成周围的点稀疏。 加载点云时间过久 */ return finishPromise } } export {Clip} /* 裁剪点云时,2D界面显示全部平面图,按楼层切换显示。 所有点云下载后点云所带的本地坐标都是场景显示的local坐标,这样才能看起来一样。 且为了使之和坐标页面对应,坐标页面的本地坐标也是local,而非mesh_local. //保存box viewer.modules.Clip.box.matrix //恢复box let a = new THREE.Matrix4().fromArray([ 14.189120116830964, 0, 0, 0, 0, 9.924790069368392, 0, 0, 0, 0, 0.8751009568437951, 0, -7.433819981633984, 1.528696446650445, 2.9412375879215977, 1 ]) a.decompose(viewer.modules.Clip.box.position,viewer.modules.Clip.box.quaternion,viewer.modules.Clip.box.scale) viewer.modules.Clip.rotateSideCamera(-93) */