xzw 1 月之前
父節點
當前提交
56ff350ca1

+ 3 - 0
package.json

@@ -32,6 +32,9 @@
     "gulp-concat": "^2.6.1",
     "gulp-connect": "^5.7.0",
     "json5": "^2.1.3",
+    "jszip": "^3.10.1",
+    "jszip-utils": "^0.1.0",
+    "npyjs": "^0.7.0",
     "rollup": "^1.31.1",
     "rollup-plugin-babel": "^4.4.0",
     "rollup-plugin-uglify": "^6.0.4",

+ 1 - 0
src/Potree.js

@@ -42,6 +42,7 @@ export * from "./PotreeRenderer.js";
 export * from "./ProfileRequest.js";
 export * from "./custom/objects/TextSprite.js";
 export * from "./custom/objects/3dgs/Splat.js";
+export * from "./custom/objects/3dgs/SplatNPY.js";
 export * from "./utils.js";
 export * from "./Version.js";
 export * from "./WorkerPool.js";

文件差異過大導致無法顯示
+ 436 - 0
src/custom/objects/3dgs/SortWorker.js


文件差異過大導致無法顯示
+ 1050 - 0
src/custom/objects/3dgs/Splat.js


+ 867 - 0
src/custom/objects/3dgs/SplatNPY.js

@@ -0,0 +1,867 @@
+
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+
+import {SortWorker, createSortWorker} from './SortWorker.js'
+
+import npyjs from 'npyjs'
+import JSZip from 'jszip'
+import JSZipUtils from 'jszip-utils'
+
+//从 GaussianSplats3D 里摘过来,用于显示webcloud型的lod 3dgs
+ 
+
+let sortCount = 0
+
+const dummyGeometry = new THREE.BufferGeometry();
+const dummyMaterial = new THREE.MeshBasicMaterial();
+
+//只允许存在至多一个Splat
+
+export class SplatNPY extends THREE.Mesh { 
+    constructor({adaptiveSize, splatEnableSwapOut,fileName}={}){  
+        super(dummyGeometry, dummyMaterial)
+        this.fileName = fileName
+        this.loadedSplatCount = 0
+        this.adaptiveSize = adaptiveSize
+        this.geometry = this.buildGeometry()
+        this.material = this.buildMaterial()
+        this.frustumCulled = false //禁止视锥体剔除
+        this.focalAdjustment = 1
+        this.integerBasedSort = true
+        this.currentNodes = [] 
+        Potree.Utils.setObjectLayers(this, 'model');
+         
+        this.pointclouds = []
+        
+        const maxSplatCount = Potree.config.maxSplatCount
+        const PosArrayType = this.integerBasedSort ? Int32Array : Float32Array;
+        
+        
+        adaptiveSize && this.switchSizeType(true) //初始化后不支持更改
+        
+        this.useSharedMemory = Potree.browser.urlHasValue('NoShareBuffer') ? false : !!self.crossOriginIsolated
+        if(!self.crossOriginIsolated){
+            alert('sharedMemory unsupported!')
+        }
+        document.title = this.useSharedMemory ? '3dgs-shareBuffer' : '3dgs-NoShareBuffer'
+        if(!this.useSharedMemory){
+            Potree.pointBudget = Potree.config.maxSplatCount = Potree.browser.isMobile() ? 1000000 : 2000000
+        }
+        if(this.useSharedMemory) splatEnableSwapOut = false //暂不支持,否则要创建太多内存
+         
+        this.sortWorker = createSortWorker(this.useSharedMemory, maxSplatCount, true, false, adaptiveSize, splatEnableSwapOut)
+         
+        this.sortWorker.onmessage = (e) => {
+            if (e.data.sortDone) {
+                this.sortRunning = false; 
+                let sortWorkerDatas = this.sortWorkerDatas
+                 
+                if(!this.useSharedMemory){//恢复数据主权
+                    
+                    sortWorkerDatas.sorted[0].centers = new Float32Array(e.data.sorted.centers);
+                    sortWorkerDatas.sorted[0].covs1 = new Float32Array(e.data.sorted.covs1)
+                    sortWorkerDatas.sorted[0].covs2 = new Float32Array(e.data.sorted.covs2)
+                    sortWorkerDatas.sorted[0].colors = new Uint8Array(e.data.sorted.colors)
+                    
+                    sortWorkerDatas.toSort.centersFloat = new Float32Array(e.data.toSort.centersFloat);  
+                    sortWorkerDatas.toSort.colors = new Uint8Array(e.data.toSort.colors);
+                    sortWorkerDatas.toSort.covs = new Float32Array(e.data.toSort.covs)
+                    
+                    if(this.integerBasedSort){
+                        sortWorkerDatas.toSort.centersInt = new Int32Array(e.data.toSort.centersInt)
+                    }    
+                    
+                }
+                let {centers, colors, covs1, covs2, nodes} = sortWorkerDatas.sorted[e.data.sortedBufferIndex]
+                this.updateRenderIndexes( centers, colors, covs1, covs2, nodes, e.data.splatRenderCount); 
+                //this.lastSortTime = e.data.sortTime;//sort耗时
+                this.sortPromiseResolver();
+                this.sortPromiseResolver = null;
+                viewer.dispatchEvent('content_changed')//this.forceRenderNextFrame();
+                 
+                sortCount++;
+                
+            } else if (e.data.sortCanceled) {
+                this.sortRunning = false;
+            } else if (e.data.sortSetupPhase1Complete) {//sortWorker init 完毕
+                console.log('Sorting web worker WASM setup complete.');
+                 
+                const CENTERS_BYTES_PER_ENTRY = this.integerBasedSort ? (Potree.defines.gs3d.BytesPerInt * 4) : (Potree.defines.gs3d.BytesPerFloat * 4);
+                 
+                 
+                if(this.useSharedMemory){
+                    this.sortWorkerDatas = {
+                        sorted:[
+                            {
+                                centers: new Float32Array(e.data.wasmMemory, e.data.sortedCentersOffset,  maxSplatCount * 3),
+                                colors: new Uint8Array(e.data.wasmMemory, e.data.sortedColorsOffset,  maxSplatCount * 4), 
+                                covs1: new Float32Array(e.data.wasmMemory, e.data.sortedCovs1Offset,  maxSplatCount * 3),
+                                covs2: new Float32Array(e.data.wasmMemory, e.data.sortedCovs2Offset,  maxSplatCount * 3),
+                            } 
+                            
+                        ],
+                        toSort:{ 
+                            centersFloat : new Float32Array(e.data.wasmMemory, e.data.centersFloatOffset, maxSplatCount * 3) ,
+                            colors: new Uint8Array(e.data.wasmMemory, e.data.colorsOffset, maxSplatCount * 4),//所有类型参数均为:buffer, byteOffset, length, 第三个参数是item个数,而非字节数
+                            covs: new Float32Array(e.data.wasmMemory, e.data.covsOffset, maxSplatCount * 6) , 
+                        }   
+                    }
+                    if(this.integerBasedSort){
+                        this.sortWorkerDatas.toSort.centersInt = new Int32Array(e.data.wasmMemory, e.data.centersIntOffset, maxSplatCount * 4)
+                    }
+                }else{
+                    this.sortWorkerDatas = {
+                        sorted:[
+                            {
+                                centers: new Float32Array(  maxSplatCount * 3),
+                                colors: new Uint8Array( maxSplatCount * 4), 
+                                covs1: new Float32Array( maxSplatCount * 3),
+                                covs2: new Float32Array(  maxSplatCount * 3),
+                            } 
+                        ],
+                        toSort:{ 
+                            centersFloat : new Float32Array( maxSplatCount * 3) ,
+                            colors: new Uint8Array( maxSplatCount * 4),//所有类型参数均为:buffer, byteOffset, length, 第三个参数是item个数,而非字节数
+                            covs: new Float32Array( maxSplatCount * 6)   
+                        } 
+                    }
+                    if(this.integerBasedSort){
+                        this.sortWorkerDatas.toSort.centersInt = new Int32Array( maxSplatCount * 4)
+                    }                    
+                }
+                  
+                
+                if(this.adaptiveSize){
+                    this.sortWorkerDatas.sorted[0].nodes = new Int32Array(e.data.wasmMemory, e.data.sortedNodeOffset,  maxSplatCount * 2) 
+                    this.sortWorkerDatas.toSort.nodes = new Int32Array(e.data.wasmMemory, e.data.nodeOffset,  maxSplatCount * 2) 
+                }
+                
+                if(this.splatEnableSwapOut){
+                    this.sortWorkerDatas.sorted.push({
+                        centers: new Float32Array(e.data.wasmMemory, e.data.sortedCentersOffset+e.data.memoryRequiredOut,  maxSplatCount * 3),
+                        colors: new Uint8Array(e.data.wasmMemory, e.data.sortedColorsOffset+e.data.memoryRequiredOut,  maxSplatCount * 4), 
+                        covs1: new Float32Array(e.data.wasmMemory, e.data.sortedCovs1Offset+e.data.memoryRequiredOut,  maxSplatCount * 3),
+                        covs2: new Float32Array(e.data.wasmMemory, e.data.sortedCovs2Offset+e.data.memoryRequiredOut,  maxSplatCount * 3),
+                    }) 
+                }
+
+                //if gpuAcceleratedSort  and dont shareMemory
+                //this.sortWorkerPrecomputedDistances = new PosArrayType(e.data.wasmMemory, e.data.precomputedDistancesOffset, maxSplatCount);
+                //this.sortWorkerTransforms = new Float32Array(e.data.wasmMemory, e.data.transformsOffset, Potree.defines.gs3d.MaxScenes * 16);
+              
+                //this.sortWorker.maxSplatCount = maxSplatCount;//总点数
+
+                console.log('Sorting web worker ready.');
+                this.updateSort()
+            }else{
+                console.log(e.data.msg)
+            }
+        }; 
+        
+        this.load()
+        
+        viewer.addEventListener('camera_changed', e => {
+            var camera = e.viewport.camera
+            var pos = camera.position
+            if (e.viewport.name == 'MainView'){
+                this.updateSort() 
+                if(e.changeInfo.projectionChanged){
+                    this.updateMaterial()             
+                }                
+            }  
+        })
+        viewer.addEventListener('pointcloud_changed', e => {
+            this.updateSort() 
+        })
+        
+        this.updateMaterial() 
+        
+        
+    }
+     
+     
+ 
+    buildGeometry(){
+        const baseGeometry = new THREE.BufferGeometry();
+        baseGeometry.setIndex([0, 1, 2, 0, 2, 3]);
+
+        // Vertices for the instanced quad
+        const positionsArray = new Float32Array(4 * 3);
+        const positions = new THREE.BufferAttribute(positionsArray, 3);
+        baseGeometry.setAttribute('position', positions);
+        positions.setXYZ(0, -1.0, -1.0, 0.0);
+        positions.setXYZ(1, -1.0, 1.0, 0.0);
+        positions.setXYZ(2, 1.0, 1.0, 0.0);
+        positions.setXYZ(3, 1.0, -1.0, 0.0);
+        positions.needsUpdate = true;
+
+        const geometry = new THREE.InstancedBufferGeometry().copy(baseGeometry);
+ 
+        
+   
+        /* 
+        //给点加序号
+        const splatIndexArray = new Uint32Array(Potree.config.maxSplatCount);
+        const splatIndexes = new THREE.InstancedBufferAttribute(splatIndexArray, 1, false);
+        splatIndexes.setUsage(THREE.DynamicDrawUsage);
+        geometry.setAttribute('splatIndex', splatIndexes);  
+        for(let i=0;i<Potree.config.maxSplatCount;i++){
+            splatIndexArray[i] = i;
+        } */ 
+    
+    
+
+        const posArray = new Float32Array(Potree.config.maxSplatCount*3);
+        const centers = new THREE.InstancedBufferAttribute(posArray, 3, false);
+        centers.setUsage(THREE.DynamicDrawUsage);
+        geometry.setAttribute('center', centers); 
+         
+        const colorArray = new Uint8Array(Potree.config.maxSplatCount*4);
+        const colors = new THREE.InstancedBufferAttribute(colorArray, 4, true);//第三个参数是normalized, 开启后 WebGL 会在内部将每个颜色分量从 [0, 255] 转换到 [0.0, 1.0] 范围 
+        colors.setUsage(THREE.DynamicDrawUsage);
+        geometry.setAttribute('color', colors);  
+        
+        const covArray1 = new Float32Array(Potree.config.maxSplatCount*3);
+        const covs1 = new THREE.InstancedBufferAttribute(covArray1, 3, false);
+        covs1.setUsage(THREE.DynamicDrawUsage);
+        geometry.setAttribute('covRow1', covs1);  
+        const covArray2 = new Float32Array(Potree.config.maxSplatCount*3);
+        const covs2 = new THREE.InstancedBufferAttribute(covArray2, 3, false);
+        covs2.setUsage(THREE.DynamicDrawUsage);
+        geometry.setAttribute('covRow2', covs2); 
+        
+        geometry.instanceCount = 0;
+        
+        if(this.adaptiveSize){
+            const nodeSthArr = new Int32Array(Potree.config.maxSplatCount*2);//node:  vnStart  level
+            const nodeSth = new THREE.InstancedBufferAttribute(nodeSthArr, 2, false);
+            nodeSth.setUsage(THREE.DynamicDrawUsage);
+            geometry.setAttribute('nodeLevelVNS', nodeSth);
+        }
+        
+
+        this.attrOrigin = {
+            center: posArray,
+            color: colorArray,
+            covRow1: covArray1,
+            covRow2: covArray2 
+        }
+        return geometry;
+    }
+    
+    
+    
+    
+    
+    buildMaterial(){
+        
+        
+        const maxScreenSpaceSplatSize =/*  512// */2048 // 这啥
+        
+        let vs = `
+            precision highp float;
+            #include <common>
+            
+            attribute vec3 center;
+            attribute vec4 color;
+            attribute vec3 covRow1;
+            attribute vec3 covRow2;
+             
+            //attribute uint splatIndex;      
+            //uniform int splatCount; 
+        
+        
+            uniform float uOctreeSize; //add 
+            uniform vec2 focal;
+            uniform float orthoZoom;
+            uniform int orthographicMode;
+            uniform int pointCloudModeEnabled;
+            uniform float inverseFocalAdjustment;
+            uniform vec2 viewport;
+            
+            
+           
+            uniform float splatScale;
+            
+            varying vec4 vColor;
+            varying vec2 vUv;
+
+            varying vec2 vPosition;
+            
+            const float sqrt8 = sqrt(8.0);
+            const float minAlpha = 1.0 / 255.0;
+            
+             
+
+            void main () {
+
+                mat4 transformModelViewMatrix = modelViewMatrix; 
+            
+                vec4 viewCenter = transformModelViewMatrix * vec4(center, 1.0);
+
+                vec4 clipCenter = projectionMatrix * viewCenter;
+
+                float clip = 1.2 * clipCenter.w;
+                if (  clipCenter.z < -clip || clipCenter.x < -clip || clipCenter.x > clip || clipCenter.y < -clip || clipCenter.y > clip) {
+                    gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
+                    return;
+                }
+
+                vPosition = position.xy;
+                vColor = color;
+                
+                //debug 测试sort是否正确. 由远及近由黑变白
+                //float r = float(splatIndex) / float(splatCount)  ;
+                //vColor = vec4(r,r,r,1.0); 
+
+                mat3 Vrk = mat3(                             // Construct the 3D covariance matrix
+                    covRow1.x, covRow1.y, covRow1.z,
+                    covRow1.y, covRow2.x, covRow2.y,
+                    covRow1.z, covRow2.y, covRow2.z
+                );
+                
+                //翻译结果:构建投影矩阵仿射近似的雅可比矩阵。它将被用来变换3D协方差矩阵,而不是直接使用实际的投影矩阵,因为使用实际投影矩阵进行变换需要包含非线性成分(透视除法),这会导致非高斯的结果。这里假设当前的投影为透视投影。
+                
+                mat3 J;
+                if (orthographicMode == 1) {
+                    J = transpose(mat3(orthoZoom, 0.0, 0.0,
+                                       0.0, orthoZoom, 0.0,
+                                       0.0, 0.0, 0.0));
+                } else {
+                    float s = 1.0 / (viewCenter.z * viewCenter.z);
+                    J = mat3(
+                        focal.x / viewCenter.z, 0., -(focal.x * viewCenter.x) * s,
+                        0., focal.y / viewCenter.z, -(focal.y * viewCenter.y) * s,
+                        0., 0., 0.
+                    );
+                }
+        
+                // Concatenate the projection approximation with the model-view transformation
+                mat3 W = transpose(mat3(transformModelViewMatrix));
+                mat3 T = W * J;
+
+                // Transform the 3D covariance matrix (Vrk) to compute the 2D covariance matrix
+                mat3 cov2Dm = transpose(T) * Vrk * T;
+                
+         
+                cov2Dm[0][0] += 0.3;
+                cov2Dm[1][1] += 0.3;
+                float compensation = 1.0;
+                
+                
+                
+                vColor.a *= compensation;
+
+                if (vColor.a < minAlpha) return;
+ 
+                vec3 cov2Dv = vec3(cov2Dm[0][0], cov2Dm[0][1], cov2Dm[1][1]);
+
+                vec3 ndcCenter = clipCenter.xyz / clipCenter.w;
+
+                float a = cov2Dv.x;
+                float d = cov2Dv.z;
+                float b = cov2Dv.y;
+                float D = a * d - b * b;
+                float trace = a + d;                        //追踪
+                float traceOver2 = 0.5 * trace;
+                float term2 = sqrt(max(0.1f, traceOver2 * traceOver2 - D));
+                float eigenValue1 = traceOver2 + term2;     //特征值L1
+                float eigenValue2 = traceOver2 - term2;     //特征值L1 //max( traceOver2 - term2, 0.25 );  
+
+                if (pointCloudModeEnabled == 1) {// each splat is rendered as a filled circle
+                    eigenValue1 = eigenValue2 = 0.2;
+                }
+
+                if (eigenValue2 <= 0.0  ) return;
+  
+                //add-----------
+                float splatScale_ = splatScale;
+                
+                #if defined adaptive_size 
+                     //splatScale_ /= getPointSizeAttenuation();
+                       float level = float(nodeLevelVNS.x);
+                     float maxLevel = 5.;
+                     float ratio1 = pow( ${ Potree.browser.urlHasValue('ratio1',true)  || 1.4}, /* max(0., */maxLevel - level/* ) */);
+                     splatScale_ *= ratio1;
+                     //vColor.a /= ratio1;
+                #endif  
+                
+                ////-----------
+ 
+                vec2 eigenVector1 = normalize(vec2(b, eigenValue1 - a));
+                // since the eigen vectors are orthogonal, we derive the second one from the first 由于本征向量是正交的,因此我们从第一个导出第二个
+                vec2 eigenVector2 = vec2(eigenVector1.y, -eigenVector1.x);
+
+                // We use sqrt(8) standard deviations偏差 instead of 3 to eliminate消除 more of the splat with a very low opacity.
+                vec2 basisVector1 = eigenVector1 * splatScale_ * min(sqrt8 * sqrt(eigenValue1), ${parseInt(maxScreenSpaceSplatSize)}.0);
+                vec2 basisVector2 = eigenVector2 * splatScale_ * min(sqrt8 * sqrt(eigenValue2), ${parseInt(maxScreenSpaceSplatSize)}.0);
+
+ 
+                vec2 ndcOffset = vec2(vPosition.x * basisVector1 + vPosition.y * basisVector2) / viewport * 2.0 * inverseFocalAdjustment;
+
+                vec4 quadPos = vec4(ndcCenter.xy + ndcOffset,   ndcCenter.z  , 1.0);
+                gl_Position = quadPos;
+
+                // Scale the position data we send to the fragment shader
+                vPosition *= sqrt8;
+            }
+        `
+        
+          
+        
+        let fs = `
+            precision highp float;
+            #include <common>
+   
+            uniform vec3 debugColor;
+
+            varying vec4 vColor;
+            varying vec2 vUv;
+
+            varying vec2 vPosition;
+
+            void main () {
+                 
+                float A = dot(vPosition, vPosition);
+                if (A > 8.0) discard;            //position的范围半径为1。指一个rectangle面中的范围。椭圆外的完全透明
+                vec3 color = vColor.rgb;
+                
+                
+                float opacity = exp(-0.5 * A) * vColor.a; 
+                 
+                gl_FragColor = vec4(color.rgb /* * opacity */, opacity);   
+                
+               
+            }
+        `; 
+        const uniforms = { 
+            'orthographicMode': {
+                'type': 'i',
+                'value': 0
+            },
+            
+            'focal': {
+                'type': 'v2',
+                'value': new THREE.Vector2()
+            },
+            'orthoZoom': {
+                'type': 'f',
+                'value': 1.0
+            },
+            'inverseFocalAdjustment': {
+                'type': 'f',
+                'value': 1.0
+            },
+            'viewport': {
+                'type': 'v2',
+                'value': new THREE.Vector2()
+            },
+          
+            'splatScale': {
+                'type': 'f',
+                'value': 1//this.adaptiveSize ? 32 : 1
+            },
+            'pointCloudModeEnabled': {
+                'type': 'i',
+                'value': 0 
+            },
+            uOctreeSize:{
+                'type': 'f',
+                value:0
+            },
+            splatCount:{type:'i',value:0} 
+            
+        };
+        
+        const material = new THREE.ShaderMaterial({
+            uniforms: uniforms,
+            vertexShader: vs,
+            fragmentShader: fs,
+            transparent: true,
+            alphaTest: 1.0,
+            blending:THREE.NormalBlending, 
+            
+            depthTest: false,
+            depthWrite: true,
+            side: THREE.DoubleSide
+        });
+
+        return material;
+    
+    }
+    
+    update(){
+        this.updateMesh();
+    }
+     
+  
+    
+    
+    
+    
+    load(){//直接装载数据 
+        
+        const handleClick_debug_npz = async () => {
+            const file = Potree.resourcePath + '/npy-3dgs/' + this.fileName + '.npy'
+            await JSZipUtils.getBinaryContent(file, async (err, data) => {
+                if (err) {
+                    throw err
+                    // or handle the error
+                }
+                const jsZip = new JSZip()
+                const npzFiles = await jsZip.loadAsync(data)
+                for (const [npy_filename,npy_data] of Object.entries(npzFiles.files)) {
+                    if (!npy_filename.endsWith('.npy')) {
+                        console.error('error .npy')
+                        return
+                    }
+
+                    const npy_array_buffer = await npzFiles.files[npy_filename].async("arraybuffer")
+                    console.log({
+                        npy_filename,
+                        npy_data,
+                        npy_array_buffer: npy_array_buffer
+                    })
+
+                    const _npyjs_ = new npyjs()
+                    console.log(await _npyjs_.parse(npy_array_buffer))
+                }
+            }
+            )
+        }
+                
+        handleClick_debug_npz()        
+                
+        /* this.datas = {}
+        this.sortWorkerDatas.toSort */
+    }
+    
+    
+    
+    
+    
+    
+    
+    gatherSceneNodesForSort = function() {
+     
+        //将要渲染的点的index写入sortWorkerIndexesToSort的buffer 
+        return function(gatherAllNodes = false) {
+          
+            let time0 = performance.now()   
+
+            const CENTERSInt_BYTES_PER_ENTRY = Potree.defines.gs3d.BytesPerInt * 4
+            const CENTERSFloat_BYTES_PER_ENTRY = Potree.defines.gs3d.BytesPerFloat * 3
+            const COLORS_BYTES_PER_ENTRY =  1*4; //4 byte
+            const COVS_BYTES_PER_ENTRY = Potree.defines.gs3d.BytesPerFloat * 6;
+            const NODES_BYTES_PER_ENTRY = 4 * 2 
+            
+            let currentByteOffset = {
+                centers:0, centersInt:0,  colors:0,  covs:0,  nodes:0
+            }
+            
+            
+     
+            
+            let {centersFloat, centersInt, colors, covs, nodes} = this.sortWorkerDatas.toSort
+            let shareBuffer = centersFloat.buffer //大伙buffer都是同一个
+   
+            this.currentNodes = Potree.visibleNodes.slice()
+            
+            let i=0, nodeCount = Potree.visibleNodes.length , splatCount = 0;  
+            
+           
+            
+            
+            while(splatCount < Potree.config.maxSplatCount && i < nodeCount){
+                
+                
+                let numPoints =  Potree.visibleNodes[i].geometryNode.numPoints
+                
+                if(numPoints + splatCount > Potree.config.maxSplatCount)break;
+                splatCount += numPoints
+                 
+                
+                const c0 = numPoints * 3, c1 = numPoints * 4, c2 = numPoints * 6, c3 = numPoints * 2  
+                
+                
+                let geo = Potree.visibleNodes[i].geometryNode.geometry
+                let destView = this.useSharedMemory ? new Float32Array(shareBuffer, centersFloat.byteOffset + currentByteOffset.centers , c0)
+                               :  new Float32Array(centersFloat.buffer, currentByteOffset.centers , c0); 
+                    destView.set(geo.attributes.centersFloat.array/* .subarray(0,c0) */); 
+                     
+                if(this.integerBasedSort){
+                    destView = this.useSharedMemory ? new Int32Array(shareBuffer, centersInt.byteOffset + currentByteOffset.centersInt , c1) 
+                                : new Int32Array(centersInt.buffer, currentByteOffset.centersInt , c1);
+                    destView.set(geo.attributes.centersInt.array/* .subarray(0,c1) */ );  
+                }
+            
+                destView = this.useSharedMemory ? new Uint8Array(shareBuffer, colors.byteOffset + currentByteOffset.colors, c1) 
+                            : new Uint8Array(colors.buffer, currentByteOffset.colors, c1);
+                destView.set(geo.attributes.rgba.array/* .subarray(0,c1) */  );
+                
+                destView = this.useSharedMemory ? new Float32Array(shareBuffer, covs.byteOffset + currentByteOffset.covs, c2) 
+                            : new Float32Array(covs.buffer, currentByteOffset.covs, c2);
+                destView.set(geo.attributes.covs.array/* .subarray(0,c2) */ );
+                
+                if(this.adaptiveSize){//效果不好,暂只支持useSharedMemory时开启
+                    destView = new Int32Array(shareBuffer, nodes.byteOffset + currentByteOffset.nodes, c3);
+                    let vnStart = this.visibilityTextureData.offsets.get(Potree.visibleNodes[i]);
+                    let level = Potree.visibleNodes[i].getLevel()
+                    for (let n = 0; n < numPoints ; n += 1) {
+                        destView[n*2] = level;
+                        destView[n*2 + 1] = vnStart;
+                    }
+                }
+                
+                currentByteOffset.centersInt += numPoints * CENTERSInt_BYTES_PER_ENTRY
+                currentByteOffset.centers += numPoints * CENTERSFloat_BYTES_PER_ENTRY
+                currentByteOffset.colors += numPoints * COLORS_BYTES_PER_ENTRY
+                currentByteOffset.covs += numPoints * COVS_BYTES_PER_ENTRY 
+                this.adaptiveSize && (currentByteOffset.nodes += numPoints * NODES_BYTES_PER_ENTRY)
+                i++                 
+            }
+                 
+            
+                 
+            this.sortWorker.postMessage({'dataInited': true  })
+            return {
+                'splatRenderCount': splatCount 
+            };
+        };
+
+    }();
+    
+    updateSort = function() {
+
+        const mvpMatrix = new THREE.Matrix4();
+        const cameraPositionArray = [];
+        const lastSortViewDir = new THREE.Vector3(0, 0, -1);
+        const sortViewDir = new THREE.Vector3(0, 0, -1);
+        const lastSortViewPos = new THREE.Vector3();
+        const sortViewOffset = new THREE.Vector3();
+        const queuedSorts = [];
+        let lastTime = 0
+        const partialSorts = [
+            {
+                'angleThreshold': 0.55,
+                'sortFractions': [0.125, 0.33333, 0.75]
+            },
+            {
+                'angleThreshold': 0.65,
+                'sortFractions': [0.33333, 0.66667]
+            },
+            {
+                'angleThreshold': 0.8,
+                'sortFractions': [0.5]
+            }
+        ];
+
+        return async function(force = false) {
+          //  Potree.visibleNodes || console.log('updateSort')
+          //  this.sortWorkerDatas || console.log('updateSort')
+            if (this.loadedSplatCount == 0 || this.sortRunning || this.unableSort || !this.sortWorkerDatas ||  !this.visible) return;
+            let camera = viewer.mainViewport.camera
+ 
+            let timeNow = Date.now() 
+            let angleDiff = 0;
+            let positionDiff = 0;
+            let needsRefreshForRotation = false;
+            let needsRefreshForPosition = false;
+            
+            if( Potree.math.closeTo(Potree.visibleNodes.length, this.currentNodes.length, 10) && (timeNow - lastTime < 1000)){//显示的nodes没太大变化的话,可能先不sort
+                sortViewDir.set(0, 0, -1).applyQuaternion(camera.quaternion);
+                angleDiff = sortViewDir.dot(lastSortViewDir);
+                positionDiff = sortViewOffset.copy(camera.position).sub(lastSortViewPos).length();
+
+                if (!force) {
+                    if (!this.sortNeededForSceneChange && !this.dynamicMode && queuedSorts.length === 0) {
+                        if (angleDiff <= 0.99) needsRefreshForRotation = true;
+                        if (positionDiff >= 1.0) needsRefreshForPosition = true;
+                        if (!needsRefreshForRotation && !needsRefreshForPosition) return setTimeout(this.updateSort.bind(this), 600)//console.log('no need refresh', Potree.visibleNodes.length, this.currentNodes.length);
+                    }
+                }   
+            }
+            
+            this.sortRunning = true;
+            const { splatRenderCount } = this.gatherSceneNodesForSort();
+            this.splatRenderCount = splatRenderCount;
+
+            
+
+            mvpMatrix.copy(camera.matrixWorld).invert();
+            mvpMatrix.premultiply(camera.projectionMatrix);
+            mvpMatrix.multiply(this.matrixWorld );
+
+            /* if (this.gpuAcceleratedSort && (queuedSorts.length <= 1 || queuedSorts.length % 2 === 0)) {
+                await this.splatMesh.computeDistancesOnGPU(mvpMatrix, this.sortWorkerPrecomputedDistances);
+            } */
+
+
+            if (this.dynamicMode ) {
+                queuedSorts.push(this.splatRenderCount);
+            } else {
+                if (queuedSorts.length === 0) {
+                    for (let partialSort of partialSorts) {
+                        if (angleDiff < partialSort.angleThreshold) {
+                            for (let sortFraction of partialSort.sortFractions) {
+                                queuedSorts.push(Math.floor(this.splatRenderCount * sortFraction));
+                            }
+                            break;
+                        }
+                    }
+                    queuedSorts.push(this.splatRenderCount);
+                }
+            }
+            if(queuedSorts.length>1){
+                //console.log(queuedSorts)
+                queuedSorts.length = 0
+            }
+            //let sortCount = Math.min(queuedSorts.shift(), this.splatRenderCount); 
+            //console.log('updateSort')
+            
+            let sortCount = this.splatRenderCount
+             
+            cameraPositionArray[0] = camera.position.x;
+            cameraPositionArray[1] = camera.position.y;
+            cameraPositionArray[2] = camera.position.z;
+
+            const sortMessage = {
+                sort : true,
+                'modelViewProj': mvpMatrix.elements,
+                'cameraPosition': cameraPositionArray,
+                'splatRenderCount': this.splatRenderCount,
+                'splatSortCount': sortCount,
+                'usePrecomputedDistances': this.gpuAcceleratedSort //也就是不用worker算而用gpu,见 computeDistancesOnGPU
+            };
+            
+            if(!this.useSharedMemory){ //通过 Transferable 对象转移所有权:无拷贝.   将内存控制权从主线程转移到 Worker,主线程中原 TypedArray 会变为不可用(长度变为 0)。
+                //把所有盆都交给worker,装满数据再回来 -,-
+                sortMessage.toSort = {
+                    centersFloat : this.sortWorkerDatas.toSort.centersFloat.buffer,  // worker.postMessage({ buffer: array.buffer }, [array.buffer]);
+                    colors : this.sortWorkerDatas.toSort.colors.buffer,   
+                    covs : this.sortWorkerDatas.toSort.covs.buffer
+                }
+               
+                this.integerBasedSort && (sortMessage.toSort.centersInt = this.sortWorkerDatas.toSort.centersInt.buffer)
+                
+                sortMessage.sorted = {
+                    centers : this.sortWorkerDatas.sorted[0].centers.buffer,
+                    colors : this.sortWorkerDatas.sorted[0].colors.buffer,
+                    covs1 : this.sortWorkerDatas.sorted[0].covs1.buffer,
+                    covs2 : this.sortWorkerDatas.sorted[0].covs2.buffer
+                    
+                }
+                
+            }
+            //如果要直接swapBuffer,内存占用太多还是算了吧
+              
+            this.sortPromise = new Promise((resolve) => {
+                this.sortPromiseResolver = resolve;
+            });
+            
+            if(this.useSharedMemory){
+                this.sortWorker.postMessage(sortMessage)
+            }else{
+                let TransferArr = [
+                    this.sortWorkerDatas.toSort.centersFloat.buffer,
+                    this.sortWorkerDatas.toSort.colors.buffer,
+                    this.sortWorkerDatas.toSort.covs.buffer,
+                    this.sortWorkerDatas.sorted[0].centers.buffer,
+                    this.sortWorkerDatas.sorted[0].colors.buffer,
+                    this.sortWorkerDatas.sorted[0].covs1.buffer,
+                    this.sortWorkerDatas.sorted[0].covs2.buffer, 
+                ]
+                TransferArr && TransferArr.push(this.sortWorkerDatas.toSort.centersInt.buffer)
+                this.sortWorker.postMessage( sortMessage, TransferArr);
+            }
+
+            //if (queuedSorts.length === 0) {
+                lastSortViewPos.copy( camera.position);
+                lastSortViewDir.copy(sortViewDir);
+            //} 
+
+            this.sortNeededForSceneChange = false;
+            lastTime = timeNow
+        };
+
+    }();
+ 
+
+    updateRenderIndexes(globalCenters, globalColors,globalCovs1,globalCovs2, nodes, renderSplatCount) { 
+     
+        const geometry = this.geometry;
+        let start = performance.now()
+        if(!this.splatEnableSwapOut || this.splatAttrSameMemory){
+            geometry.attributes.center.set(globalCenters); 
+            geometry.attributes.center.needsUpdate = true;
+            geometry.attributes.color.set(globalColors); 
+            geometry.attributes.color.needsUpdate = true;
+            geometry.attributes.covRow1.set(globalCovs1); 
+            geometry.attributes.covRow1.needsUpdate = true;
+            geometry.attributes.covRow2.set(globalCovs2); 
+            geometry.attributes.covRow2.needsUpdate = true; 
+            
+            if(this.adaptiveSize){
+                geometry.attributes.nodeLevelVNS.set(nodes); 
+                geometry.attributes.nodeLevelVNS.needsUpdate = true;
+            }
+            
+        }else{ //直接赋值速度快,需要双buffer否则会被擦写导致黑影。 splatEnableSwapOut要在sortworker init时就确定
+            geometry.attributes.center.array = globalCenters ;  
+            geometry.attributes.center.needsUpdate = true;     //不知为啥2025来再看并不会更流畅,可能这里时间减少了但gpu换buffer了需要时间吧? 另外流畅度更多取决于非sort时的,手机换buffer吃不消。
+            geometry.attributes.color.array = globalColors ; 
+            geometry.attributes.color.needsUpdate = true;
+            geometry.attributes.covRow1.array = globalCovs1 ; 
+            geometry.attributes.covRow1.needsUpdate = true;
+            geometry.attributes.covRow2.array = globalCovs2 ; 
+            geometry.attributes.covRow2.needsUpdate = true;
+        }
+        if (renderSplatCount > 0 )  geometry.instanceCount =  renderSplatCount;
+        
+        //console.log('updateRenderIndexes time:', performance.now() - start)
+        //this.material.uniforms.splatCount.value = renderSplatCount
+       
+        
+
+    }
+    
+     
+        
+
+
+    updateMaterial(){
+        if(!this.pointclouds.length)return
+        let camera = viewer.mainViewport.camera
+        //this.material.uniforms.viewport.value.copy(viewer.mainViewport.resolution2);
+        let viewport = viewer.mainViewport.resolution2
+        
+        const focalMultiplier = camera.isOrthographicCamera ? (1.0 / window.devicePixelRatio) : 1.0;
+        const focalAdjustment = this.focalAdjustment * focalMultiplier;
+        const focalLengthX = camera.projectionMatrix.elements[0] * 0.5 * viewport.x; 
+        const focalLengthY = camera.projectionMatrix.elements[5] * 0.5 * viewport.y;
+                             
+ 
+        const inverseFocalAdjustment = 1.0 / focalAdjustment;
+
+        
+        
+        this.material.uniforms.viewport.value.copy(viewport);
+        this.material.uniforms.focal.value.set(focalLengthX * focalAdjustment, focalLengthY * focalAdjustment);
+        this.material.uniforms.orthographicMode.value = camera.isOrthographicCamera ? 1 : 0;
+        this.material.uniforms.orthoZoom.value = camera.zoom || 1.0;
+        this.material.uniforms.inverseFocalAdjustment.value = inverseFocalAdjustment;
+        if (this.dynamicMode) {
+            for (let i = 0; i < this.scenes.length; i++) {
+                this.material.uniforms.transforms.value[i].copy(this.getScene(i).transform);
+            }
+        }
+        
+        this.material.uniforms.uOctreeSize.value = this.pointclouds[0].pcoGeometry.boundingBox.getSize(new THREE.Vector3()).x;
+        this.material.uniformsNeedUpdate = true;
+             
+        
+    }
+    
+   
+    
+}

+ 45 - 44
src/custom/start.js

@@ -286,55 +286,56 @@ export function start(dom, mapDom, number, options ){ //t-Zvd3w0m
             
             
             
-            
-            if(browser.urlHasValue('path')){  //https://192.168.0.59:1234/examples/4dkk.html?m=SG-r0dwv5D8vY8&formal&showAxis&path
-            let fileName =  'pathPointsNew'   
-            setTimeout(()=>{
-                Potree.loadFile(Potree.resourcePath+'/' +fileName+'.json', null, (data)=>{
+            window.addExtPath = function(data){
                 
-                    console.log(data)
-                    window.pathPoints = data
-                    let points = [] 
-                     
-                    data.forEach((e,i)=>{ 
-                        if(data[i-1] && data[i-1][0] == e[0] && data[i-1][1] == e[1] && data[i-1][2] == e[2]   )return 
-                        //e[2]+=0.2 
-                        let pos = /* Potree.math.convertVector.YupToZup(  */new THREE.Vector3().fromArray(e)/*  ) */
-                        //pos.x *= -1
-                        //pos.y *= -1
-                        let x = pos.x , y = pos.y, z=pos.z
-                        //pos.x = -x, pos.y = -y
-                        
-                        //pos.z *= -1
-                        pos.applyMatrix4(viewer.scene.pointclouds[0].transformMatrix)  //是4dkk场景里坐标 
-                        points.push(pos)
-                    }) 
-                        
-                     /* let fakeMeasure ={
-                        "measureType": "Hor MulDistance",  "unit": "metric","color": "#00c8af",
-                        points  , "datasetId": null,  "title": "test path", showDistances:false,
-                        "bus": {
-                            "all": {}
-                        },  
-                    }
-                    viewer.measuringTool.createMeasureFromData(fakeMeasure)
-                    viewer.scene.measurements[0].edgeLabels.forEach(e=>Potree.Utils.updateVisible(e,'f',false,10))
-                    */
-                    let fakeMeasure ={
-                        type : 'Path', "unit": "metric", points, width:0.1
-                    }
-                    let path = viewer.measuringTool.createMeasureFromData(fakeMeasure)
-                    for(let i=0;i<points.length;i++){
-                        path.setMarkerTitle(i,'')
-                    }
+                console.log(data)
+                window.pathPoints = data
+                let points = [] 
+                 
+                data.forEach((e,i)=>{ 
+                    if(data[i-1] && data[i-1][0] == e[0] && data[i-1][1] == e[1] && data[i-1][2] == e[2]   )return 
+                    //e[2]+=0.2 
+                    let pos = /* Potree.math.convertVector.YupToZup(  */new THREE.Vector3().fromArray(e)/*  ) */
+                 
+                    let x = pos.x , y = pos.y, z=pos.z
                     
-                })
-                
-            },1000)
+                    pos.applyMatrix4(viewer.scene.pointclouds[0].transformMatrix)  //是4dkk场景里坐标 
+                    points.push(pos)
+                }) 
+                    
+                 /* let fakeMeasure ={
+                    "measureType": "Hor MulDistance",  "unit": "metric","color": "#00c8af",
+                    points  , "datasetId": null,  "title": "test path", showDistances:false,
+                    "bus": {
+                        "all": {}
+                    },  
+                }
+                viewer.measuringTool.createMeasureFromData(fakeMeasure)
+                viewer.scene.measurements[0].edgeLabels.forEach(e=>Potree.Utils.updateVisible(e,'f',false,10))
+                */
+                let fakeMeasure ={
+                    type : 'Path', "unit": "metric", points, width:0.1
+                }
+                let path = viewer.measuringTool.createMeasureFromData(fakeMeasure)
+                for(let i=0;i<points.length;i++){
+                    path.setMarkerTitle(i,'')
+                }
+                        
+            }
             
-        }
             
             
+            if(browser.urlHasValue('path')){  //https://192.168.0.59:1234/examples/4dkk.html?m=SG-r0dwv5D8vY8&formal&showAxis&path
+                let fileName =  'pathPointsNew'   
+                setTimeout(()=>{
+                    Potree.loadFile(Potree.resourcePath+'/' +fileName+'.json', null, (data)=>{
+                        addExtPath(data) 
+                    }) 
+                },1000)
+                
+            }
+                
+            
             
             
             

+ 4 - 2
src/loader/BinaryLoader.js

@@ -122,8 +122,10 @@ export class BinaryLoader{
 					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer),maxRest);
 					geometry.setAttribute('sh', bufferAttribute);
                   
-				} 
-                
+				}else if(property === "scales") {//add test
+					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 3  );
+					geometry.setAttribute('scales', bufferAttribute);
+                }
                  
                 
                 /* else if(property != 'GS3D'){//改

+ 53 - 4
src/workers/BinaryDecoderWorker.js

@@ -16,7 +16,7 @@ const typedArrayMapping = {
 	"double": Float64Array,
 };
 
-const gs3dProplist = [
+const gs3dProplist1 = [
 'f_dc_0',  //color
 'f_dc_1',
 'f_dc_2',
@@ -75,6 +75,16 @@ const gs3dProplist = [
 'rot_3'
 ]
 
+const gs3dProplist = [
+    //'x',  'y',  'z',
+    //'nx', 'ny', 'nz',
+    'f_dc_0', 'f_dc_1', 'f_dc_2',
+    'f_rest_0',  'f_rest_1',  'f_rest_2', 'f_rest_3', 'f_rest_4', 'f_rest_5', 'f_rest_6', 'f_rest_7', 'f_rest_8', 
+    'opacity',  'scale_0', 'scale_1', 'scale_2', 
+    'rot_0', 'rot_1', 'rot_2', 'rot_3'  
+]
+
+
 let clamp = function ( value, min, max ) {
 
     return Math.max( min, Math.min( max, value ) );
@@ -131,6 +141,40 @@ class Quaternion {
 
 	}
     
+    length() {
+
+		return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+	}
+    
+    normalize() {
+
+        let l = this.length();
+
+        if ( l === 0 ) {
+
+            this._x = 0;
+            this._y = 0;
+            this._z = 0;
+            this._w = 1;
+
+        } else {
+
+            l = 1 / l;
+
+            this._x = this._x * l;
+            this._y = this._y * l;
+            this._z = this._z * l;
+            this._w = this._w * l;
+
+        }
+
+        //this._onChangeCallback();
+
+        return this;
+
+    }
+    
 }
 const _zero = new Vector3( 0, 0, 0 );
 const _one = new Vector3( 1, 1, 1 );
@@ -703,7 +747,9 @@ onmessage = function (event) {
             
             //compute cov:
             let buff3 = new ArrayBuffer(numPoints * 24); 
+            //let buff3_ = new ArrayBuffer(numPoints * 12); 
             let covs = new Float32Array(buff3);
+            //let scales = new Float32Array(buff3_);
             const offset_scale = gs3dProplist.indexOf('scale_0') //第49个数开始是      
             const offset_rot = gs3dProplist.indexOf('rot_0')    
             let scale = new  Vector3()
@@ -711,7 +757,10 @@ onmessage = function (event) {
             
             let getScale = (index)=>{ 
                 let get = (offset)=>{
-                    return Math.exp(f32[index * pointAttribute.numElements + offset + offset_scale]);
+                    let s1 = f32[index * pointAttribute.numElements + offset + offset_scale]
+                    let s = Math.exp(s1);
+                    //scales[index*3+offset] = s
+                    return s   //0.2*Math.pow(1.1, f32[index * pointAttribute.numElements + offset + offset_scale])    
                 } 
                 scale.set(get(0), get(1), get(2)) 
             }
@@ -721,7 +770,7 @@ onmessage = function (event) {
                 } 
                 //quaternion.set( get(0), get(1), get(2), get(3)) 
                 quaternion.set(get(1), get(2), get(3), get(0)) //w放到最后 另外如果compressionLevel不是0的话还要再加一步,见this.fbf(sectionFloatArray[rotationBase + 1]
-                //quaternion.normalize();
+                quaternion.normalize(); //有时候数据没归一化,结果splat看起来很大
                 
             }
             
@@ -732,7 +781,7 @@ onmessage = function (event) {
             }
             
             attributeBuffers['covs'] = { buffer: buff3, attribute: pointAttribute };
-            
+            //attributeBuffers['scales'] = { buffer: buff3_, attribute: pointAttribute };
             //sh
             if(0){
                 let sh = Potree.settings.splatSH

文件差異過大導致無法顯示
+ 4137 - 3936
yarn.lock