xzw hai 1 ano
pai
achega
22ed4591b3

+ 279 - 0
src/custom/modules/volumeCompute/Prism.js

@@ -0,0 +1,279 @@
+
+import {Measure} from "../../objects/tool/Measure.js";
+import math from "../../utils/math.js";
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+ 
+ 
+ 
+let areaPlaneMats = {
+    
+} 
+ 
+
+export class Prism extends Measure{
+    constructor(args){
+        super(args)
+        this.isPrism = true 
+        this.volumeInfo = {}
+        this.needsCompute = true
+        this.createPrismLines(new THREE.Color('#FFF') )
+        this.lineMesh.material.transparent = true
+        this.lineMesh.material.opacity = 0.5 
+        Potree.Utils.setObjectLayers(this.lineMesh, 'measure' )  
+        
+        
+        this.setTopOrBtm('zMin')
+        this.setTopOrBtm('zMax')
+        this.setHorizonHeight('btm')
+        
+        
+        
+        let update =()=>{
+            this.setHorizonHeight(this.horizonType, true)//更新 
+            this.needsCompute = true
+            this.getBound()
+        }
+        this.addEventListener('marker_moved',update)
+        this.addEventListener('changeByHistory',update)
+            
+       
+        
+        
+        /* this.addEventListener('createDone',()=>{
+            this.setHorizonHeight()//更新
+            this.updateAreaPlane()
+        })  */
+        this.updateAreaPlane()
+        
+        
+        this.getBound()
+        
+        
+        //Potree.Utils.setObjectLayers(this.lineMesh, 'bothMapAndScene' ) 
+    }
+    
+    
+    
+    
+    setHorizonHeight(v){
+        let old = this.horizonZ
+        if(typeof v == 'number'){
+            this.horizonType = 'number'
+            this.horizonZ = v  
+        }else{
+            if(v){
+                this.horizonType = v 
+            }
+            if(!this.horizonType) this.horizonType = 'btm'
+            if(this.horizonType == 'number')return 
+            let zs = this.points.map(p=>p.z).sort((a,b)=>a-b)   
+            this.horizonZ = this.horizonType == 'btm' ?  zs[0] : zs[this.points.length-1]
+        }
+         
+        
+        //this.horizonZ = THREE.Math.clamp(this.horizonZ, this.zMin, this.zMax  )
+        
+        if(this.horizonZ != old ){
+            this.needsCompute = true  
+            this.update() //update areaPlane and areaLabel pos 
+            this.dispatchEvent('horizonZChanged')
+        }
+        //console.log(this.horizonZ)
+        
+    }
+    
+   
+    setTopOrBtm(name,v){
+        if(v != void 0 ){
+            this[name] = v
+            this.updatePrismLines()
+        }else{
+            this[name] = viewer.bound.boundingBox[name == 'zMin' ? 'min' : 'max'].z
+        }   
+        this.getBound()
+        this.needsCompute = true
+    }
+    
+    
+    getBound(){
+        let bound = Potree.math.getBound(this.points)
+        bound.min.z = this.zMin
+        bound.max.z = this.zMax
+        this.prismBound = bound
+    }
+    
+    
+    
+    setVolumeInfo(volumeInfo){
+        this.volumeInfo = volumeInfo 
+        this.getVolumeString()
+        this.needsCompute = false  //但得到的不一定是对的可能 检查下
+        
+    } 
+    
+    
+    getVolumeString(){
+        if(!this.volumeInfo || this.volumeInfo.Vupper == void 0)return;
+        this.VupperString = this.getConvertString(this.volumeInfo.Vupper, 'volume', true)  
+        this.VlowerString = this.getConvertString(this.volumeInfo.Vlower, 'volume', true)   
+        this.highestString = this.getConvertString(this.volumeInfo.highest, 'distance', true)  
+        this.lowestString = this.getConvertString(this.volumeInfo.lowest, 'distance', true)  
+    }
+    
+    getConvertString(num, type, restrictUnit){
+        if(!restrictUnit)return super.getConvertString(num, type) 
+        
+        return viewer.unitConvert.convert(num, type, Potree.settings.precision, this.unitSystem, null , 
+            {//在pdf里的高度要统一单位
+                'imperial': {restrictUnit: 'Foot'},  //ft 
+                'metric': {restrictUnit: 'Meter'} 
+            }
+        )  
+    }
+    
+    
+    setUnitSystem(unitSystem){
+        let old = this.unitSystem
+        
+        super.setUnitSystem(unitSystem)
+        
+        if(unitSystem != old){
+            this.getVolumeString()
+        } 
+    }
+    
+    
+    getCenter(type){
+        if(type == 'areaPlaneCenter'){//areaPlane中心
+            return this.areaPlaneCenter
+        }
+        return super.getCenter() //点的中心
+    }
+    
+    update(){
+        super.update.apply(this, arguments)
+        this.updatePrismLines() 
+    }
+    
+    getTransformationMatrix(pointcloud) {//剪裁矩阵
+        let invMatrix = new THREE.Matrix4;
+        //把当前z移动到-0.5 ~ 0.5所需要的变换
+        let minZ = this.zMin//viewer.bound.boundingBox.min.z
+        let maxZ = this.zMax//viewer.bound.boundingBox.max.z
+        let s = 1/(maxZ-minZ) 
+        let pos = new THREE.Vector3(0,0,   -0.5 - minZ * s  ), 
+            scale = new THREE.Vector3(1,1,s); 
+        
+        invMatrix.compose( pos, new THREE.Quaternion, scale ); //先缩放后位移
+        return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose()
+    } 
+
+    
+    
+    changeStyleForScreenshot(state, {hideLabel,showName}={}){//截图前变一下外观。根据高度变化颜色
+    
+     
+        let config = Potree.settings.prismHeightColor
+        let color//, lineWidth = this.edges[0].material.lineWidth 
+        
+        
+        if(this.volumeInfo.Vupper > 0.01 && this.volumeInfo.Vlower > 0.01){
+            color = config.every.color 
+        }else if(this.volumeInfo.Vupper > this.volumeInfo.Vlower){
+            for(let i = config.dig.length-1; i>=0; i--){
+                if(this.volumeInfo.Vupper >= config.dig[i].min){ 
+                    color = config.dig[i].color  
+                    break
+                } 
+            }
+        }else{
+            for(let i = config.fill.length-1; i>=0; i--){
+                if(this.volumeInfo.Vlower >= config.fill[i].min){ 
+                    color = config.fill[i].color 
+                    break
+                }  
+            }
+        }
+        
+        color = new THREE.Color(color[0]/255,color[1]/255,color[2]/255)
+
+        let name = color.getHexString() 
+        
+        if(state){
+             
+            
+            let mat = areaPlaneMats[name] 
+            if(!mat){
+                
+                mat = new THREE.MeshBasicMaterial({
+                    color, transparent:true, opacity:0.7,
+                    side:THREE.DoubleSide, 
+                })
+                areaPlaneMats[name] = mat
+                
+            }
+            
+            
+            
+            this.oldAreaPlaneMat = this.areaPlane.material
+            this.areaPlane.material = mat
+            
+            if(hideLabel){
+                Potree.Utils.updateVisible(this.areaLabel, 'screenshot-single',false)
+            }
+            if(showName){ 
+                this.areaLabel.setText([this.name,  this.area.string])
+                /* let btm = this.areaPlaneCenter.clone().setY(this.prismBound.min.y) //置于土方2d块底部
+                this.areaLabel.setPos(btm)
+                this.areaLabel.sprite.position.y = -0.25 */
+            }
+            
+            
+            this.markers.forEach(marker=>Potree.Utils.updateVisible(marker,'screenshot-Prism',false))
+            this.edges.forEach(edge=>Potree.Utils.updateVisible(edge,'screenshot-Prism',false)) 
+            Potree.Utils.updateVisible(this.lineMesh,'screenshot-Prism',false)
+            
+            
+            this.styleRecover = ()=>{
+                
+                this.areaPlane.material = this.oldAreaPlaneMat
+                if(hideLabel){
+                    Potree.Utils.updateVisible(this.areaLabel, 'screenshot-single', true)
+                }
+                if(showName){ 
+                    this.areaLabel.setText(this.area.string) 
+                    /* this.areaLabel.setPos(this.areaPlaneCenter)
+                    this.areaLabel.sprite.position.y = 0 */
+                }
+                this.markers.forEach(marker=>Potree.Utils.updateVisible(marker,'screenshot-Prism',true))
+                this.edges.forEach(edge=>Potree.Utils.updateVisible(edge,'screenshot-Prism',true))
+                Potree.Utils.updateVisible(this.lineMesh,'screenshot-Prism',true)
+                this.styleRecover = null
+                
+                
+            }
+            
+        }else{
+             
+            this.styleRecover && this.styleRecover() 
+            
+        }
+    }
+    /* {
+            fill: [
+                {color: [236, 213, 143, 1], min: 0, max: 5},
+                {color: [223, 118, 21, 1], min: 5, max: 10},
+                // 没有min表示大于
+                {color: [186, 57, 57, 1], min: 10}
+            ],
+            dig: [
+                {color: [144, 193, 190, 1], min: 0, max: 5},
+                {color: [76, 155, 211, 1], min: 5, max: 10},
+                // 没有min表示大于
+                {color: [79, 76, 211, 1], min: 10}
+            ],
+            // 填挖并存
+            every: { color: [49, 200, 181, 1] }
+    } */
+    
+}

+ 829 - 0
src/custom/modules/volumeCompute/VolumeComputer.js

@@ -0,0 +1,829 @@
+
+/*
+import {Utils} from "../../../utils.js"; 
+import Sprite from '../../objects/Sprite.js'
+
+import browser from '../../utils/browser.js' 
+ */
+import Common from "../../utils/Common.js";
+import {ExtendView} from "../../../viewer/ExtendView.js";
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+import Viewport from "../../viewer/Viewport.js";
+import {ExtendPointCloudMaterial} from "../../../materials/ExtendPointCloudMaterial.js";
+import math from "../../utils/math.js";  
+//import {Prism} from './Prism.js'
+
+const maxWidth = 4096
+const surfaceThick = 0.1 ;//最大表面厚度 
+const pointDensity = 'screenshot'
+const maxPointBudge = Potree.config.pointDensity[pointDensity].pointBudget
+
+
+
+
+let horizonZ, zMin, zMax, lowest, highest,  wDelta,  sDelta,  deferred,  interrupt, timestamp
+
+
+
+let testPoint = false
+
+
+//const asyncTimeout = (delay) => new Promise(resolve => setTimeout(resolve, delay))
+const delayForNotify = 5,  notifyInterval = 500  
+
+
+let lastNotifyTime = Date.now()
+let curPercent
+const notify = (areas, num, str )=>{ //num: 0-1
+    return new Promise(resolve => { 
+        if(!deferred)return resolve()
+         
+        let now = Date.now()
+        if(now - lastNotifyTime < notifyInterval){ 
+            return resolve()
+        } 
+        lastNotifyTime = now
+        if(num == void 0){
+            num = curPercent
+        }else{ 
+            let s = 0, e = 1
+            if(num instanceof Function ){
+                num = num()
+            }
+             
+            areas.forEach((area)=>{
+                let s_ = e * area[0] + s * (1-area[0])
+                let e_ = e * area[1] + s * (1-area[1])
+                s = s_, e = e_
+            })
+            
+            let p = e * num + s * (1-num)  
+            
+            str == 'loadpoints' && Potree.settings.isTest &&  console.log('notify pro', p, str)
+            deferred.notify(p) 
+            curPercent = p
+        }
+        setTimeout(resolve, delayForNotify)
+    }) 
+    
+}
+
+
+
+
+
+export default class VolumeComputer extends THREE.EventDispatcher{
+    
+    constructor(){
+        super()
+        
+        this.camera = new THREE.OrthographicCamera(-100,100,-100,100 , 0.01, 100);
+        this.camera.up.set(0,0,1);
+        this.view = new ExtendView
+        
+        this.viewport = new Viewport(this.view,this.camera,{left:0,bottom:0,width:1,height:1})
+        this.renderTarget = new THREE.WebGLRenderTarget(
+            1, 1,
+            { minFilter: THREE.LinearFilter,
+                magFilter: THREE.NearestFilter,
+                format: THREE.RGBAFormat }
+        );
+        
+        this.material = new ExtendPointCloudMaterial();
+     
+		this.material.activeAttributeName =  testPoint ? "rgba" : 'prismHeight' ;// 'rgba' indices prismHeight
+        
+        this.height = {up: 2, down: 1}
+        
+        this.prisms = []
+        
+        
+        
+        
+    }
+    
+    init(){
+        viewer.scene.addEventListener('measurement_added',(e)=>{
+            if(e.measurement.isPrism) this.add(e.measurement)
+        })
+        viewer.scene.addEventListener('measurement_removed',(e)=>{
+            if(e.measurement.isPrism) this.remove(e.measurement)
+        }) 
+    }
+    
+    enter(){
+        this.entered = true 
+        viewer.measuringTool.history.clear() //避免撤销到测量线去
+        this.oldStates = {
+            rotAroundPoint : Potree.settings.rotAroundPoint
+        }
+        Potree.settings.rotAroundPoint = false
+    }
+    
+    leave(){ 
+        this.entered = false
+        Potree.settings.rotAroundPoint = this.oldStates.rotAroundPoint
+        
+    }
+    
+    add(prism){
+        this.prisms.push(prism)
+    }
+    
+    remove(prism){
+        let i = this.prisms.indexOf(prism)
+        i>-1 && this.prisms.splice(i,1)
+    }
+    
+    
+   
+    startCompute(){//test
+        this.enter();
+        
+        let deferred = $.Deferred() 
+        deferred.done((e)=>{
+            console.log('done', e)
+        })
+        deferred.fail((e)=>{
+            console.log('?fail????', e)
+        })
+        /* deferred.progress((e)=>{
+            console.log('progress', e)
+        })  */  
+         
+        this.compute(this.prisms[this.prisms.length-1],  deferred )
+        
+    }
+    
+    cancel(){
+        if(this.prismComputing){
+            interrupt = true 
+        }
+    }
+    
+   
+    
+    
+    
+    
+    //法1:只用一个顶部相机(当挖方遮挡填方时不准,但好算)
+    async compute(prism, deferred_, getResolveResult){
+        
+        if(this.prismComputing) return console.log('正在计算,请稍等')
+        
+        deferred = deferred_
+        timestamp = Date.now()
+        
+        this.prismComputing = prism 
+        prism.computing = true 
+        
+        let computeFinish = (debugStr, makeit)=>{
+            
+            makeit || deferred.reject(interrupt ? '主动取消' : (debugStr || ''))
+            
+            
+            this.prismComputing && (this.prismComputing.computing = false)
+            this.prismComputing = null
+            deferred = null
+            interrupt = false
+            debugStr && console.log(debugStr)
+            
+            Potree.settings.displayMode = statesBefore.mode
+            Potree.settings.pointDensity = statesBefore.pointDensity
+            viewer.renderer.setRenderTarget(null);
+            viewer.renderer.state.reset();
+            viewer.renderer.setScissorTest(false);
+            gl.disable(gl.SCISSOR_TEST); 
+            
+            
+            viewer.screenshoting = false 
+            viewer.mainViewport.active = true  
+            viewer.mapViewer.viewports[0].active = true 
+            viewer.magnifier.viewport.active = true 
+            console.log('pxPerMetric',pxPerMetric, 'boundSize',boundSize)
+        }
+        
+        
+        let startTime = Date.now()
+        
+        let pointcloud = viewer.scene.pointclouds[0]
+       
+        
+        let center = new THREE.Vector3
+        let boundSize = new THREE.Vector3
+        let bound = new THREE.Box3
+        horizonZ = prism.horizonZ
+        zMin = prism.zMin
+        zMax = prism.zMax
+        this.camera.far = zMax - zMin + 10
+        
+        
+        lowest = zMax,  highest = zMin
+        
+        for(let i=0;i<prism.points.length;i++){
+            bound.expandByPoint(prism.points[i])
+        }
+        bound.getCenter(center)
+        bound.getSize(boundSize) 
+        center.z = zMax + this.camera.near + 0.1
+        
+        viewer.screenshoting = true
+        
+        
+        
+        
+        let pxPerMetric = math.linearClamp(Math.max(boundSize.x,boundSize.y), [3, 20, 80, 500, 2000], [300, 100, 30, 6, 3])
+        //let pxPerMetric = math.linearClamp(Math.max(boundSize.x,boundSize.y), [5, 20, 80, 500], [300, 200, 50, 20])
+        
+        
+        
+        
+        let w = pxPerMetric * boundSize.x, h = boundSize.y * pxPerMetric
+        
+        
+        
+        let cW = 1 , cH = 1   //横向纵向渲染次数  超过maxWidth时分批渲染
+        if(w > maxWidth || h > maxWidth){
+            /* if(){//多数据集判断是否间隔过远,是的话返回
+                
+            } */ 
+            cW = Math.ceil(w / maxWidth), cH = Math.ceil(h / maxWidth)  
+            w /= cW, h /= cH   
+        }
+        w = Math.round(w)
+        h = Math.round(h) 
+        this.camera.right = w / 2
+        this.camera.left = -this.camera.right
+        this.camera.top = h / 2
+        this.camera.bottom = -this.camera.top 
+        this.viewport.resolution.set(w , h)  
+        this.renderTarget.setSize(w, h)
+        
+        
+        wDelta = boundSize.x / (w * cW )
+        sDelta = Math.pow(wDelta,  2) 
+        
+        //准备 
+        let statesBefore = {
+            mode: Potree.settings.displayMode,
+            pointDensity:Potree.settings.pointDensity
+        }
+        Potree.settings.displayMode = 'showPointCloud'
+        Potree.settings.pointDensity = 'screenshot'
+        viewer.mainViewport.active = false //暂停渲染,否则影响这边点云的加载
+        viewer.mapViewer.viewports[0].active = false 
+        viewer.magnifier.viewport.active = false //防止页面变白(似乎mainViewport被clear)
+        /* let maxLevels = new Map
+        viewer.scene.pointclouds.forEach(e=>{
+            maxLevels.set(e,e.maxLevel);//记录能达到的最大值
+        }) */
+        
+        
+        
+		{  
+			//this.material.shape = Potree.PointShape.PARABOLOID;//加上之后depth出错,后排点云会挡前排,可pick时为什么不会?
+  
+            this.material.pointSizeType = 'ADAPTIVE' //node会自动根据的其level更改点大小
+            this.material.uniforms.minSize.value = 0.1
+			this.material.uniforms.orthoMaxSize.value = 5; //px
+             
+			this.material.classification = pointcloud.material.classification;
+            this.material.recomputeClassification(); 
+            
+            /* this.material.prisms = [prism] 
+            this.material.uniforms.prismList = pointcloud.material.uniforms.prismList
+            this.material.uniforms.prismPoints = pointcloud.material.uniforms.prismPoints */
+            this.material.setClipBoxes(null,[],[],[],[prism])
+            
+            
+            
+            this.material.shaderNeedsUpdate = true
+			pointcloud.updateMaterial(this.material, null, this.camera, viewer.renderer, this.viewport.resolution);
+            
+            
+            this.material.size = this.getPointsize(pointcloud.maxLevel)   //0.025  米   //视图缩小(截图面积变大)后点会自动缩小,但至少是1个像素大小
+        }
+        
+        let gl = viewer.renderer.getContext();
+	  
+		viewer.renderer.state.buffers.depth.setTest(this.material.depthTest);
+		viewer.renderer.state.buffers.depth.setMask(this.material.depthWrite);
+		viewer.renderer.state.setBlending(THREE.NoBlending);
+        
+          
+        
+        
+        
+        let pixelCount = this.renderTarget.width * this.renderTarget.height
+		let buffer = new Uint8Array(4 * pixelCount);
+        
+        viewer.renderer.setRenderTarget(this.renderTarget);
+      
+        let results = []
+        let boundSize_ = boundSize.clone()
+            boundSize_.x /= cW,  boundSize_.y /= cH 
+        
+         
+        
+        //分批渲染:从左到右 从下到上
+        for(let i=0; i<cW; i++){
+            for(let j=0; j<cH; j++){
+                let infos = [] 
+                
+                let progressStart = (i*cH + j) / (cW * cH) ,   progressEnd = (i*cH + j+1) / (cW * cH)
+                 
+                if(interrupt){
+                    return computeFinish('23')
+                }
+                //最高处朝下看,找挖方的顶部
+                this.view.pitch = -Math.PI / 2; 
+                let endPosition = new THREE.Vector3(center.x + (i-cW/2+0.5) * boundSize_.x,  center.y + (j-cH/2+0.5) * boundSize_.y,  center.z)
+                this.view.moveOrthoCamera(this.viewport,  {boundSize: boundSize_, endPosition }, 0)
+                await this.render({name:i+'-'+j+'-第1张', infos, buffer, /* maxLevels, */ notifyArea:[[progressStart, progressEnd],[0,0.4]], computeFinish })
+                 
+                
+                if(interrupt){
+                    return computeFinish('2123')
+                }
+                
+                  
+                 
+                if(!testPoint){ 
+                    //水平面朝下看,找填方
+                    this.view.position.z = horizonZ + this.camera.near  
+                    await this.render({name:i+'-'+j+'-第2张', infos, buffer, /* maxLevels, */ notifyArea:[[progressStart, progressEnd], [0.4, 0.7]], computeFinish})   
+                    //水平面朝上看,找挖方的底部
+                   /*  this.view.position.z = horizonZ + surfaceThick //排除地面(水平面)上的点
+                    this.view.pitch = Math.PI / 2; 
+                    await this.render({reverseRow:true,name:'第3张-'+i+'-'+j, infos, buffer,maxLevels }) *///---土堆上有草地有厚度,还是去掉吧
+                    
+                    let result = {
+                        index : {i,j}, 
+                        allFills:[],
+                        fillVupper:0 ,
+                        fillVlower:0 ,
+                        Vupper : 0, 
+                        Vlower : infos[1].Vlower, // >= infos[0].Vlower 因能找到被上方遮挡住的凹槽
+                        allDiffHeights : [],
+                        infos
+                    }
+                    
+                    
+                    let waitFill = [];
+                    waitFill.count = 0
+                    let isNeighbor = (a,b)=>{
+                        return Math.abs(a.u - b.u ) <= 1 && Math.abs(a.v - b.v ) <= 1    
+                    }
+                    let isBelongToGroup = (group, item)=>{
+                        if(item[0])item = item[0]
+                        
+                        let i=group.length  
+                        while(--i >=0){//从后往前
+                            if(item.u-group[i].u > 1 && item.v-group[i].v > 1) break //距离超出,前面肯定不相邻了
+                            
+                            if(isNeighbor(group[i], item)){
+                                return true
+                            }
+                        }
+                    }
+                    
+                    
+                    
+                    
+                    //计算挖方(但是边缘附近垂直方向上很多点云,体积被削皮,最终得到的会比infos[0]少)
+                    
+                    let getValue = (u,v)=>{//当到了左上边界是否需要向前几批的查找?
+                        if(infos[0].allHeights[u] && infos[0].allHeights[u][v] != void 0 )return infos[0].allHeights[u][v] 
+                        return result.allFills[u] && result.allFills[u][v]  
+                    }
+                    
+                    let getFill = (u,v)=>{//使用周围的八个点的平均值补洞
+
+                        let neighbours = [[0,-1],[-1,0],[1,0],[0,1], [-1,-1], [1,-1], [-1,1],[1,1]]//先左右右先上后下
+                        let i=0, c=0, sum=0
+                        
+                         
+                        while(c<4 && i<8){
+                            let neigh = getValue(neighbours[i][0]+u,neighbours[i][1]+v)
+                            if(neigh != void 0){
+                                c++;
+                                sum += neigh  
+                            } 
+                            i++;
+                        } 
+                        if(c>0){  
+                            sum /= c;  
+                            result.allFills[u] || (result.allFills[u] = [])
+                            result.allFills[u][v] = sum 
+                            return sum
+                        }
+                    }
+                    
+                     
+                    let add = (v, fill)=>{
+                        if(v > 0){
+                            result.Vupper += v 
+                            fill && (result.fillVupper+=v)
+                        }else{//fill的话才会有 
+                            fill && (result.fillVlower-=v)   
+                        }
+                    }
+                        
+                        
+                    let compute = async (u,v)=>{
+                        
+                        let top = infos[0].allHeights[u][v]
+                        //let btm = infos[2].allHeights[u][v]
+                        let fill 
+                        if(top == void 0){
+                            let x = endPosition.x - boundSize_.x /2 + wDelta * (u + 0.5)
+                            let y = endPosition.y - boundSize_.y /2 + wDelta * (v + 0.5)
+                            if(!math.isPointInArea(prism.points, null, {x,y}) ){ 
+                                return //出界
+                            }
+                            fill = true  
+                            top = getFill(u,v) 
+                            if(top == void 0){  //左侧边缘有缺口时会出现
+                              
+                                Common.pushToGroupAuto([{u,v}], waitFill, null, isBelongToGroup) //按邻近点分组
+                                    
+                                waitFill.count ++  
+                                return
+                            }                            
+                        }
+                        /* let diff = top-btm
+                        let useDiff 
+                        if(btm != void 0 && diff > surfaceThick  ){ 
+                            useDiff = diff   
+                            result.allDiffHeights.push(useDiff)        
+                        }else{ 
+                            useDiff = top        //从顶部一直到水平
+                        }*/
+                        
+                        if(waitFill.length){//补  之前没有值的邻居重新获取
+                            if(interrupt){
+                                return computeFinish('12312')
+                            } 
+                            //Potree.settings.isTest &&  console.log('waitFill.count', waitFill.count, 'groupLen', waitFill.length, waitFill[0][0], waitFill[0][waitFill[0].length-1])
+                            waitFill.slice().forEach(group=>{ //每个组都看看是否跟它相邻
+                                if(isBelongToGroup(group, {u,v})){ 
+                                    let index = waitFill.indexOf(group)
+                                    waitFill.splice(index,1)
+                                    waitFill.count -= group.length  
+                                    group.forEach(e=>{
+                                        result.allFills[e.u] || (result.allFills[e.u] = [])
+                                        result.allFills[e.u][e.v] = top //该组全部都使用该体积
+                                    })
+                                    let sum = top * group.length
+                                    add(sum, fill) 
+                                }  
+                            })  
+                        }
+                          
+                        add(top, fill)
+                    }
+                    
+                    
+                    
+                    
+                    let lastNotifyTime = Date.now()  
+                    //从左到右,从下到上一列列扫描
+                    for (let u = 0; u < this.renderTarget.width; u++) {  
+                        for (let v = 0; v < this.renderTarget.height; v++) { //数据是从图的下到上存储的
+                             
+                            if(interrupt){
+                                return computeFinish('1121')
+                            } 
+                            compute(u,v);
+                        }   
+                        await notify([[progressStart, progressEnd], [0.7, 1]]  , ()=>{return u / this.renderTarget.width }, 'compute')
+                        
+                    }
+                    
+                    if(waitFill.length){
+                        console.log('waitFill怎么还有??', waitFill)
+                    }
+                    result.Vupper *= sDelta
+                    result.fillVlower *= sDelta
+                    result.fillVupper *= sDelta
+                    
+                    result.Vlower += result.fillVlower
+                    
+                    results.push(result)
+                }
+                deferred && deferred.notify(progressEnd)                
+            }
+        }
+               
+        
+        let Vupper = 0, Vlower = 0, fillVlower = 0,  fillVupper = 0
+        results.forEach((c)=>{
+            Vupper += c.Vupper; Vlower += c.Vlower;
+            fillVlower += c.fillVlower; fillVupper += c.fillVupper; 
+        })
+        
+        prism.setVolumeInfo({
+            highest,lowest,Vupper,Vlower, 
+        }) 
+        
+        let endTime = Date.now()
+         
+        
+        
+         
+        deferred && deferred.resolve(getResolveResult && getResolveResult())
+        
+        computeFinish('finish', true)
+        console.log( {Vupper,Vlower,  oldVupper: Vupper - fillVupper, oldVlower:  Vlower - fillVlower, highest,lowest, horizonZ, wDelta}, results, this.viewport.resolution, {cW,cH}, 'cost:', endTime-startTime)//firefox用时是chrome两倍,edge和chrome差不多
+    }
+   
+    
+    
+    async render({reverseRow, name, infos, buffer, /* maxLevels, */ notifyArea, computeFinish}={}){ 
+        this.view.applyToCamera(this.camera)   
+        Potree.updatePointClouds(viewer.scene.pointclouds, this.camera, this.viewport.resolution );
+        let gl = viewer.renderer.getContext();
+        let screenshot = (name)=>{
+            //if(window.testScreen){            
+                let dataUrl = Potree.Utils.renderTargetToDataUrl(this.renderTarget, this.renderTarget.width, this.renderTarget.height, viewer.renderer)
+             
+                Common.downloadFile(dataUrl, (name || 'screenshot') + '-' +  timestamp +'.png')  //为什么图片上不是只有pickWindowSize区域有颜色??
+                   
+            //} 
+        } 
+        console.log('开始渲染', name)
+    
+
+        let camera = this.camera
+        let viewport = this.viewport
+        
+        return new Promise((resolve,  reject)=>{
+            
+            let done = async ()=>{ 
+                gl.clearColor(0, 0, 0, 0);
+                viewer.renderer.clear(true, true, true);
+                  
+                //viewer.pRenderer.renderOctree(pointcloud, nodes, this.camera,  this.renderTarget ,{material:this.material});
+                viewer.pRenderer.render(viewer.scene.scenePointCloud, this.camera, this.renderTarget ,{material:this.material});  
+                
+                if(interrupt){
+                    return (reject(), computeFinish('re 23')) 
+                }
+                
+                Potree.settings.isTest && screenshot(name);
+                gl.readPixels(0, 0, this.renderTarget.width , this.renderTarget.height, gl.RGBA, gl.UNSIGNED_BYTE, buffer); //这句花费最多时间 pc:2-4, 即使只有1*1像素
+                
+                notifyArea.push([progressEnd1,1])
+              
+                let Vupper = 0, Vlower = 0
+                 
+                let upperHeights = [],lowerHeights = [], allHeights = []
+                for (let u = 0; u < this.renderTarget.width; u++) {
+                    let col = []
+                    
+                    for (let v = 0; v < this.renderTarget.height; v++) {
+                        let _v = reverseRow ? this.renderTarget.height - v - 1: v
+                        let offset = (u + _v * this.renderTarget.width); 
+                        let rgb = buffer.slice(4 * offset, 4 * offset+3), a = buffer[4 * offset+3]  
+                        if(a == 0  /* rgb[0] == 0 && rgb[1] == 0 && rgb[2] == 0  */){ //无点
+                            col.push(null)
+                        }else{
+                            let z = this.convertColorToHeight(rgb, zMin, zMax) 
+                            let h = z - horizonZ
+                            
+                            z < lowest && (lowest = z) //上表面最低点  
+                            z > highest && (highest = z) //上表面最高点 
+                             
+                            if(h>0){
+                                Vupper += h
+                                upperHeights.push(h)
+                            }else{
+                                Vlower += -h
+                                lowerHeights.push(h)
+                            }
+                            col.push(h)
+                        } 
+                        
+                        if(interrupt){ 
+                            return (reject(), computeFinish('re 023'))
+                        }
+                        //notify(notifyArea,    (u*this.renderTarget.height + v) /(this.renderTarget.width*this.renderTarget.height))        
+                    }
+                    allHeights.push(col)
+                    
+                    
+                    
+                    
+                    await notify(notifyArea,  ()=>{return  u / this.renderTarget.width},  'readPixels get')
+                    
+                    
+                    
+                    
+                }
+                Vupper*=sDelta
+                Vlower*=sDelta
+                    
+                infos.push({Vupper,Vlower,sDelta,upperHeights,lowerHeights,allHeights})
+                resolve()   
+            }
+             
+         
+            //let finish  , maxTime = 1e6
+            let progressEnd1 = math.linearClamp( this.camera.zoom, [1,1000], [0.8,0.2]) //放得越大,实际加载的点云相对越少,费时越少
+            let notifyArea_ = [...notifyArea, [0, progressEnd1]] 
+            
+            if(Potree.pointsLoading ){  //如果有要加载的node
+
+
+                let curProgress = 0,  beginTime = Date.now(), unloadBefore,
+             
+                    tryPreLoadTime = Math.round(math.linearClamp(buffer.length/this.camera.zoom/this.camera.zoom, [50,10000],[1000,15000])) 
+                    //console.log('tryPreLoadTime',tryPreLoadTime)
+            
+                let decreaseLevel = ()=>{ //降点云level 
+                     //暂时先都一起降1 
+                     let pointclouds = viewer.scene.pointclouds.filter(e=>e.visibleNodes.length)
+                     console.log('准备decreaseLevel, numVisiblePoints', Potree.numVisiblePoints, '最小numVisiblePoints占比',  1/pointclouds.length )
+                     
+                     pointclouds.forEach(e=>{
+                         let percent = e.numVisiblePoints / Potree.numVisiblePoints
+                         console.log('numVisiblePoints占总比', e.dataset_id, percent )
+                         if(percent < 1/pointclouds.length)return
+                         
+                         let old = e.maxLevel
+                         let levels = e.visibleNodes.map(e=>e.getLevel())  
+                         let actMaxLevel = Math.max.apply(null, levels) //实际加载到的最高的node level
+                         e.maxLevel = actMaxLevel - 1
+                         console.warn(e.dataset_id,'decreaseLevel,新maxLevel', actMaxLevel - 1, '原maxlevel', old   ) 
+                       
+                         //this.material.size = this.getPointsize(e.maxLevel)
+                         //this.material.size *= 2
+                     }) 
+                     
+                }
+                let finish = (makeit)=>{
+                    //viewer.removeEventListener('overPointBudget',decreaseLevel)
+                    makeit && done() 
+                    viewer.removeEventListener('update',update)
+                    viewer.removeEventListener('pointsLoaded',loaded) 
+                }
+                let update = ()=>{  
+                    Potree.updatePointClouds(viewer.scene.pointclouds, camera, viewport.resolution );
+                    if( Potree.numVisiblePoints > maxPointBudge * 0.9){ 
+                        decreaseLevel()
+                    }
+                    
+                    notify(notifyArea_,  ()=>{ 
+                       /* console.log('unloadedGeometry',Potree.unloadedGeometry.length)
+                       
+                       return curProgress */
+                       
+                       
+                       /* curProgress = THREE.Math.clamp(Potree.numVisiblePoints/predictPointCount,curProgress,1) //如果decreaseLevel了点会减少,也不管它,就停住吧,反正很快就加载好了
+                       return curProgress */
+                       
+                     
+                       if(!unloadBefore && Date.now() - beginTime < tryPreLoadTime){
+                            curProgress = (Date.now() - beginTime) / tryPreLoadTime * 0.5
+                       }else{
+                            if(!unloadBefore){
+                                unloadBefore = Potree.unloadedGeometry.length
+                                //console.log('unloadBefore', unloadBefore)
+                            }                                
+                            unloadBefore = Math.max(Potree.unloadedGeometry.length, unloadBefore) //unloadedGeometry 刚开始可能越涨越多 - - 所以进度条会卡住
+                            
+                            let curProgress_ = 0.5 + 0.5 * (1 - Potree.unloadedGeometry.length / unloadBefore )  
+                            
+                            curProgress = Math.max(curProgress,curProgress_)
+                            
+                       } 
+                       
+                       //console.log('curProgress ', curProgress, 'numVisiblePoints', Potree.numVisiblePoints, 'unloadedGeometry',Potree.unloadedGeometry.length)
+                       
+                       return curProgress 
+                    }, 'loadpoints')        
+                    
+                    
+                    interrupt && (finish(false),  reject(), computeFinish('正在loadPoints,unloadedGeometry个数'+Potree.unloadedGeometry.length )) //曾经遇到遇到bug 不发送pointsLoaded 一直update  一直有3个unloadedGeometry加载不了。。 是否为每个加载的node加个计时?
+                } 
+                viewer.addEventListener('update',update)
+                
+                let loaded = ()=>{ //点云加载完时(不一定准确)  
+                    console.warn('加载完毕', ' numVisiblePoints', Potree.numVisiblePoints )                         
+                    finish(true)
+                } 
+                viewer.addEventListener('pointsLoaded', loaded)
+                
+                 
+                
+                
+                
+              
+                /* let predictPointCount = Math.round(math.linearClamp(buffer.length, [1e5, maxWidth*maxWidth*4],[1e6,  maxPointBudge ]))//最终要加载到的数目
+                 */
+                 
+                
+                /* let overTime = ()=>{
+                    if(document.hidden){
+                        return setTimeout(overTime, maxTime)  
+                    }
+                    if(!finish)console.warn('超时, numVisiblePoints', Potree.numVisiblePoints)  
+                    dealDone()
+                }
+                 
+                setTimeout(overTime, maxTime) */
+            }else{
+                notify(notifyArea_,  1) 
+                done()
+            }  
+        })
+        
+    }
+    
+     
+    convertColorToHeight(color, zMin, zMax){
+        let percent = 0
+        let a = 1 
+        for(let i=0;i<3;i++){
+            a = i == 2 ? a/255 : a / 256 
+            percent += a * color[i];
+        }
+         
+        return (zMax - zMin) * percent + zMin; 
+    }
+    
+    
+    getPointsize(maxLevel){//尽量铺满  size单位是米,需要和点云缝隙同宽
+    
+        return 0.2
+         
+        
+        
+        let s =  /* this.material.spacing */ 100 / Math.pow(2, maxLevel - 1)  //adaptive的话在shader中已经乘了spacing  
+        console.log('getPointsize', s, 'maxLevel',maxLevel)
+        return s  //会不会直接返回1就好?  
+        //不能太小。假如gl_pointsize计算结果是0.1,那么显示就是1px;当level将级后,放大2倍为0.2,也还是1px,看起来就稀疏了。所以要保证这个数在最大level时gl_pointsize计算结果差不多是1px.(虽然我会在渲染大尺寸面积时降画布尺寸,但不能保证刚刚好,且因点云个数不可控不会降尺寸太狠)
+    } 
+    
+    
+    download(prisms){
+        
+        if(this.prisms.length == 0){
+            return null
+        } 
+        
+        
+        var visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection'))
+        let data = {   
+            transformation_matrix: visiPointclouds.map((cloud)=>{
+                let data = {
+                    id: cloud.dataset_id, 
+                    matrix: new THREE.Matrix4().elements,  //参照downloadNoCrop,给默认值,表示没有最外层裁剪
+                    visiMatrixes: prisms.map(prism=>{return {
+                        matrix: prism.getTransformationMatrix(cloud).elements,//剪裁框
+                        points: prism.points.map(p=>{return {x:p.x,  y:p.y}})
+                    }}), 
+                    modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
+                }  
+                return data
+            }) ,
+            aabb: "b-12742000 -12742000 -12742000 12742000 12742000 12742000" //剪裁空间 
+        }
+        
+        return data
+    }
+    
+    
+    //体积多边形棱柱,z需要变换到-0.5 ~ 0.5
+    
+
+}
+
+/* 
+
+    渲染像素pxPerMetric越高,边缘越细腻、准确;   点size越小,越不容易遮盖住下方点云,造成体积偏大。但会不会有底部点云透出来?
+
+
+    该版本计算用到两张截图,第一张为整体的俯视图,包括挖方和填方;第二张仅包含填方。
+    缺陷:在补洞时,只利用第一张图的结果,所以整体的挖方会偏多,填方偏少(因为在挖方填方同时存在时,没有分别补挖方和填方,而是放在一起,而上方可能遮盖下方的)
+    尝试过第一张只绘制挖方部分,但是在遍历时就需要多遍历一次,测得的用时更多一些,结果却没变化
+    也尝试过补洞时挖方填方分别补各自的,但这样要考虑更多的东西,用时多了两倍以上。(因为目前规定无论范围内实际有没有点云都补上,非挖即填;如果分开的话,填方边缘也都补上肯定是错的,无法找到挖和填的界限,无法分清是只有挖还是填还是两者均有。如果要探测周围点,耗时很多)
+    
+
+
+
+
+    如果数据集修改了,土方量需要手动重算。所以场景刷新后显示的体积有可能是错的。
+    之前记的,太麻烦了,延期处理:
+        1 一般情况:我需要记录每个土方量范围内存在的数据集的位置,如
+
+        {datasetId:"1745733728954093568", location:[113.59550104511611, 22.36677932324081, 0] ,orientation: 0.1}
+
+        如果刷新后土方量范围内存在的数据集发生改变,将重算。
+
+
+        2 其他特殊情况,如裁剪,难以知道裁剪了哪块,所以只能全部重算
+ 
+
+
+ */