Pārlūkot izejas kodu

内存管理 热点更新

xzw 8 mēneši atpakaļ
vecāks
revīzija
dcd1f18db3
38 mainītis faili ar 1128 papildinājumiem un 390 dzēšanām
  1. 1 1
      examples/4dkk.html
  2. 1 1
      libs/Cesium/Cesium.js
  3. 9 15
      libs/stats.js/stats.js
  4. 82 26
      libs/three.js/3dtiles/three-loader-3dtiles.esm.js
  5. 8 1
      libs/three.js/loaders/GLTFLoader.js
  6. 1 1
      src/Features.js
  7. 1 1
      src/Potree.js
  8. 3 0
      src/custom/materials/DepthBasicMaterial.js
  9. 5 5
      src/custom/materials/ModelTextureMaterial.js
  10. 60 69
      src/custom/mergeStartTest.js
  11. 2 1
      src/custom/modules/datasetAlignment/Alignment.js
  12. 346 32
      src/custom/modules/mergeModel/MergeEditor.js
  13. 57 38
      src/custom/modules/panoEdit/panoEditor.js
  14. 7 4
      src/custom/modules/panos/DepthImageSampler.js
  15. 24 19
      src/custom/modules/panos/Images360.js
  16. 1 2
      src/custom/modules/panos/Panorama.js
  17. 1 1
      src/custom/modules/panos/tile/QualityManager.js
  18. 10 14
      src/custom/modules/panos/tile/TileDownloader.js
  19. 1 1
      src/custom/objects/Sprite.js
  20. 200 52
      src/custom/objects/Tag.js
  21. 1 1
      src/custom/objects/tool/Measure.js
  22. 1 1
      src/custom/objects/tool/TagTool.js
  23. 28 6
      src/custom/potree.shim.js
  24. 9 7
      src/custom/settings.js
  25. 5 1
      src/custom/start.js
  26. 3 3
      src/custom/three.shim.js
  27. 2 2
      src/custom/utils/Common.js
  28. 1 1
      src/custom/utils/CursorDeal.js
  29. 2 2
      src/custom/utils/DrawUtil.js
  30. 2 2
      src/custom/utils/browser.js
  31. 5 3
      src/custom/utils/transitions.js
  32. 214 48
      src/custom/viewer/ViewerNew.js
  33. 4 6
      src/custom/viewer/viewerBase.js
  34. 1 1
      src/materials/ExtendEyeDomeLightingMaterial.js
  35. 7 2
      src/materials/shaders/depthBasic.fs
  36. 8 5
      src/navigation/InputHandlerNew.js
  37. 13 13
      src/viewer/EDLRendererNew.js
  38. 2 2
      src/viewer/ExtendView.js

+ 1 - 1
examples/4dkk.html

@@ -30,7 +30,7 @@
 	<script src="../../build/potree/potree.js"></script>
 	<script src="../../libs/plasio/js/laslaz.js"></script>
 	
-	<script src="../../libs/stats.js/stats.js"></script>
+	<!-- <script src="../../libs/stats.js/stats.js"></script> -->
 	<div class="potree_container" style="position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; ">
 		<div id="potree_render_area" style="background-image: url('../../build/potree/resources/images/background.jpg');">
             

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
libs/Cesium/Cesium.js


+ 9 - 15
libs/stats.js/stats.js

@@ -1,19 +1,14 @@
-(function (global, factory) {
-	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
-	typeof define === 'function' && define.amd ? define(factory) :
-	(global.Stats = factory());
-}(this, (function () { 'use strict';
-
-/**
- * @author mrdoob / http://mrdoob.com/
- */
-
+ 
+ 
+ 
+ 
+ 
 var Stats = function () {
 
 	var mode = 0;
 
 	var container = document.createElement( 'div' );
-	container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
+	container.style.cssText = 'position:fixed;top:0;left:60px;cursor:pointer;opacity:0.9;z-index:10000';
 	container.addEventListener( 'click', function ( event ) {
 
 		event.preventDefault();
@@ -173,7 +168,6 @@ Stats.Panel = function ( name, fg, bg ) {
 	};
 
 };
-
-return Stats;
-
-})));
+ 
+ 
+ export default Stats

+ 82 - 26
libs/three.js/3dtiles/three-loader-3dtiles.esm.js

@@ -43,19 +43,61 @@ function getGpuMemoryUsage(win = window){//总的
     
     viewer.setAllTilesets(model=>{
         let tileset3D = model.runtime.getTileset()
-        c += tileset3D.gpuMemoryUsageInBytes
+        c += tileset3D.gpuMemoryUsageInBytes        //不会因为tileset或tile隐藏而降低,除非释放了资源
     })
      
     if(win.parent != win){//还有父级页面。 暂时只有子级需要考虑父级,假设父级在前台时子级已经销毁
         c += getGpuMemoryUsage(win.parent)
     } 
     
-    viewer.tiles3dMemoryUsage = c
+    viewer.tiles3dMemoryUsage = c       
     
     return  c
 }
 
 
+function updateQuality(){
+    const maximumMemory = Potree.settings.tiles3DMaxMemory * 1024 * 1024;
+    let memory = getGpuMemoryUsage()
+     
+   
+    let r = memory / maximumMemory
+    viewer.setAllTilesets(model=>{
+        let tileset = model.runtime.getTileset()
+        let e = tileset.options.maximumScreenSpaceError * THREE.Math.clamp(r, 0.9, 1.1)
+        e = THREE.Math.clamp(e, tileset.options.initialMaxSSE, 1000)
+        tileset.options.maximumScreenSpaceError = e
+    })
+    
+    
+    
+}
+
+
+
+function changeModelPointCount(tile, type){
+    let  posCount , texArea   
+    if(type == 'add'){
+        let o = viewer.getObjectPointCount(tile.tileContent) 
+        posCount = o.posCount,  texArea = o.texArea 
+        tile.posCount = posCount,   tile.texArea = texArea  
+    }else{
+        posCount = -tile.posCount,   texArea = -tile.texArea 
+    }
+ 
+    viewer.memoryModelCountInfo.tilePosCount += posCount 
+    viewer.memoryModelCountInfo.tileTexArea += texArea  
+} 
+
+
+
+
+
+
+
+
+
+
 function __awaiter(thisArg, _arguments, P, generator) {
     function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
     return new (P || (P = Promise))(function (resolve, reject) {
@@ -7543,12 +7585,7 @@ class TilesetCache {
     while (node !== sentinel && ( getGpuMemoryUsage() > maximumMemoryUsageInBytes || trimTiles)) {
       const tile = node.item;
       node = node.next;
-      this.unloadTile(tileset, tile, unloadCallback); 
-        
-        if(viewer.visiVertexCount > 1500000){ //add:  针对部分特别精细的很卡如  https://4dkk.4dage.com/scene_view_data/SG-t-WReTBN204dQ/images/3dtiles/tileset.json
-            tileset.options.maximumScreenSpaceError *= 1.1
-            tileset.options.maximumScreenSpaceError = Math.min(1000, tileset.options.maximumScreenSpaceError) 
-        }
+      this.unloadTile(tileset, tile, unloadCallback);  
     } 
     
   }
@@ -9256,7 +9293,8 @@ class TileHeader {
      
     _defineProperty(this, "rtcCenterState", rtcCenterGlobal);//add  初始时的状态,当mesh加载完检查下
     
-     
+    _defineProperty(this, "posCount", 0);//add 
+    _defineProperty(this, "texArea", 0);//add 
     
     this.header = header;
     this.tileset = tileset;
@@ -9315,6 +9353,7 @@ class TileHeader {
     Object.seal(this);
   }
 
+ 
   destroy() {
     this.header = null;
   }
@@ -9381,6 +9420,14 @@ class TileHeader {
         throw new Error('Unsupported tileset type');
     }
   }
+  
+  get byteLength(){//add
+    let len = this.content?.byteLength || this.content?.gltf?.buffers[0].byteLength || 0  // 后者为glTF型的tile
+     
+    if(len == 0 && this.tileContent)console.log('tile.byteLength == 0???') //url为json的话确实为0, 模型被删除后mesh没了也为0
+      
+    return len
+  }
 
   _getPriority() {
     const traverser = this.tileset._traverser;
@@ -10123,7 +10170,7 @@ class Tileset3D extends EventDispatcher{//xzw add EventDispatcher
   }
 
   destroy() {
-    this._destroy();
+    this._destroy(); 
   }
 
   isLoaded() {
@@ -10496,17 +10543,20 @@ class Tileset3D extends EventDispatcher{//xzw add EventDispatcher
   _updateCacheStats(tile) {
     this.stats.get(TILES_LOADED).incrementCount();
     this.stats.get(TILES_IN_MEMORY).incrementCount();
-    this.gpuMemoryUsageInBytes += tile.content.byteLength || 0;
+    this.gpuMemoryUsageInBytes += tile.byteLength 
     this.stats.get(TILES_GPU_MEMORY).count = this.gpuMemoryUsageInBytes;
+     
   }
 
   _unloadTile(tile) {
-    this.gpuMemoryUsageInBytes -= tile.content && tile.content.byteLength || 0;
+    this.gpuMemoryUsageInBytes -= tile.byteLength //
     this.stats.get(TILES_IN_MEMORY).decrementCount();
     this.stats.get(TILES_UNLOADED).incrementCount();
     this.stats.get(TILES_GPU_MEMORY).count = this.gpuMemoryUsageInBytes;
     this.options.onTileUnload(tile);
     tile.unloadContent();
+    changeModelPointCount(tile, 'sub') 
+    
   }
 
   _destroy() {
@@ -10526,6 +10576,8 @@ class Tileset3D extends EventDispatcher{//xzw add EventDispatcher
       this._destroyTile(tile);
     }
 
+    getGpuMemoryUsage() 
+
     this.root = null;
   }
 
@@ -14047,7 +14099,7 @@ class GLTFScenegraph {
 
     if (this.gltf.buffers && this.gltf.buffers[0]) {
       this.byteLength = this.gltf.buffers[0].byteLength;
-      this.sourceBuffers = [this.gltf.buffers[0]];
+      this.sourceBuffers = [this.gltf.buffers[0]]; 
     }
   }
 
@@ -17573,6 +17625,7 @@ class Loader3DTiles {
             const tileOptions = {
                 maximumMemoryUsage: options.maximumMemoryUsage,
                 maximumScreenSpaceError: options.maximumScreenSpaceError,
+                initialMaxSSE: options.maximumScreenSpaceError,
                 viewDistanceScale: options.viewDistanceScale,
                 skipLevelOfDetail: options.skipLevelOfDetail,
                 updateTransforms: options.updateTransforms,
@@ -17599,6 +17652,9 @@ class Loader3DTiles {
                         tileContent.name = tile.id //add
                         tileContent.tile = tile //add
                         
+                        changeModelPointCount(tile, 'add') 
+                        
+                        
                         root.add(renderMap[tile.id]);
                         if (options.debug) {
                             const box = loadersBoundingBoxToMesh(tile);
@@ -17778,7 +17834,7 @@ class Loader3DTiles {
                             if(!renderMap[tile.id].visible){
                                 if(viewer.visiVertexCount<maxVertexVisi){
                                     renderMap[tile.id].visible = true;
-                                    viewer.visiVertexCount += renderMap[tile.id].vertexCount  //xzw add
+                                    viewer.visiVertexCount += tile.posCount   //renderMap[tile.id].vertexCount  //xzw add
                                     options.debug && (boxMap[tile.id].material.opacity = 1 , boxMap[tile.id].children[0].sprite.material.opacity = 1 )
                                 }else{
                                     //console.log('超出', visiVertexCount)
@@ -17796,7 +17852,7 @@ class Loader3DTiles {
                                 renderMap[tile.id].visible = false;
                                 options.debug && (boxMap[tile.id].material.opacity = 0.1, boxMap[tile.id].children[0].sprite.material.opacity = 0.1)
                                 
-                                viewer.visiVertexCount -= renderMap[tile.id].vertexCount  //xzw add
+                                viewer.visiVertexCount -= tile.posCount //renderMap[tile.id].vertexCount  //xzw add
                             } 
                         }
                     }
@@ -17910,6 +17966,9 @@ class Loader3DTiles {
                         cameraReference = camera;
                         rendererReference = renderer; 
                         timer += dt;
+                         
+                        //updateQuality()
+                        
                         
                         if(!ifForce) ifForce = tileset.nextForceUpdate
                         tileset.nextForceUpdate = false
@@ -17933,12 +17992,9 @@ class Loader3DTiles {
                                 if (options.debug) {
                                    /*  console.log('move', tileset.root.id, boxMap[tileset.root.id].matrix.elements,  boxMap[tileset.root.id].matrixWorld.elements)
                                     boxMap[tileset.root.id].matrix.copy(root.matrixWorld); 
-                                    boxMap[tileset.root.id].matrixWorld.copy(root.matrixWorld);  */
-                                    
-                                    
+                                    boxMap[tileset.root.id].matrixWorld.copy(root.matrixWorld);  */ 
                                     //boxMap[tileset.root.id].applyMatrix4(threeMat);
-                                    tileBoxes.matrix.copy(root.matrixWorld);
-                                    
+                                    tileBoxes.matrix.copy(root.matrixWorld); 
                                     //boxMap[tileset.root.id].matrixWorld.copy(threeMat);
                                     //boxMap[tileset.root.id].applyMatrix4(lastRootTransform);//boxMap[tileset.root.id].applyMatrix4(lastRootTransform);
                                     //boxMap[tileset.root.id].updateWorldMatrix() 
@@ -17958,9 +18014,9 @@ class Loader3DTiles {
                                     tilesetUpdate(tileset, renderMap, renderer, camera);
                                 }
                             }
-                        }/* else{
-                            console.log('11')
-                        } */
+                        } 
+                        
+                        
                     },
                     dispose: function () {
                         disposeFlag = true;
@@ -18025,7 +18081,7 @@ function createGLTFNodes(gltfLoader, tile, unlitMaterial, options, rootTransform
                 tile.contentUrl ? tile.contentUrl.substr(0, tile.contentUrl.lastIndexOf('/') + 1) : '', 
                 (gltf) => {
                     const tileContent = gltf.scenes[0]; 
-                    tileContent.vertexCount = 0   //xzw add
+                    //tileContent.vertexCount = 0   //xzw add
                     tile.tileContent = tileContent //xzw add
                      
                     let needUpdate  
@@ -18097,7 +18153,7 @@ function createGLTFNodes(gltfLoader, tile, unlitMaterial, options, rootTransform
                                 mesh.geometry.computeVertexNormals();
                             } 
                             //xzw add:
-                            tileContent.vertexCount += mesh.geometry.attributes.position.count
+                            //tileContent.vertexCount += mesh.geometry.attributes.position.count
                              
                             
                             
@@ -18253,7 +18309,7 @@ get isVisibleAndInRequestVolume() {
 显示所有tile似乎也不会卡顿. 但好像影响了加载顺序从而变慢了。visiVertexCount过量。 怀疑应该还是文件的bound有问题。
 原本会消失的地方虽然不会消失了但一直是模糊的
 
-
+被隐藏的tiles对应的mesh也是仅隐藏,并未删除
 
 =========
 访问所有tile:

+ 8 - 1
libs/three.js/loaders/GLTFLoader.js

@@ -3346,11 +3346,18 @@ class GLTFParser {
                             tex.minFilter = THREE.LinearFilter
                             tex.generateMipmaps = false   
                         } */
-                        //console.log(tex.image.width, tex.image.height)
+                        if(tex.image.width >= 8192 || tex.image.heigh >= 8192){
+                            console.log(tex.image.width, tex.image.height) 
+                            /* 麒麟chromium会显示不了,变白;奇安信变花;
+                                firefox 变黑乱七八糟  报错 WebGL warning: texImage: Driver ran out of memory during upload.  WebGL warning: generateMipmap: The texture's base level must be complete. 
+                            */
+                        }
+                        
                            
                         resolve(tex) 
                     },null,(e)=>{
                         console.log('error load tex',e)
+                        reject()
                     })  
                     
                 }

+ 1 - 1
src/Features.js

@@ -57,7 +57,7 @@ export const Features = (function () {
 			isSupported: function () {
 				let supported = true;
 
-				supported = supported && gl.getExtension('EXT_frag_depth');
+				supported = supported && (gl.getExtension('EXT_frag_depth') || Potree.settings.isWebgl2); //麒麟系统firefox支持webgl2但这里用webgl1的content得不到EXT_frag_depth
 				supported = supported && gl.getExtension('OES_texture_float');
 				supported = supported && gl.getParameter(gl.MAX_VARYING_VECTORS) >= 8;
 

+ 1 - 1
src/Potree.js

@@ -370,7 +370,7 @@ Clicking the fifth button exports the configured data to the clipboard. You can
 
 
 
-export function setNeighborGui(){
+export function setNeighborGui(){//&neighGui
     if(!Potree.settings.showNeighSetGui)return
     
     

+ 3 - 0
src/custom/materials/DepthBasicMaterial.js

@@ -24,6 +24,9 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
             maxClipFactor :  { type: 'f', 	value: o.maxClipFactor || 1 },  //0-1 
             maxOcclusionFactor :  { type: 'f', 	value: o.maxOcclusionFactor || 1 },  //0-1
             mapScale:  { type: 'f', 	value:  o.mapScale || 1 },  //0-1
+            
+            startClipDis:  { type: 'f', 	value:  o.startClipDis || 0 },  //开始逐渐消失的距离
+            startOcclusDis:  { type: 'f', 	value:  o.startOcclusDis || 0 },  //开始逐渐褪色的距离
 		}  
         
         

+ 5 - 5
src/custom/materials/ModelTextureMaterial.js

@@ -4,8 +4,8 @@ import {Features} from "../../Features.js";
                                       
 
 const prefixVertex ="precision highp float;\nprecision highp int;\n\nuniform mat4 modelMatrix;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat4 viewMatrix;\nuniform mat3 normalMatrix;\nuniform vec3 cameraPosition;\n attribute vec3 position;\n attribute vec3 normal;\n attribute vec2 uv;\n"
-const prefixFragment ="precision highp float;\nprecision highp int;\n\nuniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"
- 
+const prefixFragment ="#extension GL_EXT_frag_depth : enable \n precision highp float;\nprecision highp int;\n\nuniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"
+                        // otherwise error: 'GL_EXT_frag_depth' : extension is disabled
 let shader = {
 	 
 		uniforms: { 
@@ -142,7 +142,7 @@ let shader = {
 
         `,
         fragmentShader: prefixFragment + `
-            #extension GL_EXT_frag_depth : enable        // otherwise error: 'GL_EXT_frag_depth' : extension is disabled
+                    
             
             #define PI 3.141592653 
             
@@ -317,9 +317,9 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
 	constructor( ){ 
     
         let defines = {depthTexUVyLimit: Potree.config.depthTexUVyLimit}
-        if(Potree.browser.maybeLowQilin()){
+        if(Potree.browser.maybeQilin()){
             defines.UnableMixTwoDepth = 1  //该系统在开启硬件加速后,webgl容易出bug。如过渡时黑屏报错,因无法将两个depth叠加。见bug记录
-        } 
+        }  
         let {vs,fs} = Common.changeShaderToWebgl2(shader.vertexShader, shader.fragmentShader, 'RawShaderMaterial')
         
         super({

+ 60 - 69
src/custom/mergeStartTest.js

@@ -15,7 +15,7 @@ import * as GaussianSplats3D from "../../libs/gaussian/gaussian-splats-3d.module
  
 import cameraLight from './utils/cameraLight.js'
 //多元融合模块   
-let objIndex = 1
+ 
 
 var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
 
@@ -88,7 +88,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             viewer.updateModelBound()
             let {boundSize, center} = viewer.bound
            
-            Potree.Log(`中心点: ${math.toPrecision(center.toArray(),2)}, boundSize: ${math.toPrecision(boundSize.toArray(),2)} ` , null, 12)
+            Potree.Log(`中心点: ${Potree.math.toPrecision(center.toArray(),2)}, boundSize: ${Potree.math.toPrecision(boundSize.toArray(),2)} ` , null, 12)
             
             if(!Potree.settings.isOfficial){
                 Potree.loadMapEntity('all') //加载floorplan 
@@ -119,7 +119,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
               
             {//初始位置 
                 var urlFirstView = false
-                var panoId = browser.urlHasValue('pano',true);
+                var panoId = Potree.browser.urlHasValue('pano',true);
                 if(panoId !== ''){
                     var pos
                     var pano = viewer.images360.panos.find(e=>e.id==panoId);
@@ -155,7 +155,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             Alignment.translate(pointcloud, new THREE.Vector3(location[0], location[1], dataset.location[2])) 
             
             pointcloud.updateMatrixWorld() 
-            Potree.Log(`点云${pointcloud.dataset_id}旋转值:${pointcloud.orientationUser}, 位置${math.toPrecision(pointcloud.translateUser.toArray(),3)}, 经纬度 ${locationLonLat}, spacing ${pointcloud.material.spacing}`, null, 17 )
+            Potree.Log(`点云${pointcloud.dataset_id}旋转值:${pointcloud.orientationUser}, 位置${Potree.math.toPrecision(pointcloud.translateUser.toArray(),3)}, 经纬度 ${locationLonLat}, spacing ${pointcloud.material.spacing}`, null, 17 )
              
         }
         
@@ -329,7 +329,12 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
              
          
     let tilesetUrls = [   
-    
+        'https://4dkk.4dage.com/fusion/testb3dm/test001/tileset.json',  //体育中心
+      // `${Potree.resourcePath}/models/3dtiles/SG-CPwbgVaPvcY/tileset.json`,
+      // 'https://4dkk.4dage.com/scene_view_data/SG-CPwbgVaPvcY/images/3dtiles/tileset.json?_=1730963063808',
+         
+        'https://4dkk.4dage.com/fusion/testb3dm/modelId_613/tileset.json',//"952.16MB"  港一
+         
         'https://4dkk.4dage.com/fusion/test/b3dm/testModel/tileset.json',  //靠近后tile缺块,但box看起来没错, 似乎只是文件问题,tile的children少了
         'https://4dkk.4dage.com/scene_view_data/SG-t-Wm2b883yI4z/images/3dtiles/tileset.json',
     
@@ -344,7 +349,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
         //(`scene_view_data/{num}/images/3dtiles/tileset.json?_=${Date.now()}`),
         'https://4dkk.4dage.com/fusion/test/b3dm/tileset.json',  //高层小区  模型无问题
         
-        'https://4dkk.4dage.com/fusion/testb3dm/modelId_613/tileset.json',//"952.16MB"  港一
+      
         'https://4dkk.4dage.com/fusion/testb3dm/modelId_609/tileset.json',//618.37MB  田野 'https://4dkk.4dage.com/fusion/test/b3dmtest001/tileset.json',
         'https://4dkk.4dage.com/fusion/test/model/modelId_614/tileset.json',//172.97MB 国家电网 //'https://4dkk.4dage.com/fusion/test/model/modelId_602/tileset.json',
         'https://4dkk.4dage.com/fusion/test/model/modelId_602/Tile_016_011/tileset.json', //modelId_614的一部分
@@ -352,9 +357,45 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
         //'https://4dkk.4dage.com/fusion/test/model/modelId_570/3dt/3dtiles.json', //only has boundingVolume.sphere  拉远了特别模糊,凑近了模型不太对
         
         
-    ], tileIndex = 0   
-          
-      
+    ], tileIndex = 0
+    
+     let glbUrls = [ 
+        
+        `${Potree.resourcePath}/models/glb/26k.glb`, 
+
+        `${Potree.resourcePath}/models/glb/cloud_glb_239.glb`, 
+        `${Potree.resourcePath}/models/glb/ModernJPHouseSofa44216499.glb`,
+        `${Potree.resourcePath}/models/glb/block-远看有裂缝.glb`, //includes 8192 tex 
+        
+        `${Potree.resourcePath}/models/glb/archmodels95_043.glb`, 
+       `${Potree.resourcePath}/models/glb/ModernJPHouseSofa44105209-3.7M.glb`, 
+        
+      ], glbIndex = 0
+
+
+    let shapeUrls = [
+        Potree.resourcePath+'/models/shapefiles/shape-jiangmen/jiangmen.shp',
+        Potree.resourcePath+'/models/shapefiles/来时路线.shp',
+        Potree.resourcePath+'/models/shapefiles/冲撞路线.shp',
+        Potree.resourcePath+'/models/shapefiles/逃离路线.shp',
+    ], shapeIndex = 0
+     
+    
+    
+    let objUrls = [
+        Potree.resourcePath+'/models/obj/monitor_12M/archmodels95_043',
+        Potree.resourcePath+'/models/obj/28M/GW1H',
+        //Potree.resourcePath+'/models/obj/496M/Model', 
+        Potree.resourcePath+'/models/obj/75M/Tile_+070_+051', 
+        Potree.resourcePath+'/models/obj/74M/Tile_+008_+004', 
+         
+    ], objIndex = 0
+    
+    
+    
+    
+    
+    
     
     let modelType,  modelEditing, MergeEditor = viewer.modules.MergeEditor
     Potree.addModel = function(name, done, url, fixPose){ 
@@ -363,6 +404,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
         modelType = name
         
         let loadDone = (model)=>{ 
+            MergeEditor.modelAdded(model)
             if(!fixPose){
                 modelEditing = model; 
       //          MergeEditor.setModelBtmHeight(model, 0) //默认离地高度为0                
@@ -505,27 +547,13 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                     } 
                 },callback,onprogress)
             }else if(name == 'obj'){
-                /* var path = `${Potree.resourcePath}/models/13/`       
-                viewer.loadModel({
-                    name, 
-                    objurl: path+'w13.OBJ',
-                    mtlurl: path+'w13.mtl',  
-                    transform : { 
-                        rotation : [Math.PI/2,  0,   0],
-                        position : [0,0,0]  
-                    }  
-                },callback,onprogress)  */  
-                     
-                //var path = `${Potree.resourcePath}/models/obj/28M/`
-                //var path = `${Potree.resourcePath}/models/obj/monitor_12M/`
-                var path = `${Potree.resourcePath}/models/obj/test/${objIndex++}/`
-                
-                
+                  
+                let path = objUrls[objIndex++]
                 
                 viewer.loadModel({
                     name, 
-                    objurl: path+ 'mesh.obj', //chuizi.obj' /* 'GW1H.obj' */,  //解析时间4.392s
-                    mtlurl: path+ 'mesh.mtl',    
+                    objurl: path+'.obj',  
+                    mtlurl: path+'.mtl',    
                     transform : { 
                         rotation : [0,  0,   0],
                         position : [0,0,0]  
@@ -541,50 +569,14 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                         rotation : [0,  0,   0],
                         position : [0,0,0]  
                     }  
-                },callback,onprogress) */
-                
-                 
+                },callback,onprogress) */ 
                 
-                
-                
-                /* var path = `${Potree.resourcePath}/models/obj/496M/`
-                viewer.loadModel({
-                    name, 
-                    objurl: path+'Model.obj',  //解析时间25.629 s  期间弹出崩溃提示。很卡 
-                    mtlurl: path+'Model.mtl',     
-                    transform : { 
-                        rotation : [0,  0,   0],
-                        position : [0,0,0]  
-                    }  
-                },callback,onprogress) */
-                
-            }else if(name == 'glb'){
-                 
-                //let angle = 0
-                //let fileName = 'block-远看有裂缝.glb'//'87b3a367bc3e4273832cb4fa398782e5.glb'    
-                
-                 
-                /* let angle = Math.PI/2
-                let fileName = 'coffeemat.glb',  //0.3s */
-                 
-                  
-                /* let angle =  Math.PI/2
-                let fileName = 'ModernJPHouseSofa44216499.glb' // 21M 
-                   */
-                
-                 
-                let angle = 0
-                let fileName = 'archmodels95_043.glb'//'block-远看有裂缝.glb'//'archmodels95_043.glb'//纯色
-                
-                //'26k.glb' //176M  
-                  
-               
-                
-                var path = `${Potree.resourcePath}/models/glb/` 
+            }else if(name == 'glb'){  
+                let angle = 0  
                 viewer.loadModel({ 
                     fileType:'glb',
                     name, 
-                    url: path+fileName,
+                    url: glbUrls[glbIndex++],
                     transform : { 
                         rotation : [angle,  0,   0],
                         position : [0,0,0]  
@@ -720,8 +712,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 viewer.loadModel({   
                     fileType: 'shp',  
                     name : 'shp',
-                    url:  Potree.resourcePath+'/models/shape-jiangmen/jiangmen.shp',
-                    
+                    url: url || shapeUrls[shapeIndex++],
                 },callback,onprogress)
       
                

+ 2 - 1
src/custom/modules/datasetAlignment/Alignment.js

@@ -288,7 +288,8 @@ var Alignment = {
     
     rotate:function(pointcloud, deg, angle){//绕各自中心水平转动(各自的position)   假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
         let qua 
-        if(typeof(angle) == 'number'){
+        
+        if(deg || typeof(angle) == 'number'){
             angle = angle != void 0 ? angle : THREE.Math.degToRad(deg)   //正逆负顺             
         }else{
             qua = angle

+ 346 - 32
src/custom/modules/mergeModel/MergeEditor.js

@@ -62,7 +62,7 @@ let MergeEditor = {
         viewer.scene.scene.add(this.boxHelper)
         Potree.Utils.updateVisible(this.boxHelper,'unselect',false)
         
-        
+        this.lastMemoryState = {}
         
     
         this.history = new History({ 
@@ -332,9 +332,10 @@ let MergeEditor = {
         }) */
         
                    
-        viewer.addEventListener('camera_changed',()=>{
-            Common.intervalTool.isWaiting('updateMemoryUsage', ()=>{  
-                 this.updateMemoryUsage()
+        viewer.addEventListener('camera_changed',()=>{//其实静止时内存也会变,因为在加载
+            this.lastMemoryState.history = [] //clear
+            Common.intervalTool.isWaiting('updateMemoryUsage', ()=>{   
+                return this.updateMemoryUsage()
             },  1000) 
         }) 
          
@@ -446,6 +447,35 @@ let MergeEditor = {
         let models = this.getAllObjects()
         return models.find(e=>e.dataset_id == id)
     },
+    
+    changeModelPointCount(object, type){
+        let  posCount , texArea   
+        if(type == 'add'){
+            let o = viewer.getObjectPointCount(object) 
+            posCount = o.posCount,  texArea = o.texArea 
+            object.posCount = posCount,   object.texArea = texArea
+        }else{
+            posCount = -object.posCount,   texArea = -object.texArea 
+        }
+ 
+        viewer.memoryModelCountInfo.otherPosCount += posCount 
+        viewer.memoryModelCountInfo.otherTexArea += texArea  
+    },
+
+
+    modelAdded(model){
+        /* model.addEventListener('isVisible',(e)=>{ 
+            if(e.reason == "overlinePass")return
+            //console.log(e)
+            viewer.addEventListener('update',()=>{ //下一次更新结束后
+                this.updateMemoryUsage()
+            },{once:true})  
+        }) */
+        if(model.fileType != '3dTiles'){
+            this.changeModelPointCount(model,'add')
+        }
+        this.updateMemoryUsage()
+    },
     removeModel(model){
         if(this.selected == model) this.selectModel(null)
         let dispose = (e)=>{
@@ -460,13 +490,27 @@ let MergeEditor = {
                 dispose(e) 
             })
             viewer.objs.remove(model)
-            this.updateMemoryUsage()
+            
         }   
         
         model.panos?.slice().forEach(e=>{
             e.dispose()
         }) 
         viewer.images360.tileDownloader.setPanoData(viewer.images360.panos, [] );
+        
+        if(model.fileType == '3dTiles'){
+            model.traverse(child=>{
+                if(child.runtime){
+                    child.runtime.getTileset().destroy()
+                    return {stopContinue:true}
+                }
+            })
+            
+        }
+        if(model.fileType != '3dTiles'){
+            this.changeModelPointCount(model,'sub')
+        }
+        this.updateMemoryUsage()
     },
     
     selectModel(model, state=true, fitBound, by2d){
@@ -775,20 +819,11 @@ let MergeEditor = {
         
     },
     
-    modelAdded(model){
-        model.addEventListener('isVisible',(e)=>{ 
-            if(e.reason == "overlinePass")return
-            //console.log(e)
-            viewer.addEventListener('update',()=>{ //下一次更新结束后
-                this.updateMemoryUsage()
-            },{once:true})  
-        })
-        this.updateMemoryUsage()
-    },
     
     
     
-    updateMemoryUsage(){
+    
+    updateMemoryUsage1(){
         
         //obj暂时不管其贴图大小, 因为顶点造成的不仅是内存还有卡顿所以先只看顶点
         const maxMemory = Potree.config.tiles3DMaxMemory + 100 //M  实际估计是这个的10倍
@@ -797,7 +832,10 @@ let MergeEditor = {
         const eachVisiCPointWeight = eachCloudPointWeight * 5    // 或 maxMemory / (6*1000*1000) 大概值接近 (再除以一个数是因为显示的要比内存中的耗更多资源
         const eachGltfPosWeight = 100/1000/1000  //M  每个顶点pos是3*4个字节?法线3*4和uv2*4  其实还有贴图 
         let posCount=0
-
+        
+        let eachTexPxWeight = 1 / 10 / 1000 / 1000   //不知道“图片缓存”会不会引起崩溃, 它不在“内存占用空间”中, 所以单位weight值设置小一点
+        let panosWeight = viewer.images360.panos.filter(e=>e.depthTex).length * 0.7 + viewer.images360.tileDownloader.tilesCount * 512*512 * eachTexPxWeight //深度图和全景图
+        let texArea = 0
         
         viewer.objs.children.forEach(e=>{
             if(!e.visible)return
@@ -805,28 +843,35 @@ let MergeEditor = {
                 e.traverse((mesh)=>{
                     if(mesh.geometry){
                         posCount += mesh.geometry.attributes.position.count
-                    } 
+                    }
+                    if(mesh.material?.map){
+                        texArea += mesh.material?.map.image.width * mesh.material?.map.image.height
+                    }
                 })
             }else if(e.fileType == '3dTiles'){
                   
             }
         })
-        
+         
         //获取点云的内存限制
-        let objWeight = posCount*eachObjPosWeight
+        let objWeight = posCount*eachObjPosWeight +    texArea * eachTexPxWeight
         let laserWeight = Potree.numVisiblePoints  * eachCloudPointWeight //点云实际显示所占大小
         let laserMemoryWeight =  Potree.lru.numPoints * eachCloudPointWeight   //点云所使用内存大小
-        let tiles3DWeight = viewer.tiles3dVisiVCount * eachGltfPosWeight //M   3dTiles所占内存大小
+        let tiles3DWeight = (viewer.visiVertexCount || 0) * eachGltfPosWeight //M   3dTiles所占内存大小   
         let tiles3DMemoryWeight = viewer.tiles3dMemoryUsage / 1000 / 1000  //M   3dTiles显示的所占内存大小
         
         /* let min = 0.1, max = 6, minP = 100, maxP = 1000000; 
         let ratio = Math.round(math.linearClamp(score, minP, maxP, max, min ));  */
-	    let rest = maxMemory - objWeight - tiles3DWeight         
+	    let rest = maxMemory - objWeight - tiles3DWeight - panosWeight        
         Potree.pointBudget = THREE.Math.clamp(Math.round(rest/eachVisiCPointWeight), Potree.config.pointDensity.low.pointBudget,   1.5*Potree.config.pointDensity.high.pointBudget) 
-        
+        //Potree.settings.maxLRUPoints
         //获取3dTiles的内存限制 
-        let tiles3DMaxMemory = maxMemory - Math.round(objWeight + laserWeight)
-        Potree.settings.tiles3DMaxMemory = THREE.Math.clamp(tiles3DMaxMemory , 30, Potree.config.tiles3DMaxMemory )
+        let tiles3DMaxMemory = maxMemory - Math.round(objWeight + laserWeight + panosWeight)
+        
+        window.cesiumViewer && (tiles3DMaxMemory-=30)
+        
+        Potree.settings.tiles3DMaxMemory = THREE.Math.clamp(tiles3DMaxMemory , 30, Potree.config.tiles3DMaxMemory ) 
+        
         
         
         
@@ -839,10 +884,262 @@ let MergeEditor = {
         
         //总内存 = 内存占用空间+图片缓存 , obj的缓存比较多在图片中
         
+       
+        
+        //尽量使任务管理器里的内存占用空间不超过2G
+        /* 
+            崩溃历史:
+            
+            cesium容易先崩溃,并弹窗报An error occurred while rendering.  Rendering has stopped.
+            体育中心:http://192.168.0.140/index.html?caseId=1&app=1&token=1#/fuseEdit/path   
+            罗敏电脑崩溃 ,报了ces的崩溃错误。内存占用空间2196M, memory.usedJSheapSize:1812M 崩溃时正在外部浏览模型(导览崩溃过),可能是3dtiles
+        
+        
+         */
+    },
+    
+      /* 0.0000621791536865594
+      0.00009005265640549425
+      0.00007493883414610654
+      0.00006503514775640251
+      0.000063906496015 */
+      
+      
+    
+    updateMemoryUsage(){//新  注意 模型即使隐藏也不会降低内存占用,只是会降低卡顿。  未支持3dgs
+        //疑问:1 在不超过最大内存的前提下会因为显示太多模型崩溃吗? 2如何获知每个浏览器最大内存支持?看了一圈,jsHeapSizeLimit都一样怎么办?
+        //3dtiles(或模型)的大小是直接用文件大小还是根据点和贴图尺寸计算呢,感觉它会分成两部分,加载的和显示的,都需要考虑。
+        //obj暂时不管其贴图大小, 因为顶点造成的不仅是内存还有卡顿所以先只看顶点
+        if(window.stopUpdateM) return
+        
+        //let start = performance.now();
+        
+        let old = {
+            pointBudget : Potree.pointBudget,
+            tiles3DMaxMemory: Potree.settings.tiles3DMaxMemory,
+            maxLRUPoints : Potree.settings.maxLRUPoints
+        }
+        
+        const maxMemory =  1500 //1500 //M 整体占用内存限制 (不考虑峰值,要等静止后退下来的值)      //什么都不加载可能就占了300M
+        const eachObjPosWeight = 100/1000/1000  //M  每个顶点pos是3*4个字节?法线3*4和uv2*4 + faceIndex比较难说  
+        const eachCloudPointWeight = 70/1000/1000 //M 每个点 pos + 颜色 + 法线 大概 根据测试算出来也是这个值   0.00007
+        //const eachVisiCPointWeight = eachCloudPointWeight * 5    // 或 maxMemory / (6*1000*1000) 大概值接近 (再除以一个数是因为显示的要比内存中的耗更多资源
+        const eachTexPxWeight = 1 / 10 / 1000 / 1000   //每个模型都不一样只能取个大概
+        const eachTexPxWeightTiles = eachTexPxWeight * 1.5 //感觉3dtiles的比glb和obj都高很多,是因为文件还在缓存里吗
+    
+        
+        let posCount=0,   posCount3dTiles = 0, visiPosCount = 0
+        
+ 
+        let panosWeight = 0.1 * (viewer.images360.panos.filter(e=>e.depthTex).length * 0.7 + viewer.images360.tileDownloader.tilesCount * 512*512 * eachTexPxWeight) //深度图和全景图(但似乎是额外放图片缓存里?因为没有增加tex, panoExit后就dispose了, 所以乘以一个小系数。担心图片缓存也可能导致崩溃)
+        //let texArea = 0, texArea3dTiles = 0, visiTexArea = 0
+        //texArea = viewer.memoryModelCountInfo.otherTexArea + viewer.memoryModelCountInfo.tileTexArea
+        //posCount = viewer.memoryModelCountInfo.otherPosCount + viewer.memoryModelCountInfo.tilePosCount 
+        //posCount3dTiles = viewer.memoryModelCountInfo.tilePosCount
+        //texArea3dTiles = viewer.memoryModelCountInfo.tileTexArea
+         
+         
+       
+        let otherModelWeight = viewer.memoryModelCountInfo.otherPosCount*eachObjPosWeight + viewer.memoryModelCountInfo.otherTexArea * eachTexPxWeight
+        let tilesWeight = (viewer.memoryModelCountInfo.tilePosCount*eachObjPosWeight + viewer.memoryModelCountInfo.tileTexArea * eachTexPxWeightTiles) * 1.5  //3dtiles太占内存了
+        let modelWeight = tilesWeight + otherModelWeight //posCount*eachObjPosWeight + texArea * eachTexPxWeight
+        //为何obj、glb过后会降很多,而3dtiles降的很少? 不过考虑到glb更卡,且加载瞬间确实占用高,还是不把otherModelWeight改少了吧
+         
+        Potree.tilesWeight = tilesWeight
+        Potree.modelWeight = modelWeight//所有模型
+        //Potree.modelVisiWeight = visiPosCount*eachObjPosWeight + visiTexArea * eachTexPxWeight 
+        
+        //经测试,发现模型并不会因为不可见而降低内存,3dtiles加载满了之后拉远,可见weight下降了但内存没降。
+        //so,需要测试崩溃是否只与内存有关,如果内存爆了,但都不可见,非常流畅,还会崩溃吗
+        
+        
+         
+        
+         
+        let laserVisiWeight = Potree.numVisiblePoints  * eachCloudPointWeight //点云实际显示所占大小
+        let laserWeight =  Potree.lru.numPoints * eachCloudPointWeight   //点云所使用内存大小
+        Potree.laserWeight = laserWeight
+
+
+        let fixedPart = modelWeight - tilesWeight + panosWeight  + (window.cesiumViewer ? 30:0) //ces:刚加载时60,静止一段时间后20
+        Potree.allWeight =  Potree.laserWeight + fixedPart + tilesWeight 
+        let tiles3dMemoryUsage = viewer.tiles3dMemoryUsage/1024/1024
+         
+        
+        let overRatio = Potree.allWeight / maxMemory 
+        
+        let visiRatio1 = posCount3dTiles == 0 ? Infinity : viewer.visiVertexCount / posCount3dTiles 
+        let visiRatio2 = Potree.lru.numPoints == 0 ? Infinity : Potree.numVisiblePoints / Potree.lru.numPoints  
+        let resolved 
+         
+        let restMemory = maxMemory - fixedPart //去除固定的part后
+             
+         
+        if(overRatio > 1){
+            
+            
+            
+            const MinRestMemory = 400 
+            if(restMemory < MinRestMemory){ 
+                Common.intervalTool.isWaiting('updateMemoryUsage', ()=>{  
+                    let warnText  
+                    if(this.lastMemoryState.restMemory < 0){
+                        warnText = '固定内存已经超额!没有剩余空间给点云和3dtiles。' 
+                    }else if(this.lastMemoryState.restMemory < MinRestMemory){
+                        warnText = '固定内存过多!' 
+                    }
+                    if(warnText){
+                        warnText += '请减少glb类模型, 当前固定内存占用大小:' + fixedPart                
+                        console.error(warnText)
+                    }
+                },  10000)
+            }
+            
+            
+            
+            
+            if(visiRatio1 < visiRatio2 || Potree.lru.numPoints <= Potree.config.pointDensity.low.pointBudget / 10 * 1.1 ){//占比小的需要缩减缓存容器
+                  
+                if(Potree.settings.tiles3DMaxMemory < tiles3dMemoryUsage / 2 && this.lastMemoryState.tiles3dMemoryUsage == tiles3dMemoryUsage //已经很难减少了,这时候tiles3DMaxMemory很可能降到个位数
+                || Potree.settings.tiles3DMaxMemory < 2){
+                    resolved = false
+                }else{
+                    Potree.settings.tiles3DMaxMemory = Math.min(tiles3dMemoryUsage, Potree.settings.tiles3DMaxMemory)//先降到已使用的数据量
+                    Potree.settings.tiles3DMaxMemory > 1 && (Potree.settings.tiles3DMaxMemory *= 0.8 ) //注意,降低该值不一定会降低 tiles3dMemoryUsage,只有后续改变sse才行 viewer.tiles3dMemoryUsage / tiles3DMaxMemory 可能大于1
+                    resolved = true
+                }
+            }
+            if(!resolved){// visiRatio1 > visiRatio2   
+                
+                if( visiRatio2 > 0.9){//压缩太小了,或者说没什么缓存就超额了,只能继续减少显示个数
+                    if(Potree.pointBudget < Potree.config.pointDensity.low.pointBudget){
+                        //console.error('Potree.pointBudget已经很小了!', Potree.pointBudget)
+                    }else{
+                        Potree.pointBudget *= 0.85 
+                        resolved = true
+                    } 
+                }else{   
+                    Potree.pointBudget = THREE.Math.clamp(Potree.numVisiblePoints, Potree.config.pointDensity.low.pointBudget / 10,   Potree.pointBudget*0.8 )//先往降到可见数量方向降 (当画面中可见点确实很少时允许降低到low之下)
+                    
+                    Potree.settings.maxLRUPoints = Math.min(Potree.lru.numPoints, Potree.settings.maxLRUPoints);//先降到已使用的数量
+                    Potree.settings.maxLRUPoints *= 0.8    
+                    resolved = true
+                } 
+            }
+           
+        }else{//恢复部分好难写
+            //优先恢复实际使用(可见)内存,然后是缓存
+            //额外缓存少的,实际使用内存被压缩的概率也大
+            //visiRatio1 == Infinity && (visiRatio1 = 0)  //因这里大的才会被改所以改一下
+            
+            visiRatio1 *= tiles3dMemoryUsage / Potree.settings.tiles3DMaxMemory //用point数量有时候不准,明明内存不够了不可见的tile还存在,所以×memory的占比
+            visiRatio2 = Potree.pointBudget == 0 ? 0 : Potree.numVisiblePoints / Potree.pointBudget //使用率越高,说明越需要内存. 因为恢复时先提高使用内存所以改一下
+             
+            let r = Math.min(1, visiRatio1 , visiRatio2 ) //如果另一个visiRatio 很小,s就可以再小一些,使内存超过一点,然后朝另一方倾斜,另一方去缩减
+            let s = 1 - Math.max(0.8,  overRatio * r )
+             
+            //太难判断是哪个更需要内存了,所以两个都涨吧 
+           
+           
+            if(visiRatio2 > visiRatio1){ 
+                let s_ = visiRatio2 / (visiRatio1 + visiRatio2) * s + 1
+                const addCount = 500000 * s_
+                if(Potree.pointBudget < Potree.config.pointDensity.high.pointBudget*0.9){
+                    Potree.pointBudget += addCount
+                }else{
+                    Potree.settings.maxLRUPoints += addCount
+                }
+                 
+            } 
+                    
+                
+            if(Potree.settings.tiles3DMaxMemory < Potree.config.tiles3DMaxMemory ){  
+                let s_ = visiRatio1 / (visiRatio1 + visiRatio2) * s + 1
+                const addCount = 20 * s_
+                Potree.settings.tiles3DMaxMemory += addCount 
+                Potree.settings.tiles3DMaxMemory = Math.min(Potree.settings.tiles3DMaxMemory, Potree.config.tiles3DMaxMemory)
+            }
+                   
+            
+            resolved = true
+        } 
+        
+        let min = overRatio > 1 ? 1.1 : 1.5,   max = 5;  
+        Potree.settings.maxLRUPoints = THREE.Math.clamp(Potree.settings.maxLRUPoints , Potree.pointBudget * min, Potree.pointBudget * max)
+                 
+        //压缩时先压缩缓存,然后是使用内存; 恢复时相反。
+        
+        //3dtiles不像点云那样可以在加载时拦截,使不高于tiles3DMaxMemory,只能自动动态调节maxSSE,所以有可能调节完还是降低不了
+          
+        //一次只更新一点,一点点调节,因为可能不是一个原因造成
+          
+        
+          
+        
+        let changed
+        if(old.pointBudget != Potree.pointBudget || old.tiles3DMaxMemory != Potree.settings.tiles3DMaxMemory || old.maxLRUPoints != Potree.settings.maxLRUPoints ){
+                
+            if(old.tiles3DMaxMemory != Potree.settings.tiles3DMaxMemory){
+                viewer.setAllTilesets(model=>{ 
+                    model.runtime.getTileset().nextForceUpdate = true  
+                    model.runtime.getTileset().needRenderNext = true 
+                })
+            }else{
+                viewer.dispatchEvent('content_changed')
+            } 
+            changed = true
+        }
+        
+       {
+            
+            let mSSE = math.linearClamp(Potree.fpsRendered2, [2, 52], [500,  70]) //有效降低卡顿 和贴图变白概率。越卡越提高msse
+            let r = tiles3dMemoryUsage / Potree.settings.tiles3DMaxMemory 
+            viewer.setAllTilesets(model=>{
+                let tileset = model.runtime.getTileset()
+                tileset.options.initialMaxSSE = mSSE  
+                let e = tileset.options.maximumScreenSpaceError * THREE.Math.clamp(r, 0.8, 1.2)
+                e = THREE.Math.clamp(e, mSSE, mSSE+400) //如果有很卡的模型可能需要1000,但太高了降回来很慢,就假设模型都正常的吧
+                tileset.options.maximumScreenSpaceError = e
+           
+            })   
+        }
+        
+        //针对很卡的设备:(卡的也容易崩溃)
+        Potree.config.pointDensity.high.pointBudget = math.linearClamp(Potree.fpsRendered2, [2, 50], [1e6,  6e6])
+        
+         
+        
+        
+        let history = this.lastMemoryState.history || []
+        history.push(overRatio) 
+        this.lastMemoryState = {
+            tiles3dMemoryUsage,
+            history,
+            restMemory            
+        }
+        
+       
+        
         
+        //console.log('cost', performance.now() - start)  
+        return changed && ((overRatio > 1.1 || overRatio < 0.9) ||  
+            history.slice(-7).length < 7 || history.slice(-7).some(overRatio=>overRatio > 1.1 || overRatio < 0.9) //后十个都在最大内存范围左右抖动,则不继续更新,直到相机变化  
+        )
+       
+          
+       
+	    //难点:要防止抖动,否则3dtiles的sse变大变小,相机静止时还在清晰和模糊间变来变去。好在点云是可以较精细调节,tile是间接控制的
+        
+        //模型删除后还会占用一定内存,尤其是3dtile几乎降不了,怎么回事?是否删除的模型还要算进来 
     },
     
     
+    
+     
+    
+    
+    
+    
     setGroundPlaneImg(src,scale,angle){//设置地面图
         
         this.goundScale = scale || 1, this.goundAngle = angle || 0
@@ -890,17 +1187,29 @@ let MergeEditor = {
     
 }   
     
-    
-    
-  
-    
+    /* 
+    watch:
+        performance.memory.usedJSHeapSize / 1e6 
+        Potree.settings.tiles3DMaxMemory 
+        viewer.tiles3dMemoryUsage 
+        Potree.tilesWeight 
+        Potree.modelWeight 
+        Potree.modelVisiWeight 
+        Potree.allWeight 
+        Potree.lru.numPoints 
+        Potree.pointBudget 
+        Potree.numVisiblePoints 
+        Potree.settings.maxLRUPoints 
+
+        
+     */
     
 export default MergeEditor  
     
-    
-/*  
+     
+/*   
 
-note:
+note: 
 
 要注意getHoveredElements只在getIntersect时才使interactables包含加载的model, 也就是model上不能有使之成为interactables的事件,否则在鼠标hover到模型上开始转动的一瞬间很卡。
 
@@ -910,4 +1219,9 @@ window.pano2 = viewer.modules.PanoEditor.selectedPano //选中第二个点后输
 viewer.modules.PanoEditor.linkChange(window.pano1, window.pano2, 'remove') //断开链接
 
 
+如果直接刷新,内存管理器中的内存会有之前的残留。不知道是不是真实的加入这次的内存了。
+并且,如果第一次加载的是几乎不占内存的模型,如glb,内存也会一下子涨30,不过等一会儿会降。4M的3dtiles会涨60.   每次加载模型后都会内存突增,但过后会降。 为何obj、glb过后会降很多,而3dtiles降的很少。?
+
+国产系统8192贴图直接出错,4096的贴图过多也出错。texImage: Driver ran out of memory during upload.
+
 */ 

+ 57 - 38
src/custom/modules/panoEdit/panoEditor.js

@@ -82,9 +82,9 @@ class PanoEditor extends THREE.EventDispatcher{
     
     constructor(){
         super()
-        this.panoGroup = [], //分组 
+        
         this.viewports = {},
-        this.panoLink = {}, 
+        
         this.panoMeshs = new THREE.Object3D,
         this.lineMeshes = new THREE.Object3D 
         this.views = {}
@@ -94,11 +94,31 @@ class PanoEditor extends THREE.EventDispatcher{
         this.selectedPano;
         this.selectedGroup;
         this.operation; 
-        this.visiblePanos = []
-        
+        this.visiblePanos = [] 
         this.suggestLines = []
     }
-     
+    
+    
+    
+    initPanoLink(){
+        this.panoGroup = [] //分组 
+        this.panoLink = {} 
+        
+        images360.panos.forEach((pano)=>{
+            this.panoLink[pano.id] = {}
+            this.panoGroup.push([pano])
+        })
+        
+        images360.panos.forEach((pano)=>{ 
+            pano.visibles = pano.panoData.visibles || pano.panoData.links_data.map(e=>e.index) //后者为新版
+            pano.rtkState = pano.panoData.has_rtk ? pano.panoData.use_rtk : null  
+            pano.visibles.forEach(index=>{//visibles中存的是下标!
+                this.linkChange(pano, images360.getPano(index,'index'), 'add')
+            })
+        })
+        
+        //console.log('panoLink',this.panoLink)
+    } 
     init(){ 
         Alignment = viewer.modules.Alignment 
         
@@ -231,9 +251,9 @@ class PanoEditor extends THREE.EventDispatcher{
             viewer.scene.pointclouds.forEach(e=>{
                 e.material.color = pointColor.default  
             }) 
-            viewer.setEDLEnabled(true) //为了降一倍的绘制. 同时用描边增强立体感,弥补点云稀疏
+            /* viewer.setEDLEnabled(true) //为了降一倍的绘制. 同时用描边增强立体感,弥补点云稀疏
             viewer.setEDLRadius(1)
-            viewer.setEDLStrength(0.01)
+            viewer.setEDLStrength(0.01) */
  
             
             
@@ -357,7 +377,7 @@ class PanoEditor extends THREE.EventDispatcher{
          
         if(this.lastExitView)this.switchView(this.lastExitView)
         else{
-            Potree.settings.isTest || this.switchView('top')
+            /* Potree.settings.isTest ||  */this.switchView('top')
         }
        
         viewer.setFOV(100) //最好能把屋顶砍了,这样不用进屋子就能看到墙壁是否互相贴合,但是加clip比较麻烦,就暂时把fov改大吧
@@ -538,7 +558,8 @@ class PanoEditor extends THREE.EventDispatcher{
             }else{ */
                 changeMat() 
             //}
-            this.transformControls._gizmo.hideAxis = {rotate:['e'] }
+             
+            this.transformControls._gizmo.hideAxis = this.version == 'old' ? {rotate:['e','x','y'] } : {rotate:['e'] }
             
             
             Potree.Utils.updateVisible(viewer.reticule, 'force', true)
@@ -576,12 +597,17 @@ class PanoEditor extends THREE.EventDispatcher{
                 let panos = images360.panos.filter(e=>e.circle.visible)
                 
                 //尽量靠近画布中心,且距离相机较近
-                let nearestPano = Common.sortByScore(panos , [], [(pano)=>{
+                let vecMap = new Map
+                let nearestPano = Common.sortByScore(panos , [(pano)=>{
                     let vec = new THREE.Vector3().subVectors(pano.position,  lastView.position);
+                    vecMap.set(pano,vec)
+                    return vec.length() > 0 //否则后面算不出
+                }], [(pano)=>{
+                    let vec = vecMap.get(pano)
                     let dis = vec.dot(direction);  
                     return dis < 0 ? dis * 10 : - dis 
                 },(pano)=>{
-                    let vec = new THREE.Vector3().subVectors(pano.position,  lastView.position);
+                    let vec = vecMap.get(pano)
                     let angle = vec.angleTo(direction)
                     return  - angle * 70 
                 }], true);  
@@ -930,20 +956,7 @@ class PanoEditor extends THREE.EventDispatcher{
      
     /////////////////////////////////
     
-    initPanoLink(){
-        images360.panos.forEach((pano)=>{
-            this.panoLink[pano.id] = {}
-            this.panoGroup.push([pano])
-        })
-         
-        images360.panos.forEach((pano)=>{ 
-            pano.visibles.forEach(index=>{//visibles中存的是下标!
-                this.linkChange(pano, images360.getPano(index,'index'), 'add')
-            })
-        })
-        
-        //console.log('panoLink',this.panoLink)
-    }
+    
     
     
     
@@ -1549,8 +1562,12 @@ class PanoEditor extends THREE.EventDispatcher{
     }
     
     cancelEdit(){//回退数据
-        this.changeDatas(Potree.settings.datasetsPanos,true)  
-        this.panoGroup = [] //分组 
+        this.changeDatas(Potree.settings.datasetsPanos,true)   
+        
+        this.lineMeshes.children.forEach(e=>e.geometry.dispose())
+        this.lineMeshes.children.length = 0  //clear
+           
+        
         this.initPanoLink()
     }
     
@@ -1582,18 +1599,20 @@ class PanoEditor extends THREE.EventDispatcher{
                     use_rtk : !!pano.rtkState   
                 })
                 
-                if(pano.panoData.visibles){
-                    delete data.visibles 
-                } 
-                
-                
-                let old_links_data = pano.panoData.links_data || []
-                data.links_data = visibles.map(e=>{
-                    let old = old_links_data.find(a=>a.index == e)
-                    if(old && (isNaN(old.MAE) || !old.MAE)) old.MAE = 0
-                    return old ||  {index:e, "MAE": 0,  "overlap": 0} //随便写一个0
-                })
+                if(this.version != 'old' ){
+                    if(pano.panoData.visibles){
+                        delete data.visibles 
+                    }  
+                    let old_links_data = pano.panoData.links_data || []
+                    data.links_data = visibles.map(e=>{
+                        let old = old_links_data.find(a=>a.index == e)
+                        if(old && (isNaN(old.MAE) || !old.MAE)) old.MAE = 0
+                        return old ||  {index:e, "MAE": 0,  "overlap": 0} //随便写一个0
+                    })
                 
+                }else{
+                    data.visibles = visibles
+                }
                 
                 
                 return data

+ 7 - 4
src/custom/modules/panos/DepthImageSampler.js

@@ -56,7 +56,7 @@ class DepthImageSampler extends THREE.EventDispatcher{
             this.context.drawImage(img, 0, 0) 
             let o = this.context.getImageData(0, 0, img.width , img.height )
             let data = o && o.data;     //getImageData 1px时 : pc chrome 耗时0.01毫秒左右(排除第一次的50) , 但firefox: 4。但换贴图之后就多达5甚至几十  
-            console.warn('changeImg', pano.id, !!data ) 
+            //console.warn('changeImg', pano.id, !!data ) 
             //this.img = img
             if(!data){
                 console.error('getImageData 得不到?!why!')
@@ -179,7 +179,10 @@ class DepthImageSampler extends THREE.EventDispatcher{
         
         
         
-        if (!depth){
+        if (!depth ){
+            if(this.ignoreTopAndBtmHole){
+                return null 
+            }
             const margin =  0.1
             if(uv.y > 1-Potree.config.depthTexUVyLimit){//漫游点底部识别不到的区域,给一个地板高度
                 
@@ -187,7 +190,7 @@ class DepthImageSampler extends THREE.EventDispatcher{
                 location.copy(dir).multiplyScalar(depth).add(origin);
                 let normal = new THREE.Vector3(0,0,1)
                 
-                return {location, normal, depth, uv} 
+                return {location, normal, distance:depth,  uv} 
             }else if(uv.y < Potree.config.depthTexUVyLimit){
                 let ceilZ = currentPano.getCeilHeight()
                 if(ceilZ == Infinity)return !1
@@ -195,7 +198,7 @@ class DepthImageSampler extends THREE.EventDispatcher{
                     depth = (ceilZ - origin.z - margin) / dir.z
                     location.copy(dir).multiplyScalar(depth).add(origin);
                     let normal = new THREE.Vector3(0,0,-1)
-                    return {location, normal, depth, uv} 
+                    return {location, normal, distance:depth,  uv} 
                 } 
             }
             //console.log('无穷远')

+ 24 - 19
src/custom/modules/panos/Images360.js

@@ -65,8 +65,7 @@ export class Images360 extends THREE.EventDispatcher{
  
 		this.panos = [];
         this.neighbourMap = {}
-        this.disMap = {}
-        
+        this.disMap = {} 
 		this.node = new THREE.Object3D();
         this.node.name = 'ImagesNode'
          
@@ -730,11 +729,23 @@ export class Images360 extends THREE.EventDispatcher{
         this.projectedPano1 = o.pano1
         
     }
+    
+    
+    get latestToPano(){
+        return this._latestToPano
+    }  
+    
+    set latestToPano(toPano){
+        this._latestToPano = toPano
+        
+        //console.warn('latestToPano',toPano)
+    }  
 
-
-	cancelFlyToPano(toPano){//取消当前已有的飞行准备,前提是相机还未移动 
-        if(viewer.mainViewport.view.isFlying() || toPano && this.latestToPano != toPano)return
-        //Potree.Log('cancelFlyToPano', this.latestToPano && this.latestToPano.pano.id)
+	cancelFlyToPano(toPano){//取消当前已有的飞行准备,前提是相机还未移动  //2024.11.13删除isFlying的判断,因为在done中延时1之后居然isFlying有概率还是true   bugID=47743
+        if(/* viewer.mainViewport.view.isFlying() ||  */toPano && this.latestToPano != toPano){
+            return
+        }
+        //Potree.Log('cancelFlyToPano', this.latestToPano && this.latestToPano.pano.id)  
         this.nextPano = null 
         this.latestToPano = null 
     }
@@ -757,7 +768,7 @@ export class Images360 extends THREE.EventDispatcher{
                 this.updateClosestPano(this.closestPano,false) //飞行结束后取消点击漫游点时得到的closestPano
             }else{ 
             }
-           
+            
             this.dispatchEvent({type:'flyToPanoDone', makeIt, disturb})
             this.fastTranMaskPass.stop() 
             toPano.deferred && toPano.deferred.resolve(makeIt)  //测量线截图时发现,resolve需要写在flying=false 后才行。
@@ -846,12 +857,7 @@ export class Images360 extends THREE.EventDispatcher{
         
         const endPosition = pano.position.clone() 
         let T = Potree.config.transitionsTime
-        /* let maxTime = this.isAtPano() ? T.panoToPanoMax : T.flyIn 
-        let duration = toPano.duration == void 0 ? (T.flyMinTime+Math.min(T.flytimeDistanceMultiplier * dis, maxTime)) : toPano.duration 
-         */
-        /* let maxDis = this.isAtPano() ? T.maxDistanceThreshold : T.maxDistanceThresholdFlyIn
-        let duration = toPano.duration == void 0 ? Math.min(dis, T.maxDistanceThreshold) * T.flytimeDistanceMultiplier + T.flyMinTime : toPano.duration 
-          */
+    
         
         let maxDis = 7
         let duration = toPano.duration == void 0 ? Math.min(dis, maxDis) * T.flytimeDistanceMultiplier + T.flyMinTime : toPano.duration 
@@ -2217,7 +2223,7 @@ export class Images360 extends THREE.EventDispatcher{
                 var face = new THREE.Object3D;
                 for(var i=0;i<8;i++){
                     for(var j=0;j<8;j++){
-                        var tile = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({
+                        var tile = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({//之后有必要的话像4dkk一样换成ModelTextureMaterial,加深度图 
                             //side:THREE.DoubleSide
                             transparent:true,
                             opacity : 0.4,
@@ -2246,8 +2252,7 @@ export class Images360 extends THREE.EventDispatcher{
                             tile.add(tile.scoreLabel)  */
                             
                         }
-                        
-                        tile.visible = false 
+                        Potree.Utils.updateVisible(tile,'show',false) 
                         tile.tileX = i
                         tile.tileY = j
                         tile.cubeFace = cubeIndex
@@ -2378,7 +2383,7 @@ export class Images360 extends THREE.EventDispatcher{
         if (needRecover.length) {
             let maxCount = Common.getBestCount( '4kmaxTileRecover',  0,  2,   1.5,   6,  false, 2 )
             let count = 0
-            console.log(maxCount)
+            //console.log(maxCount)
             needRecover.forEach((e, i) => {
                 //只更新若干个,因为太耗时了, 其余的等下帧更新
                 if (count >= maxCount) return
@@ -2426,7 +2431,7 @@ export class Images360 extends THREE.EventDispatcher{
         tile.material.transparent = false
         
         
-        tile.visible = true
+        Potree.Utils.updateVisible(tile,'show',true) 
         tile.material.needsUpdate = true  //发现每次开始放大但还未放大到4k时也会把之前加载过的4k加载
     }
     
@@ -2461,7 +2466,7 @@ export class Images360 extends THREE.EventDispatcher{
 
              
             tile.material.needsUpdate = true
-            tile.visible = false
+            Potree.Utils.updateVisible(tile,'show',false) 
 
             //this.highMapCube.texLoadedCount --
             //console.log('resetTile'/* , tile.cubeFace, tile.tileX, tile.tileY */)

+ 1 - 2
src/custom/modules/panos/Panorama.js

@@ -124,8 +124,7 @@ class Panorama extends THREE.EventDispatcher{
                 this.quaternion2 = this.quaternion.clone()
                 this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion,  rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度
                 
-            this.visibles = o.visibles || o.links_data.map(e=>e.index) //后者为新版
-            this.rtkState = o.has_rtk ? o.use_rtk : null 
+            
             
             const height = 1.4; //相机高度
             this.originFloorPosition = this.originPosition.clone()

+ 1 - 1
src/custom/modules/panos/tile/QualityManager.js

@@ -25,7 +25,7 @@ export default class QualityManager {
         
         this.maxRenderTargetSize = browser.isMobile() ? 2048 : 4096  //add
           
-        this.maxRenderTargetSize = Math.min( viewer.renderer.capabilities.maxCubemapSize,  this.maxRenderTargetSize)
+        this.maxRenderTargetSize = Math.min( viewer.renderer.capabilities.maxCubemapSize,  this.maxRenderTargetSize)//部分系统老浏览器只能2048
         
         this.init()
     }

+ 10 - 14
src/custom/modules/panos/tile/TileDownloader.js

@@ -40,7 +40,7 @@ class TileDownloader extends THREE.EventDispatcher{
             Fail: 3
         });
         
-                                                                                  
+        this.tilesCount = 0 //add  加载好的tile数目                                                                      
          
         /* viewer.addEventListener('pageVisible', (e)=>{//不可见时不refreshUpdateInterval 
             //console.log('visibilitychange:', state)
@@ -120,22 +120,18 @@ class TileDownloader extends THREE.EventDispatcher{
             }
             //return e 
         }else if( viewer.scene.pointclouds.some(e=>e.hasDepthTex)){//不加载贴图也要获得nearPanos
-            viewer.lastFrameChanged && Potree.Common.intervalTool.isWaiting('processPriorityQueue', ()=>{ 
+            viewer.lastFrameChanged && Potree.Common.intervalTool.isWaiting('processPriorityQueue2', ()=>{ 
                 this.tilePrioritizer.getNearPanos(this.panos)  
             }, time)
                 
         }
   
         
-        time = Potree.Common.getBestCountFPS('filterDepthTex',  false,  10000, 88, 10, 63  ) 
-        Potree.Common.intervalTool.isWaiting('filterDepthTex', ()=>{ 
+        let time2 = 1000//Potree.Common.getBestCountFPS('filterDepthTex',  false,  5000, 88, 2, 63  ) 
+        Potree.Common.intervalTool.isWaiting('filterDepthTex', ()=>{  
             this.tilePrioritizer.filterDepthTex(this.panos)//下载深度图
-        }, time)
-            
-            
-            
-         
-         
+        }, time2)
+             
         
     }
 
@@ -250,9 +246,7 @@ class TileDownloader extends THREE.EventDispatcher{
         this.loadImage(o, 0, a, s)
     }
 
-    startDownload(e) {//开始下载啦
-        //console.log('startDownload')
-        
+    startDownload(e) {//开始下载啦 
         startdownloads.push(e) 
         e.local2SrcFailed = this.local2SrcFailed
         e.status = DownloadStatus.Downloading;
@@ -299,7 +293,7 @@ class TileDownloader extends THREE.EventDispatcher{
                 console.warn('没下完')
             }
             
-            
+            this.tilesCount ++; //add
             
             e.image = t,
             this.dispatchEvent({type:TileDownloaderEvents.TileDownloadSuccess, desc:n} ) 
@@ -621,6 +615,8 @@ TileDownloader.DOWNLOAD_RETRIES = 4;
 还有depthTex,一张700k左右。
 
 
+viewer.images360.tileDownloader.downloadDescriptors 
+viewer.images360.panoRenderer.tileDirectory
 
  */
 export default TileDownloader

+ 1 - 1
src/custom/objects/Sprite.js

@@ -289,7 +289,7 @@ export default class Sprite extends THREE.Mesh{
          
         this.needsUpdate = false
         this.useViewport = e.viewport
-         
+        this.dispatchEvent('spriteUpdated')
     }
     
     applyMatrix(e){

+ 200 - 52
src/custom/objects/Tag.js

@@ -7,105 +7,253 @@ import {LineDraw, MeshDraw} from "../utils/DrawUtil.js";
 import {TextSprite} from './TextSprite.js' 
 import Sprite from './Sprite.js' 
 
+import DepthBasicMaterial from "../materials/DepthBasicMaterial.js";
+
+
+
+const depthMatProp = {  //为了防止拉远后因放大而一半嵌入墙。 
+    useDepth : true , 
+    startClipDis  : 0.5,
+    clipDistance : 1,//消失距离      
+    startOcclusDis: 0.5,
+    occlusionDistance: 0.9,//变为backColor距离 
+    maxOcclusionFactor:0.7,
+    maxClipFactor:1
+    
+}
+
 const renderOrders = {
-    line: 0 ,
+    line: 3 ,
     spot: 15, //高过模型
+    label: 17
 }
 const planeGeo = new THREE.PlaneGeometry(1,1)
 let texLoader = new THREE.TextureLoader() 
 
-let lineMat = new THREE.LineBasicMaterial({
-    color: '#ffffff', 
-})
-let spotMat 
+let lineMat  
+ 
 const defaultLineLength = 0.6
 const defaultSpotScale = 0.4
 
+const titleHeight = {uponSpot:0.1, uponLine:0}
+
+
+const Vectors = {
+    UP : new THREE.Vector3(0,1,0),
+    ZERO: new THREE.Vector3()
+}
+
+
+
 class Tag extends THREE.Object3D{
     constructor(o){
         
         super()
         
-         
+        this.title = o.title 
         this.lineLength = o.lineLength != void 0 ? o.lineLength : defaultLineLength
         this.position.copy(o.position)
-        this.normal = o.normal != void 0 ? o.normal : new THREE.Vector3(0,0,-1)
+        this.normal = o.normal != void 0 ? o.normal : new THREE.Vector3(0,0, 1)
         this.root = o.root
+        this.dragEnable = true 
+        this.build(o)
         
         
-        //this.matrixAutoUpdate = false
-        
-        this.build()
-        
-        /* this.spot.addEventListener('mouseover',()=>{
-            
-             
-        }) */
         
     }
     
     
     
     
-    build(){
-        
-        if(!spotMat){
-            spotMat = new THREE.MeshBasicMaterial({
-                transparent:true,
-                map: texLoader.load(Potree.resourcePath+'/textures/spot_default.png' ),  
-            })
-        }
-        let endPos = this.normal.clone().multiplyScalar(this.lineLength) 
-        
-  
-        this.line = LineDraw.createLine([
-            new THREE.Vector3(0,0,0), 
-            endPos
-        ],  {mat:lineMat})
-        
-        
+    build(o){
+         
+        lineMat || (lineMat = LineDraw.createFatLineMat(Object.assign({},depthMatProp, {
+            color: '#ffffff',   useDepth :true,
+            lineWidth: 1
+        })))
         let group = new THREE.Object3D()
-        this.spot = new THREE.Mesh(planeGeo, spotMat)  
+        this.spot = new THREE.Mesh(planeGeo, new DepthBasicMaterial(Object.assign({},depthMatProp,{
+            transparent:true,
+            map: texLoader.load(Potree.resourcePath+'/textures/spot_default.png' ),  
+        })))  
         this.spot.scale.set(defaultSpotScale,defaultSpotScale,defaultSpotScale) 
-        this.titleLabel = new TextSprite({root: group, text:'1', sizeInfo:{width2d:200}, 
+        this.spot.renderOrder = renderOrders.spot;
+        
+        this.line = LineDraw.createFatLine([],  {mat:lineMat})
+        this.line.addEventListener('drag',(e)=>{ 
+            this.dragEnable && this.changePos(e)
+        })
+        this.spot.addEventListener('drag',(e)=>{
+            this.dragEnable && this.onMesh && this.changePos(e)
+        })            
+        //拖拽线来移动。虽然理想方式是拟真,拖拽时不改变在线上的位置,使之平移,但仔细想想似乎办不到。因为墙面normal是不固定的,尤其在交界处难以确定。不知鼠标在空中的位置,即使是平行镜头移动也无法满足所有情况。matterport是加了底座,移动也是改变底座中心。
+    
+        
+    
+        this.titleLabel = new TextSprite(Object.assign({},depthMatProp,{
+            root: group, text: this.title, sizeInfo:{width2d:200}, 
             textColor:{r:255,g:255,b:255,a:1.0},
-            backgroundColor:{r:0,g:0,b:0,a:0.8},
+            backgroundColor:{r:0,g:0,b:0,a:0.7},
             borderRadius: 6,  
             fontsize:13,  fontWeight:'',//thick
-            renderOrder : renderOrders.spot, pickOrder:renderOrders.spot,
-        }) //更新sprite时,实际更新的是root: spot的矩阵
-        this.spot.renderOrder = renderOrders.spot;
-        /* const mainLabelProp = { 
-            backgroundColor: {r: defaultColor.r*255, g: defaultColor.g*255, b: defaultColor.b*255, a:config.measure.default.opacity},
-            textColor: {r: textColor.r*255, g: textColor.g*255, b: textColor.b*255, a: 1.0},
-            fontsize:16, 
+            renderOrder : renderOrders.label, pickOrder:renderOrders.label,
             useDepth : true ,
-            renderOrder : 5, pickOrder:5, 
-        } */
-            
-        this.titleLabel.position.set(0,0.4,0)
-        this.titleLabel.sprite.material.depthTest = this.titleLabel.sprite.material.depthWrite = true
+        })) //更新sprite时,实际更新的是root: spot的矩阵
         
+        this.updateTitlePos()  
+        group.add(this.titleLabel)
         
-        group.position.copy(endPos)
+        let mouseover = (e)=>{
+            this.dispatchEvent('mouseover')
+        }
+        let mouseleave = (e)=>{
+            this.dispatchEvent('mouseleave')
+        }
+        let click = (e)=>{
+            this.dispatchEvent('click')
+        }
+        this.spot.addEventListener('mouseover',mouseover)
+        this.spot.addEventListener('mouseleave',mouseleave)
+        this.titleLabel.addEventListener('mouseover',mouseover)
+        this.titleLabel.addEventListener('mouseleave',mouseleave)
+        this.spot.addEventListener('click',click)
+        this.titleLabel.addEventListener('click',click)
+          
+     
         group.add(this.spot)
-        group.add(this.titleLabel)
         this.add(group);
         this.add(this.line)
         
-        
+        this.updatePose()
         
         
         viewer.scene.tags.add(this)
         
+        this.titleLabel.sprite.addEventListener('spriteUpdated',()=>{
+            this.updateDepthParams()
+        })
+    }
+    
+    updateDepthParams(){//为了避免热点嵌入墙壁,实时根据其大小更新材质系数。 但是在倾斜的角度看由于遮挡距离很大肯定会嵌入的
+        let s = this.titleLabel.parent.scale.x 
+        let names = ['clipDistance', 'occlusionDistance', 'startClipDis', 'startOcclusDis']
+        let titleSize = Math.max(this.titleLabel.sprite.scale.x, this.titleLabel.sprite.scale.y) * s
+         
+        names.forEach(name=>{
+            this.titleLabel.sprite.material.uniforms[name].value = depthMatProp[name] * titleSize
+        })
+         
+        if(this.onMesh){//not sprite,还原。 大概能不被崎岖的3dtiles地面遮住就行
+            names.forEach(name=>{
+                this.spot.material.uniforms[name].value = depthMatProp[name] 
+            }) 
+        }else{
+            let spotSize = this.spot.scale.x * s
+            names.forEach(name=>{
+                this.spot.material.uniforms[name].value = depthMatProp[name] * spotSize
+            })
+        
+        }
+        
         
     }
     
     
+    updatePose( ){
+        let endPos = this.normal.clone().multiplyScalar(this.lineLength) 
+        LineDraw.updateLine(this.line, [new THREE.Vector3(0,0,0), endPos])
+        this.titleLabel.parent.position.copy(endPos) 
+        viewer.dispatchEvent('content_changed')
+        
+        
+    }
+    
+    changeLineLen(len){
+        this.lineLength = len
+        this.updatePose() 
+    }
+    
+    
+    
+    changePos(e){ 
+        if(!e.intersect?.location)return
+        this.root = e.intersect.pointcloud || e.intersect.object
+        let localPos = Potree.Utils.datasetPosTransform({ toDataset: true,  pointcloud:e.intersect.pointcloud,  object:e.intersect.object,  position:e.intersect.location })
+        this.position.copy(localPos)
+        this.normal.copy(e.intersect.localNormal)
+        this.setNorQua()
+        this.updatePose()
+        this.dispatchEvent('posChanged')
+    }
+    
+    
     changeTitle(title){
         this.titleLabel.changeText(title)
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    changeMap(url){
+        let map = texLoader.load(url,()=>{
+            viewer.dispatchEvent('content_changed') 
+        })
+        this.spot.material.map = map
+        
+    }
+    
+    setFaceAngle(faceAngle = 0) {
+        //this.baseQuaternion = quaternion.clone()
+        
+        let delta = faceAngle - (this.faceAngle || 0)
+        //this.plane.quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(-faceAngle))
+        this.spot.rotateOnAxis(new THREE.Vector3(0,0,1), THREE.Math.degToRad(delta) )
+        //this.updateLabelPose()
+        this.faceAngle = faceAngle
+        viewer.dispatchEvent('content_changed')
     }
     
+  
+    
+    changeOnMesh(onMesh){
+        this.onMesh = onMesh
+        if(onMesh){ 
+            this.add(this.spot)
+            this.titleLabel.position.y = 0
+            this.spot.position.set(0,0,0.01)//在mesh之上偏移一点
+            this.setNorQua() 
+            this.line.renderOrder = renderOrders.spot+1 //比spot高,但比label低
+        }else{
+            this.titleLabel.parent.add(this.spot) 
+            this.updateTitlePos()
+            this.spot.position.set(0,0,0)
+            this.spot.quaternion.set(0,0,0,1)//this.titleLabel.update()
+            this.line.renderOrder = renderOrders.line //还原
+        }
+        this.updateDepthParams()
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    changeSpotScale(s){
+        this.spot.scale.set(s,s,s)
+       
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    updateTitlePos(){
+        this.onMesh || (this.titleLabel.position.y = titleHeight.uponSpot + this.spot.scale.x)
+    }
+    
+    setNorQua() {
+        if(!this.onMesh)return
+        this.spot.quaternion.setFromRotationMatrix(new THREE.Matrix4().lookAt(this.normal, Vectors.ZERO, Vectors.UP))
+        this.setFaceAngle(this.faceAngle) //quaternion被重置了,所以再设置一下faceAngle
+    }
+    
+    /* 
+    如果要像四维看看那样,在地面上时保持初始转向镜头的话,需要矫正且保存quaternion。且要根据世界normal判断是否在地面, 会随着模型改变, 所以也没法仅保存normal去矫正。
+    要不然就要直接改变faceAngle
+     */
+    
+    
     
     updateMatrixWorld(force){ //重写,只为了将root当做parent
          
@@ -140,11 +288,11 @@ class Tag extends THREE.Object3D{
     
     dispose(){
         this.parent.remove(this);
-        this.titleLabel.dispose()
+        this.titleLabel?.dispose()
         
     } 
     
-    
+     
     
 }
 

+ 1 - 1
src/custom/objects/tool/Measure.js

@@ -830,7 +830,7 @@ export class Measure extends ctrlPolygon{
             this.markers.forEach(e=>this.setMarkerSelected(e, 'hover', 'selectAll' ) )
             
             this.edges.forEach(e=>{
-                e.renderOrder = Potree.config.renderOrders.lines + 1
+                e.renderOrder = Potree.config.renderOrders.line + 1
                 e.material = this.getLineMat('edgeSelect')   
             }) 
             

+ 1 - 1
src/custom/objects/tool/TagTool.js

@@ -67,7 +67,7 @@ export class TagTool extends THREE.EventDispatcher{
 
             
             let tag = new Tag({
-                title: '1', position: localPos,  normal:e.intersect.normal,
+                title: '1', position: localPos,  normal:e.intersect.localNormal,
                 root: e.intersect.pointcloud || e.intersect.object
             }) 
             

+ 28 - 6
src/custom/potree.shim.js

@@ -140,7 +140,7 @@ var texLoader = new THREE.TextureLoader()
 
 
 {//Features
-    let gl_
+    let gl_, webgl2Support
     Features.EXT_DEPTH = { 
         isSupported: function (gl) { 
          
@@ -159,9 +159,10 @@ var texLoader = new THREE.TextureLoader()
             return (typeof WebGL2RenderingContext != 'undefined' && gl instanceof WebGL2RenderingContext) || gl.getExtension('EXT_frag_depth'); //shader中的GL_EXT_frag_depth需要判断一下detectIOS吗。。
         }
     } 
+    
     /* Features.getMaxMapSize = (gl)=>{
         // 查询最大立方体贴图纹理尺寸
-        gl = gl || gl_
+        gl = gl || gl_ 
         gl_ = gl
          
         let info = {
@@ -172,7 +173,26 @@ var texLoader = new THREE.TextureLoader()
             console.warn('cubeMap最大仅支持', info.cubeMap)
         }
         return info       
-    } */
+    }  */
+    Features.webgl2RealSupport = ()=>{
+        if(webgl2Support != void 0){
+            return webgl2Support
+        }
+        
+        let gl
+        try {
+            var canvas = document.createElement('canvas')
+            if(window.WebGL2RenderingContext){ //遇到有设备(iphone8 plus ios14.1 型号MQ8F2CH/A)直接获取webgl2后会点云和全景图闪烁,WebGL2RenderingContext和得到的context是undefined。但是为何4dkk不会闪烁
+                gl = canvas.getContext('webgl2') //麒麟系统chromium 128 到这一步才获取失败 如果直接对最终的canvas获取webgl2,会造成多viewport无法单独渲染以及clearAlpha透明失败
+            }
+        }catch (e) {
+            console.log(e)  
+        }
+        webgl2Support = !!gl 
+        return webgl2Support 
+    }
+    
+    
 }
 
 
@@ -413,13 +433,15 @@ Utils.getMousePointCloudIntersection = function(viewport, mouse, pointer, camera
     
     
     if (selectedPointcloud) {
+        let localNormal = closestPoint.normal && new THREE.Vector3().fromArray(closestPoint.normal) 
         return {
             location: closestIntersection,
             distance: closestDistance,
             pointcloud: selectedPointcloud,
             point: closestPoint,
             pointclouds: allPointclouds, //add  
-            normal: closestPoint.normal && new THREE.Vector3().fromArray(closestPoint.normal ).applyMatrix4(selectedPointcloud.rotateMatrix)//add
+            localNormal: localNormal,
+            normal: localNormal?.clone().applyMatrix4(selectedPointcloud.rotateMatrix)//add
         };
     } else {
         return null;
@@ -2036,13 +2058,13 @@ Object.assign( PointCloudOctreeGeometry.prototype, THREE.EventDispatcher.prototy
 
 
 LRU.prototype.freeMemory = function(){
-    
+     
     if (this.elements <= 1) {
         return;
     } 
     let memoryRatio = browser.isMobile() ? 2 : 5;
     //改成navvis的,使用pointBudget,否则四屏点云闪烁。 (似乎要比updateVisiblede的node时限制要宽些,作为缓存继续存着。否则会闪烁)
-    let max = THREE.Math.clamp( viewer.viewports.length * memoryRatio * Potree.pointBudget, 0, 40e6)
+    let max = THREE.Math.clamp( viewer.viewports.length * memoryRatio * Potree.pointBudget, 0, Potree.settings.maxLRUPoints)
        
     for (; this.numPoints > max;  ) { 
         var node = this.getLRUItem();

+ 9 - 7
src/custom/settings.js

@@ -67,12 +67,12 @@ const config = {//配置参数   不可修改
     
     
     urls:{
-        //localTextures:'../resources/textures/', 
-        prefix1: 'https://laser-oss.4dkankan.com',//oss 
+        //prefix: //当前网站域名
+        prefix1: 'https://laser-oss.4dkankan.com',//点云oss 
         prefix2: 'https://testlaser.4dkankan.com',
-        prefix3: 'https://4dkk.4dage.com',
-        prefix4: 'https://uat-laser.4dkankan.com',//test.4dkankan
-        prefix5: 'https://laser.4dkankan.com/backend',
+        prefix3: 'https://4dkk.4dage.com',//全景图oss 
+        prefix4: 'https://uat-laser.4dkankan.com', //测试服 线上当前网站接口域名  
+        prefix5: 'https://laser.4dkankan.com/backend',//正式服 线上当前网站接口域名
         prefix6: 'https://mix3d.4dkankan.com/backend',  //融合
         prefix7: 'https://xfhd.4dkankan.com/backend',   //融合      
     },
@@ -97,6 +97,7 @@ const config = {//配置参数   不可修改
     },
     minNodeSize:30, // perspectiveCamera允许加载的node的最小可见像素宽度。越大越省性能
     tiles3DMaxMemory: 200,//M. 最大支持3dTiles的内存大小 超出会崩溃。  改太小太大都会卡,太大崩溃
+    //崩溃历史: 200罗敏电脑崩
     pointDensity:{
         magnifier:{  
             maxLevelPercent: 1,
@@ -260,7 +261,7 @@ const config = {//配置参数   不可修改
         measureLabel: 8,
         sorptionSign:10,
         model:10,
-        
+        line: 3,
         
         magnifier:50,
     }, 
@@ -338,7 +339,7 @@ const config = {//配置参数   不可修改
         "deep orange": [ 255,61,0],
          
     },
-    
+    maxLRUPoints:40e6,
     depthTexUVyLimit: 0.141, // 在这个范围内是没有深度的,从图片算的0.14003, 设置为稍大于这个数值
     
     
@@ -401,6 +402,7 @@ let settings = {//设置   可修改
     displayMode:'',
     isTest ,
     prefix: getPrefix(),
+    maxLRUPoints: config.maxLRUPoints,
     pointDensity: '',    UserPointDensity:'',//pointDensity会随着进入不同的模块而自动改变,UserPointDensity记录了用户的设置
     UserDensityPercent:null,//点云密度百分比 
     ifShowMarker:true,//显示漫游点

+ 5 - 1
src/custom/start.js

@@ -817,6 +817,10 @@ export function mergeEditStart(dom, mapDom){
             let sceneCode = url
             Potree.loadDatasets((data)=>{ 
                 let originDataset = data.find(e=>e.sceneCode == sceneCode);//只加载初始数据集  
+                /* if(!originDataset){
+                    //应该是data为空,原因未知
+                } */
+                
                 let timeStamp = originDataset.updateTime ? originDataset.updateTime.replace(/[^0-9]/ig,'') : '';  //每重算一次后缀随updateTime更新一次 
                 //let cloudPath = `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${sceneCode}/data/${sceneCode}/webcloud/cloud.js` 
                 let cloudPath = `${Potree.settings.urls.prefix1}/${originDataset.webBin}`  //webBin添加原因:每次裁剪之类的操作会换路径,因为oss文件缓存太严重,更新慢
@@ -1137,7 +1141,7 @@ export function mergeEditStart(dom, mapDom){
         }else{  
             
              //else if(prop.type == 'las' || prop.type == 'ply' || prop.type == 'laz' ) 
- 
+            prop.url instanceof Array && (prop.url = prop.url[0]) //deal bug
             Potree.loadPointCloudScene(prop.url, prop.type, prop.modelId, prop.title, (pointcloud)=>{  
                 pointcloud.matrixAutoUpdate = true
                 pointcloud.initialPosition = pointcloud.position.clone() 

+ 3 - 3
src/custom/three.shim.js

@@ -222,9 +222,9 @@ THREE.EventDispatcher.prototype.traverse = function(callback){
 
 
 
-THREE.Object3D.prototype.traverse = function ( callback ) {
+THREE.Object3D.prototype.traverse = function ( callback, lastResult ) {
 
-    let result = callback( this );
+    let result = callback( this, lastResult ); //lastResult一般用于收集祖先的visible
     if(result && result.stopContinue){//xzw add
         return 
     }
@@ -232,7 +232,7 @@ THREE.Object3D.prototype.traverse = function ( callback ) {
     const children = this.children;
 
     for ( let i = 0, l = children.length; i < l; i ++ ) {  
-        children[ i ] && children[ i ].traverse( callback ); 
+        children[ i ] && children[ i ].traverse( callback, result ); 
     } 
 }   
 

+ 2 - 2
src/custom/utils/Common.js

@@ -391,13 +391,13 @@ var Common = {
                 } 
             }else{
                 count = maxCount  //  ? 
-            }  
+            }   
             //主要在手机端有效果。 
             return count 
         }
     })(),
     
-    getBestCountFPS(name, ifLog, minCount=1, maxCount=6, minFps = 10, maxFps = 62, minPanoCount = 200, maxPanoCount = 1000 ){
+    getBestCountFPS(name, ifLog, minCount=1, maxCount=6, minFps = 10, maxFps = 60, minPanoCount = 200, maxPanoCount = 1000 ){
         
         let fps = Potree.fps
         

+ 1 - 1
src/custom/utils/CursorDeal.js

@@ -9,7 +9,7 @@ var CursorDeal = {
     priorityEvent : [//在前面的优先级高
         {'zoomInCloud':'zoom-in'},
         {'hoverPano':'pointer'}, 
-          
+        {"notAllowed-default":'not-allowed'},   
         {'connectPano':`url({Potree.resourcePath}/images/connect.png),auto`},
         {'disconnectPano':`url({Potree.resourcePath}/images/connect-dis.png),auto`},
          

+ 2 - 2
src/custom/utils/DrawUtil.js

@@ -52,7 +52,7 @@ var LineDraw = {
         
         
         var line = new THREE.LineSegments(new THREE.BufferGeometry, mat);
-		line.renderOrder = o.renderOrder || Potree.config.renderOrders.lines
+		line.renderOrder = o.renderOrder || Potree.config.renderOrders.line
   
         this.moveLine(line, posArr)
         
@@ -121,7 +121,7 @@ var LineDraw = {
 		//line.computeLineDistances();
         line.uncontinuous = o.uncontinuous //线不连续,由线段组成
 		line.scale.set( 1, 1, 1 );
-		line.renderOrder = Potree.config.renderOrders.lines;
+		line.renderOrder = Potree.config.renderOrders.line;
         
         this.moveFatLine(line, posArr)
         

+ 2 - 2
src/custom/utils/browser.js

@@ -327,8 +327,8 @@ var browser = {
 
         return false
     },
-    maybeLowQilin(){//可能是银河麒麟桌面系统,webgl容易出bug 2024.10
-        return window.navigator.userAgent.toLowerCase().includes('linux') && viewer.renderer.capabilities.maxCubemapSize <= 2048
+    maybeQilin(){//可能是银河麒麟桌面系统,webgl容易出bug 2024.10
+        return window.navigator.userAgent.toLowerCase().includes('linux')  
          
     }
 }

+ 5 - 3
src/custom/utils/transitions.js

@@ -465,9 +465,10 @@ var transitions = {
 			var is = e.id == t;
 			
 			if(is && dealCancelFun){
-                e.cancelFun && cancels.push(e.cancelFun)
-				//e.cancelFun && e.cancelFun()
-			} 
+                e.cancelFun && cancels.push(e.cancelFun) 
+			}/* else if(is && e.cancelFun){
+                console.warn('cancelById', e.id,e.name)
+            } */
             return !is
         })
         
@@ -476,6 +477,7 @@ var transitions = {
         
     },
     cancel: function(e) {
+        //console.warn('cancel', e )
         this.funcs = this.funcs.filter(function(t) {
             return t.func !== e
         })

+ 214 - 48
src/custom/viewer/ViewerNew.js

@@ -10,6 +10,8 @@ import {PLYLoader} from "../../../libs/three.js/loaders/PLYLoader.js";
 //--------以上文件只在部分工程中添加-------------------------
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
+import Stats from "../../../libs/stats.js/stats.js";
+
 import {ClipTask, ClipMethod, CameraMode, LengthUnits, ElevationGradientRepeat} from "../../defines.js";
 import {TagTool} from "../objects/tool/TagTool.js"; 
 import Compass from "../objects/tool/Compass.js";
@@ -123,7 +125,9 @@ export class Viewer extends ViewerBase{
         window.viewer = this
         mapArea = mapArea_                 
         
-        
+        if(this.renderer.capabilities.isWebGL2){
+            Potree.settings.isWebgl2 = true  //是否启用webgl2
+        } 
          
         if(Potree.settings.editType == "pano" || Potree.settings.editType == "merge"){
             this.modules = { 
@@ -175,18 +179,38 @@ export class Viewer extends ViewerBase{
                 },10000) */
             },2000)
             
+             
             
-            this.fpsCollect = [] 
-            Potree.fps = 30  //随便写一个
-        }
-        setTimeout(()=>{
-            
-            console.log('depthSamChangeImg',Potree.timeCollect.depthSamChangeImg.median,  'sortByScore',Potree.timeCollect.sortByScore.median)
-            console.log('fps',Potree.fps)
+            {
+                this.fpsCollect = {} 
+                let init = (name)=>{ 
+                    this.fpsCollect[name] = [] 
+                    this.fpsCollect[name].durSum = 0   
+                }
+                init('short')
+                init('long')
+                init('rendered')
+                init('renderedLong') 
+                init('camChanged')
+                init('camChangedLong')
+                Potree.fps = 40  //随便写一个
+                Potree.fps2 = 40  //随便写一个
+                Potree.fpsRendered = 35  //随便写一个
+                Potree.fpsRendered2 = 35  //随便写一个
+                Potree.fpsCamChanged = 30  //随便写一个
+                Potree.fpsCamChanged2 = 30  //随便写一个
+                //this.pageHiddenCollect = []
+            } 
             
-        },25000) 
+            setTimeout(()=>{ 
+                console.log('depthSamChangeImg',Potree.timeCollect.depthSamChangeImg.median,  'sortByScore',Potree.timeCollect.sortByScore.median)
+                console.log('fps',Potree.fps, Potree.fps2,Potree.fpsRendered )
+                
+            },25000)
+        }
+         
   
-        
+        this.memoryModelCountInfo = {tilePosCount:0,tileTexArea:0,otherPosCount:0,otherTexArea:0}
         this.navigateMode = 'free' // 'panorama'; 'free'自由模式是只显示点云或者未进入到漫游点, 
         this.isEdit = true
         this.waitQueue = []  
@@ -371,9 +395,9 @@ export class Viewer extends ViewerBase{
                 this.initDragAndDrop();
             }
 
-            if(typeof Stats !== "undefined"){
+            if(Potree.settings.isTest){
                 this.stats = new Stats();
-                this.stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom
+                this.stats.showPanel( 2 ); // 0: fps, 1: ms, 2: mb, 3+: custom
                 document.body.appendChild( this.stats.dom );
             }
 
@@ -491,8 +515,7 @@ export class Viewer extends ViewerBase{
                 this.mainViewport = new Viewport(  this.scene.view, this.scene.cameraP, {
                     left:0, bottom:0, width:1, height: 1, name:'MainView' 
                 }) 
-                this.viewports = [this.mainViewport]
-                
+                this.viewports = [this.mainViewport]       
                 Potree.settings.showCompass && (this.compass = new Compass(Potree.settings.compassDom, this.mainViewport));
                 
                  
@@ -746,6 +769,7 @@ export class Viewer extends ViewerBase{
         this.addEventListener('allLoaded', ()=>{
             setTimeout(this.testPointcloudsMaxLevel.bind(this), 1000) //延迟一丢丢,等画面出现
             
+            //Potree.settings.renderAllViewports = browser.maybeQilin() && this.renderer.capabilities.maxCubemapSize <= 2048 && //麒麟chromium若获取过webgl2只渲染一个viewport的话其他的会变黑
             
             this.scene.pointclouds.forEach(pointcloud=>{
                 pointcloud.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走)
@@ -774,7 +798,7 @@ export class Viewer extends ViewerBase{
              
              
             this.createHackMesh() 
-        }) 
+        },{once:true}) 
         
          
         
@@ -875,6 +899,12 @@ export class Viewer extends ViewerBase{
                 let v = !document.hidden
                 //console.warn('visibilitychange', v )
                 this.dispatchEvent({type:'pageVisible', v } )
+                //this.pageHiddenCollect.push({time:performance.now, visible:v})
+                
+                if(!v){
+                    let marks = performance.getEntriesByName("loop-start")
+                    marks.length && (marks[marks.length-1].pageHidden = true)
+                }
                 
                 if(this.screenshoting && !v){//截图过程中离开页面需要照常loop。但是尽量别离开页面,效果只能达到90%
                     interval = setInterval(()=>{
@@ -977,7 +1007,32 @@ export class Viewer extends ViewerBase{
         
     }
     
-    
+    getObjectPointCount(object){
+        let posCount = 0, texArea = 0
+        object.traverse((mesh, o={})=>{
+            //let visi = o.visible === false ? false : mesh.visible    //mesh.realVisible() //如果祖先不可见,此mesh一定不可见
+            if(mesh.geometry){
+                posCount += mesh.geometry.attributes.position.count 
+                //visi && (visiPosCount += mesh.geometry.attributes.position.count)
+            }
+            /* if(mesh.material && !mesh.material.map){
+                console.log('!mesh.material.map ?')
+            } */
+            if(mesh.material){
+                let mats = (mesh.material instanceof Array) ? mesh.material : [mesh.material]
+                mats.forEach(mat =>{
+                    if(mat.map){
+                        texArea += mat.map.image.width * mat.map.image.height  
+                        //visi && (visiTexArea += a)
+                    }
+                })
+            }
+            
+            
+            //return {visible:visi}
+        })
+        return {posCount , texArea }
+    }
      
     
     ifPointBlockedByIntersect(point , panoId,  soon ){//点是否被遮挡
@@ -3507,7 +3562,7 @@ export class Viewer extends ViewerBase{
             if('renderBeforeCloud' in params){
                 this.scene.scene.traverse((object)=>{
                     if(object.material){  
-                        let transparent = object.material.opacity<1 || object.material.mapTransparent || !object.material.depthTest
+                        let transparent = object.material.opacity<1 || object.material.mapTransparent || !object.material.depthTest || !object.material.depthWrite //不写入深度的往往最后来比较
                         Potree.Utils.updateVisible(object, 'renderOpa',  params.renderBeforeCloud != transparent)  
                         //点云之前渲染的话隐藏半透明的, 点云之后渲染的话隐藏不透明的。  depthTest==false的也最后渲染。 mapTransparent是手动加的,代表确定该贴图含有透明部分,即使opacity为1
                     } 
@@ -3538,10 +3593,11 @@ export class Viewer extends ViewerBase{
         
         
         let mesh = new THREE.Mesh(viewer.images360.panos[0].marker.geometry, new THREE.MeshBasicMaterial({color:"#F00",side:2/* ,depthTest:false */}))
-            mesh.visible = false 
+            Potree.Utils.updateVisible(mesh,'show',false)
+            
         this.images360.node.add(mesh)
         
-        
+         
         let updatePos = ()=>{
             let dir = this.mainViewport.view.direction
             let radius = this.images360.cube.scale.length()
@@ -3558,22 +3614,19 @@ export class Viewer extends ViewerBase{
             } 
         })
         
-        this.images360.addEventListener( 'flyToPanoDone', e=>{
-            if(this.images360.currentPano.depthTex){
-                mesh.visible = false
+        let judge = ()=>{
+            if(!this.images360.currentPano?.depthTex && Potree.settings.displayMode == 'showPanos'){
+                Potree.Utils.updateVisible(mesh,'show',true)
             }else{ 
-                mesh.visible = true
-            } 
-        })
-            
-        this.images360.addEventListener( 'endChangeMode', e=>{
-            if(e.mode == 'showPanos' && !this.images360.currentPano.depthTex){
-                mesh.visible = true
-                updatePos()
-            }else{
-                mesh.visible = false
+                Potree.Utils.updateVisible(mesh,'show',false)
             }
-        })
+            
+        }
+        
+        
+        this.images360.addEventListener( 'flyToPanoDone', judge)
+            
+        this.images360.addEventListener( 'endChangeMode', judge)
         
 
         //不知道如果用点云计算非当前视角下的block会不会黑闪,如热点遮挡计算
@@ -3639,13 +3692,13 @@ export class Viewer extends ViewerBase{
 	async render(params={}){//add params 
         viewer.addTimeMark('render','start')
         const vrActive = this.renderer.xr.isPresenting;
-        let SiteModel = viewer.modules.SiteModel 
+        let {SiteModel,PanoEditor} = viewer.modules
         
         if(this.screenshoting && !params.screenshot)return //正在截图
    
         let s = SiteModel.editing && SiteModel.selected && (SiteModel.selected.buildType == 'room' || SiteModel.selected.buildType == 'floor') //空间模型的房间选中材质是需要depth的,这时候需要绘制两次点云
          
-        Potree.settings.pointEnableRT = !this.screenshoting && (this.scene.measurements.filter(e=>e.visible).length > 0 || s )
+        Potree.settings.pointEnableRT = !this.screenshoting && (this.scene.measurements.filter(e=>e.visible).length > 0 || s || PanoEditor?.entered || this.scene.tags.children.some(e=>e.visible))
         
          
         if(vrActive){
@@ -3654,11 +3707,12 @@ export class Viewer extends ViewerBase{
             let specialRender =  !!params.viewports 
             let viewports = params.viewports || this.viewports
              
-            if(!this.needRender){
+            if(!this.needRender && !Potree.settings.renderAllViewports){
                 viewports = viewports.filter(v=>v.needRender) //可以渲染的条件是viewer或viewport的needRender为true
             }
             viewports = viewports.filter(v=>v.active) 
             if(viewports.length > 0){   
+                 
                 params.viewports = viewports  
                 if(this.outlinePass.selectedObjects.length && this.outlinePass.edgeStrength > 0 && !params.screenshot 
                    // || this.images360.fastTranMaskPass.enabled
@@ -3667,7 +3721,14 @@ export class Viewer extends ViewerBase{
                     this.composer.render(scenes, null, this.viewports, this.renderDefault.bind(this));  
                 }else{  
                     await this.renderDefault(params);
-                } 
+                }
+                
+              
+                {
+                    let marks = performance.getEntriesByName("loop-start")
+                    marks.length && (marks[marks.length-1].rendered = true )
+                }
+                
             } 
             if(!specialRender) this.needRender = false            
         }
@@ -5028,6 +5089,7 @@ export class Viewer extends ViewerBase{
 			this.stats.end();
 		}
         
+        this.warnMemory()   
        
         viewer.addTimeMark('loop','end')
         viewer.addTimeMark('loopWaitNext','start')
@@ -5036,23 +5098,127 @@ export class Viewer extends ViewerBase{
         //Potree.measureTimings = 1
 	}
     
-    computeFps(){ 
+    warnMemory(){//内存高于2000可能崩溃
+        if(!performance.memory)return //国产麒麟firefox不支持
+        let size = performance.memory.usedJSHeapSize / 1e6 
+        let minSize = 1500, maxSize = 3000, intervalTime = math.linearClamp(size, [minSize, maxSize*1.2], [20000, 3000])
+        if(size > minSize){ 
+            Potree.Common.intervalTool.isWaiting('processPriorityQueue', () => {  
+                size = Math.round(size)
+                let L = math.linearClamp(size, [minSize, maxSize], [1, 0.5]);  //L从白到红是1到0.5
+                Potree.Log('warning!内存告急!usedJSHeapSize:' + size ,  {   
+                    font: {
+                        color:  "#"+new THREE.Color().setHSL(0,1,L).getHexString()
+                    }
+                })
+            }, intervalTime); 
+        }
+    } 
+    
+    computeFps11(){ 
         let marks = performance.getEntriesByName("loop-start");
-        if(marks.length){//每次都获取最后一个加入列队。因为marks会定期清除,所以自己记录一个列队
-            
+        
+        let start = performance.now()
+        if(marks.length && !document.hidden){//每次都获取最后一个加入列队。因为marks会定期清除,所以自己记录一个列队。(hidden时手动loop的不会计入)
+             
             let last = marks[marks.length-1]
             this.fpsCollect.push(last)
-            if(this.fpsCollect.length>50){
+            
+            let maxCount = 1000
+            if(this.fpsCollect.length>maxCount){
                 this.fpsCollect.splice(0,1)
-            }
-            if(this.fpsCollect.length > 1){
-                let dur = (last.startTime - this.fpsCollect[0].startTime) / 1000
-                Potree.fps = this.fpsCollect.length / dur      //有的手机高刷可以达到90以上
             } 
+            
+            let getFps = (sampleCount, oldValue, requestRendered )=>{
+                let samples = this.fpsCollect.slice(-sampleCount)
+                let invalidTime = 0
+                let lastInvalid, invalidCount = 0  
+                samples.forEach((frame,i)=>{ //扣除document.hidden时的时间
+                    if((frame.pageHidden || requestRendered && !frame.rendered) && i!=samples.length-1){ //最后一个必须执行下面那段,最后再减1
+                        invalidCount ++;
+                        lastInvalid || (lastInvalid = frame)
+                    }else{
+                        if(lastInvalid){
+                           invalidTime += frame.startTime - lastInvalid.startTime
+                           lastInvalid = null 
+                        }
+                    }
+                })
+                
+                let dur = (last.startTime - samples[0].startTime - invalidTime) / 1000
+                let frameCount = samples.length  - invalidCount - 1 //有效帧数 
+                if(frameCount > 1){
+                    if(requestRendered){
+                        console.log(frameCount,dur,frameCount / dur)
+                    }
+                    return frameCount / dur  //有的手机高刷可以达到90以上
+                }else{ 
+                    return oldValue
+                }
+                
+            }
+            
+            Potree.fps = getFps(70, Potree.fps)//即时
+            Potree.fps2 = getFps(maxCount, Potree.fps2) //较长时段
+            Potree.fpsRendered = getFps(70, Potree.fpsRendered, true) //一般只有加载东西或渲染的时候才卡,所以渲染过的fps更有意义。 暂时无视地图或其他viewer的渲染 (但一段时间内不一定有渲染,所以不一定得到)
+            //缺点:调试时因暂停导致fps变低  
         }
+        Potree.lastComFpsDur = performance.now() - start
          
-    }
-     
+    } 
+    
+    computeFps(){ 
+        let marks = performance.getEntriesByName("loop-start");
+        
+        let start = performance.now()
+        if(marks.length && !document.hidden){//每次都获取最后一个加入列队。因为marks会定期清除,所以自己记录一个列队。(hidden时手动loop的不会计入)
+             
+            let curFrame = marks[marks.length-1]
+             
+            let lastFrame = this.fpsCollect.lastFrame
+            if(lastFrame && !lastFrame.pageHidden){
+                let desc = {
+                    dur : curFrame.startTime - lastFrame.startTime
+                }
+                let joinIn = (name, maxCount)=>{
+                    let collect = this.fpsCollect[name]
+                    collect.push(desc)
+                    collect.durSum += desc.dur
+                    if(collect.length > maxCount){
+                        collect.durSum -= collect[0].dur
+                        collect.splice(0,1)
+                    }
+                    if(collect.length > 0){
+                        collect.fps = collect.length / collect.durSum * 1000
+                    }
+                }
+                joinIn('short',100)
+                joinIn('long',5000) 
+                if(lastFrame.rendered){
+                    joinIn('rendered',30),  joinIn('renderedLong',300)
+                    //lastFrame.cameraChanged && (joinIn('camChanged',30),  joinIn('camChangedLong',300))
+                }
+                 
+                Potree.fps = this.fpsCollect.short.fps //即时
+                Potree.fps2 = this.fpsCollect.long.fps  //较长时段
+                Potree.fpsRendered = this.fpsCollect.rendered.fps //一般只有加载东西或渲染的时候才卡,所以渲染过的fps更有意义。 暂时无视地图或其他viewer的渲染 (但一段时间内不一定有渲染,所以不一定得到)
+                Potree.fpsRendered2 = this.fpsCollect.renderedLong.fps  
+                /* Potree.fpsCamChanged = this.fpsCollect.camChanged.fps  
+                Potree.fpsCamChanged2 = this.fpsCollect.camChangedLong.fps   */
+                 
+                
+            }
+             
+            curFrame.cameraChanged = this.lastFrameChanged  
+            this.fpsCollect.lastFrame = curFrame
+             
+       }
+        Potree.lastComFpsDur = performance.now() - start
+         
+    }  
+    
+    
+    
 	postError(content, params = {}){
 		let message = this.postMessage(content, params);
 
@@ -5554,7 +5720,7 @@ export class Viewer extends ViewerBase{
                 options: {      
                     //dracoDecoderPath: '../utils/loaders/DRACOLoader/draco',
                     //basisTranscoderPath: '../utils/loaders/KTX2Loader/basis',
-                    maximumScreenSpaceError: fileInfo.maximumScreenSpaceError || 80,  //越小越清晰。           如果本身tiles很密很小这个值就不能很大。
+                    maximumScreenSpaceError: fileInfo.maximumScreenSpaceError || 80 ,  //越小越清晰。           如果本身tiles很密很小这个值就不能很大。
                     //maxDepth: 100, 
                     //maximumMemoryUsage: 100, //缓存大小,见tiles3DMaxMemory。单位M(但实际结果是 2.5*maximumMemoryUsage + 750  。超过2G会崩, 所以应该小于540) 若太小,密集的tile反复加载很卡. (任务管理器刷新网页后若内存不掉就要结束进程否则虚高)
                     debug: browser.urlHasValue('tilesBox'),  //show box  

+ 4 - 6
src/custom/viewer/viewerBase.js

@@ -47,14 +47,12 @@ export class ViewerBase extends THREE.EventDispatcher{
         }; 
 
         let canvas = document.createElement("canvas");
-                                     
-        let webglVer = (Potree.browser.urlHasValue('webgl1') || !window.WebGL2RenderingContext) ? 'webgl' : 'webgl2' //遇到有设备(iphone8 plus ios14.1 型号MQ8F2CH/A)直接获取webgl2后会点云和全景图闪烁,WebGL2RenderingContext和得到的context是undefined。但是为何4dkk不会闪烁
-        
+                                            //Potree.settings.renderAllViewports = browser.maybeQilin() && this.renderer.capabilities.maxCubemapSize <= 2048 && //麒麟chromium若获取过webgl2只渲染一个viewport的话其他的会变黑
+     
+        let webglVer = (args.webgl1 || Potree.browser.urlHasValue('webgl1') || !Potree.Features.webgl2RealSupport() ) ? 'webgl' : 'webgl2' 
         let context = canvas.getContext(webglVer, contextAttributes );
  
-        if(context && webglVer == 'webgl2' ){
-            Potree.settings.isWebgl2 = true
-        } 
+        
 
         
 

+ 1 - 1
src/materials/ExtendEyeDomeLightingMaterial.js

@@ -29,7 +29,7 @@ export class ExtendEyeDomeLightingMaterial extends EyeDomeLightingMaterial{
         let {vs,fs} = Common.changeShaderToWebgl2(
             defines + Shaders['edl_new.vs'], defines + Shaders['edl_new.fs'], 'RawShaderMaterial'
         )
-        this.glslVersion = '300 es'
+        this.glslVersion = Potree.settings.isWebgl2 && '300 es'
         this.vertexShader = vs
         this.fragmentShader = fs 
         

+ 7 - 2
src/materials/shaders/depthBasic.fs

@@ -35,6 +35,8 @@ uniform vec3 baseColor;
     uniform vec3 backColor;
     uniform float occlusionDistance;
     uniform float clipDistance;
+    uniform float startClipDis;
+    uniform float startOcclusDis;
     uniform float maxClipFactor;
     uniform float maxOcclusionFactor;
     //uniform bool uUseOrthographicCamera;                                    
@@ -89,8 +91,11 @@ void main() {
             // of the current fragment withing those zones.
             
             
-            mixFactor = clamp(delta / occlusionDistance, 0.0, maxOcclusionFactor);
-            clipFactor = clamp(delta / clipDistance, 0.0, maxClipFactor);
+            mixFactor = clamp((delta - startOcclusDis) / (occlusionDistance - startOcclusDis), 0.0, maxOcclusionFactor);
+            clipFactor = clamp((delta - startClipDis) / (clipDistance - startClipDis), 0.0, maxClipFactor);
+            
+            
+            
         }
         
         // If the fragment is totally transparent, don't bother drawing it

+ 8 - 5
src/navigation/InputHandlerNew.js

@@ -951,15 +951,18 @@ export class InputHandler extends THREE.EventDispatcher {
                 
                 
                 if(allElements[0]){
-                    let normal 
+                    let normal, localNormal 
                     if(allElements[0].object.fileType == '3dgs' ){
                         normal = allElements[0].normal
                     }else{ 
-                        let nor = allElements[0].face && allElements[0].face.normal 
-                        if(nor){
+                        localNormal = allElements[0].face && allElements[0].face.normal 
+                        if(localNormal){ 
                             let quaternion = new THREE.Quaternion
                             allElements[0].oriObject.matrixWorld.decompose( new THREE.Vector3, quaternion, new THREE.Vector3 ) 
-                            normal = nor.applyQuaternion(quaternion) 
+                            normal = localNormal.clone().applyQuaternion(quaternion) 
+                            
+                            allElements[0].object.matrix.decompose( new THREE.Vector3, quaternion, new THREE.Vector3 ) 
+                            localNormal = normal.applyQuaternion(quaternion.invert()) //改为在最外层model上无旋转时的normal
                         }
                     }
                     
@@ -967,7 +970,7 @@ export class InputHandler extends THREE.EventDispatcher {
                     intersectOnModel = {//模拟点云的intersectPoint的结构写法
                         hoveredElement : allElements[0] ,
                         location: allElements[0].point,
-                        //point: {normal: allElements[0].face.normal },
+                        localNormal,
                         normal,
                         distance: allElements[0].distance,
                         object: allElements[0].object

+ 13 - 13
src/viewer/EDLRendererNew.js

@@ -98,18 +98,18 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
         
         
         if(Features.EXT_DEPTH.isSupported()){ 
-            if(params.rtEDL){
-                renderer.setRenderTarget( params.rtEDL);
-                renderer.clear() 
-            }else{
-                var rtEDL = this.getRtEDL(params.viewport)
-                if(rtEDL){
-                    renderer.setRenderTarget( rtEDL );
-                    renderer.setClearAlpha(0)
-                    renderer.clear( true, true, true );
-                }
-                
+            let rtEDL = params.rtEDL || this.getRtEDL(params.viewport)
+            
+                 
+            if(rtEDL){
+                renderer.setRenderTarget( rtEDL );
+                let oldAlpha = renderer.getClearAlpha()
+                renderer.setClearAlpha(0) 
+                renderer.clear( true, true, true );
+                renderer.setClearAlpha(oldAlpha)
             }
+                
+            
         }
         
         
@@ -341,8 +341,8 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
                 uniforms.opacity.value = viewer.edlOpacity; // HACK 
                  
                 Utils.screenPass.render(renderer, this.edlMaterial, target);  //相当于一个描边后期特效。 缺点: 因为target上的没有抗锯齿,所以点云在晃动镜头时会不稳定地闪烁1px位置。优点:比不打开edl少绘制一次点云,更流畅了?!
-            }else if(Potree.settings.useRTPoint && rtEDL){ 
-            
+            }else if(Potree.settings.useRTPoint && rtEDL && visiblePointClouds2.every(e=>e.material.opacity>=1)){ 
+                //半透明点云在clearAlpha为非1的状态下截图 或 在useRTPoints时绘制到屏幕上透明度不对,亮度变低 。 只能在半透明时关闭一下useRTPoint
                 this.recoverToScreenMat.uniforms.tDiffuse.value = rtEDL.texture; 
                 if(this.recoverToScreenMat.defines.useDepth){
                     this.recoverToScreenMat.uniforms.depthTex.value = rtEDL.depthTexture; 

+ 2 - 2
src/viewer/ExtendView.js

@@ -211,12 +211,12 @@ class ExtendView extends View {
         let done = ()=>{ //一定要旋转和位移都结束了才能执行
             
             let f = ()=>{ 
-                this.position.copy(endPosition)  //因为延时1后control的update会导致位置改变
+                this.position.copy(endPosition)  //因为延时 后control的update会导致位置改变
                 info.callback && info.callback()   
                 this.dispatchEvent('flyingDone')  
             }
             if(info.duration){
-                setTimeout(f,1)//延迟是为了使isFlying先为false
+                setTimeout(f,10)//延迟是为了使isFlying先为false  1有概率不够
             }else{
                 f()  //有的需要迅速执行回调
             }