Forráskód Böngészése

fix: 方向键控制漫游方向错误+麒麟系统webgl兼容

xzw 9 hónapja
szülő
commit
9794649029
43 módosított fájl, 5669 hozzáadás és 2855 törlés
  1. 2 2
      examples/lion_las.html
  2. 1 0
      examples/lion_laz.html
  3. 3 3
      gulpfile.js
  4. 3863 1965
      libs/gaussian/gaussian-splats-3d.module.js
  5. 0 1
      libs/gaussian/gaussian-splats-3d.module.js.map
  6. 0 2
      libs/gaussian/gaussian-splats-3d.module.min.js
  7. 0 1
      libs/gaussian/gaussian-splats-3d.module.min.js.map
  8. 1 1
      libs/three.js/loaders/STLLoader.js
  9. 12 12
      src/ExtendPointCloudOctree.js
  10. 294 7
      src/Potree.js
  11. 4 4
      src/PotreeRendererNew.js
  12. 29 7
      src/custom/materials/ModelTextureMaterial.js
  13. 48 35
      src/custom/mergeStartTest.js
  14. 177 11
      src/custom/modules/CameraAnimation/CamAniEditor.js
  15. 42 15
      src/custom/modules/datasetAlignment/Alignment.js
  16. 31 18
      src/custom/modules/mergeModel/MergeEditor.js
  17. 273 117
      src/custom/modules/panoEdit/panoEditor.js
  18. 25 19
      src/custom/modules/panos/DepthImageSampler.js
  19. 200 154
      src/custom/modules/panos/Images360.js
  20. 49 58
      src/custom/modules/panos/Panorama.js
  21. 6 4
      src/custom/modules/panos/tile/PanoRenderer.js
  22. 3 0
      src/custom/modules/panos/tile/QualityManager.js
  23. 42 12
      src/custom/modules/panos/tile/TileDownloader.js
  24. 61 39
      src/custom/modules/panos/tile/TilePrioritizer.js
  25. 57 60
      src/custom/modules/siteModel/SiteModel.js
  26. 0 2
      src/custom/modules/volumeCompute/Prism.js
  27. 6 4
      src/custom/objects/Reticule.js
  28. 6 2
      src/custom/objects/TextSprite.js
  29. 22 8
      src/custom/objects/tool/TransformControls.js
  30. 27 9
      src/custom/potree.shim.js
  31. 7 4
      src/custom/settings.js
  32. 54 184
      src/custom/start.js
  33. 75 8
      src/custom/utils/Common.js
  34. 4 0
      src/custom/utils/browser.js
  35. 2 2
      src/custom/utils/transitions.js
  36. 179 54
      src/custom/viewer/ViewerNew.js
  37. 21 14
      src/custom/viewer/map/Map.js
  38. 9 3
      src/custom/viewer/map/MapViewer.js
  39. 3 3
      src/navigation/FirstPersonControlsNew.js
  40. 2 1
      src/navigation/InputHandlerNew.js
  41. 24 7
      src/navigation/OrbitControlsNew.js
  42. 4 2
      src/viewer/EDLRendererNew.js
  43. 1 1
      src/viewer/ExtendScene.js

+ 2 - 2
examples/lion_las.html

@@ -56,7 +56,7 @@
 		});
 		
 		// Sigeom
-		Potree.loadPointCloud("../pointclouds/lion_takanawa_las/cloud.js", "lion", function(e){
+        Potree.loadPointCloud("../pointclouds/lion_takanawa_las/cloud.js", "lion", "lion", 0, function(e){ 
 			viewer.scene.addPointCloud(e.pointcloud);
 			
 			let material = e.pointcloud.material;
@@ -69,7 +69,7 @@
 			
 			viewer.fitToScreen();
 		});
-		
+		//还有一种一整个的las文件,无法打开。这里加载的是类似bin的分层加载的二进制文件
 	</script>
 	
 	

+ 1 - 0
examples/lion_laz.html

@@ -57,6 +57,7 @@
 			//viewer.toggleSidebar();
 		});
 		//cloudPath, dataset.name ,datasetCode, timeStamp, 
+         
 		Potree.loadPointCloud("../pointclouds/lion_takanawa_laz/cloud.js", "lion", "lion", 0, function(e){
 			viewer.scene.addPointCloud(e.pointcloud);
 			

+ 3 - 3
gulpfile.js

@@ -219,9 +219,9 @@ gulp.task('watch', gulp.parallel("build", "pack", "webserver", async function()
 		'src/**/*.html',
 		'src/**/*.vs',
 		'src/**/*.fs',
-		'resources/**/*',
-		'examples//**/*.json',
-		'!resources/icons/index.html',
+		//'resources/**/*',
+		//'examples//**/*.json',
+		//'!resources/icons/index.html',
 	];
 
 	watch(watchlist, gulp.series("build", "pack"));

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3863 - 1965
libs/gaussian/gaussian-splats-3d.module.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 1
libs/gaussian/gaussian-splats-3d.module.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 2
libs/gaussian/gaussian-splats-3d.module.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 1
libs/gaussian/gaussian-splats-3d.module.min.js.map


+ 1 - 1
libs/three.js/loaders/STLLoader.js

@@ -6,7 +6,7 @@ import {
 	Loader,
 	LoaderUtils,
 	Vector3
-} from '../../../build/three.module.js';
+} from  '../build/three.module.js';
 
 /**
  * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.

+ 12 - 12
src/ExtendPointCloudOctree.js

@@ -114,7 +114,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
          
         let levels = this.visibleNodes.map(e=>e.getLevel())
         let actMaxLevel = Math.max.apply(null, levels) //实际加载到的最高的node level
-        if(actMaxLevel <  this.maxLevel)return true// 还没加载到能加载到的最高。  但在细节设置较低时,排除作用微弱。
+        if(actMaxLevel <  this.maxLevel)return true// 还没加载到能加载到的最高。  但在细节设置较低时,排除作用微弱。 
         
          
         //尝试加载出更高级的level 
@@ -270,7 +270,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
 		let nodes = this.nodesOnRay(this.visibleNodes, ray);
  
 		if (nodes.length === 0) { 
-            
+            //console.log('nodes.length === 0')
 			return null;
 		}
         //console.log('nodes.length != 0', this.name)
@@ -333,6 +333,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
 			parseInt(pixelPos.y - (pickWindowSize - 1) / 2),
 			parseInt(pickWindowSize), parseInt(pickWindowSize));
              */ //---这段没用 
+             
         pickState.renderTarget.scissor.set(parseInt(pixelPos.x - (pickWindowSize - 1) / 2),  parseInt(pixelPos.y - (pickWindowSize - 1) / 2),parseInt(pickWindowSize), parseInt(pickWindowSize)); 
         pickState.renderTarget.scissorTest = true 
 
@@ -343,13 +344,13 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
 
 		{ // RENDER
 			renderer.setRenderTarget(pickState.renderTarget);
-			gl.clearColor(0, 0, 0, 0);
-			//renderer.clear(true, true, true);
+            //gl.clearColor(0, 0, 0, 0);
+            renderer.setClearColor(0x000000, 0 )
 
 			let tmp = this.material;
 			this.material = pickMaterial;
             
-			pRenderer.renderOctree(this, nodes, camera, pickState.renderTarget); //只绘制ray pick到的部分nodes
+            pRenderer.renderOctree(this, nodes, camera, pickState.renderTarget); //只绘制ray pick到的部分nodes
             screenshot();
             
 
@@ -376,8 +377,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
 		renderer.setScissorTest(false);
 		gl.disable(gl.SCISSOR_TEST);
         
-        
-        
+   
 		let pixels = buffer;
 		let ibuffer = new Uint32Array(buffer.buffer); //四个数整合成一个
 
@@ -556,7 +556,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
     // 设置点大小
     changePointSize(num, sizeFitToLevel) {
         let size, nodeMaxLevel
-        
+        //console.error('changePointSize',num)
         let dontRender = viewer.dealBeforeRender
          
         if(this.material.pointSizeType != PointSizeType.ATTENUATED){
@@ -569,7 +569,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
                 this.temp.pointSize = num_ 
             }
             num_ = num_ / (Potree.config.material.realPointSize / Potree.config.material.pointSize) //兼容 
-               
+            num_ *= this.scale.x ;//for mergeEditor   
             //num_ = Math.pow(num_, 1.05) * 5 
              
             
@@ -579,7 +579,7 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
                 let str = this.temp.pointSize+':'+this.maxLevel+':'+ nodeMaxLevel
                 let value = this.temp.sizeFitToLevel[str]  //储存。防止每次渲染(反复切换density)都要算。
                 if(value){
-                    size = value
+                    size = value *= this.scale.x  
                 }else{
                     if(this.maxLevel == Infinity)return
                     let base = this.material.spacing / Math.pow(2, this.maxLevel) //点云大小在level为0时设置为spacing,每长一级,大小就除以2.  (不同场景还是会有偏差)
@@ -593,13 +593,13 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
                     this.temp.sizeFitToLevel[str] = size
                 }
             }else{ 
-                let base = 0.015 
+                let base = 0.015 //2024.8.21 又要求需要不同场景点云大小相同,所以只能固定值了  (正式环境这两个场景 SG-5yxFuWhEMlg SG-XZNyvCDk0jz)
                 //let base = this.material.spacing / Math.pow(2,  nodeMaxLevel) //在保证最高级别大小刚好的情况下,不改变level降低后的大小
                 //base的数值理论上应该是右侧算出来的,但发现有的场景nodeMaxLevel和nodeMaxLevelPredict差别较大的点云显示也过大,而直接换成固定值反而可以适应所有场景。该固定值来源于 getHighestNodeSpacing 最小值,修改了下。(会不会是我们的相机其实该值是固定的,根据该值算出的spacing才是有误差的? 如果换了相机是否要改值?)
                 //2022-12-21又换回非固定值。因为有的场景如SS-t-t01myDqnfE的两个数据集密集程度差别很大,应该将稀疏点云的大小设置的大些。 但是这样的缺点是两个数据集因相接处有大有小无法融合。
                 size = base * 20 * num_ 
             } 
-            
+            //不同数据集点云最高级别时的间距不同,但不知道如何计算,  级别越高点云越密,spacing越大初始级别越密。 但实际并非2的level次方,底数可能1-2之间
         }  
         //console.log('changePointSize:'  + this.dataset_id + ' , num: ' + (num && num.toPrecision(3)) + ' , size: ' + size.toPrecision(3),  'nodeMaxLevel', nodeMaxLevel.toPrecision(3), 'testMaxNodeCount',this.testMaxNodeCount     /* this.material.spacing */)
         if(size){

+ 294 - 7
src/Potree.js

@@ -228,6 +228,275 @@ export async function loadDatasets(callback,sceneCode,onError,prefix){//之后
 }
 
 
+
+export function loadNeighborFile(){
+    if(Potree.config.neighbourPath){
+        let path = `${Potree.scriptPath}/${Potree.config.neighbourPath}`
+       
+        loadFile(path, null, (data)=>{
+            Potree.extraNeighbours = data
+            for(let datasetid in data){
+                let pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == datasetid)
+                
+                let types = ['enable','unable']
+                types.forEach(type=>{
+                    if(!data[datasetid][type])return
+                    for(let id1 in data[datasetid][type]){
+                        data[datasetid][type][id1].forEach(id2=>{
+                            let pano2,  sid = id2
+                            if(!id2.includes('|')){
+                                sid = datasetid+'|'+id2
+                            } 
+                            Potree.setExtraNeighbour(datasetid+'|'+id1, sid, type, {dontWrite:true}) 
+                        })
+                    } 
+                }) 
+            }
+        }) 
+    }
+}
+
+
+
+/* 
+Potree.setExtraNeighbour('1765955963253690368|90','1768872851646451713|7', 'enable' )
+
+JSON.stringify(Potree.extraNeighbours)
+ */
+export function setExtraNeighbour(sid1,sid2,type, {dontWrite}={}){
+    if(!Potree.extraNeighbours)Potree.extraNeighbours = {};
+    
+    
+    (()=>{
+        if(!dontWrite){  
+            if(sid1 > sid2){//顺序固定一下要不然两边都要写
+                let temp = sid1     //对调
+                sid1 = sid2
+                sid2 = temp  
+            }
+            
+            let id1 = sid1.split('|')[1], id2, object    
+            object = Potree.extraNeighbours[sid1.split('|')[0]]
+            if(!object) {
+                if(type == 'clear')return
+                object = Potree.extraNeighbours[sid1.split('|')[0]] = {}
+            }
+            if(type!='clear'){
+                if(!object[type]){
+                    object[type] = {}
+                }
+                
+                if(!object[type][id1]){
+                    object[type][id1] = []
+                }
+            }
+            
+            
+            if(sid1.split('|')[0] == sid2.split('|')[0]){
+                id2 = sid2.split('|')[1]
+            }else{
+                id2 = sid2
+            }
+            
+            let remove = (type_)=>{
+                if(object[type_] && object[type_][id1] ){
+                    let index = object[type_][id1].indexOf(id2)
+                    index>-1 && object[type_][id1].splice(index,1)
+                }
+            }
+            
+            if(type != 'clear'){
+                if(!object[type][id1].includes(id2)){
+                    object[type][id1].push(id2)
+                }
+            }else{
+                remove('unable')
+            }
+            
+            let anotherType = type == 'enable' ? 'unable' : 'enable'
+            remove(anotherType)
+             
+            
+        } 
+    })()     
+    
+    let pano1 = viewer.images360.getPano(sid1,'sid'),
+        pano2 = viewer.images360.getPano(sid2,'sid')
+        
+        
+    if(type == 'enable'){
+       
+        viewer.images360.neighbourMap[pano1.id][pano2.id] = 1 //用0和1 表示是在此被强制修改的
+        viewer.images360.neighbourMap[pano2.id][pano1.id] = 1
+        pano1.neighbours.push(pano2)
+        pano2.neighbours.push(pano1) 
+        
+    }else{
+        if(type == 'clear') {
+            viewer.images360.neighbourMap[pano1.id][pano2.id] = undefined
+            viewer.images360.neighbourMap[pano2.id][pano1.id] = undefined
+        }else{
+            viewer.images360.neighbourMap[pano1.id][pano2.id] = 0
+            viewer.images360.neighbourMap[pano2.id][pano1.id] = 0
+        }
+        let index = pano1.neighbours.indexOf(pano2)
+        index>-1 && pano1.neighbours.splice(index,1)
+        index = pano2.neighbours.indexOf(pano1)
+        index>-1 && pano2.neighbours.splice(index,1) 
+        
+    }
+    
+
+}
+
+/* 开启漫游点通行的强制设置: 在浏览器中加入后缀 &neighGui,如 http://127.0.0.1:5002/offline.html?m=1829339452406239232&neighGui#/
+    开启后面板右侧会出现一列按钮。 在需要更改连通性的两个漫游点点击第一个按钮,就能选中点位。然后点击第二个按钮可以强行连接漫游点,使原本无法通行的两个漫游点可以互相通行;
+    类似的,第三个按钮则是强行断开漫游点,使原本可以通行的两个漫游点无法通行。 第四个按钮则用于取消设置。点击第五个按钮可以导出设置好的数据在剪贴板里,直接粘贴到extraNeighbours.js里保存, 该文件在离线包的 www\static\lib\potree里。下次刷新场景设置就生效了。
+     
+    
+    注意:settings中neighbourPath需要设置才会读取extraNeighbours.js。  如果新的离线包也需要设置的话就先创建一个空的extraNeighbours.js
+ 
+ Enable Roaming Point Passage Forced Settings: 
+Add the suffix &neighGui to the URL in the browser, for example:http://127.0.0.1:5002/offline.html?m=1829339452406239232&neighGui#/
+
+󠁪After enabling, a column of buttons will appear on the right side of the backend panel.
+
+To change the connectivity between two roaming points, click the first button on both points to select them. Then, click the second button to forcibly connect the roaming points, allowing two originally unconnected roaming points to become interconnected.Similarly, the third button forcibly disconnects the roaming points, preventing two originally connected roaming points from being accessible to each other.The fourth button is used to cancel the settings.
+
+Clicking the fifth button exports the configured data to the clipboard. You can directly paste this data into extraNeighbours.js located in the offline package's     www\static\lib\potree   directory. The settings will take effect the next time you refresh the scene.
+ 
+ 
+ */
+
+
+
+export function setNeighborGui(){
+    if(!Potree.settings.showNeighSetGui)return
+    
+    
+    let button1 = $('<button style="bottom:70%; background:#fff;">Select current roaming point</button>')
+    let button2 = $('<button style="bottom:60%; background:#0F4;">Make the two points absolutely connected</button>')
+    let button3 = $('<button style="bottom:50%; background:#766;">Make the two points absolutely disconnected</button>')
+    let button4 = $('<button style="bottom:40%; background:#ffffff4d;">Clear the connection state of these two points</button>');
+    let button5 = $('<button style="bottom:30%; background:#cacfff;">Copy Connections data </button>');
+    ([button1,button2,button3,button4,button5]).forEach(button=>{
+        button.css({
+            right:'4px','z-index':100, position:'absolute', width:'130px',padding:'4px','border-radius':'6px',color:'#222'
+        })
+        viewer.renderer.domElement.parentElement.appendChild(button[0])
+    })
+    
+    
+    
+    const bgColor = {
+        normal: {r: 0, g: 0, b: 0, a: 0.4 },
+        connected :  {r: 0, g: 240, b: 100, a: 0.6 },
+        disconnected :  {r: 130, g: 120, b: 120, a: 0.6 },
+    }
+    let createLabel = ()=>{  
+        let panoLabel = new Potree.TextSprite({
+            sizeInfo: {minSize : 40 ,  maxSize : 150,   nearBound : 1, farBound : 50},
+            backgroundColor:bgColor.normal,
+            textColor:{r: 255, g: 255, b: 255, a: 1 }, 
+            margin:{x:20,y:14},
+            fontsize:20,
+            borderRadius: 20,
+            renderOrder:10,
+            depthTest:false,
+            text:'default'
+        }); 
+        viewer.images360.node.add(panoLabel);
+        panoLabel.visible = false
+        Potree.Utils.setObjectLayers(panoLabel,'bothMapAndScene')
+        return panoLabel
+    }
+    let panoLabels = [createLabel(), createLabel()]
+    
+    let panoSelected = []
+    let updateButton = ()=>{
+        if(panoSelected.length == 2){
+            button2.show()
+            button3.show()
+            button4.show()
+        }else{
+            button2.hide()
+            button3.hide()
+            button4.hide()
+        }
+            
+        
+    }
+    
+    let updateLabel = ()=>{
+        let i=0
+        let connectState
+        if(panoSelected.length == 2){
+             connectState = viewer.images360.neighbourMap[panoSelected[0].id][panoSelected[1].id]
+              
+        } 
+        while(i<2){
+            if(panoSelected[i]) {
+                panoLabels[i].visible = true 
+                panoLabels[i].position.copy(panoSelected[i].position)
+                panoLabels[i].position.z -= 0.5
+                
+                
+                let text = ['SID of current point:',panoSelected[i].sid]
+                if(connectState === 1) {
+                    text.push('Already absolutely connected to:', panoSelected[(i+1)%2].sid)
+                    panoLabels[i].setBackgroundColor(bgColor.connected)
+                }else if(connectState === 0){
+                    text.push('Already absolutely disconnected to:', panoSelected[(i+1)%2].sid)
+                    panoLabels[i].setBackgroundColor(bgColor.disconnected)
+                }else{
+                    panoLabels[i].setBackgroundColor(bgColor.normal)
+                }
+                
+                panoLabels[i].setText(text)
+            }else{
+                panoLabels[i].visible = false 
+            }
+            i++
+        }
+        viewer.dispatchEvent('content_changed')
+        viewer.mapViewer.dispatchEvent('content_changed')
+    }
+    
+    button1.on('click',(e)=>{
+        if(!viewer.images360.isAtPano())return alert('Please walk to the roaming point before clicking')
+        if(panoSelected.includes(viewer.images360.currentPano))return
+        if(panoSelected.length==2){
+            panoSelected.splice(0,1)
+        }
+        panoSelected.push(viewer.images360.currentPano)
+        
+        updateLabel()
+        updateButton()
+    })
+    button2.on('click',(e)=>{
+        Potree.setExtraNeighbour(panoSelected[0].sid,panoSelected[1].sid,'enable')
+        updateLabel()
+    })
+    button3.on('click',(e)=>{
+        Potree.setExtraNeighbour(panoSelected[0].sid,panoSelected[1].sid,'unable')
+        updateLabel()
+    })
+    button4.on('click',(e)=>{
+        Potree.setExtraNeighbour(panoSelected[0].sid,panoSelected[1].sid,'clear')
+        updateLabel()
+    })
+    button5.on('click',(e)=>{
+        navigator.clipboard.writeText(JSON.stringify(Potree.extraNeighbours||{}))
+    })
+    
+    updateButton()
+    updateLabel()
+     
+}
+
+
+
+
 //目前上传平面图后如果不点击保存按钮,数据还是旧的不生效
 export async function loadMapEntity(datasetId, force){ 
     if(!Potree.settings.floorplanEnable && !force && Potree.fileServer  )return /* 等待平面图类型定义好会加载 */
@@ -312,17 +581,17 @@ export async function loadPanosInfo(callback){
     
 }
 
-export function load4dkkPanos(sceneCode, model, done){ //加载四维看看的漫游点并转换
+export function load4dkkPanos(sceneCode, model, defaultRotation, done, tileRes){ //加载四维看看的漫游点并转换
     model.is4dkkModel = true
     model.panos = []
     //模拟点云,需要rotX(90)+平移一段才能和四维看看的bound一样
-    let rot1M = new THREE.Matrix4()
+    let rot1M = new THREE.Matrix4().makeRotationFromEuler(defaultRotation)
     let pos1 = new THREE.Vector3
      
     if(model.fileType == '3dTiles'){
         pos1.fromArray(model.runtime.getTileset().tileset.root.boundingVolume.box.slice(0,3))//必须要平移一段才能重合 
         pos1.copy(Potree.math.convertVector.ZupToYup(pos1))
-        rot1M.makeRotationX(Math.PI/2);
+        //rot1M.makeRotationX(Math.PI/2);
     } 
     let pos1M = new THREE.Matrix4().setPosition(pos1)
     model.posRot1MatrixInvert = new THREE.Matrix4().multiplyMatrices(pos1M, rot1M ).invert();
@@ -330,13 +599,24 @@ export function load4dkkPanos(sceneCode, model, done){ //加载四维看看的
     
     model.transformMatrix = new THREE.Matrix4
     model.rotateMatrix = new THREE.Matrix4
+    model.transformInvMatrix = new THREE.Matrix4
+    model.rotateInvMatrix = new THREE.Matrix4
+    
     model.datasetData = {
-        sceneVersion : 'V4'
+        sceneVersion : 'V4' 
     }
-    model.sceneCode = sceneCode
+    model.tileRes = tileRes
     model.bound = new THREE.Box3
     
-    let path = `https://4dkk.4dage.com/scene_view_data/${sceneCode}/images/vision.txt`
+    let path 
+    if(sceneCode.includes('/') ){
+        path = sceneCode   
+    }else{
+        path = `https://4dkk.4dage.com/scene_view_data/${sceneCode}/images/vision.txt`
+    }
+    model.sceneCode = sceneCode
+     
+    
     loadFile(path,{},(data)=>{
          
         let panoData = data.sweepLocations.map(e=>{
@@ -372,7 +652,7 @@ export function load4dkkPanos(sceneCode, model, done){ //加载四维看看的
         
         
         viewer.modules.MergeEditor.modelTransformCallback(model, true) //初始化pano的pose
-        done()
+        done && done()
     })
     /*  neighbours: e.visibles3 || e.visibles,
         noBlocks: e.visibles2,
@@ -628,6 +908,13 @@ export function loadPointCloud(path, name, sceneCode, timeStamp, callback, onErr
 };
 
 
+
+
+
+
+
+
+
 // add selectgroup
 (function($){
 	$.fn.extend({

+ 4 - 4
src/PotreeRendererNew.js

@@ -725,7 +725,8 @@ export class Renderer {
 
 
 	renderNodes(octree, nodes, visibilityTextureData, camera, target, shader, params) {
- 
+   
+
 		viewer.addTimeMark('renderNodes','start')
 
 		let gl = this.gl;
@@ -743,8 +744,7 @@ export class Renderer {
 		let mat4holder = new Float32Array(16);
 
 		let i = 0;
-        
-        
+         
         //---------从renderOctree搬到这----
         shader.setUniform1f("size", material.usePanoMap ? Potree.config.material.absolutePanoramaSize * Math.min(window.devicePixelRatio,2) : material.size);//usePanoMap时控制在不大不小的范围内感觉较好,考虑到有的点云稀疏,用大一点的点
         shader.setUniform1f("uOpacity", material.usePanoMap ? 1: material.opacity);
@@ -1774,7 +1774,7 @@ export class Renderer {
             
             traversalResult.octrees.forEach(tree=>{
                 if(tree.material.opacity==1){
-                    tree._z = Infinity //不透明的先渲染
+                    tree._z = Infinity //不透明的先渲染   //2024.10发现没用,会透过不透明的看到后面透明的orz
                 }else{
                     let center = tree.boundCenter ? tree.boundCenter.clone() : tree.boundingBox.getCenter(tree.boundCenter).applyMatrix4(tree.matrixWorld) 
                     center.project(camera) 

+ 29 - 7
src/custom/materials/ModelTextureMaterial.js

@@ -273,15 +273,35 @@ let shader = {
 
                     vec4 clipPos = ndcPos / gl_FragCoord.w;
                     vec4 eyePos = inverseProjectionMatrix * clipPos;
-                    vec2 depth0 = vec2(0.0,0.0); 
-                    #if defined(usePanoMap0)
-                        depth0 = getDepth(vWorldPosition0N, depthMap0, cameraHeight0, ceilHeight0, eyePos);
+                    vec2 depth0 = vec2(0.0,0.0);
+                    vec2 depth1 = vec2(0.0,0.0);
+                    float depth = 0.0;
+                    
+                    bool useDepth0 = false;
+                    bool useDepth1 = true;
+                    
+                    
+                    #if defined(usePanoMap0) 
+                        useDepth0 = true;
+                        #if defined(UnableMixTwoDepth) 
+                            if(progress_<0.5){
+                                useDepth1 = false;
+                            }else{
+                                useDepth0 = false; 
+                            }
+                        #endif
+                        if(useDepth0) depth0 = getDepth(vWorldPosition0N, depthMap0, cameraHeight0, ceilHeight0, eyePos);
                     #endif
-                    vec2 depth1 = getDepth(vWorldPosition1N, depthMap1, cameraHeight1, ceilHeight1, eyePos);
                     
+                    if(useDepth1) depth1 = getDepth(vWorldPosition1N, depthMap1, cameraHeight1, ceilHeight1, eyePos);
                     
-                    gl_FragDepthEXT = mix(depth0.y,depth1.y,progress_); 
-                    gl_FragDepthEXT = clamp(gl_FragDepthEXT, 0.0, 1.0);    //防止部分手机出现黑块。ios 16  。 因为我给的超远值超出范围
+                    
+                    #if defined(UnableMixTwoDepth)
+                        depth = useDepth0 ? depth0.y : depth1.y;//不支持叠加,只能用其中一个,过渡时无法渐变
+                    #else
+                        depth = mix(depth0.y,depth1.y,progress_); 
+                    #endif
+                    gl_FragDepthEXT = clamp(depth, 0.0, 1.0);    //防止部分手机出现黑块。ios 16  。 因为我给的超远值超出范围
                     
 
                 #endif
@@ -297,7 +317,9 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
 	constructor( ){ 
     
         let defines = {depthTexUVyLimit: Potree.config.depthTexUVyLimit}
-         
+        if(Potree.browser.maybeLowQilin()){
+            defines.UnableMixTwoDepth = 1  //该系统在开启硬件加速后,webgl容易出bug。如过渡时黑屏报错,因无法将两个depth叠加。见bug记录
+        } 
         let {vs,fs} = Common.changeShaderToWebgl2(shader.vertexShader, shader.fragmentShader, 'RawShaderMaterial')
         
         super({

+ 48 - 35
src/custom/mergeStartTest.js

@@ -1,21 +1,27 @@
+ 
+/* 
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {settings, config} from './settings.js' 
 import math from './utils/math.js' 
 import browser from './utils/browser.js' 
-import {Utils} from "./../utils.js"
-import cameraLight from './utils/cameraLight.js'  
+import {Utils} from "./../utils.js"  
 import {MeshDraw} from './utils/DrawUtil.js' 
 import './three.shim.js'  
-import "./potree.shim.js"
+import "./potree.shim.js" 
+ */  //注释原因:在start中已经import了,而该文件没被打包在potree.js内, 重复import的话会访问src内的文件
+
+import * as GaussianSplats3D from "../../libs/gaussian/gaussian-splats-3d.module.js";
 
  
- 
+import cameraLight from './utils/cameraLight.js'
+//多元融合模块   
+let objIndex = 1
+
+var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
 
 
-//多元融合模块
 
 
-var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     //设置:
     Potree.settings.editType = 'merge'
     Potree.settings.sidebar = 'sidebar2.html'
@@ -34,7 +40,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     let Alignment = viewer.modules.Alignment
        
 	viewer.setEDLEnabled(false);
-    viewer.setFOV(config.view.fov); 
+    viewer.setFOV(Potree.config.view.fov); 
     viewer.loadSettingsFromURL(); 
     
     {
@@ -236,7 +242,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 
         }) 
         
-    } 
+    }   
     
     let setMatrix = (pointcloud)=>{//为了漫游点变换,要算一下 类似setMatrix
         pointcloud.updateMatrixWorld()       
@@ -249,7 +255,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
         pointcloud.updateBound()
         pointcloud.getPanosBound()  
         viewer.updateModelBound()
-    } 
+    }   
 
     let moveModel = (e)=>{//根据鼠标移动的位置改变位置
           
@@ -324,7 +330,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
          
     let tilesetUrls = [   
     
-    
+        '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',
     
     
@@ -351,14 +357,13 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
       
     
     let modelType,  modelEditing, MergeEditor = viewer.modules.MergeEditor
-    Potree.addModel = function(name, done, url){ 
-        
-        let isFirstLoad = true
+    Potree.addModel = function(name, done, url, fixPose){ 
+      
         cancelMove()
         modelType = name
         
         let loadDone = (model)=>{ 
-            if(isFirstLoad){
+            if(!fixPose){
                 modelEditing = model; 
       //          MergeEditor.setModelBtmHeight(model, 0) //默认离地高度为0                
                 /* if(name == '3dTiles'){
@@ -367,7 +372,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                         confirmPos()
                     },1)
                 }else{ */
-                    
+                   
                     viewer.addEventListener('global_mousemove', moveModel); 
                     viewer.addEventListener('global_click', confirmPos, 3);
                 //} 
@@ -407,7 +412,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             
             
             model.addEventListener('changeSelect',(e)=>{
-                 e.selected ? MergeEditor.transformControls.attach(model) : MergeEditor.transformControls.detach()
+                 e.selected ? MergeEditor.transformControls.attach(model, e.clickPos) : MergeEditor.transformControls.detach()
                 //viewer.transformObject(e.selected ? model : null);
             }) 
 
@@ -512,12 +517,15 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 },callback,onprogress)  */  
                      
                 //var path = `${Potree.resourcePath}/models/obj/28M/`
-                var path = `${Potree.resourcePath}/models/obj/monitor_12M/`
+                //var path = `${Potree.resourcePath}/models/obj/monitor_12M/`
+                var path = `${Potree.resourcePath}/models/obj/test/${objIndex++}/`
+                
+                
                 
                 viewer.loadModel({
                     name, 
-                    objurl: path+ 'archmodels95_043.obj', //chuizi.obj' /* 'GW1H.obj' */,  //解析时间4.392s
-                    mtlurl: path+ 'archmodels95_043.mtl',    
+                    objurl: path+ 'mesh.obj', //chuizi.obj' /* 'GW1H.obj' */,  //解析时间4.392s
+                    mtlurl: path+ 'mesh.mtl',    
                     transform : { 
                         rotation : [0,  0,   0],
                         position : [0,0,0]  
@@ -700,42 +708,47 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                             extrude(model1, model2)
                         },onprogress)
                  },onprogress)
-                   
+                     
             }else if(name == 'shp'){
 
                 let callback = (object)=>{ 
                     object.isModel = true  
                     loadDone(object)
                 } 
- 
+       
                 
-                viewer.loadModel({  
+                viewer.loadModel({   
                     fileType: 'shp',  
                     name : 'shp',
                     url:  Potree.resourcePath+'/models/shape-jiangmen/jiangmen.shp',
                     
                 },callback,onprogress)
       
-              
+               
                 //shpModel.position.set(-330000, 900000,10)//尽量移动到原点。原位置在江门那
-                 
-            
+                  
+             
             }else if(name == '3dgs'){ 
-            
+                
                 let callback = (object)=>{ 
-                    object.isModel = true 
-                    
+                    object.isModel = true  
                     loadDone(object)
-                } 
+                }  
+                 
+                 
                 viewer.loadModel({  
                     fileType: '3dgs',   
-                    url: Potree.resourcePath+'/models/gaussian/bonsai.ksplat', 
-                    
+                    url: url || Potree.resourcePath+ '/models/gaussian/0829_2.ksplat',   //meet-room
+                    antialiased: Potree.browser.urlHasValue('aa'),
+                    transform : { 
+                        rotation : [-Math.PI/2,  0,   0],
+                        position : [0,0,10]  
+                    }   
                 },callback,onprogress)
             }
         }  
     }
-    
+     
     let getModelByName = (name)=>{
         if(name == 'laser'){
             return viewer.scene.pointclouds[0]
@@ -784,7 +797,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
               
     }
     
-    
+ 
     
    /* setTimeout(()=>{
     
@@ -803,7 +816,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
          Potree.addModel('3dTiles')
          viewer.addEventListener('modelLoaded',(e)=>{
              
-                confirmPos()
+                confirmPos() 
              
         }) 
      },2000) */
@@ -811,7 +824,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
  
  
  
- 
+  
  
  
  

+ 177 - 11
src/custom/modules/CameraAnimation/CamAniEditor.js

@@ -1,7 +1,7 @@
 import * as THREE from "../../../../libs/three.js/build/three.module.js"; 
 import {CameraAnimation} from './CameraAnimation.js'
 
-
+import {  transitions } from '../../utils/transitions.js'
 
 let CamAniEditor = {
     
@@ -37,20 +37,181 @@ let CamAniEditor = {
     removeAnimation(animation){
         animation.dispatchEvent('dispose')
         viewer.scene.removeCameraAnimation(animation)
-    }
+    },
     
     
     
      
-    
-    
-    
-    
-    
-    
-    
-    
-    
+     
+    createMulAnimation(data){//新版,包含漫游点
+        let event_ = new THREE.EventDispatcher
+        let sections = [] //分段, 分成animation和漫游点
+        let aniIndex = 0
+        let curAni, addAnimation = ()=>{ 
+            if(curAni){ 
+                let data_ = {
+                    name : 'animation_'+aniIndex,
+                    duration:  curAni.slice(0, curAni.length - 1).reduce(function (total, currentValue) { return total + currentValue.time }, 0), //总时长(要去掉最后一个,因为已到终点,该点time无意义)
+                    useDurSlice: data.useDurSlice,
+                    points: curAni
+                } 
+                let ani = this.createAnimation(data_) 
+                sections.push(ani)
+                ani.originIndexStart = data.points.indexOf(curAni[0]) 
+                ani.addEventListener('updateCurrentIndex',(e)=>{
+                    emitIndex(e.currentIndex) 
+                })
+                curAni = null
+            }
+        }
+        data.points.forEach((e,i)=>{//切分为若干个animation,中间是漫游点的衔接
+            if(e.panoId != void 0){ //暂定:有panoId的都是全景图模式
+                let pano = e.model.panos.find(a=>a.originID == e.panoId)
+                if(pano){
+                    addAnimation()  //如果前面是动画,先截断  
+                    e.pano = pano
+                    e.rotInModel = new THREE.Quaternion().fromArray(e.rotInModel)
+                    e.quaternion = Potree.Utils.datasetRotTransform({ fromDataset: true, quaternion: e.rotInModel, getQuaternion: true, object:e.model })  
+
+                    sections.push(e)
+                    
+                } 
+            }else{
+                if(curAni){
+                    curAni.push(e)
+                }else{//非pano
+                    if(sections.length>0){
+                        let last = sections[sections.length-1] //因为要从上一个pano出来,所以起始点是上一个pano
+                        last.position = last.pano.position
+                        last.target = last.position.clone().add(new THREE.Vector3(0,0,-1).applyQuaternion(last.quaternion))
+                        curAni = [last,e]   
+                    }else{
+                        curAni = [e]
+                    } 
+                }
+            }
+        })
+        addAnimation()
+        
+        
+        
+        
+        
+        
+        let currentPlay 
+        let emitIndex = (indexInAni=0)=>{
+            let ani = sections[currentPlay] 
+            let currentIndex = 0
+            if(ani instanceof CameraAnimation){
+                currentIndex = ani.originIndexStart + indexInAni
+            }else{
+                currentIndex = Math.max(0, data.points.indexOf(ani) - 1)
+            }
+            event_.dispatchEvent({type:'updateCurrentIndex', currentIndex  })    
+        }
+        
+        let result = {
+            event_,
+            
+            play(){//从头播放 
+                
+                let playNext = (index)=>{
+                    if(index == sections.length){
+                        return event_.dispatchEvent('playDone')
+                    }
+                    
+                    let ani = sections[index]
+                    currentPlay = index 
+                    emitIndex()
+                    
+                    if(ani instanceof CameraAnimation){
+                        Potree.settings.displayMode = 'showPointCloud'
+                        ani.addEventListener('playDone', () => {
+                            playNext(index + 1)
+                        },{once:true}) 
+                        ani.play()
+                    }else{
+                        let pano = ani.pano
+                        let last = sections[index-1],  duration,  quaternion = ani.quaternion
+                        if(last){ 
+                            if(last instanceof CameraAnimation){
+                                duration = last.durations[last.durations.length - 1] //最后一个时间
+                            }else{
+                                duration = last.time 
+                            }
+                            duration *= 1000
+                        }else{
+                            duration = 600 //起始
+                        }
+                        viewer.images360.flyToPano({pano, quaternion, duration, callback:()=>{
+                            
+                            let next = ()=>{
+                                let stayTime = (!sections[index-1] || sections[index-1] instanceof CameraAnimation) && sections[index+1] && sections[index+1] instanceof CameraAnimation ? 1000 : 300  //前后都是点云模式的话停留久一点。最好在页面上能设置
+                                console.log('stayTime',stayTime)
+                                setTimeout(()=>{
+                                    currentPlay != void 0 && playNext(index + 1)
+                                },stayTime) //稍作停留,不然点云-全景-点云的话根本看不到全景
+                            }
+                            if(/* ani.displayMode == 'showPanos' &&  */Potree.settings.displayMode != 'showPanos'){//刚加载完就飞走吗?
+                                Potree.settings.displayMode = 'showPanos'
+                                viewer.images360.addEventListener('endChangeMode',()=>{
+                                    next()
+                                },{once:true})  
+                            }else{
+                                next()
+                            }
+                            //
+                        }}) 
+                    }
+                }
+                playNext(0)
+                
+           
+            },
+            
+            stop(){
+                let ani = sections[currentPlay]
+                if(ani instanceof CameraAnimation){
+                    ani.pause()
+                }else{
+                    let view = viewer.mainViewport.view
+                    if(!view.isFlying('pos')){//前后是相同漫游点,仅旋转
+                        view.cancelFlying('rotate')
+                    }else{//加快结束
+                        let o = transitions.getById(view.FlyTransition)[0]
+                        let restDur = o.duration - o.current;
+                        const hopeStopTime = 300 //希望最多等待时间
+                        if(restDur>hopeStopTime){
+                            let r = o.duration / (o.current + hopeStopTime)
+                            transitions.adjustSpeed(view.FlyTransition, r)
+                            transitions.adjustSpeed(view.LookTransition, r)
+                        } 
+                    } 
+                    //如果有地图,还要改地图的view
+                    
+                }
+                
+                currentPlay = null
+            },
+            
+            remove(){
+                sections.forEach(ani=>{
+                    if(ani instanceof CameraAnimation){
+                        CamAniEditor.removeAnimation(ani) 
+                    }
+                })
+            }
+            
+            //再把中间的缓动去除
+            
+        }
+        return result 
+    }
+        
+        
+        
+        
+   
     
     
     
@@ -61,4 +222,9 @@ let CamAniEditor = {
 }
 
 
+
+  
+
+
+
 export default CamAniEditor

+ 42 - 15
src/custom/modules/datasetAlignment/Alignment.js

@@ -41,7 +41,7 @@ var Alignment = {
         return pointclouds.map(e=>{
             return {
                 sid : e.dataset_id+e.name,
-                orientationUser : e.orientationUser,
+                orientationUser : typeof(e.orientationUser) == 'number' ? e.orientationUser : e.orientationUser.clone() ,
                 translateUser : e.translateUser.clone(),
             }
         } )
@@ -66,7 +66,7 @@ var Alignment = {
         
         
         viewer.fpControls.addEventListener("transformPointcloud",(e)=>{ 
-            if(e.pointclouds[0].dataset_id == Potree.settings.originDatasetId){//禁止手动移动初始数据集
+            if(e.pointclouds[0].dataset_id == Potree.settings.originDatasetId && Potree.settings.editType != 'pano'){//禁止手动移动初始数据集
                 return this.bus.dispatchEvent('forbitMoveOriginDataset') 
             }
             
@@ -107,15 +107,19 @@ var Alignment = {
                     let center = e.pointclouds[0].translateUser //旋转中心是第一个点云的位置  
                     if(e.intersect.equals(center))return
                     if(!transfromInfo.vecStart){  
-                        transfromInfo.orientationUser = e.pointclouds[0].orientationUser  
+                        //transfromInfo.orientationUser = e.pointclouds[0].orientationUser  
                         transfromInfo.vecStart = new THREE.Vector3().subVectors(e.intersectStart, center).setZ(0)  
-                           
+                        transfromInfo.lastQua = new THREE.Quaternion()   
                     }else{ 
                     
                         let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
                         let angle = math.getAngle(transfromInfo.vecStart,vec,'z')   
-                        let diffAngle = transfromInfo.orientationUser + angle - transfromInfo.pointclouds[0].orientationUser
+                        //let diffAngle = transfromInfo.orientationUser + angle - transfromInfo.pointclouds[0].orientationUser
+                        
+                        let diffQuaFromStart = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), angle)   
+                        let diffQua = new THREE.Quaternion().multiplyQuaternions(diffQuaFromStart, transfromInfo.lastQua.clone().invert())
                         
+                        transfromInfo.lastQua.copy(diffQuaFromStart)
                         transfromInfo.pointclouds.forEach(cloud=>{ 
                             
                            /*  let centerNoTranfrom = Potree.Utils.datasetPosTransform({ toDataset: true, pointcloud:cloud, position: center }) //中心点在数据集中的位置
@@ -127,7 +131,7 @@ var Alignment = {
                             //let centerNow1 = Potree.Utils.datasetPosTransform({ fromDataset: true, pointcloud:transfromInfo.pointcloud, position: centerNoTranfrom }) //中心点的现在位置
                                  */ 
                                  
-                            Alignment.rotateAround(center, cloud, null, diffAngle)    
+                            Alignment.rotateAround(center, cloud, diffQua)    
                                 
                                  
                         }) 
@@ -221,9 +225,13 @@ var Alignment = {
     setMatrix :  function(pointcloud){
         var vec1 = pointcloud.position     //position为数据集内部的偏移,在navvis中对应的是dataset.pointCloudSceneNode的children[0].position
         var vec2 = pointcloud.translateUser
-        var angle = pointcloud.orientationUser
+         
         var pos1Matrix = new THREE.Matrix4().setPosition(vec1);//先移动到点云本身应该在的初始位置(在4dkk里和其他应用中都是在这个位置的,也能和漫游点对应上)
-        var rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle)//再旋转 
+         
+        var rotMatrix = typeof(pointcloud.orientationUser ) == 'number' ?  //再旋转 
+                        new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), pointcloud.orientationUser ) :
+                        new THREE.Matrix4().makeRotationFromQuaternion(pointcloud.orientationUser)
+                        
         var pos2Matrix = new THREE.Matrix4().setPosition(vec2);//最后是平移
          
         var matrix = new THREE.Matrix4().multiplyMatrices(pos2Matrix, rotMatrix);
@@ -263,23 +271,42 @@ var Alignment = {
     },
     
     
-    rotateAround(center, pointcloud, deg, angle){//绕center点转动
-        var angle = angle != void 0 ? angle : THREE.Math.degToRad(deg) 
+    rotateAround(center, pointcloud, angle){//绕center点水平转动
+        
         let vec1 = new THREE.Vector3().subVectors(pointcloud.translateUser, center);
-        let rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle)
+        let rotMatrix = typeof(angle) == 'number' ? 
+                        new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle) :  
+                        new THREE.Matrix4().makeRotationFromQuaternion(angle)
+         
         let vec2 = vec1.clone().applyMatrix4(rotMatrix) //将到旋转中心的偏差也转动
         let vec3 = new THREE.Vector3().subVectors(vec2,vec1); //这个就是多出来的一步translateUser
-        this.rotate(pointcloud, deg, angle) 
+        this.rotate(pointcloud, null, angle) 
         this.translate(pointcloud, vec3)
         //绕点转动就是比普通转动多一步移动到相对center的某个位置。 1 初始点云移动到自己的position; 2 移动一个vec1  3绕原点旋转 4再移动一个原本的translateUser。 绘制出来后发现移动量就是第二步vec旋转后的偏移
     },
     
     
-    rotate:function(pointcloud, deg, angle){//绕各自中心转动(各自的position)   假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
-        var angle = angle != void 0 ? angle : THREE.Math.degToRad(deg)   //正逆负顺
-        pointcloud.orientationUser += angle
+    rotate:function(pointcloud, deg, angle){//绕各自中心水平转动(各自的position)   假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
+        let qua 
+        if(typeof(angle) == 'number'){
+            angle = angle != void 0 ? angle : THREE.Math.degToRad(deg)   //正逆负顺             
+        }else{
+            qua = angle
+        }
+        
+        if(typeof(pointcloud.orientationUser) == 'number' ){
+            pointcloud.orientationUser += angle
+        }else{ 
+            if(!qua){
+                qua = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), angle)     
+            } 
+            pointcloud.orientationUser.premultiply(qua)
+        }
+        
         Alignment.setMatrix(pointcloud)
     },
+    
+     
     translate:function(pointcloud, vec){
         pointcloud.translateUser.add(vec)
         Alignment.setMatrix(pointcloud)

+ 31 - 18
src/custom/modules/mergeModel/MergeEditor.js

@@ -191,11 +191,16 @@ let MergeEditor = {
             if(e.intersect){
                 let object = e.intersect.object || e.intersect.pointcloud
                 let objects = this.getAllObjects()
+                let posInModel = Potree.Utils.datasetPosTransform({ toDataset: true, position: e.intersect.location.clone(),  object })
                 if(objects.includes(object) && this.selected != object){ 
-                    this.selectModel(object) 
+                    this.selectModel(object, posInModel) 
                 }else{
                     //if(!viewer.inputHandler.selection[0]){//正在平移和旋转,不允许取消
-                        this.selectModel(null)
+                      if(this.selected == object && this.transformControls.mode == 'translate'){
+                          this.selectModel(object, posInModel)  //update click pos
+                      }else{
+                          this.selectModel(null)
+                      }
                     //}
                 }                    
             }else{
@@ -217,7 +222,6 @@ let MergeEditor = {
         //Potree.Utils.updateVisible(viewer.reticule, 'force', false)
         viewer.composer.scaleRatio = 1
         viewer.composer.readTarget = false
-        viewer.mainViewport.camera.near = 0.05; // too small will result in z-fighting
         
         viewer.addEventListener('updateModelBound', (e)=>{
             if(this.split){ 
@@ -459,8 +463,10 @@ let MergeEditor = {
             this.updateMemoryUsage()
         }   
         
-        
-        
+        model.panos?.slice().forEach(e=>{
+            e.dispose()
+        }) 
+        viewer.images360.tileDownloader.setPanoData(viewer.images360.panos, [] );
     },
     
     selectModel(model, state=true, fitBound, by2d){
@@ -468,7 +474,9 @@ let MergeEditor = {
             model = this.selected
             state = false
         }
-         
+        if(!by2d && model){
+            model.dispatchEvent({type:'changeSelect', selected : state,  clickPos:state})
+        }
         if(state){
             if(this.selected){
                 if(this.selected == model) return
@@ -500,9 +508,7 @@ let MergeEditor = {
         
         
         
-        if(!by2d && model){
-            model.dispatchEvent({type:'changeSelect', selected : state})
-        }
+        
         
          
 
@@ -523,12 +529,13 @@ let MergeEditor = {
         this.boxHelper.position.copy(center)
          
         this.boxHelper.quaternion.copy(model.quaternion)  
+        viewer.dispatchEvent('content_changed')
     },
     
      
     
     showModelOutline(model, state){ 
-        if(Potree.settings.mergeType2 || model ? model.fileType == '3dgs' : this.boxHelper.visible  ){//高斯很卡
+        if(Potree.settings.mergeType2 || (model ? model.fileType == '3dgs' : this.boxHelper.visible)  ){//高斯很卡
             if(state !== false ){
                 this.updateBoxHelper(model) 
                 Potree.Utils.updateVisible(this.boxHelper,'unselect',true)
@@ -695,18 +702,22 @@ let MergeEditor = {
         //反向求transformMatrix  参考Alignment.js   移动漫游点 
         if(model.isPointcloud && model.transformMatrix){ 
             model.transformMatrix.multiplyMatrices(model.matrix, model.pos1MatrixInvert)   
-            model.transformInvMatrix.copy(model.transformMatrix).invert()      
-            model.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(model.rotation);            
+            model.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(model.rotation);  
             model.panos.forEach(e=>e.transformByPointcloud())
         }else if(model.panos){ 
             model.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(model.rotation).multiply(model.rot1MatrixInvert);   
- 
             model.transformMatrix.multiplyMatrices(model.matrix, model.posRot1MatrixInvert)   
             model.panos.forEach(e=>e.transformByPointcloud())
             model.bound = model.boundingBox.clone().applyMatrix4(model.matrixWorld)
         } 
-        
-        
+        if(model.panos){
+            model.transformInvMatrix.copy(model.transformMatrix).invert()  
+            model.rotateInvMatrix.copy(model.rotateMatrix).invert()
+            
+            model.panos.forEach(e=>{
+                e.marker.scale.copy(model.scale)
+            })
+        }
         
         
         
@@ -716,7 +727,7 @@ let MergeEditor = {
         
         
         //--------
-        this.updateBoxHelper(model)
+        this.selected == model && this.updateBoxHelper(model)
         
     },
     
@@ -893,8 +904,10 @@ note:
 
 要注意getHoveredElements只在getIntersect时才使interactables包含加载的model, 也就是model上不能有使之成为interactables的事件,否则在鼠标hover到模型上开始转动的一瞬间很卡。
 
-
-
+控制台断开完全重合的漫游点的方法
+window.pano1 = viewer.modules.PanoEditor.selectedPano //选中第一个点后输入这行,得到第一个漫游点
+window.pano2 = viewer.modules.PanoEditor.selectedPano //选中第二个点后输入这行,得到第二个漫游点
+viewer.modules.PanoEditor.linkChange(window.pano1, window.pano2, 'remove') //断开链接
 
 
 */ 

+ 273 - 117
src/custom/modules/panoEdit/panoEditor.js

@@ -19,7 +19,16 @@ let images360, Alignment,  SiteModel, suggestCircleMat
 
 const texLoader = new THREE.TextureLoader() 
     texLoader.crossOrigin = "anonymous" 
-const rotQua = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI)    
+    
+    
+    
+const rotQua = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), -Math.PI/2 )   
+    
+const rotQua2 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1),  -Math.PI   )  
+    
+
+
+ 
 const lineMats = {}
 const circleMats = {}
  
@@ -91,7 +100,7 @@ class PanoEditor extends THREE.EventDispatcher{
     }
      
     init(){ 
-        
+        Alignment = viewer.modules.Alignment 
         
         {//init lineMats
             lineMats.default = LineDraw.createFatLineMat({
@@ -138,7 +147,7 @@ class PanoEditor extends THREE.EventDispatcher{
         
         viewer.addEventListener('allLoaded',()=>{
             images360 = viewer.images360 
-            Alignment = viewer.modules.Alignment 
+            
             SiteModel = viewer.modules.SiteModel
             
 
@@ -147,7 +156,7 @@ class PanoEditor extends THREE.EventDispatcher{
             this.lineMeshes.name = 'lineMeshes'
             viewer.scene.scene.add(this.lineMeshes)
             
-            Potree.settings.ifShowMarker = false
+            
             
             {
                 this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{
@@ -156,7 +165,7 @@ class PanoEditor extends THREE.EventDispatcher{
                 }); 
                 this.transformControls.setSize(1.5)
                 viewer.scene.scene.add(this.transformControls)
-                this.transformControls._gizmo.hideAxis = {/* translate:['x','y'], */ rotate:['x','y','e'] }
+                this.transformControls._gizmo.hideAxis = {/* translate:['x','y'], */ rotate:['e'] }
                 this.transformControls.setRotateMethod(2)
                 
                 this.fakeMarkerForTran = new THREE.Mesh(new THREE.BoxBufferGeometry(0.3,0.3,0.3) , new THREE.MeshBasicMaterial({
@@ -173,7 +182,7 @@ class PanoEditor extends THREE.EventDispatcher{
                         this.selectedClouds.forEach(cloud=>Alignment.translate(cloud, moveVec))
                     }else{
                         let center = this.selectedPano.position;
-                        let forward = new THREE.Vector3(0,1,0);
+                        /* let forward = new THREE.Vector3(0,1,0);
                         let vec1 = forward.clone().applyQuaternion(this.fakeMarkerForTran.oldState.quaternion)
                         let vec2 = forward.clone().applyQuaternion(this.fakeMarkerForTran.quaternion)
                          
@@ -181,7 +190,14 @@ class PanoEditor extends THREE.EventDispatcher{
                             
                         this.selectedClouds.forEach(cloud=>{ 
                             Alignment.rotateAround(center, cloud, null, diffAngle)    
-                        })
+                        }) */
+                        
+                        
+                        let diffQua = new THREE.Quaternion().multiplyQuaternions(this.fakeMarkerForTran.quaternion, this.fakeMarkerForTran.oldState.quaternion.clone().invert())
+                        
+                        this.selectedClouds.forEach(cloud=>{ 
+                            Alignment.rotateAround(center, cloud, diffQua)    
+                        }) 
                     }
                     
                     
@@ -216,46 +232,18 @@ class PanoEditor extends THREE.EventDispatcher{
                 e.material.color = pointColor.default  
             }) 
             viewer.setEDLEnabled(true) //为了降一倍的绘制. 同时用描边增强立体感,弥补点云稀疏
-            viewer.setEDLRadius(3)
-            viewer.setEDLStrength(0.02)
+            viewer.setEDLRadius(1)
+            viewer.setEDLStrength(0.01)
+ 
             
             
             
-            this.switchView('top')
-            
-            {//默认选择一个楼层
-                let panoVisiReady, siteModelReady;
-                let floorInit = ()=>{
-                    if(!panoVisiReady || !siteModelReady)return
-                    setTimeout(()=>{
-                        if(this.currentFloor == 'all'){//还未选择楼层的话
-                            let floor = SiteModel.entities.find(e=>e.buildType == 'floor' && e.panos.length) //选择有漫游点的一层
-                            if(!floor){
-                                floor = 'all'  //SiteModel.entities.find(e=>e.buildType == 'floor')
-                                console.log('没有一层有漫游点?!')
-                            }
-                            console.log('initDataDone') 
-                        
-                            console.log('gotoFloor 1') 
-                            this.gotoFloor(floor)
-                        }
-                    },1) //2d那边用了nextTick ,so setTimeout here
-                }
-                
-                SiteModel.bus.addEventListener('initDataDone',()=>{ 
-                    siteModelReady = true;
-                    floorInit() 
-                },{once:true})
-                this.addEventListener('panoVisiReady',()=>{//2d初始化完成,才可以由3d修改pano显示 (因为在之前2d会给每个pano传来显示的消息,在这之前的修改都会别覆盖)
-                    panoVisiReady = true 
-                    floorInit()
-                },{once:true})   
-            }
             
             Alignment.bus.addEventListener('switchHandle', this.updateCursor.bind(this))
             
             
             viewer.addEventListener('global_click',(e)=>{
+                if(this.entered)return
                 if(e.button === THREE.MOUSE.RIGHT){//取消旋转和平移
                      //console.log('right click',e)
                      this.setLinkOperateState('addLink',false)
@@ -320,46 +308,129 @@ class PanoEditor extends THREE.EventDispatcher{
                 }
                 
                 viewer.addEventListener('global_mousemove', (e)=>{ 
-                    update(e)  
+                    if(this.entered)   update(e)  
                 })
                  
                 //this.addEventListener('updateLinkGuideLine', update)
                 
             }
             
-            /* 
-            viewer.inputHandler.addEventListener('keydown', (e)=>{
-                if(e.event.key == "r" ){ 
-                    this.setTranMode('rotate') 
-                }else if(e.event.key == "t"){
-                    this.setTranMode('translate') 
-                }
-            }) */      
-            /* { 
-                viewer.addEventListener('camera_changed', (e)=>{ 
-                    Common.intervalTool.isWaiting('updatePointLevels', ()=>{  
-                        this.updatePointLevels()
-                    }, 1050)
-                })
-
-                setTimeout(()=>{ 
-                    this.updatePointLevels()
-                }, viewer.scene.pointclouds.length*150)  //等待差不多updat出了正确的visibleNode时                
-            } */
-            
-            
+           
             
             this.panoReposCallback = ()=>{
-                viewer.controls.setTarget(this.selectedPano.position) //3d时绕其为中心转动
-            }
+                //viewer.controls.setTarget(this.selectedPano.position) //3d时绕其为中心转动
+            } 
+            
+            this.inited = true
+            this.dispatchEvent('allLoaded')
         }) 
+        
+        this.leave() 
+        
     }
+     
+    
+    
+    enter(){
+        if(this.entered)return
+        
+        this.requestEnter = true
+        if(!this.inited ){
+            if(!this.initCallback){
+                this.initCallback = this.enter.bind(this)
+                this.addEventListener('allLoaded',this.initCallback)
+            }
+            return
+        }
+        
+        
+        Potree.settings.displayMode = 'showPointCloud'
+        Potree.settings.ifShowMarker = false
+        Potree.Utils.updateVisible(this.panoMeshs, 'entered', true)
+        Potree.Utils.updateVisible(this.lineMeshes, 'entered', true) 
+        this.transformControls.setSpace('local')
+        Potree.settings.unableNavigate = true
+         
+        let originFloor = viewer.modules.SiteModel.inEntity  //还未编辑时的楼层 
+        if(originFloor?.buildType != 'floor')originFloor = null
+          
+         
+        if(this.lastExitView)this.switchView(this.lastExitView)
+        else{
+            Potree.settings.isTest || this.switchView('top')
+        }
+       
+        viewer.setFOV(100) //最好能把屋顶砍了,这样不用进屋子就能看到墙壁是否互相贴合,但是加clip比较麻烦,就暂时把fov改大吧
+        //过后恢复  
+        {//默认选择一个楼层
+            let panoVisiReady   ;
+            let floorInit = ()=>{
+                if(!panoVisiReady || !SiteModel.initDataDone || !this.entered  )return
+                setTimeout(()=>{
+                    if(this.currentFloor == 'all' || !this.currentFloor ){//还未选择楼层的话
+                        let floor = originFloor || SiteModel.entities.find(e=>e.buildType == 'floor' && e.panos.length) //选择有漫游点的一层
+                         
+                        if(!floor){
+                            floor = 'all'  //SiteModel.entities.find(e=>e.buildType == 'floor')
+                            console.log('没有一层有漫游点?!')
+                        }
+                        console.log('initDataDone') 
+                     
+                        this.gotoFloor(floor)
+                    }
+                },1) //2d那边用了nextTick ,so setTimeout here
+            }
+            
+            SiteModel.bus.addEventListener('initDataDone',()=>{  
+                floorInit() 
+            },{once:true})
+            this.addEventListener('panoVisiReady',()=>{//2d初始化完成,才可以由3d修改pano显示 (因为在之前2d会给每个pano传来显示的消息,在这之前的修改都会别覆盖)
+                panoVisiReady = true 
+                floorInit()
+            },{once:true})   
+        } 
+        this.entered = true
+    }
+    
+    
+    leave(){
+        this.requestEnter = false
+        Potree.settings.displayMode  = 'showPanos'
+        Potree.settings.ifShowMarker = true
+        Potree.Utils.updateVisible(this.panoMeshs, 'entered', false)
+        Potree.Utils.updateVisible(this.lineMeshes, 'entered', false) 
+        Potree.settings.unableNavigate = false
+        viewer.setFOV(Potree.config.view.fov)
+        this.selectPano(null)
+        this.currentFloor = null 
+        this.lastExitView = this.activeViewName 
+        if(this.inited){
+            this.activeViewName && this.switchView('mainView',true)
+            viewer.images360.panos.forEach(pano=>{   
+                this.switchPanoVisible(pano,true) 
+            }) 
+            this.setLinkOperateState('addLink', false, false)
+            this.setLinkOperateState('removeLink', false, false)
+            this.setZoomInState(false, false)
+            this.setTranMode(null)
+            Alignment.history.clear()
+        }
+        this.entered = false
+        
+        if(this.initCallback){
+            this.removeEventListener('allLoaded',this.initCallback)
+            this.initCallback = null
+        }
+    }
+    
+    
+    
     
     
     setTranMode(mode){//rotate or translate 
-        console.log('setTranMode',mode)
+        //console.log('setTranMode',mode)
         this.tranMode = mode
-        if(this.activeViewName == 'mainView'){
+        if(this.activeViewName != 'top'){
             mode && this.transformControls.setMode(mode)
             this.updateTranCtl() 
         }else{
@@ -375,7 +446,7 @@ class PanoEditor extends THREE.EventDispatcher{
     }
     
     updateTranCtl(){// 设置3D页面的transformControls相关
-        if(!this.tranMode || !this.selectedPano || this.activeViewName != 'mainView' ) {
+        if(!this.tranMode || !this.selectedPano || this.activeViewName == 'top' ) {
             return this.transformControls.detach() 
         }else if(this.checkIfAllLinked({group:this.selectedGroup})){
             this.dispatchEvent('needToDisConnect')
@@ -387,7 +458,7 @@ class PanoEditor extends THREE.EventDispatcher{
         this.fakeMarkerForTran.quaternion.copy(quaternion)
         this.fakeMarkerForTran.oldState = {
             position: position.clone(),
-            quaternion: quaternion.clone(),
+            quaternion: quaternion.clone() 
         }
     }
     
@@ -414,7 +485,9 @@ class PanoEditor extends THREE.EventDispatcher{
         
     }  
     
-    switchView(name ){//替换view和camera到mainViewport
+    switchView(name, dontChangeView){//替换view和camera到mainViewport
+         
+        if(name == this.activeViewName)return 
          
         let view = this.views[name]
         let camera = this.cameras[name]
@@ -429,12 +502,19 @@ class PanoEditor extends THREE.EventDispatcher{
         viewer.mainViewport.camera = camera 
         if(lastCamera)lastView.zoom = lastCamera.zoom
         
-        this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )  
-        this.targetPlane.projectPoint(view.position, this.shiftTarget )  //target转换到过模型中心的平面,以保证镜头一定在模型外
-        view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport))
-        if(view.zoom)camera.zoom = view.zoom//恢复上次的zoom
-         
-       
+        if(name != 'mainView'){
+            this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )  
+            this.targetPlane.projectPoint(view.position, this.shiftTarget )  //target转换到过模型中心的平面,以保证镜头一定在模型外
+            if(!dontChangeView){
+                view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport))
+            }
+            if(view.zoom){ 
+                camera.zoom = view.zoom//恢复上次的zoom
+            }
+        }
+        
+        
+        this.transformControls.setCamera(camera)
         
         viewer.updateScreenSize({forceUpdateSize:true})//更新camera aspect  left等
         this.updateCursor()
@@ -458,38 +538,39 @@ class PanoEditor extends THREE.EventDispatcher{
             }else{ */
                 changeMat() 
             //}
+            this.transformControls._gizmo.hideAxis = {rotate:['e'] }
             
-             
             
             Potree.Utils.updateVisible(viewer.reticule, 'force', true)
                 
             if(lastView){//2d->3d
-                
-                view.copy(lastView)
-                 
-                let direction = view.direction
-                let panos = images360.panos.filter(e=>e.circle.visible)
-                let nearestPano = Common.sortByScore(panos , [], [(pano)=>{
-                    let vec = new THREE.Vector3().subVectors(pano.position,  view.position);
-                    return -vec.dot(direction); 
-                }], true);
-                
-                //console.log('最近',nearestPano )
-                
-                if(nearestPano && nearestPano[0] ){ //尽量不变画面范围,使pano点保持原位,转换到mainView
-                    let halfHeight = lastCamera.top/lastCamera.zoom 
-                    let dis = halfHeight / Math.tan( THREE.Math.degToRad(camera.fov/2)) 
-                    view.position.add(direction.clone().multiplyScalar(-nearestPano[0].score - dis))
-                    //console.log('getCloser', -nearestPano[0].score - dis)
-                    this.lastDisToPano = dis //记录一下
+                if(!dontChangeView){
+                    view.copy(lastView)
+                     
+                    let direction = view.direction
+                    let panos = images360.panos.filter(e=>e.circle.visible)
+                    let nearestPano = Common.sortByScore(panos , [], [(pano)=>{
+                        let vec = new THREE.Vector3().subVectors(pano.position,  view.position);
+                        return -vec.dot(direction); 
+                    }], true);
+                    
+                    //console.log('最近',nearestPano )
+                    
+                    if(nearestPano && nearestPano[0] ){ //尽量不变画面范围,使pano点保持原位,转换到mainView
+                        let halfHeight = lastCamera.top/lastCamera.zoom 
+                        let dis = halfHeight / Math.tan( THREE.Math.degToRad(camera.fov/2)) 
+                        view.position.add(direction.clone().multiplyScalar(-nearestPano[0].score - dis))
+                        //console.log('getCloser', -nearestPano[0].score - dis)
+                        this.lastDisToPano = dis //记录一下
+                    }
                 }
-                
             }
             
             viewer.fpControls.lockKey = false
             
         }else{ 
-             
+            
+            
             if(this.lastViewName == 'mainView'){//3d->2d
                 let direction = lastView.direction
                 let panos = images360.panos.filter(e=>e.circle.visible)
@@ -585,11 +666,11 @@ class PanoEditor extends THREE.EventDispatcher{
                 viewer.mainViewport.rotateSide = false
             }
             viewer.fpControls.lockKey = true
-             
+            this.transformControls._gizmo.hideAxis = {translate:['x','y'],rotate:['x','y','z'] }  
         } 
         
         
-       
+        
         this.updateTranCtl()
         this.setTranMode(this.tranMode) // update
         this.setZoomInState(false) //取消放大模式
@@ -609,7 +690,7 @@ class PanoEditor extends THREE.EventDispatcher{
                 this.viewportFitBound()
             },10)
         }
-         
+        
         this.gotoFloor(this.currentFloor, true, 0, null, true)
         
     } 
@@ -727,13 +808,15 @@ class PanoEditor extends THREE.EventDispatcher{
         return floor.panosBound
     }
     
-    
+    setPanoVisible(pano,v,reason='default',level=0,type){
+        Potree.Utils.updateVisible(pano.circle ,reason, v, level,type) 
+        Potree.Utils.updateVisible(pano, reason, v, level, type)
+        Potree.Utils.updateVisible(pano.pointcloud, reason, v, level, type)
+    }
     
     switchPanoVisible(pano, v, informBy2d){ 
         //console.log(pano.id,v) 
-        Potree.Utils.updateVisible(pano.circle , 'panoEditor', v) 
-        Potree.Utils.updateVisible(pano, 'panoEditor', v)
-        Potree.Utils.updateVisible(pano.pointcloud, 'panoEditor', v)
+        this.setPanoVisible(pano,v,'panoEditor')
         if(v){
             this.visiblePanos.includes(pano) || this.visiblePanos.push(pano)
         }else{
@@ -853,7 +936,7 @@ class PanoEditor extends THREE.EventDispatcher{
             this.panoGroup.push([pano])
         })
          
-        images360.panos.forEach((pano)=>{
+        images360.panos.forEach((pano)=>{ 
             pano.visibles.forEach(index=>{//visibles中存的是下标!
                 this.linkChange(pano, images360.getPano(index,'index'), 'add')
             })
@@ -1207,7 +1290,7 @@ class PanoEditor extends THREE.EventDispatcher{
             this.selectedPano.circle.material = circleMats['selected' + '_'+ this.getPanoRtkState(this.selectedPano) ] 
             this.selectedPano.circle.renderOrder = this.selectedPano.circle.pickOrder = renderOrders.circleSelected //侧视图能显示在最前
             
-            viewer.controls.setTarget(this.selectedPano.position) //3d时绕其为中心转动 
+            //viewer.controls.setTarget(this.selectedPano.position) //3d时绕其为中心转动 (注释原因:不想变来变去不习惯,难以查看任意角落细节)
             this.selectedPano.addEventListener('rePos',this.panoReposCallback)
 
             
@@ -1234,7 +1317,7 @@ class PanoEditor extends THREE.EventDispatcher{
             }
                  
         }else{
-            viewer.controls.setTarget(null)  
+            //viewer.controls.setTarget(null)  
         }
         
         
@@ -1296,12 +1379,13 @@ class PanoEditor extends THREE.EventDispatcher{
     } */
     
     getPanoRtkState(pano){
-        return pano.panosData.has_rtk ? pano.rtkState ? 'rtk_on' : 'rtk_off' : 'normal'
+        return pano.panoData.has_rtk ? pano.rtkState ? 'rtk_on' : 'rtk_off' : 'normal'
     } 
     
     setPanoRtkState(pano,state){ 
         pano.rtkState = state 
         pano.circle.material = circleMats[(this.selectedPano == pano ? 'selected' : 'default') + '_'+ this.getPanoRtkState(pano) ]
+        viewer.dispatchEvent('content_changed')
     }
     
     updateSelectGroup(){//更新选中的组
@@ -1415,12 +1499,67 @@ class PanoEditor extends THREE.EventDispatcher{
     getPanoPose(pano){
         let pose = {
             position:  pano.position.clone(),
-            quaternion:  new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua)  ,
+            quaternion:  new THREE.Quaternion().multiplyQuaternions(new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix),rotQua)   //new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua)  ,
         }
         return pose
         
     }
     
+    initCloud(pointcloud){//根据数据重新摆放点位 
+        let data = pointcloud.panos[0].panoData
+        let dataPosition = new THREE.Vector3().copy(data.pose.translation) 
+        let dataQuaternion = new THREE.Quaternion().copy(data.pose.rotation) 
+        //let dataRotation = new THREE.Euler().setFromQuaternion(dataQuaternion)
+      
+        
+        /* if(data.uuid == '0'){
+            dataQuaternion.w = -0.6712156629074
+            dataQuaternion.x = 0.0085280084224
+            dataQuaternion.y = -0.0007020006933
+            dataQuaternion.z = 0.741212732038
+        }  */ 
+    
+     
+       
+        //pointcloud.orientationUser = dataRotation.z + Math.PI 
+        pointcloud.orientationUser = new THREE.Quaternion().multiplyQuaternions(dataQuaternion,rotQua2)//新版
+        
+        pointcloud.translateUser.copy(dataPosition)
+        Alignment.setMatrix(pointcloud);
+    }
+    
+    changeDatas(data, applyToCloud){//替换data
+        for(let dataset_id in data){
+            data[dataset_id].panos = []
+            
+            if(data[dataset_id].sweepLocations){//来自exportSavingData 
+                data[dataset_id].panoData = data[dataset_id].sweepLocations
+                delete data[dataset_id].sweepLocations
+            } 
+            data[dataset_id].panoData.forEach(e=>{
+                let pano = viewer.images360.getPano(dataset_id + '|' + e.uuid, 'sid')
+                pano.panoData = e
+                data[dataset_id].panos.push(pano)
+                if(applyToCloud){
+                    this.initCloud(pano.pointcloud)
+                }
+            }) 
+        }
+        Potree.settings.datasetsPanos = data
+    }
+    
+    cancelEdit(){//回退数据
+        this.changeDatas(Potree.settings.datasetsPanos,true)  
+        this.panoGroup = [] //分组 
+        this.initPanoLink()
+    }
+    
+    save(){ 
+        let data = this.exportSavingData() 
+        this.changeDatas(Potree.Common.CloneObject(data))  
+        return data 
+    }
+    
     exportSavingData(){//输出漫游点新的坐标和朝向、以及连接信息
         let sweepLocations = {}
         
@@ -1434,21 +1573,30 @@ class PanoEditor extends THREE.EventDispatcher{
                     }
                 }
                 let {position, quaternion} = this.getPanoPose(pano);
-                return Object.assign({},  pano.panosData,  {
-                    uuid: pano.uuid, 
-                    /* pose:{
-                        translation: dealData(pano.position.clone() ),
-                        rotation: dealData(new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua)  ),
-                    }, */
+                
+                data = Object.assign({}, pano.panoData, {
                     pose : { 
                         translation : dealData(position), 
                         rotation : dealData(quaternion) 
-                    },
-                    
-                    visibles,
+                    }, 
                     use_rtk : !!pano.rtkState   
-                    //subgroup: 0,group: 1, "id_view":..
                 })
+                
+                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
+                })
+                
+                
+                
+                return data
             })
             
             sweepLocations[datasetId] = {sweepLocations:data}
@@ -1491,6 +1639,14 @@ class PanoEditor extends THREE.EventDispatcher{
     
     如果未全部相连,不能保存
     
+    
+    点位编辑页加载的漫游点坐标朝向和4dkk与主页的都不一样,是单独的一套。
+
+    问题:漫游点移动后不改变其楼层吗?(虽然也无法改变,因为上面那句)
+    
+    
+    
+    注:quaternion四个数都取相反数结果不变,即(x,y,z,w) == (-x,-y,-z,-w), 不信都转成Euler就会一样。而用angleTo比较却不是0
  */
 
 export default new PanoEditor()

+ 25 - 19
src/custom/modules/panos/DepthImageSampler.js

@@ -3,7 +3,8 @@ import * as THREE from "../../../../libs/three.js/build/three.module.js";
 import math from "../../utils/math.js";
 import browser from "../../utils/browser.js";
 
-
+let imgCount = {}
+window.imgCount = imgCount
 
 class DepthImageSampler extends THREE.EventDispatcher{
     
@@ -20,8 +21,8 @@ class DepthImageSampler extends THREE.EventDispatcher{
         canvas.style.top = canvas.style.left = 0
         canvas.style['z-index'] = 100
          */
-        this.maxDataCount = browser.isMobile() ? 6 : 20;  //手机会崩溃. 平均每张图为8M数据量(以200个点的园区为例,加载时间久一些后,总内存=700 + 每张图的8M * maxDataCount)
-        this.maxNeighCount = browser.isMobile() ? 3 : 14;  //包含在maxDataCount内的nearPanos最大个数.至少比maxDataCount少3个,留出空位给最近更新的pano
+        this.maxDataCount = browser.isMobile() ? 12 : 20;  //手机会崩溃. 平均每张图为8M数据量(以200个点的园区为例,加载时间久一些后,总内存=700 + 每张图的8M * maxDataCount)
+        this.maxNeighCount = browser.isMobile() ? 9 : 14;  //包含在maxDataCount内的nearPanos最大个数.至少比maxDataCount少3个,留出空位给最近更新的pano
          
         
         this.nearPanos = []
@@ -32,6 +33,9 @@ class DepthImageSampler extends THREE.EventDispatcher{
     }
     
     changeImg(img, pano){
+         
+        imgCount[pano.id] = (imgCount[pano.id] || 0 )+1
+        
         
         this.pano = pano
         let item = this.imgDatas.find(p=>p.pano == pano) 
@@ -52,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.log('changeImg', pano.id, !!data ) 
+            console.warn('changeImg', pano.id, !!data ) 
             //this.img = img
             if(!data){
                 console.error('getImageData 得不到?!why!')
@@ -110,12 +114,12 @@ class DepthImageSampler extends THREE.EventDispatcher{
         if(!depth && useNeighIfZero){ //遇到过有的相隔很远且有阻挡的两个漫游点间居然depth为0,没有阻挡?但是周围点有四个非零。所以为了修正会飞到很远的点加个识别周围像素的depth 。 2024.3.19
             
             let results = [] 
-            let d = 0, sum = 0, count = 0, maxResDis = 1
+            let d = 0, sum = 0, count = 0, maxResDis = 1, padding = 2 //间隔
             outer2: while(d++<maxResDis){
                 outer: for(let i=-d;i<=d;i++){
                     for(let j=-d;j<=d;j++){
                         if(i==-d || i==d || j==-d || j==d ){//外围
-                             let depth1 = get(x+i,y+j)
+                             let depth1 = get(x+i*padding,y+j*padding)
                              if(depth1!=0){
                                  results.push(depth1)
                              }
@@ -130,7 +134,7 @@ class DepthImageSampler extends THREE.EventDispatcher{
             //console.log('useNeighIfZero', results, depth)
         }
         
-        return depth
+        return {depth,x,y}
             
         
     } 
@@ -168,28 +172,30 @@ class DepthImageSampler extends THREE.EventDispatcher{
         let dirInPano = dir.clone().applyMatrix4(currentPano.panoMatrix2Inverse).normalize(); //转化为考虑漫游点旋转的方向
         let uv = math.getUVfromDir(dirInPano)//转化为uv
         
-        let distance = this.getDepth(  uv.x, uv.y, useNeighIfZero);
+        let {depth,x,y} = this.getDepth(  uv.x, uv.y, useNeighIfZero);
+            depth *= currentPano.pointcloud.scale.x 
+        
         //viewer.addTimeMark(markName,'end')
         
         
         
-        if (!distance){
+        if (!depth){
             const margin =  0.1
             if(uv.y > 1-Potree.config.depthTexUVyLimit){//漫游点底部识别不到的区域,给一个地板高度
                 
-                distance = (currentPano.floorPosition.z - origin.z - margin) / dir.z
-                location.copy(dir).multiplyScalar(distance).add(origin);
+                depth = (currentPano.floorPosition.z - origin.z - margin) / dir.z
+                location.copy(dir).multiplyScalar(depth).add(origin);
                 let normal = new THREE.Vector3(0,0,1)
                 
-                return {location, normal, distance} 
+                return {location, normal, depth, uv} 
             }else if(uv.y < Potree.config.depthTexUVyLimit){
                 let ceilZ = currentPano.getCeilHeight()
                 if(ceilZ == Infinity)return !1
                 else{ 
-                    distance = (ceilZ - origin.z - margin) / dir.z
-                    location.copy(dir).multiplyScalar(distance).add(origin);
+                    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, distance} 
+                    return {location, normal, depth, uv} 
                 } 
             }
             //console.log('无穷远')
@@ -198,11 +204,11 @@ class DepthImageSampler extends THREE.EventDispatcher{
         
         
         
-        //console.log('distance', distance,  dirInPano.clone().multiplyScalar(distance)) 
+        //console.log('depth', depth,  dirInPano.clone().multiplyScalar(depth)) 
         
         
         
-        location.copy(dir).multiplyScalar(distance).add(origin);
+        location.copy(dir).multiplyScalar(depth).add(origin);
         
         if(!onlyPos){
            
@@ -225,7 +231,7 @@ class DepthImageSampler extends THREE.EventDispatcher{
         } */ 
         //console.log(location, normal,  distance)
           
-        return {location, normal,  distance} 
+        return {location, normal,  distance:depth,  x,y, uv } 
     }
     
     
@@ -243,7 +249,7 @@ class DepthImageSampler extends THREE.EventDispatcher{
         
         let dir = math.getDirFromUV(uv2)//从uv获取到方向
         dir.applyMatrix4(viewer.images360.currentPano.panoMatrix2)
-        let depth = this.getDepth(uv2.x, uv2.y);
+        let {depth} = this.getDepth(uv2.x, uv2.y);
         /* if(Math.abs(depth - this.mainDepth) > 0.3){
             console.log('Math.abs(depth - this.mainDepth) > 0.3')
         } */

+ 200 - 154
src/custom/modules/panos/Images360.js

@@ -16,7 +16,7 @@ import TilePrioritizer from './tile/TilePrioritizer.js'
 import {transitions, easing, lerp} from "../../utils/transitions.js";
 import DepthImageSampler from './DepthImageSampler.js' 
 let {PanoSizeClass,Vectors,GLCubeFaces, PanoramaEvents} = Potree.defines
-
+ 
     
 
  
@@ -47,7 +47,12 @@ const directionFactor = 400 //原先10,几乎只往距离近的走了;设置
  
  
  
- 
+let getID = (function(){
+    let id = 0
+    return function(){
+        return id ++
+    }
+})()
         
  
  
@@ -60,7 +65,7 @@ export class Images360 extends THREE.EventDispatcher{
  
 		this.panos = [];
         this.neighbourMap = {}
-        
+        this.disMap = {}
         
 		this.node = new THREE.Object3D();
         this.node.name = 'ImagesNode'
@@ -75,28 +80,23 @@ export class Images360 extends THREE.EventDispatcher{
             this.cube.layers.set(Potree.config.renderLayers.skybox)
             this.cube.name = 'skyboxCube'
             viewer.scene.scene.add(this.cube)
-            
+          
+
+ 
+          
             
             if(Potree.settings.mergeType2 && Potree.settings.modelSkybox){
                 this.materialInside = new ModelTextureMaterial( )
                 this.materialInside.dontChangeDepth = true //chunk本身深度是对的,不用深度图,否则过渡会有很多裂痕
                 this.cube.material.defines.depth_background = '' //skybox的深度改得向后一些, 避免盖住chunk,造成坑坑洼洼
             
-                this.addEventListener('endChangeMode',(e)=>{ 
-                    if( this.currentPano?.pointcloud?.is4dkkModel){
-                        let model = this.currentPano.pointcloud
-                        if(model.fileType == '3dTiles'){ 
-                            viewer.setAllTilesets(model, (child)=>{
-                                child.runtime.limit2lowestDepth(e.mode == 'showPanos')
-                                child.runtime.getTileset().tiles.forEach(e=>{ 
-                                    this.judgeModelMat(e.tileContent)  
-                                }) 
-                            }) 
-                        }else{
-                            this.judgeModelMat(model)
-                        }
-                    }
-                }) 
+                this.addEventListener('endChangeMode',(e)=>{  
+                    viewer.objs.children.forEach(e=>{
+                        if(e.panos) this.changeModelMat(e)
+                    }) //干脆全部换,就不容易错
+                       
+                })  
+                
             }
         }
         if(Potree.settings.testCube){
@@ -177,10 +177,10 @@ export class Images360 extends THREE.EventDispatcher{
         let click = (e) => {//不用"mouseup" 是因为 mouseup有drag object时也会触发 
             if(e.clickElement ||
                 Potree.settings.unableNavigate || this.flying  || !e.isTouch && e.button != THREE.MOUSE.LEFT || e.drag &&  e.drag.object //拖拽结束时不算
-               || Potree.settings.editType == 'pano' && viewer.modules.PanoEditor.activeViewName != 'mainView'
+              /*  || Potree.settings.editType == 'pano' && viewer.modules.PanoEditor.entered */
                //||   Potree.settings.editType == 'merge' && !e.intersectPoint || viewer.inputHandler.hoveredElements[0] && viewer.inputHandler.hoveredElements[0].isModel && e.intersectPoint.distance > viewer.inputHandler.hoveredElements[0].distance
                || Potree.settings.editType == 'merge' && !Potree.settings.mergeType2
-               || Potree.settings.mergeType2 && Potree.settings.displayMode == 'showPointCloud'
+               //|| Potree.settings.mergeType2 && Potree.settings.displayMode == 'showPointCloud' 
             )  return 
              
             if(Potree.settings.editType != 'pano' && Potree.settings.editType != 'merge'){
@@ -610,15 +610,29 @@ export class Images360 extends THREE.EventDispatcher{
 	}; 
     
     
+     changeModelMat(model/* , isCurModel */){ 
+        if(model?.is4dkkModel){ 
+            if(model.fileType == '3dTiles'){ 
+                viewer.setAllTilesets(model, (child)=>{
+                    child.runtime.limit2lowestDepth(Potree.settings.displayMode == 'showPanos')
+                    child.runtime.getTileset().tiles.forEach(e=>{ 
+                        this.judgeModelMat(e.tileContent/* , isCurModel */)  
+                    }) 
+                }) 
+            }else{
+                this.judgeModelMat(model/* , isCurModel */)
+            }
+        }
+    }
     
-    judgeModelMat(object){
+    judgeModelMat(object/* , isCurModel */){
         if(!(Potree.settings.mergeType2 && Potree.settings.modelSkybox))return
         object.traverse(mesh=>{
             if(mesh.material){
                 if(!mesh.materialOutside){
                     mesh.materialOutside = mesh.material
-                }
-
+                } 
+                //mesh.material = Potree.settings.displayMode == 'showPanos' && (this.nextPano?.pointcloud == object || this.currentPano.pointcloud == object || isCurModel) ? this.materialInside : mesh.materialOutside 
                 mesh.material = Potree.settings.displayMode == 'showPanos' ? this.materialInside : mesh.materialOutside 
                 Potree.Utils.setObjectLayers(mesh,  Potree.settings.displayMode == 'showPanos' ? 'skybox' : 'model' ) //为了渲染到rtEDL
                  
@@ -638,7 +652,7 @@ export class Images360 extends THREE.EventDispatcher{
     
     findNearestPano(pos, panos=this.panos){
         pos = pos ? new THREE.Vector3().copy(pos) : this.position
-        let result = Common.sortByScore(panos,[Images360.filters.isEnabled()],[e=>-e.position.distanceTo(pos)])
+        let result = Common.sortByScore(panos,[Images360.filters.isEnabled()],[e=>-e.position.distanceTo(pos) / e.pointcloud.scale.x])
         let pano = result[0] && result[0].item
         return pano
         
@@ -778,6 +792,7 @@ export class Images360 extends THREE.EventDispatcher{
          
         this.nextPano = pano 
         this.latestToPano = toPano
+        
         //this.flying = true  //防止新的请求
         //Potree.Log('flyToPano:'+pano.id + ' , duration:'+toPano.duration, null, 12)  
         
@@ -980,6 +995,7 @@ export class Images360 extends THREE.EventDispatcher{
                     this.lastPano = this.currentPano //记录,调试
                     this.currentPano = pano;
                     
+                    
                     this.nextPano = null;
                     if(Potree.settings.displayMode == 'showPanos'){
                         viewer.scene.pointclouds.forEach(e=>{
@@ -1009,10 +1025,7 @@ export class Images360 extends THREE.EventDispatcher{
 
 
 
-
-
-
-
+   
     beforeFlyToPano(toPano){
         if(this.currentPano != toPano.pano) {
             if(Potree.settings.displayMode == 'showPanos'){
@@ -1275,7 +1288,7 @@ export class Images360 extends THREE.EventDispatcher{
     isNeighbour(pano0, pano1, {dontCompute, onlyUseTex,  computeDirFirst, computeTwoDir}={}){//是否之间没有遮挡(在加载visibles之前,自己算) 最好pano0是currentPano
          
         if(!pano0 || !pano1 )return
-        
+        if(!viewer.scene.pointclouds.some(e=>e.hasDepthTex))return true
         let margin = 0.1;
         
         let map0 = this.neighbourMap[pano0.id]; //主
@@ -1303,7 +1316,7 @@ export class Images360 extends THREE.EventDispatcher{
         
         
         
-        let logSids = [/* '1739923562316697600|3','1739923562316697600|2' */]
+        let logSids = [  /*  '1566756240057044992|4','1566756240057044992|5'  */ ]
         let ifLog = pano0.sid == logSids[0] && pano1.sid == logSids[1] || pano0.sid == logSids[1] && pano1.sid == logSids[0]
         if(ifLog){
             console.log(2)
@@ -1362,27 +1375,30 @@ export class Images360 extends THREE.EventDispatcher{
         */
         
         
-        
-        
+                
+          
         
         //三个方向 : position0到position1, position0到floorPosition1, position1到floorPosition0。 只要有一个满足ifNeighbour就为true。 不过为了不使sampler总换图,先只考虑从主pano到副pano的方向
   
         let getNeighbour = (mainPano, subPano   )=>{
             
             let dirPoints = [[subPano.position, mainPano.position]] //注: 点A能到B不代表点B能到A,因为拍摄时物体会移动,或点位相对位置不对,无论是否是意外遮挡都且记下来,反正最后只要有一方可行就算相邻。
-           
-            if(dis < 20){//在远处去掉对floorPosition的判断
-                dirPoints.push([subPano.floorPosition.clone().add(new THREE.Vector3(0,0,0.1)), mainPano.position])
-            }
-            if(dis < 12){//为了防止楼梯拐角、杂点遮挡。 尽量只在穿墙时不可通行
-                dirPoints.push([subPano.position.clone().add(new THREE.Vector3(0,0,0.8)), mainPano.position])
-                
-                let normal = math.getNormal2d({p1:subPano.position, p2:mainPano.position}).multiplyScalar(0.3)  //左右方向
-                
-                dirPoints.push([subPano.position.clone().add(new THREE.Vector3(normal.x,normal.y,0.1)), mainPano.position])
-                dirPoints.push([subPano.position.clone().add(new THREE.Vector3(-normal.x,-normal.y,0.1)), mainPano.position])
-            }
-             
+            if(new THREE.Vector2().subVectors(subPano.position,mainPano.position).length()>1e-4){//xy都不同
+            
+                if(dis < 25){//在远处去掉对floorPosition的判断
+                    dirPoints.push([subPano.floorPosition.clone().add(new THREE.Vector3(0,0,0.1)), mainPano.position])
+                }
+                if(dis < 15){//为了防止楼梯拐角、杂点遮挡。 尽量只在穿墙时不可通行
+                    dirPoints.push([subPano.position.clone().add(new THREE.Vector3(0,0,0.8)), mainPano.position])
+                    
+                    let normal = math.getNormal2d({p1:subPano.position, p2:mainPano.position}).multiplyScalar(0.3)  //左右方向
+                    
+                    dirPoints.push([subPano.position.clone().add(new THREE.Vector3(normal.x,normal.y,0.1)), mainPano.position])
+                    dirPoints.push([subPano.position.clone().add(new THREE.Vector3(-normal.x,-normal.y,0.1)), mainPano.position])
+                }
+            }else{
+                console.warn('两个点位的xy几乎相同', mainPano.id,  subPano.id)
+            }  
             //console.warn('getNeighbour', mainPano.id,subPano.id)
             for(let i=0; i<dirPoints.length; i++){
                 let dir = new THREE.Vector3().subVectors(dirPoints[i][0], dirPoints[i][1]).normalize();
@@ -1415,7 +1431,7 @@ export class Images360 extends THREE.EventDispatcher{
                  
                   
                 if( map0[pano1.id] === false || map1[pano0.id] === false){
-                    if(dis<20) {//再检查下 只要两方都没有被完全遮挡就算可通行 针对杂点较多的情况  
+                    if(dis<25) {//再检查下 只要两方都没有被完全遮挡就算可通行 针对杂点较多的情况  
                         if(pano0.depthTex && pano1.depthTex){
                             ifNeighbour = false 
                             let a = ifSheltered(pano0,pano1)
@@ -1429,8 +1445,8 @@ export class Images360 extends THREE.EventDispatcher{
                         }                        
                     }else{
                         //标记为无需再计算, 因已经失败。  当map0和map1都不为void 0后就无法再计算了。
-                        if(map0[pano1.id] === void 0) map0[pano1.id] = 0 ;
-                        if(map1[pano0.id] === void 0) map1[pano0.id] = 0 ; 
+                        if(map0[pano1.id] === void 0) map0[pano1.id] = false;
+                        if(map1[pano0.id] === void 0) map1[pano0.id] = false; 
                     }
                 }
 
@@ -1489,7 +1505,7 @@ export class Images360 extends THREE.EventDispatcher{
          
 
         
-        if(map0[pano1.id] && map1[pano0.id]){
+        if(map0[pano1.id] && map1[pano0.id]){  //要获得双向需要depthTex都加载好,可能要很久时间
             pano0.neighbours.includes(pano1) || pano0.neighbours.push(pano1)
             pano1.neighbours.includes(pano0) || pano1.neighbours.push(pano0)
         }
@@ -1505,7 +1521,7 @@ export class Images360 extends THREE.EventDispatcher{
 
     bump(direction) {//撞墙弹回效果
         if (!this.bumping && !this.latestToPano) {  
-            let distance = Potree.settings.displayMode == 'showPanos' ? 0.15 : 0.12;//感觉点云模式比全景模式更明显,所以降低
+            let distance = (Potree.settings.displayMode == 'showPanos' ? 0.15 : 0.12) * (this.currentPano?.pointcloud.scale.x||1);//感觉点云模式比全景模式更明显,所以降低
             let currentPos = this.position.clone()
             let endPosition = new THREE.Vector3().addVectors(this.position, direction.clone().multiplyScalar(distance)) 
             
@@ -1626,71 +1642,39 @@ export class Images360 extends THREE.EventDispatcher{
         var o = option2 ? "angle" : "direction";
         
         var floor = viewer.modules.SiteModel.currentFloor;
-        var entity = viewer.modules.SiteModel.inEntity;
-        
-        var getHeightDis = (pano)=>{
-            if(floor && !floor.panos.includes(pano) && pano.position.z < this.position.z){ //若是上方的漫游点,就正常走。因为一般不会点击天花板。
-                return this.position.z - pano.position.z  
-            }else{
-                return 0
-            }  
-        } 
-        
-        
-        let disSquareMap = new Map()
-        this.panos.forEach(pano=>{
-            let dis2 = pano.position.distanceToSquared(this.position);  //距离目标点
-            disSquareMap.set(pano, dis2)
-        })
-        
+        var entity = viewer.modules.SiteModel.inEntity; 
+        let scaleFactor = Math.pow( this.currentPano?.pointcloud.scale.x || 1 , 2) 
         let changeTexCount = 0, maxWaitDur = 300  
             //maxSamplerChangeTex = THREE.Math.clamp( maxWaitDur / Potree.timeCollect.depthSampler.median, 2, 10)  //计算换贴图最大数目
-        
-        
+         
         
         var request = [//必要条件 
             Images360.filters.not(this.currentPano),
             Images360.filters.isEnabled(), 
             //Images360.filters.inFloorDirection( this.position, direction, option1 ),  //原先用inPanoDirection,但容易穿楼层,当mouse较低或较高 //因不束缚纵向所以可能往相反反向
             (pano)=>{ 
-                /* let isNeighbour = this.isNeighbour(this.currentPano, pano, true, true); //不计算的
-                if(isNeighbour == void 0){
-                    let willChangeTex = pano.depthTex && !this.depthSampler.imgDatas.some(e=>e.pano == pano)
-                    if(changeTexCount < maxSamplerChangeTex || !willChangeTex ){
-                        isNeighbour = this.isNeighbour(this.currentPano, pano, false, true);//计算
-                        if(willChangeTex && isNeighbour != void 0) changeTexCount++ 
-                    }else{
-                        if(disSquareMap.get(pano) < 500)return true //因为费时,超过一定个数就不计算了,距离近的话先可通行。
-                    } 
-                } */ 
+                 
                 // 不会再changeTex了
                 let isNeighbour = this.isNeighbour(this.currentPano, pano,  {onlyUseTex:true});  
                  
-                if(isNeighbour || pano.noNeighbour && disSquareMap.get(pano) < 200){//在靠近孤立点时可以通行。但是不好把握这个距离,太远的话很多地方都会不小心到孤立点,太近的话可能永远到不了。
+                if(isNeighbour || pano.noNeighbour && Images360.scoreFunctions.distanceSquaredNew(this.currentPano)(pano) > -200  /* disSquareMap.get(pano) < 200 */){//在靠近孤立点时可以通行。但是不好把握这个距离,太远的话很多地方都会不小心到孤立点,太近的话可能永远到不了。
                     return true
                 } 
             },
-            /* (pano)=>{ //防止不小心穿越地板到下一层, 尽量走楼梯,实在没有楼梯或楼梯漫游点稀疏的话就通过楼层按钮。
-                let dis = getHeightDis(pano)
-                //console.log('getHeightDis',pano.id,dis)
-                if(dis < 3){//不能超过最大高度差( 最大高度差暂定为接近一层楼的高度。)(最好的解决方案是设置漫游可行)
-                    return true
-                }else{
-                    return this.isNeighbour(this.currentPano, pano)
-                } 
-            }  */
+             
         ] 
-        
-        if(!byKey){
+        if(byKey){
+            request.push(Images360.filters.inPanoDirection( this.position, direction, option1))
+        }else{
             request.push(Images360.filters.inPanoDirection( this.position, this.getDirection(), option1/* , true */))   //垂直方向上再稍微限制一下, 要接近视线方向,避免点击前方时因无路而到下一楼。但不能太高,否则楼梯上稍微朝下点击都到不了上方。之所以使用视线方向是因为镜头方向比鼠标方向目的性更强。
         }
 
          
         var list = [//决胜项目
-            (pano)=>{
-                return -disSquareMap.get(pano)
-            },
-            
+            /* (pano)=>{
+                return -disSquareMap.get(pano) 
+            }, */
+            Images360.scoreFunctions.distanceSquaredNew(this.currentPano),
             Images360.scoreFunctions[o]( this.position, direction, true),
                
             (pano)=>{
@@ -2142,10 +2126,13 @@ export class Images360 extends THREE.EventDispatcher{
     
 
     updateZoomPano() { 
+    
         if (!this.panoRenderer.zoomPanoRenderingDisabled && Potree.settings.displayMode == 'showPanos') {
             var currentPano = this.currentPano;
             if (currentPano) {
                 
+                
+                
                 let levelThreshold1 = Potree.settings.navTileClass == '1k' ? 1.3 : 1.7 , levelThreshold2 = 2
                  
                 var t = this.zoomLevel > levelThreshold1,
@@ -2160,7 +2147,12 @@ export class Images360 extends THREE.EventDispatcher{
                     this.panoRenderer.renderPanoTiles(pano.id, null, !1, !1);
                     pano.setZoomed(ifZoom);
                 }
-                  
+                
+                
+                if(currentPano.pointcloud.tileRes == '2k'){//融合页面
+                    return currentPano.zoomed && o(currentPano, !1); 
+                }
+                   
                 
                 if (r && (!currentPano.zoomed || this.qualityManager.zoomLevelResolution && this.qualityManager.zoomLevelResolution != '4k')) {//needZoom
                     currentPano.zoomed || o(currentPano, !0); 
@@ -2531,13 +2523,16 @@ export class Images360 extends THREE.EventDispatcher{
         
         data.forEach((info)=>{  
             //if(Potree.fileServer){
-                info.id = this.panos.length             //把info的id的一长串数字改简单点
+                info.id = getID()            //把info的id的一长串数字改简单点
                 info.pointcloud = pointcloud
             //} 
             let pano = new Panorama( info, this   );
              
             pano.addEventListener('dispose',(e)=>{
                 if(this.closestPano == pano) this.closestPano = null
+                if(this.currentPano == pano && Potree.settings.displayMode == 'showPanos') {
+                     Potree.settings.displayMode = 'showPointCloud'
+                }
             })
             
             this.panos.push(pano);
@@ -2553,19 +2548,11 @@ export class Images360 extends THREE.EventDispatcher{
     }
     
     
-    loadDone(){
-        Potree.Utils.setObjectLayers(this.node, 'sceneObjects')
-   
-          
+    loadDone(){ 
         
         this.tileDownloader.setPanoData(this.panos, [] );
 
-        {        
-            let minSize = new THREE.Vector3(1,1,1)
-            this.bound = math.getBoundByPoints(this.panos.map(e=>e.position), minSize) 
-            viewer.scene.pointclouds.forEach(pointcloud=>pointcloud.getPanosBound()) 
-              
-        }
+        this.updatePanoBound()
           
         if(viewer.scene.pointclouds.some(e=>e.panos.length == 0)){
             //console.warn('存在数据集没有pano');
@@ -2573,6 +2560,11 @@ export class Images360 extends THREE.EventDispatcher{
         }   
     }
     
+    updatePanoBound(){
+        let minSize = new THREE.Vector3(1,1,1)
+        this.bound = math.getBoundByPoints(this.panos.map(e=>e.position), minSize) 
+        Potree.settings.editType == 'pano' || viewer.scene.pointclouds.forEach(pointcloud=>pointcloud.getPanosBound()) 
+    }
     
     
     getPano(value, typeName='id'){ //默认找的是id,也可以是sid、uuid
@@ -2587,28 +2579,38 @@ export class Images360 extends THREE.EventDispatcher{
 
 //判断当前点是否加载了全景图
 Images360.prototype.checkAndWaitForPanoLoad = function() {
-    var isLoadedPanos = {},
-        LoadedTimePanos = {},
-        loadedCallback = {}, //add
-        maxTime = 5e3;
-    var withinTime = function() {
-        for (var panoId in isLoadedPanos)
-            if (isLoadedPanos.hasOwnProperty(panoId) && isLoadedPanos[panoId]) {
+    var loadingPanos = {},
+    LoadedTimePanos = {},
+    loadedCallback = {}, //add
+    maxTime = 5e3
+    /* var withinTime = function() { 
+        for (var panoId in loadingPanos)
+            if (loadingPanos.hasOwnProperty(panoId) && loadingPanos[panoId]) {
                 var differTime = performance.now() - LoadedTimePanos[panoId];
                 if (differTime < maxTime)
                     return !0
             }
         return !1
-    }
+    } */
+    
+    // withinTime 改为只判断当前请求的点。原始代码的callback是针对任意pano的,所以遍历所有漫游点,只要有在加载的就返回,但已不满足需求(4dkk)。2024:9 发现bim分屏有问题,然后发现4dkk已经改过这,遂搬过来
     
+    var withinTime = function (pano) {
+        //5秒之内还在加载的话,直接返回仍在加载状态,否则重新判断
+        if (loadingPanos.hasOwnProperty(pano.id) && loadingPanos[pano.id]) {
+            //还在加载
+            var differTime = performance.now() - LoadedTimePanos[pano.id]
+            if (differTime < maxTime) return !0
+        }
+    }
     
     
     
     return function(pano, basePanoSize, doneFun1, doneFun2, progressCallback, iswait, isclear, p   ) {
         loadedCallback[pano.id] = doneFun1//add 因为有可能之前请求的没加doneFun1, 如果加载好就执行最新的doneFun1
          
-        if (withinTime()){//距离上次请求时间很近
-            //console.log(11) 
+        if (withinTime(pano)){//距离上次请求时间很近
+            //console.log('withinTime',window.name) 
             return !0;    //这里感觉应该是!1
         } 
         
@@ -2616,7 +2618,7 @@ Images360.prototype.checkAndWaitForPanoLoad = function() {
         let changeMode = (e)=>{
             if(e.mode == 'showPointCloud'){
                 console.warn('切到点云模式了,就删除loadedCallback记录',pano.id)//否则再次转为showPanos会被withinTime阻拦
-                delete isLoadedPanos[pano.id] 
+                delete loadingPanos[pano.id] 
                 viewer.cancelLoad(pano)
                 
                 loadedCallback[pano.id] && loadedCallback[pano.id]();  //可以飞行了
@@ -2626,14 +2628,11 @@ Images360.prototype.checkAndWaitForPanoLoad = function() {
         }
         this.addEventListener('requestMode', changeMode)
             
-        
-        
-        
-        
+         
         
         var callback1 = (param1, param2)=>{ 
-            setTimeout(()=>{
-                isLoadedPanos[pano.id] = !1;
+            setTimeout(()=>{ 
+                loadingPanos[pano.id] = !1;
                 loadedCallback[pano.id] && loadedCallback[pano.id](param1, param2); 
                 this.removeEventListener('requestMode', changeMode)
             },1)
@@ -2642,19 +2641,21 @@ Images360.prototype.checkAndWaitForPanoLoad = function() {
 
         var callback2 = (param)=>{//没有看到有传doneFun2的
             setTimeout(()=>{
-                isLoadedPanos[pano.id] = !1;
+                loadingPanos[pano.id] = !1;
                 doneFun2 && doneFun2(param); 
             },1)
         } 
 
         try {
             null !== iswait && void 0 !== iswait || (iswait = !0); 
-            isLoadedPanos[pano.id] = this.checkAndWaitForTiledPanoLoad(pano, basePanoSize, callback1, callback2, progressCallback, iswait, isclear, p   );
+            loadingPanos[pano.id] = this.checkAndWaitForTiledPanoLoad(pano, basePanoSize, callback1, callback2, progressCallback, iswait, isclear, p   );
             //true代表没加载好
-            isLoadedPanos[pano.id] && (LoadedTimePanos[pano.id] = performance.now());
-            return isLoadedPanos[pano.id];
+            if (loadingPanos[pano.id]) {
+                LoadedTimePanos[pano.id] = performance.now()
+            }
+            return loadingPanos[pano.id];
         } catch (msg) {
-            isLoadedPanos[pano.id] = !1;
+            loadingPanos[pano.id] = !1;
             LoadedTimePanos[pano.id] = performance.now() - maxTime;
             throw msg;
         }
@@ -2664,32 +2665,36 @@ Images360.prototype.checkAndWaitForPanoLoad = function() {
 
 Images360.prototype.getNeighbours = function(){ //逐渐自动获取neighbours。 200个点差不多在半分钟内算完
     let lastIndex, inited//标记上次查询到哪,防止重新sortByScore
-    
+    let startTime = 0, lastTime = 0
     
     
     return function( interacted){
-          
-        if(!this.currentPano || viewer.mainViewport.view.isFlying() || viewer.lastFrameChanged || viewer.inputHandler.drag /* interacted */){ //拖拽时不更新,否则移动端卡
+           
+        if(!this.currentPano || viewer.mainViewport.view.isFlying() || viewer.lastFrameChanged || viewer.inputHandler.drag  //拖拽时不更新,否则移动端卡
+            || !viewer.scene.pointclouds.some(e=>e.hasDepthTex) //目前无深度图的话不判断可行,全部可通行
+        ){ 
             return lastIndex = 0; 
         }
           
         let nearPanos = this.tileDownloader.tilePrioritizer.nearPanos;
         if(!nearPanos)return; 
-        //let startTime = Date.now()
+        if(Date.now() - lastTime < 50)return//不要每帧都算
+        
         
+        startTime = lastTime = Date.now()
         let panos = [this.currentPano, ...nearPanos ]
         this.depthSampler.updateNearPanos(panos)
         
-        let maxWaitDur = browser.isMobile() ? 40 : 60  
-        let changeCount = 0,  maxChangeTex = browser.isMobile() ? 1 : 3,    getCount = 0 
+        let maxWaitDur = browser.isMobile() ? 2 : 4  
+        let changeCount = 0, getCount = 0, maxChangeTex = 1
         let changeTexCount = ()=>{
             changeCount ++;
         }
         let median = Potree.timeCollect.depthSamChangeImg.median 
-          
+        let loopOutCount = 0
         
         let ifOverTime = ()=>{ 
-            let is = changeCount >= maxChangeTex || changeCount * median  +  getCount * 0.01 > maxWaitDur//不换贴图也要一丢丢计算时间 
+            let is = changeCount >= maxChangeTex || changeCount * median  +  getCount * 0.01 + 0.0004 * panos.length * loopOutCount > maxWaitDur//不换贴图也要一丢丢计算时间 
             /* if(is){
                 console.log('OverTime, changeCount', changeCount)
             } */  
@@ -2697,15 +2702,24 @@ Images360.prototype.getNeighbours = function(){ //逐渐自动获取neighbours
         }
         this.depthSampler.addEventListener('changeImg', changeTexCount)
         
-        outer: for(let i=lastIndex,j=panos.length; i<j; i++){
+       
+        
+        
+        const maxChangePanoCount = Math.min(panos.length,this.depthSampler.maxDataCount) //在lastIndex清空前最多可以有的mainPano的数量。  限制数量,否则一直changeTex很卡
+          
+        outer: for(let i=lastIndex,j=panos.length; i<maxChangePanoCount/* j */; i++){
             let pano = panos[i];
             let others = panos.slice(i+1, j)
             lastIndex = i
             if(!pano.pointcloud.hasDepthTex && i>0)break; //点云的情况下最好不改相机位置,因为其他位置点云还没加载完,所以不判断当前点以外的漫游点
-            var g = Common.sortByScore(others, [ ], [
-                Images360.scoreFunctions.distanceSquared(pano.position)
-            ]);
              
+            viewer.addTimeMark('sortByScore','start')
+            var g = Common.sortByScore(others, [ ], [  
+                Images360.scoreFunctions.distanceSquaredNew(pano)    //900个点时至少耗时0.3
+            ]); 
+            loopOutCount ++
+            viewer.addTimeMark('sortByScore','end')
+            
             for(let a=0, b=g.length; a<b; a++){
                 let item = g[a]
                 if(item.item == pano)continue  
@@ -2714,7 +2728,12 @@ Images360.prototype.getNeighbours = function(){ //逐渐自动获取neighbours
                 //console.log('check isNeighbour', pano.id, item.item.id)
                  
                 let byCloud = !pano.pointcloud.hasDepthTex
-                let result = this.isNeighbour(pano, item.item,  {onlyUseTex: !byCloud, computeDirFirst:true, computeTwoDir:true})//计算 
+                //const computeTwoDir = <maxChangePanoCount  
+                
+                const computeTwoDir = this.depthSampler.nearPanos.includes(item.item) || this.depthSampler.imgDatas.some(e=>e.pano == item.item)
+                //一定数量内允许双向计算,也就是允许changeTex. 这样一来,每到一个新的pano最多只有开头一秒卡一点,不然一直changeTex很卡
+                
+                let result = this.isNeighbour(pano, item.item,  {onlyUseTex: !byCloud, computeDirFirst:true, computeTwoDir})//计算 
                 
                 if(result != void 0){//计算了 (byTex时其实并不一定)
                     //console.log('提前计算neighbor', pano.id,  item.item.id)
@@ -2734,6 +2753,11 @@ Images360.prototype.getNeighbours = function(){ //逐渐自动获取neighbours
             }
             
             lastIndex = i+1  //这轮结束
+            
+            
+            if(ifOverTime()){
+                break outer
+            }
         } 
          
         /* if(changeCount){
@@ -2746,13 +2770,18 @@ Images360.prototype.getNeighbours = function(){ //逐渐自动获取neighbours
         if(!inited){
             inited = true
             this.addEventListener('loadedDepthImg',(e)=>{
-                //console.log('loadedDepthImg',e.pano)  
-                lastIndex = 0           //主要针对刚打开场景第一个点有杂点时单向不成功,要立即进一步双向计算ifShelter
+                //console.log('loadedDepthImg',e.pano)
+                const computeTwoDir = this.depthSampler.nearPanos.includes(e.pano) || this.depthSampler.imgDatas.some(a=>e.pano == a.pano)
+                if(computeTwoDir){
+                    lastIndex = 0           //主要针对刚打开场景第一个点有杂点时单向不成功,要立即进一步双向计算ifShelter
+                }
             })
         }
         
-        /* let costTime = Date.now() - startTime
-        costTime > maxWaitDur && console.log( 'costTime:',costTime)  */
+        let costTime = Date.now() - startTime   //控制好时间,否则转动时卡(主要是开始拖拽时延迟). 尽量在转完一圈使附近disMap都算完后costTime和maxWaitDur接近
+        //costTime > maxWaitDur && changeCount == 0  && console.log( 'costTime:',costTime) 
+        //console.log('getCount',getCount)
+         
     } 
 
 }()
@@ -2761,10 +2790,11 @@ Images360.prototype.getNeighbours = function(){ //逐渐自动获取neighbours
 Images360.filters = { 
     inPanoDirection : function(pos, dir, i, log) { //pano在mouse的方向上
         return function(pano) { 
+            var o = pano.position.clone().sub(pos).normalize() 
+            if(o.dot(dir) > i)return true
             var r = pano.floorPosition.clone().sub(pos).normalize() 
-            var o = pano.position.clone().sub(pos).normalize()
-            log && console.log('dire',pano.id, r.dot(dir),  o.dot(dir) )
-            return r.dot(dir) > i || o.dot(dir) > i  
+            if(r.dot(dir) > i)return true
+            
         }
     },
     inFloorDirection: function(pos, dir, min, log) { //pano在mouse的水平方向上
@@ -2816,7 +2846,7 @@ Images360.filters = {
 Images360.scoreFunctions = {
    direction: function(curPos, dir, ifLog) {
         return function(pano) { 
-            var pos1 = /* pano.floorPosition */  pano.position         //旧:改为权重放在marker上,这样对有斜坡的更准确,如上楼, 但这样近距离的pano角度就会向下了,以致于走不到
+            var pos1 = /* pano.floorPosition */  pano.position  //旧:改为权重放在marker上,这样对有斜坡的更准确,如上楼, 但这样近距离的pano角度就会向下了,以致于走不到
             var n = pos1.clone().sub(curPos).normalize();
             //ifLog && console.log('direction', pano.id, n.dot(dir) * directionFactor )
             return n.dot(dir) * directionFactor  
@@ -2834,12 +2864,28 @@ Images360.scoreFunctions = {
     }, 
     distanceSquared: function(pos1, r=1 ) { 
         if(pos1.position)pos1 = pos1.position
-        return  function(pano) {//许钟文 改
+        return function(pano) {//许钟文 改
             var pos2 = pano.position.clone() 
             return pos1.distanceToSquared(pos2) * -1 * r;
         }
     },
-     
+    distanceSquaredNew: function(mainPano, r=1 ) {  
+        let disMap = viewer.images360.disMap
+        
+        return function(pano) {//许钟文 改
+            if(!disMap[mainPano.id]) disMap[mainPano.id] = {}
+            if(!disMap[pano.id]) disMap[pano.id] = {}
+            let dis   
+            if(disMap[mainPano.id][pano.id] != void 0) dis = disMap[mainPano.id][pano.id]
+            else if(disMap[pano.id][mainPano.id]) dis = disMap[pano.id][mainPano.id]
+            else{
+                dis = Images360.scoreFunctions.distanceSquared(mainPano,1)(pano) 
+                disMap[mainPano.id][pano.id] = dis  
+            }
+            return dis * r
+             
+        }
+    },
     angle: function(e, t) {
         return function(i) {
             var n = i.position.clone().sub(e).normalize();
@@ -3103,7 +3149,7 @@ Images360.prototype.updateCube = (function(){//增加细分的版本,且垂直
                         //let r = maxR - ( maxR - minR) * THREE.Math.clamp((dis2d - minDis)  / (maxDis - minDis),0,1) //dis2d越大,角度要越小  //THREE.Math.smoothstep(currentDis,  op.nearBound,  op.farBound);
                         let r = math.linearClamp(dis2d, [minDis,maxDis],   [maxR,  minR]) 
                         //console.log('dis2d',dis2d,'r',r) 
-                         
+                          
                         
                         let angles = ((browser.isMobile()/* || dis2d<4 */)? [60] : [50,70]   /*  [35,65] */).map(deg=>{ //正的在左边  尽量能够平分中间这段墙体。 (角度为从中心向外)
                             let angle = THREE.Math.clamp(deg * r, 5, 80);

+ 49 - 58
src/custom/modules/panos/Panorama.js

@@ -41,7 +41,7 @@ const labelProp2 = {
 let markerTex
 
 //显示全景图时marker没有被遮挡,如果需要,要换成depthBasicMaterial  或者直接把skybox的深度修改(拿到深度贴图后更如此)
-let planeGeo = new THREE.PlaneBufferGeometry(0.2,0.2);
+let planeGeo = new THREE.PlaneBufferGeometry(0.4,0.4);
 
 
 
@@ -55,7 +55,8 @@ let sm = new THREE.MeshBasicMaterial({/* side: THREE.BackSide */});
 var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1),  Math.PI/2 ); //使用的是刚好适合全景图的,给cube贴图需要转90°
 //var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0),  -Math.PI/2 ); //4dkk->navvis
 //var rot901 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), -Math.PI/2 ); //整张球幕图要旋转下
-//rot90 = new THREE.Quaternion().multiplyQuaternions(  rot901, rot90)   
+//rot90 = new THREE.Quaternion().multiplyQuaternions(  rot901, rot90) 
+const rotQua2 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI   )  
 var old = null;
 /* 
 转成四维看看的axis:
@@ -69,7 +70,7 @@ var a = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.
 //暂时直接用4dkkconsole输出的数据 
 class Panorama extends THREE.EventDispatcher{
 
-	constructor(o,  images360){//file, time, longitude, latitude, altitude, course, pitch, roll
+	constructor(o,  images360){
         super()
         this.id = o.id; //唯一标识
         this.images360 = images360
@@ -84,13 +85,7 @@ class Panorama extends THREE.EventDispatcher{
             }
             this.label2 && Potree.Utils.updateVisible(this.label2, 'panoVisi', e.visible)
         })
-        /*  
-        漫游点可见性:旧
-            level       reason                           类型
-            2(最高)buildingChange(不在此楼层)        unvisible   
-            1       modeIsShowPanos(漫游模式)          visible    //不记得为什么加这个了,所以重写
-            0       pointcloudVisi(隐藏了数据集)       unvisible
-         */
+         
          
          /* 
         漫游点可见性:新
@@ -102,52 +97,47 @@ class Panorama extends THREE.EventDispatcher{
          
         
         if(Potree.settings.editType == 'pano'){//漫游点拼合编辑
-            this.uuid = o.uuid  //因为有多个数据集 所以会重复
-            this.index = this.originID = o.index  //下标, 用于visibles
+            this.uuid = this.originID = o.uuid  //对应4dkk中的id,可能不连续
+            this.index = o.index  //下标, 用于visibles
+              
+            
             this.pointcloud = viewer.scene.pointclouds.find(e=>e.panoUuid == o.uuid) 
             this.pointcloud.panos.push(this)
             this.sid = this.pointcloud.dataset_id + '|' + this.uuid  //不会更改的标记  用于entity.panos里的标记
             
             delete o.pointcloud
-            this.panosData = o
+            this.panoData = o
             
             
-            //数据中原本的位置朝向
+            /* //数据中原本的位置朝向
             this.dataPosition = new THREE.Vector3().copy(o.pose.translation) 
             this.dataQuaternion = new THREE.Quaternion().copy(o.pose.rotation) 
-            this.dataRotation = new THREE.Euler().setFromQuaternion(this.dataQuaternion) 
+            this.dataRotation = new THREE.Euler().setFromQuaternion(this.dataQuaternion)  */
             
             
-            //因为位置朝向随着点云位置改变,所以直接运用到点云上,这里清零
-            this.originPosition = new THREE.Vector3()   //{x: 0, y: 0, z: 0}
-            this.quaternion = new THREE.Quaternion()  //{w: 0, x: 0, y: 0, z: 1}
-            //this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
-            this.visibles = o.visibles 
+            //因为位置朝向随着点云位置改变,所以直接改变点云,这里清零
+            this.originPosition = new THREE.Vector3()  
+            this.quaternion = new THREE.Quaternion().copy(rotQua2);
+      
+            this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion
+
+                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()
             this.originFloorPosition.z -= height
             
-            
-            /* this.originPosition = new THREE.Vector3().copy(o.pose.translation)  //{x: 0, y: 0, z: 0}
-            this.quaternion = new THREE.Quaternion().copy(o.pose.rotation) //{w: 0, x: 0, y: 0, z: 1}
-            //this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
-            this.visibles = o.visibles 
-            this.pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == o.uuid) 
-            this.pointcloud.panos.push(this)
-            
-            const height = 1.5; //相机高度
-            this.originFloorPosition = this.originPosition.clone()
-            this.originFloorPosition.z -= height
-             */
-            
+       
             
         }else{
             this.originPosition = new THREE.Vector3().fromArray(o.dataset_location)   //完全对应vision.txt的translation
             this.originFloorPosition = new THREE.Vector3().fromArray(o.dataset_floor_location)
             
-            this.originID = parseInt(o.file_id)//"file_id":"00022"对应是原本的4dkk的id --来自vision.txt
+            this.originID = parseInt(o.file_id)//"file_id":"00022" 对应是4dkk的id --来自vision.txt
              
             this.pointcloud = o.pointcloud //viewer.scene.pointclouds.find(e=>e.dataset_id == o.dataset_id) || viewer.scene.pointclouds[0]
             this.pointcloud.panos.push(this)
@@ -176,19 +166,13 @@ class Panorama extends THREE.EventDispatcher{
                 
                 this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk)
                 
-             //}
-             
-             
-                //this.quaternion1 = Potree.Utils.QuaternionFactory.fromArray(o.dataset_orientation)
-                //同quaternion
-
-          
-            //let xy = this.transform.forward([this.longitude, this.latitude]);  
-            //this.file = `https://4dkk.4dage.com/images/images${Potree.settings.number}/pan/high/${this.id}.jpg`
-            
-             this.neighbours = [];
             
         }
+        
+        
+        
+        this.neighbours = []; 
+        
         this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion) 
         this.build()
         this.transformByPointcloud() //初始化位移
@@ -274,6 +258,9 @@ class Panorama extends THREE.EventDispatcher{
         texture.magFilter = THREE.LinearFilter 
         texture.minFilter = THREE.LinearFilter
         texture.generateMipmaps = false
+        
+        
+        //平均一张0.75M。2048*1024。 和tile一样加载后永不删除,是否会造成崩溃?
 	}
  
     
@@ -293,13 +280,8 @@ class Panorama extends THREE.EventDispatcher{
             //this.quaternion = quaternion
         } 
          
-        /* let marker = new THREE.Mesh(planeGeo, this.getMarkerMat() ) 
-            
-            //marker.lookAt(marker.up) 
-            marker.scale.set(2,2,2)  */
             
         let marker = new THREE.Mesh(planeGeo, this.getMarkerMat() ) //new Sprite({mat:this.getMarkerMat(), dontFixOrient:true })
-            marker.scale.set(2,2,2)//marker.scale.set(0.4,0.4,0.4)
             marker.name = 'marker_'+this.id
             marker.up.set(0,0,1)
             
@@ -311,7 +293,7 @@ class Panorama extends THREE.EventDispatcher{
         marker.pano = this;
         this.images360.node.add(marker)
         Potree.settings.isTest && this.addLabel()
-        //this.addLabel2() 
+     
          
         marker.addEventListener('mouseover', this.hoverOn.bind(this));  
         marker.addEventListener('mouseleave', this.hoverOff.bind(this)); 
@@ -321,7 +303,7 @@ class Panorama extends THREE.EventDispatcher{
     
     
     transformByPointcloud(){
-        
+        this.ceilZ = null //need reset
         let position = this.originPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);//也可以用datasetPosTransform算
         let floorPosition = this.originFloorPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);
         this.setPosition(position, floorPosition) 
@@ -339,8 +321,13 @@ class Panorama extends THREE.EventDispatcher{
         this.position = position
         this.floorPosition = floorPosition
         //this.mesh.position.copy(this.position)
-        this.marker.position.copy(this.floorPosition) 
-        this.marker.position.z+=0.04//会被点云遮住
+        this.marker.position.copy(this.floorPosition)
+        this.marker.lookAt(position) //融合页面marker可能跟随模型倾斜
+        
+        let upVec = new THREE.Vector3().subVectors(position, floorPosition).normalize().multiplyScalar(0.04*this.pointcloud.scale.x)
+        this.marker.position.add(upVec) //this.marker.position.z+=0.04//会被点云遮住
+        
+        
         if(this.label){ 
             if(Potree.settings.editType == 'pano'){
                 this.label.position.copy(this.position)
@@ -591,13 +578,17 @@ class Panorama extends THREE.EventDispatcher{
         
         let i = viewer.images360.panos.indexOf(this);
         if(i==-1)return
-        
-        this.marker.parent.remove(this.marker)
+        viewer.images360.panos.splice(i,1);
+          
+        i = this.pointcloud.panos.indexOf(this)
+        this.pointcloud.panos.splice(i,1);
         
         
+        this.marker.parent.remove(this.marker)
+         
         this.removeTextLabel()
         if(this.depthTex) this.depthTex.dispose()
-        viewer.images360.panos.splice(i,1);
+             
         
         this.dispatchEvent('dispose')
         //删除tile贴图、depthTex等以后再写
@@ -781,7 +772,7 @@ Panorama.prototype.loadTiledPano = function() {
             this.images360.tileDownloader.clearForceQueue() 
             this.images360.tileDownloader.forceQueueTilesForPano(this, size, dir, h, u, download) 
             this.tiledPanoRenderTarget = this.images360.panoRenderer.activateTiledPano(this, this.images360.qualityManager.getMaxNavPanoSize(), o) 
-            this.images360.panoRenderer.renderPanoTiles(this.id, dirs, a)
+            this.images360.panoRenderer.renderPanoTiles(this.id, dirs, a) //将512的先贴上
             
         }else{
             //console.log('早已经全加载好了' +size, this.id)

+ 6 - 4
src/custom/modules/panos/tile/PanoRenderer.js

@@ -114,7 +114,7 @@ class PanoRenderer extends THREE.EventDispatcher{
         return this.activeRenderTargetDescriptors[e]
     }
 
-    setActiveRenderTargetDescriptor(e, t) {
+    setActiveRenderTargetDescriptor(e, t) { 
         this.activeRenderTargetDescriptors[e] = t
     }
 
@@ -141,7 +141,8 @@ class PanoRenderer extends THREE.EventDispatcher{
     }
 
 
-    activateTiledPano(pano, size, i) {
+    activateTiledPano(pano, size, i) { 
+        
         i && this.clearAllQueuedUploads();
         for (var n = 0; n < TileUtils.FACES_PER_PANO; n++)
             this.initTileTree(pano.id, n, this.qualityManager.getMaxPossiblePanoSize()); //得到this.tileTrees[pano.id],arr[6]
@@ -313,7 +314,8 @@ class PanoRenderer extends THREE.EventDispatcher{
         var n = this.getActiveRenderTargetDescriptor(e.panoId); 
     
         if (this.isRenderTargetDescriptorValid(n) && e.downloaded && !this.isTileUploaded(e) && (!e.uploadQueued || i) 
-            && (!(e.panoSize > this.qualityManager.getMaxNavPanoSize())|| this.zoomingActive)) {
+            && (!(e.panoSize > this.qualityManager.getMaxNavPanoSize()) || this.zoomingActive &&  viewer.images360.getPano(e.panoId).tileRes != '2k')) {
+ 
             
             var r = this.getUploadQueueForPano(e.panoId);
             //console.log(window.sceneName, 'queueTileUpload: ', e.panoId, e.tileIndex,   i)
@@ -537,7 +539,7 @@ class PanoRenderer extends THREE.EventDispatcher{
         i.direction = (new THREE.Vector3).copy(e.direction);
         i.node = null;
         i.level = TileTree.getLevelCountForSize(TileUtils.TILE_SIZE, i.panoSize);
-        if (this.isPanoActive(i.panoId)) {
+        if (this.isPanoActive(i.panoId)) { 
             var n = this.getTileTree(i.panoId, i.face);
             var r = n.getSubNode(i.panoSize, i.tileX, i.tileY);
             this.linkTileAndNode(i, r);

+ 3 - 0
src/custom/modules/panos/tile/QualityManager.js

@@ -24,6 +24,9 @@ export default class QualityManager {
         this.qualityManager = this;
         
         this.maxRenderTargetSize = browser.isMobile() ? 2048 : 4096  //add
+          
+        this.maxRenderTargetSize = Math.min( viewer.renderer.capabilities.maxCubemapSize,  this.maxRenderTargetSize)
+        
         this.init()
     }
 

+ 42 - 12
src/custom/modules/panos/tile/TileDownloader.js

@@ -103,25 +103,38 @@ class TileDownloader extends THREE.EventDispatcher{
                 .bind(this), e)
     } */
 
-    update() { 
+    update() {
+        if(!this.panos)return
+        let time = Potree.Common.getBestCountFPS('processPriorityQueue', false, 700, 66 ) 
         if(this.downloadCubeTex){ //可以下载贴图
             var e = this.forceQueue.length > 0;
             this.processQueueForDownloading(this.forceQueue);
             if (this.processPriorityQueue) {  
+                
                 Potree.Common.intervalTool.isWaiting('processPriorityQueue', ()=>{ //延时update,防止崩溃 , 未到时间就拦截(第一次直接执行)
-                    this.queuePrioritizedTilesForPanos(this.panos)   //这句比较耗时 降四倍时大概1-2毫秒
-                }, 66)
+                    this.downloadCubeTex && this.queuePrioritizedTilesForPanos(this.panos)   //这句比较耗时 降四倍时大概1-2毫秒
+                }, time)
                 
                 this.priorityQueue.length > 0 && (e = !0);
                 this.processQueueForDownloading(this.priorityQueue);
             }
             //return e 
-        } 
+        }else if( viewer.scene.pointclouds.some(e=>e.hasDepthTex)){//不加载贴图也要获得nearPanos
+            viewer.lastFrameChanged && Potree.Common.intervalTool.isWaiting('processPriorityQueue', ()=>{ 
+                this.tilePrioritizer.getNearPanos(this.panos)  
+            }, time)
+                
+        }
+  
+        
+        time = Potree.Common.getBestCountFPS('filterDepthTex',  false,  10000, 88, 10, 63  ) 
         Potree.Common.intervalTool.isWaiting('filterDepthTex', ()=>{ 
             this.tilePrioritizer.filterDepthTex(this.panos)//下载深度图
-        }, 77)
-        
-        
+        }, time)
+            
+            
+            
+         
          
         
     }
@@ -129,10 +142,14 @@ class TileDownloader extends THREE.EventDispatcher{
     
 
     queuePrioritizedTilesForPanos(e) {
+        
+        viewer.addTimeMark('queuePrioritizedTilesForPanos','start')
         this.tilePrioritizer && (this.clearQueue(this.priorityQueue),
             this.tilePrioritizer.filterAndPrioritize(this.priorityQueue, e, this),
             this.clearFromQueue(this.priorityQueue, DownloadStatus.None, !0),  //去除state为DownloadStatus.None的(可能是去除已经在下载的)
             this.setStatusOrRemoveForAllDescriptors(this.priorityQueue, DownloadStatus.Queued))
+        viewer.addTimeMark('queuePrioritizedTilesForPanos','end')
+    
     }
 
     clearQueue(e) {//停止下载并清空
@@ -184,7 +201,7 @@ class TileDownloader extends THREE.EventDispatcher{
     processQueueForDownloading(e, t) {//执行下载任务
         this.cleanupActiveDownloads();
         if(e.length){
-            let concurrentDownloads = Potree.Common.getBestCount('concurrentDownloads',1,6,    1.8, 14/* ,true */) //flying ? (isMobile ? 2 : 3) : 6 
+            let concurrentDownloads = Potree.Common.getBestCount('concurrentDownloads',1,6,    1.8, 13    /* ,true   */ )  
             
             if (this.activeDownloads.length < concurrentDownloads || t) {
                 var i = t ? e.length : concurrentDownloads - this.activeDownloads.length;
@@ -421,7 +438,7 @@ TileDownloader.prototype.forceQueueTilesForPano = function() {//根据条件开
         for (var u = this.getTileDownloadDescriptors(pano, size), d = 0; d < u.length; d++) {
             var p = u[d];
             p.status !== DownloadStatus.None && p.status !== DownloadStatus.Queued || e.push(p)
-        }
+        }//挑出没下载的开始下载
         if (dir && e.length > 0) {
             TilePrioritizer.sortPanoTiles(e, pano, dir) //按最佳方向排序e
             t.length = 0 
@@ -484,7 +501,8 @@ TileDownloader.prototype.getTileUrl = function() {
             tileSize = o.tileSize,
             tileIndex = o.tileIndex,
             sceneCode = o.pano.pointcloud.sceneCode,
-            useV4url = Potree.settings.useV4url && o.pano.pointcloud.datasetData.sceneVersion == 'V4'        //v4的全景图等路径不一样  
+            useV4url = Potree.settings.useV4url && (!o.pano.pointcloud.datasetData || o.pano.pointcloud.datasetData.sceneVersion == 'V4')        //v4的全景图等路径不一样  
+        var resolution = o.pano.pointcloud.tileRes || '4k'
         var metadata = {sceneScheme:10}  
         
         
@@ -506,7 +524,7 @@ TileDownloader.prototype.getTileUrl = function() {
           
         }else{//阿里云oss的规则   if (metadata.sceneScheme == 10) 
             
-            d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
+            d = 'tiles/'+ resolution +'/' + id + '_skybox' + h + '.jpg?x-oss-process=';
             if (e[panoSize] == '512') {
                 d += 'image/resize,h_512';
             } else {
@@ -514,7 +532,7 @@ TileDownloader.prototype.getTileUrl = function() {
                 if (e[panoSize] == '1k' || e[panoSize] == '2k') {  //https://4dkk.4dage.com/images/imagesx4iqYDG3/tiles/4k/122_skybox0.jpg?x-oss-process=image/resize,m_lfit,w_1024/crop,w_512,h_512,x_511,y_0
                     d += 'image/resize,m_lfit,w_' + panoSize + '/crop,w_512,h_512,';
                 } else {
-                    d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
+                    d = 'tiles/'+ resolution +'/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
                 }
                 //起始位置
                 if (t.tileX == 0) {
@@ -593,4 +611,16 @@ TileDownloader.DOWNLOAD_RETRIES = 4;
     concurrentDownloads: TileDownloader.tilegen ? 6 : 2
 }) */
 
+
+
+
+/* 
+
+漫游点的tile虽然tex会被释放,但图片还存在viewer.images360.tileDownloader.downloadDescriptors每个tile的image中
+于是内存会一直增加,是否需要删除呢?
+还有depthTex,一张700k左右。
+
+
+
+ */
 export default TileDownloader

+ 61 - 39
src/custom/modules/panos/tile/TilePrioritizer.js

@@ -47,6 +47,10 @@ export default class TilePrioritizer {//优先级处理序列
         this.highSize = o;
         this.ultraHighSize = a;
         this.priorityCriteria = new TilePrioritizer.PriorityCriteria(null, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 0, -1));
+    
+        
+    
+    
     }
 
     updateCriteria(e, t, i, n) {//由player更新
@@ -59,8 +63,11 @@ export default class TilePrioritizer {//优先级处理序列
         this.maxNavQuality = this.qualityManager.getMaxNavPanoSize(),
         this.maxZoomQuality = this.qualityManager.getMaxZoomPanoSize()
           
-            
+             
     }
+    
+    
+    
 
     canDownloadSize(e) {
         return this.maxNavQuality >= e || this.maxZoomQuality >= e && this.zoomingActive
@@ -84,26 +91,67 @@ export default class TilePrioritizer {//优先级处理序列
             }
         return i
     } */
+ 
+    getNearPanos(panos){
+        if(!this.priorityCriteria.pano || viewer.mainViewport.view.isFlying())return
+        let cameraDirLocals = this.priorityCriteria.cameraDirs.vectorForward
+           
+        this.populateScoredPanos(panos, [], cameraDirLocals , Infinity, true); 
+        
+    } 
 
-    populateScoredPanos(e, t, i, dirs, a, dontFilterDir) {
+    filterDepthTex(panos) {// 下载depthTex
+        if(!Potree.settings.useDepthTex )return
+        viewer.addTimeMark('filterDepthTex','start')
+        
+        let dlCount = 1  //window.slowDown ? 0 : (browser.isMobile() ? 1 : Common.getBestCountFPS('loadDepTexCount', false, 1, 2, 57, 62   ) )
+        
+        let loadingCount = panos.filter(p=>p.depthTexLoading).length
+        if(viewer.mapViewer){
+            let mapLayer = viewer.mapViewer.mapLayer 
+            loadingCount +=  mapLayer.loadingInProgress * 1.5 + mapLayer.waitQueue.length  
+        }
+        
+        if(loadingCount<dlCount){
+            //console.log('dlCount-loadingCount', dlCount-loadingCount)
+            (this.nearPanos || panos).filter(p=>!p.depthTex).slice(0, dlCount-loadingCount).forEach(p=>p.loadDepthImg())  
+        }
+        
+        viewer.addTimeMark('filterDepthTex','end')
+    }
+
+    
+    populateScoredPanos(/* e, */ t, i, dirs, count, dontFilterDir) {
         i = i || [],
             i.length = 0;
+        viewer.addTimeMark('populateScoredPanos','start')    
             
+        var s = [Images360.filters.not(this.priorityCriteria.pano)],
+            l = [Images360.scoreFunctions.distanceSquaredNew(this.priorityCriteria.pano), Images360.scoreFunctions.direction(this.priorityCriteria.pano.position, dirs)] 
             
-        var s = [Images360.filters.not(e)],
-            l = [Images360.scoreFunctions.distanceSquared(e), Images360.scoreFunctions.direction(e.position, dirs)],
-            c = Common.sortByScore(t, s, l);  
-        if(!dontFilterDir){
-            s.push(Images360.filters.inPanoDirection(e.position, dirs, TilePrioritizer.DIRECTION_SCORE_STRICTNESS),)
-        }    
-            
+        /* if(!dontFilterDir){//太耗时了,尤其是点多时,直接去掉,反正有count限制。如果必须的话,使用direction中得到的值
+            s.push(Images360.filters.inPanoDirection(this.priorityCriteria.pano.position, dirs, TilePrioritizer.DIRECTION_SCORE_STRICTNESS) )
+        } */  
+        let c = Common.sortByScore(t, s, l);  
+        //900个点的时候populateScoredPanos已经耗时1.8
+        
+        
+        viewer.addTimeMark('populateScoredPanos','end')    
+        
+
+        this.nearPanos = c.map(e=>e.item);   //xzw add   用于获得邻近点位序列  
+
+        
         if (c)
-            for (var h = 0; h < c.length && h < a; h++) {
+            for (var h = 0; h < c.length && h < count; h++) {
                 var u = c[h].item;
                 i.push(u)
             }
+             
+            
         return i
     }
+    
 
     queueTilesForPanos(e, t, i, n, r) {
         for (var o = 0, a = 0; a < t.length; a++) {
@@ -214,32 +262,6 @@ TilePrioritizer.insertSortedPanoTile = function (e, t, pano, dir) {
 
 
 
-TilePrioritizer.prototype.filterDepthTex = function (panos ) {// 下载depthTex
-    if(!Potree.settings.useDepthTex || !this.priorityCriteria.pano || viewer.mainViewport.view.isFlying())return
-    
-    let cameraDirLocals = this.priorityCriteria.cameraDirs.vectorForward
-    let nearPanos = [] //用于获得邻近点位序列 
-    
-    this.populateScoredPanos(this.priorityCriteria.pano, panos, nearPanos, cameraDirLocals , Infinity, true);
-    
-    
-     
-    let dlCount = browser.isMobile() ? 1 : 2; 
-    
-    let loadingCount = panos.filter(p=>p.depthTexLoading).length
-    if(viewer.mapViewer){
-        let mapLayer = viewer.mapViewer.mapLayer 
-        loadingCount +=  mapLayer.loadingInProgress * 1.5 + mapLayer.waitQueue.length  
-    }
-    
-    if(loadingCount<dlCount){
-        nearPanos.filter(p=>!p.depthTex).slice(0, dlCount-loadingCount).forEach(p=>p.loadDepthImg())  
-    }
-    this.nearPanos = nearPanos; 
-}
-
-
-
 TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先加载的 pano和tile (有点复杂,没看很懂)
     var e = [],
         t = [],
@@ -261,7 +283,7 @@ TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先
         let cameraDirLocals = this.priorityCriteria.cameraDirs.vectorForward
          
         //获得视野范围内的邻近点位序列t
-        this.populateScoredPanos(this.priorityCriteria.pano, panos, t, cameraDirLocals , TilePrioritizer.MAX_SCORED_PANOS_TOCONSIDER);
+        this.populateScoredPanos(/* this.priorityCriteria.pano,  */panos, t, cameraDirLocals , TilePrioritizer.MAX_SCORED_PANOS_TOCONSIDER);
         
         
         //t.filter(p=>!p.depthTex).slice(0, Potree.config.depTexDlCount).forEach(p=>p.loadDepthImg()) //add
@@ -301,7 +323,7 @@ TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, c, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
         }
 
-        if (this.canDownloadSize(h)) {//4096
+        if (this.canDownloadSize(h) && this.priorityCriteria.pano.pointcloud.tileRes != '2k') {//4096
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, h, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
         } 
         TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);//排序
@@ -316,7 +338,7 @@ TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, c, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
         }
 
-        if (this.canDownloadSize(h)) {//4096
+        if (this.canDownloadSize(h) && this.priorityCriteria.pano.pointcloud.tileRes != '2k') {//4096
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, h, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
         }
 

+ 57 - 60
src/custom/modules/siteModel/SiteModel.js

@@ -33,40 +33,64 @@ var SiteModel = {
         this.meshGroup.name = 'siteModel' 
         this.SplitScreen = SplitScreen4Views
         
-        if(Potree.settings.editType == 'pano'){
+        /* if(Potree.settings.editType == 'pano'){
             return
+        } */
+        if(Potree.settings.editType != 'pano'){
+            this.createHeightPull();
+        
+        
+        
+            this.history = new History({ 
+                applyData: (data)=>{ 
+                    if(data.entity.parent && data.entity.selected ){
+                        data = Potree.Common.CloneObject(data) //避免使用后更改数据又被使用
+                        data.entity.reDraw() 
+                        data.entity.initData(data) 
+                        data.isNew || data.entity.addMidMarkers()
+                        data.entity.isNew = data.isNew
+                        data.entity.dispatchEvent('changeByHistory') 
+                        return true
+                    }  
+                },
+                getData:(entity)=>{  
+                    //if(entity.isNew)return
+                    return {
+                        entity, 
+                        points: entity.points.map(e=>e.clone()),
+                        ifDraw: true,
+                        isNew : entity.isNew,
+                    }
+                } 
+            })  
+            { 
+                let pressDelete = (e)=>{ 
+                    if(e.keyCode == KeyCodes.BACKSPACE || e.keyCode == KeyCodes.DELETE){ 
+                        if(this.selectedMarker){
+                            let entity = this.selectedMarker.parent
+                            let index = entity.markers.indexOf(this.selectedMarker) 
+                            entity.removeMarker(index) 
+                            
+                            if(entity.points.length<2){//删到只剩一个点时重新画(如果是hole的点,直接删除hole吧?)
+                                this.startInsertion('resume',entity)       
+                            }
+                        }
+                    }
+                } 
+                viewer.inputHandler.addEventListener('keydown', pressDelete) 
+                
+                
+            }
+            
+            {
+                let updated = false
+                this.bus.addEventListener('updated',()=>{  
+                    Common.intervalTool.isWaiting('siteModelUpdated', ()=>{  
+                        this.changedCallback()  
+                    },500)
+                })
+            }
         }
-          
-        this.createHeightPull();
-        
-        
-        
-        this.history = new History({ 
-            applyData: (data)=>{ 
-                if(data.entity.parent && data.entity.selected ){
-                    data = Potree.Common.CloneObject(data) //避免使用后更改数据又被使用
-                    data.entity.reDraw() 
-                    data.entity.initData(data) 
-                    data.isNew || data.entity.addMidMarkers()
-                    data.entity.isNew = data.isNew
-                    data.entity.dispatchEvent('changeByHistory') 
-                    return true
-                }  
-            },
-            getData:(entity)=>{  
-                //if(entity.isNew)return
-                return {
-                    entity, 
-                    points: entity.points.map(e=>e.clone()),
-                    ifDraw: true,
-                    isNew : entity.isNew,
-                }
-            } 
-        })  
-        
-        
-        
-        
         
         
         if(Potree.settings.isTest && ifDrawDatasetBound){
@@ -99,36 +123,9 @@ var SiteModel = {
                 }
             })
         }
+         
         
         
-        { 
-            let pressDelete = (e)=>{ 
-                if(e.keyCode == KeyCodes.BACKSPACE || e.keyCode == KeyCodes.DELETE){ 
-                    if(this.selectedMarker){
-                        let entity = this.selectedMarker.parent
-                        let index = entity.markers.indexOf(this.selectedMarker) 
-                        entity.removeMarker(index) 
-                        
-                        if(entity.points.length<2){//删到只剩一个点时重新画(如果是hole的点,直接删除hole吧?)
-                            this.startInsertion('resume',entity)       
-                        }
-                    }
-                }
-            } 
-            viewer.inputHandler.addEventListener('keydown', pressDelete) 
-            
-            
-        }
-        
-        
-        {
-            let updated = false
-            this.bus.addEventListener('updated',()=>{  
-                Common.intervalTool.isWaiting('siteModelUpdated', ()=>{  
-                    this.changedCallback()  
-                },500)
-            })
-        }
     }, 
     
     changedCallback(){

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

@@ -8,8 +8,6 @@ import * as THREE from "../../../../libs/three.js/build/three.module.js";
 let areaPlaneMats = {
     
 } 
-
-
  
 export class Prism extends Measure{
     constructor(args){

+ 6 - 4
src/custom/objects/Reticule.js

@@ -116,10 +116,9 @@ export default class Reticule extends THREE.Mesh{
 
     hide(duration = 500){ 
         if(this.hidden)return
-        
- 
-        
+         
         this.hidden = !0 
+         
         transitions.start(lerp.property(this.material , "opacity", 0, ()=>{//progress
             this.dispatchEvent({type:'update' })
             //viewer.dispatchEvent('content_changed')
@@ -228,7 +227,10 @@ export default class Reticule extends THREE.Mesh{
          
         if (Potree.Utils.getObjVisiByReason(this, 'force')) {//没有被强制隐藏,如进入某个页面后强制不显示
             if (!intersect /* || !intersect.point.normal */){ 
-                 return //this.hide();   
+                 
+                /* let {origin,direction} = viewer.inputHandler.getMouseDirection()//---防止无depthTex的场景全景时画面中只有一个mesh的话会变黑
+                intersect = {location:new THREE.Vector3().addVectors(direction, origin), normal:direction} */
+                return
             }
                 
             var atMap = !intersect.location

+ 6 - 2
src/custom/objects/TextSprite.js

@@ -39,9 +39,11 @@ export class TextSprite extends THREE.Object3D{
         this.borderColor = options.borderColor  ? Common.CloneObject(options.borderColor):{ r: 0, g: 0, b: 0, a: 0.0 };
 		this.borderRadius = options.borderRadius || 6;
         this.margin = options.margin
+        this.textAlign = options.textAlign || 'center'
+        this.name = options.name
         this.setText(options.text)
-        this.name = options.name 
          
+        
 		//this.setText(text);
         
         
@@ -232,8 +234,10 @@ export class TextSprite extends THREE.Object3D{
             //console.log(actualBoundingBoxAscent)
 
             //console.log(this.text, 'y' , y, 'actualBoundingBoxAscent', metrics.actualBoundingBoxAscent,'expand',expand )
-            let textLeftSpace = (textMaxWidth - infos[i].width) / 2
+            let textLeftSpace = this.textAlign == 'center' ? (textMaxWidth - infos[i].width) / 2 : this.textAlign == 'left' ? 0  :  textMaxWidth - infos[i].width
             let x = this.rectBorderThick + margin.x + textLeftSpace
+            
+            
             // text color
             if (this.textBorderThick) {
                 context.strokeStyle = 'rgba(' + this.textBorderColor.r + ',' + this.textBorderColor.g + ',' + this.textBorderColor.b + ',' + this.textBorderColor.a + ')'

+ 22 - 8
src/custom/objects/tool/TransformControls.js

@@ -16,7 +16,7 @@ var TransformControls = function ( camera, domElement, options ) {
 	THREE.Object3D.call( this );
 
     
-	this.visible = false;
+	Potree.Utils.updateVisible(this, 'attach', false)
 	this.domElement = domElement;
  
 	var _gizmo = new TransformControlsGizmo(options);
@@ -170,12 +170,13 @@ var TransformControls = function ( camera, domElement, options ) {
 	};
 
 	// Set current object
-	this.attach = function ( object ) {
+	this.attach = function ( object, clickPos ) {
 
 		this.object = object;
-		this.visible = true;
+        Potree.Utils.updateVisible(this, 'attach', true) 
 		//Config.keyCon = false;//add
         //this.linesAssistance.setVisible(true)
+        this.clickPos = clickPos?.isVector3 ? clickPos : null
         viewer.dispatchEvent('content_changed') 
 		return this;
 
@@ -185,10 +186,9 @@ var TransformControls = function ( camera, domElement, options ) {
 	this.detach = function () {
 
 		this.object = undefined;
-		this.visible = false;
 		this.axis = null;
 		//Config.keyCon = true;//add
-        
+        Potree.Utils.updateVisible(this, 'attach', false)
         //this.linesAssistance.setVisible(false)
         viewer.dispatchEvent('content_changed') 
 		return this;
@@ -257,8 +257,10 @@ var TransformControls = function ( camera, domElement, options ) {
 			this.object.matrixWorld.decompose( worldPosition, worldQuaternion, worldScale );
             
             
-            //add
-            if(this.object.boundingBox){
+            if(Potree.settings.mergeTransCtlOnClick && this.mode == 'translate' && this.clickPos ){//允许位移控件出现在点击的位置
+                let pos = this.clickPos.clone().applyMatrix4(this.object.matrixWorld)
+                worldPosition.copy(pos)
+            }else if(this.object.boundingBox){
                 let boundingBox = this.object.boundingBox.clone().applyMatrix4(this.object.matrixWorld)
                 boundingBox.getCenter(worldPosition) //bound中心
                 if(this.pivotOnBottom){
@@ -279,7 +281,7 @@ var TransformControls = function ( camera, domElement, options ) {
         
         
         if(this.camera.type == "OrthographicCamera"){//xzw add
-            eye.copy( this.view.direction)
+            eye.set(0,0,-1).applyQuaternion(this.camera.quaternion) // this.view.direction
         }else{
             eye.copy( cameraPosition ).sub( worldPosition ).normalize();
         }
@@ -918,7 +920,15 @@ var TransformControls = function ( camera, domElement, options ) {
 		scope.space = space;
 
 	};
+    
+    this.setCamera = function ( camera ){//add
 
+		scope.camera = camera;
+
+	}; 
+    
+    
+    
 	this.update = function () {
 
 		console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' );
@@ -1979,6 +1989,10 @@ var TransformControlsPlane = function (options) {
    
 export { TransformControls, TransformControlsGizmo, TransformControlsPlane };
 
+
+
+
+
  /*  
     备注:
     

+ 27 - 9
src/custom/potree.shim.js

@@ -159,7 +159,20 @@ 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
+         
+        let info = {
+            cubeMap: gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE),
+            tex: gl.getParameter( gl.MAX_TEXTURE_SIZE ) 
+        } 
+        if(info.cubeMap < 4096){
+            console.warn('cubeMap最大仅支持', info.cubeMap)
+        }
+        return info       
+    } */
 }
 
 
@@ -349,9 +362,7 @@ Utils.getMousePointCloudIntersection = function(viewport, mouse, pointer, camera
     for(let pointcloud of pointclouds){ 
          
         let point = pointcloud.pick(viewer, viewport, camera, ray, pickParams );
-        
-      
-        
+         
         if(!point){
             continue;
         }
@@ -662,8 +673,9 @@ Utils.datasetPosTransform = function(o={}){
 }
 
 Utils.datasetRotTransform = function(o={}){
-    let pointcloud = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId)
-    if(pointcloud){
+    let object = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId)
+                    || o.object || viewer.objs.children.find(e=>e.dataset_id == o.datasetId)
+    if(object){
         var matrix, newMatrix, result
         
         if(o.rotation){
@@ -675,7 +687,13 @@ Utils.datasetRotTransform = function(o={}){
         }else{
             return
         } 
-        let rotateMatrix = o.fromDataset ? pointcloud.rotateMatrix : pointcloud.rotateInvMatrix
+        let rotateMatrix = o.fromDataset ? object.rotateMatrix : object.rotateInvMatrix
+        if(!rotateMatrix){
+            rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(object.rotation)   //如果是没有漫游点的模型,在此临时获取一个,但和有漫游点的比会有所不同,因为没有初始旋转(如转90度)
+            if(o.toDataset){
+                rotateMatrix.invert()
+            }
+        }
         newMatrix = new THREE.Matrix4().multiplyMatrices(rotateMatrix, matrix  ) 
          
         if(o.getRotation){
@@ -1553,8 +1571,8 @@ Potree.updateVisibility = function(pointclouds, camera, areaSize){
     unloadedGeometry = unloadedGeometry.filter(e=>!e.loadFailed) //过滤加载失败的,否则有失败的就无法发送加载完成
     
     if(unloadedGeometry.length){//加载点云 
-        
-        let maxNodesLoading = Common.getBestCount('unloadedGeometry', viewer.lastFrameChanged?1:3, 6,  4, 15 /*  , true  */ )//dur在iphoneX中静止有7,pc是2  //!lastFrameChanged静止时加速下载
+        let idleCount = Common.getBestCountFPS('unloadedGeometry', false, 1, 3) //即使静止,因为加载不仅影响这一帧,所以低fps还是加载少一些
+        let maxNodesLoading = Common.getBestCount('unloadedGeometry', viewer.lastFrameChanged?1:idleCount, idleCount+4,  3, 14  /*  , true  */   )//dur在iphoneX中静止有7,pc是2  //!lastFrameChanged静止时加速下载
         //THREE.Math.clamp(Math.round(9 - dur), 1, 6 ) 
         //console.log('unloadedGeometry', unloadedGeometry.length)
         //主要在手机端有效果。不改之前在展示的点云较多时前进会卡。

+ 7 - 4
src/custom/settings.js

@@ -87,7 +87,7 @@ const config = {//配置参数   不可修改
     ,
     view:{
         fov:70,  //navvis:50 
-        near:0.1,
+        near:0.05 , // too small will result in z-fighting
         far: 10000,
     },
     
@@ -96,7 +96,7 @@ const config = {//配置参数   不可修改
         cameraHeight : 1000,  //最高  ,注意(如sitemodel)其他的物体不能超过这个高度
     },
     minNodeSize:30, // perspectiveCamera允许加载的node的最小可见像素宽度。越大越省性能
-    tiles3DMaxMemory: 300,//M. 最大支持3dTiles的内存大小 超出会崩溃。  改太小太大都会卡,太大崩溃
+    tiles3DMaxMemory: 200,//M. 最大支持3dTiles的内存大小 超出会崩溃。  改太小太大都会卡,太大崩溃
     pointDensity:{
         magnifier:{  
             maxLevelPercent: 1,
@@ -340,6 +340,9 @@ const config = {//配置参数   不可修改
     },
     
     depthTexUVyLimit: 0.141, // 在这个范围内是没有深度的,从图片算的0.14003, 设置为稍大于这个数值
+    
+    
+    //neighbourPath:'data/1111/extraNeighbours.js' //本地版手动额外配置,权重最高
 }
 
  
@@ -392,7 +395,7 @@ let settings = {//设置   可修改
     originDatasetId:'',//场景原本的数据集id,应该就是数据集第一个吧
     isOfficial:false,
     webSite:'testdata',//正式:'datav1', //不同环境对应的静态文件的地址不同
-     
+    // language : "zh"
     isLocal:false, //是否本地 局域网版本
     libsUrl:'../libs/',
     displayMode:'',
@@ -474,7 +477,7 @@ let settings = {//设置   可修改
     dragPolyBeyondPoint: browser.urlHasValue('dragPolyBeyondPoint'),  //ctrlPolygon是否可以拖拽到没点云的地方
     panoZoomByPointer: false,//全景图是否定点缩放
     areaAtNotPlane: false,
-    
+    showNeighSetGui: browser.urlHasValue('neighGui'),
     
     //fastTran: isTest
 }

+ 54 - 184
src/custom/start.js

@@ -1,6 +1,5 @@
 import * as THREE from "../../libs/three.js/build/three.module.js";
 
-
  
 
 
@@ -9,15 +8,18 @@ import browser from './utils/browser.js'
 import './three.shim.js'  
 import "./potree.shim.js"
  
-
+window.THREE = THREE
 export function start(dom, mapDom, number ){ //t-Zvd3w0m
     /* {
         let obj = JSON.parse(localStorage.getItem('setting'))
         for(let i in obj){
             console.log(i + ': ' +  obj[i])
-        }
+        }  
     }
-     */ 
+     */  
+      
+     
+    console.log('2024.9') 
     Potree.settings.number = number || 't-o5YMR13'// 't-iksBApb'// 写在viewer前
  
     if(!Potree.settings.isOfficial){ 
@@ -30,168 +32,7 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
     if(browser.urlHasValue('timing'))Potree.measureTimings = 1
     
     let viewer = new Potree.Viewer(dom , mapDom);
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    /* viewer.addEventListener('allLoaded',()=>{
-        
-        
-        
-            const baseGeometry = new THREE.BufferGeometry();
-            baseGeometry.setIndex([0, 1, 2, 0, 2, 3]);
-         
-            const positionsArray = new Float32Array(4 * 3);
-            const positions = new THREE.BufferAttribute(positionsArray, 3);
-            baseGeometry.setAttribute('position', positions);
-            positions.setXYZ(0, -1.0, -1.0, 0.0);
-            positions.setXYZ(1, -1.0, 1.0, 0.0);
-            positions.setXYZ(2, 1.0, 1.0, 0.0);
-            positions.setXYZ(3, 1.0, -1.0, 0.0);
-            positions.needsUpdate = true;
-            const geometry = new THREE.InstancedBufferGeometry().copy(baseGeometry);
-            let maxSplatCount = 8
-            // Splat index buffer
-            const splatIndexArray = new Uint32Array(maxSplatCount);
-            const splatIndexes = new THREE.InstancedBufferAttribute(splatIndexArray, 1, false);
-            splatIndexes.setUsage(THREE.DynamicDrawUsage);
-            geometry.setAttribute('splatIndex', splatIndexes);
-            
-            const splatPosArray = new Float32Array(maxSplatCount*3);
-            const splatPoses = new THREE.InstancedBufferAttribute(splatPosArray, 3, false);
-            splatPoses.setUsage(THREE.DynamicDrawUsage);
-            geometry.setAttribute('splatCenter', splatPoses);
-            
-            splatPosArray.set([0,0,0,
-            1,1,1,
-            2,2,2,
-            3,3,3,
-            4,4,4,
-            5,5,5,
-            6,6,6,
-            7,7,7])
-            
-            let vertexShaderSource = ` 
-                precision highp float;
-                #include <common>
-                attribute vec3 splatCenter;
-                
-                attribute float splatIndex;
-                  
-                varying vec2 vPosition;
-
-                const float sqrt8 = sqrt(8.0);
-                const float minAlpha = 1.0 / 255.0;
- 
-                void main () {
-
-                    mat4 transformModelViewMatrix = modelViewMatrix;
-                    vec4 viewCenter = transformModelViewMatrix * vec4(splatCenter, 1.0);
-                    vec4 clipCenter = projectionMatrix * viewCenter;
-
-                    float clip = 1.2 * clipCenter.w;
-                    if (clipCenter.z < -clip || clipCenter.x < -clip || clipCenter.x > clip || clipCenter.y < -clip || clipCenter.y > clip) {
-                        gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
-                        return;
-                    }
-
-                    vPosition = position.xy;
-                     
-                    vec3 ndcCenter = clipCenter.xyz / clipCenter.w;
- 
-                    float eigenValue1 = 0.2;
-                    float eigenValue2 = 0.2;
-                    
-                    //vec2 eigenVector1 = normalize(vec2(b, eigenValue1 - a)); 
-                    vec2 eigenVector1 = normalize(vec2(0.5, 0.5));
-                    vec2 eigenVector2 = vec2(eigenVector1.y, -eigenVector1.x);
-
-                    vec2 basisVector1 = eigenVector1  * min(sqrt8 * sqrt(eigenValue1), 2048.0);
-                    vec2 basisVector2 = eigenVector2  * min(sqrt8 * sqrt(eigenValue2), 2048.0);
- 
-
-                    vec2 ndcOffset = vec2(vPosition.x * basisVector1 + vPosition.y * basisVector2) / viewport * 2.0  ;
-
-                    gl_Position = vec4(ndcCenter.xy + ndcOffset,  ndcCenter.z , 1.0);
-                     
-                    vPosition *= sqrt8;
-                }`;
-
-            //面片模拟椭球,总是有厚度
-            const fragmentShaderSource = `
-                precision highp float;
-                #include <common>
-                
-                uniform vec3 color;
-                varying vec2 vPosition;
-
-                void main () {
-                    float A = dot(vPosition, vPosition);
-                    
-                    if (A > 8.0) discard;            //position的范围半径为1。指一个rectangle面中的范围。椭圆外的完全透明
-                     
-                    float opacity = exp(-0.5 * A) * vColor.a;   //周围的透明度降低
-
-                    gl_FragColor = vec4(color.rgb, opacity);
-                }`;
-
-            const uniforms = { 
-                'viewport': {
-                    'type': 'v2',
-                    'value': new THREE.Vector2()
-                } ,
-                color:{
-                    'type': 'v3',
-                    'value': new THREE.Color()
-                }
-            };
-
-             
-
-            const material = new THREE.ShaderMaterial({
-                uniforms: uniforms,
-                vertexShader: vertexShaderSource,
-                fragmentShader: fragmentShaderSource,
-                transparent: true,
-                alphaTest: 1.0,
-                blending: THREE.NormalBlending,
-                depthTest: true,
-                depthWrite: false,
-                side: THREE.DoubleSide
-            });
-            
-            window.splatMesh = new THREE.Mesh(geometry, material)
-        
-            viewer.scene.scene.add(window.splatMesh)
-            
-            
-            viewer.addEventListener('resize',(e)=>{
-                if(e.viewport.name == 'mainViewport'){
-                    uniforms.viewport.copy(e.viewport.resolution)
-                }
-            })
-            
-            window.setSplatIndexes = (globalIndexes)=>{ 
-                geometry.attributes.splatIndex.set(globalIndexes);  
-                geometry.attributes.splatIndex.needsUpdate = true;
-                geometry.instanceCount = globalIndexes.length
-            }
-    })
-    
-    
-     */
-    
-    
-    
-    
-    
-    
-    
+      
     let Alignment = viewer.modules.Alignment
      
 	viewer.setEDLEnabled(false);
@@ -240,7 +81,8 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
             if(!Potree.settings.isOfficial){
                 Potree.loadMapEntity('all') //加载floorplan 
             }
-            
+            Potree.loadNeighborFile()
+            Potree.setNeighborGui();
             
             if(!ifReload){    
                 viewer.dispatchEvent({type:'loadPointCloudDone'})
@@ -623,7 +465,7 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
         
     }       
 
-    window.THREE = THREE
+     
     window.buttonFunction = function(){
         
         
@@ -712,15 +554,14 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
  */
 //=======================================================================
 
-
-export function panoEditStart(dom, number, fileServer){
+ 
+export function panoEditStart(dom, number, EditCloudsArgs){
     Potree.settings.editType = 'pano'
     Potree.settings.number = number 
-    
-    Potree.settings.unableNavigate = true
+     
     Potree.settings.sizeFitToLevel = true//当type为衰减模式时自动根据level调节大小。每长一级,大小就除以2
      
-    
+    let datasetData
     let viewer = new Potree.Viewer(dom); 
     let Alignment = viewer.modules.Alignment
 	viewer.setEDLEnabled(false);
@@ -739,10 +580,11 @@ export function panoEditStart(dom, number, fileServer){
         Potree.settings.sizeFitToLevel = true 
     }
     
+    
+     
+    
     var pointcloudLoadDone = function( ){//所有点云cloud.js加载完毕后 
-         
-        
-        
+          
         viewer.scene.pointclouds.forEach(c=>{
             transformPointcloud(c)
         })
@@ -774,12 +616,15 @@ export function panoEditStart(dom, number, fileServer){
     var transformPointcloud = (pointcloud )=>{ //初始化位置  
         viewer.sidebar && viewer.sidebar.addAlignmentButton(pointcloud) 
          
-        let orientation =  pointcloud.panos[0].dataRotation.z + Math.PI
+        /* let orientation =  pointcloud.panos[0].dataRotation.z + Math.PI
         
         let location = pointcloud.panos[0].dataPosition.clone()//.negate()
         
         Alignment.rotate(pointcloud, null,  orientation  )   
-        Alignment.translate(pointcloud, location )  
+        Alignment.translate(pointcloud, location )   */
+        
+        viewer.modules.PanoEditor.initCloud(pointcloud)
+        
         
         pointcloud.updateMatrixWorld()
           
@@ -799,7 +644,7 @@ export function panoEditStart(dom, number, fileServer){
         let pointcloudLoaded = 0
         
         let datasetsCount = Object.keys(Potree.settings.datasetsPanos).length
-       
+        let datasetData = Potree.datasetData.find(e=>e.datasetId = datasetId)  
        
         panoData.forEach((pano, index)=>{
             //let cloudPath = `${Potree.scriptPath}/data/panoEdit/uuidcloud/${pano.uuid}/cloud.js` 
@@ -818,7 +663,7 @@ export function panoEditStart(dom, number, fileServer){
             let timeStamp = 0
             pano.index = index //注意:index不等于uuid,因为有的uuid缺失。但是visibles中存的是下标! 
             
-            Potree.loadPointCloud(cloudPath, name , name, timeStamp, e => { //开始加载点云
+            Potree.loadPointCloud(cloudPath, name , number, timeStamp, e => { //开始加载点云
                 let scene = viewer.scene;
                 let pointcloud = e.pointcloud; 
                 let config = Potree.config.material
@@ -833,7 +678,12 @@ export function panoEditStart(dom, number, fileServer){
                 pointcloud.dataset_id = datasetId;   //多个点云指向一个datasetId
                 pointcloud.panoUuid = pano.uuid
                 pointcloud.timeStamp = timeStamp
-                  
+                
+                if(datasetData){//非4dkk
+                    pointcloud.datasetData = datasetData
+                    pointcloud.hasDepthTex = Potree.settings.useDepthTex && !!datasetData.has_depth   
+                }
+                
                 //transformPointcloud(pointcloud, pano)
                 scene.addPointCloud(pointcloud);
                 pointcloudLoaded ++;
@@ -857,8 +707,26 @@ export function panoEditStart(dom, number, fileServer){
         Potree.settings.datasetsPano = {'testDataset':null}
         Potree.loadPanosInfo( data=>{loadPanosDone('testDataset',  data.sweepLocations)} )  
     
+    } 
+    
+    Potree.loadPanosCloudStart = (EditCloudsArgs)=>{
+        Potree.loadDatasets((datasets)=>{ 
+            Potree.datasetData = datasets   //4dkk的加载为空[]
+            
+            if(!(EditCloudsArgs instanceof Array)) EditCloudsArgs = [EditCloudsArgs] 
+            EditCloudsArgs.forEach((e,i)=>{ //加载点云和漫游点
+                if(e.datasetId == void 0){
+                    console.error('没有datasetId ')// 看看和看见没有或为0
+                    e.datasetId = i; //经常没有datasetId所以自己加
+                }  
+                Potree.settings.datasetsPanos[e.datasetId] = null
+                Potree.loadPanosDone(e.datasetId, e.clouds)
+                
+            }) 
+        },number)
     }
     
+    EditCloudsArgs && Potree.loadPanosCloudStart(EditCloudsArgs)
 }
 
 
@@ -1041,7 +909,7 @@ export function mergeEditStart(dom, mapDom){
                 //model.rotation.setFromVector3(prop.rotation) 
                 model.rotation.copy(prop.rotation) 
             }
-            if(prop.scale){
+            if(prop.scale != void 0){
                 model.scale.set(prop.scale,prop.scale,prop.scale)
             }
               
@@ -1111,8 +979,8 @@ export function mergeEditStart(dom, mapDom){
             MergeEditor.getBoundCenter(model) //初始化
             //model.lastMatrixWorld = model.matrixWorld.clone()
             model.lastMatrixWorld = new THREE.Matrix4
-            MergeEditor.modelTransformCallback(model)
-            
+            MergeEditor.modelTransformCallback(model, true)
+            prop.scale != void 0 && model.isPointcloud && model.changePointSize()   //有的被缩放的很小导致testMaxNodeLevel时距离较远时被return 但点云过大急需changesize
             
             
             
@@ -1294,6 +1162,8 @@ export function mergeEditStart(dom, mapDom){
             
         }
     }
+    
+    
     return {THREE}
 }
  

+ 75 - 8
src/custom/utils/Common.js

@@ -237,15 +237,25 @@ var Common = {
         cb && cb();
     }, 
      
-    replaceAll : function (str, f, e) {
+    replaceAll : function (str, f, e ) {
         //f全部替换成e
-        var reg = new RegExp(f, "g"); //创建正则RegExp对象  
-        return str.replace(reg, e);
+       
+        if(str.replaceAll ) return str.replaceAll(f, e)
+        else{ 
+            let escapeRegExp = (string)=>{
+                return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+            }
+             
+            return str.replace(new RegExp(escapeRegExp(f), 'g'), e);
+     
+            /* var reg = new RegExp(f, "g"); //创建正则RegExp对象    这个没法转换'('
+            return str.replace(reg, e);        //str.split(f).join(e); */
+        }
     }, 
       
     dealURL(url){ 
-        let urlNew = this.replaceAll(url, "\\+", "%2B");// 浏览器似乎不支持访问带+的地址
-        
+        let urlNew = this.replaceAll(url, "+", "%2B"); //this.replaceAll(url, "\\+", "%2B");// 浏览器似乎不支持访问带+的地址
+    
         urlNew = this.replaceAll(urlNew, "/.//", "/") //去除双斜杠(/.//)
          
           
@@ -387,7 +397,25 @@ var Common = {
         }
     })(),
     
-    
+    getBestCountFPS(name, ifLog, minCount=1, maxCount=6, minFps = 10, maxFps = 62, minPanoCount = 200, maxPanoCount = 1000 ){
+        
+        let fps = Potree.fps
+        
+        let count = math.linearClamp(fps, [minFps, maxFps],   [minCount,  maxCount])
+         
+        let count2 =  math.linearClamp(viewer.images360.panos.length, [minPanoCount, maxPanoCount],   [minCount,  maxCount])
+        count = (count + count2)/2
+       
+        count = Math.round(count)
+        
+        
+        if(ifLog){ 
+           name && count && console.log(name, count , ' ,fps:', fps.toFixed(3))
+        }
+        
+        return count
+        
+    },
         
     batchHandling : {//分批处理
         
@@ -535,7 +563,7 @@ var Common = {
  
     },
     
-    changeShaderToWebgl2(vs, fs, matType, otherReplaces=[]){//部分shader要根据webgl版本作更改
+    /* changeShaderToWebgl2(vs, fs, matType, otherReplaces=[]){//部分shader要根据webgl版本作更改
         if(!Potree.settings.isWebgl2)return {vs, fs}
         let turnTo300 = matType != 'ShaderMaterial' && (vs.includes('gl_FragDepthEXT') || fs.includes('gl_FragDepthEXT') ) 
         let addV300 = turnTo300 && matType != 'RawShaderMaterial' // RawShaderMaterial直接material.glslVersion = '300 es' 以加在define之前
@@ -574,7 +602,46 @@ var Common = {
         //console.log('成功替换为webgl2' )
         return {vs,fs}
     }//three.js的shaderMaterial也有替换功能,搜 '#define gl_FragDepthEXT gl_FragDepth',
-    
+     */
+    changeShaderToWebgl2(vs, fs, matType, otherReplaces=[]){//部分shader要根据webgl版本作更改
+        if(!Potree.settings.isWebgl2)return {vs, fs}
+        let turnTo300 = matType != 'ShaderMaterial' && (vs.includes('gl_FragDepthEXT') || fs.includes('gl_FragDepthEXT') ) 
+        let addV300 = turnTo300 && matType != 'RawShaderMaterial' // RawShaderMaterial直接material.glslVersion = '300 es' 以加在define之前
+        let change = (shader, shaderType)=>{ 
+            let newShader = shader
+            
+            if(turnTo300){ //非shaderMaterial需要手动改为300 es的写法
+                addV300 && (newShader = '#version 300 es \n' + newShader)   //需要加 #version 300 es。 three.js自带的渲染会自动加所以不用
+                newShader = this.replaceAll(newShader, 'varying ', shaderType == 'vs' ? 'out ' : 'in ')
+                newShader = this.replaceAll(newShader, 'attribute ', 'in ') 
+                if(shaderType == 'fs'){
+                    newShader = this.replaceAll(newShader, 'gl_FragColor', 'fragColor')
+                    newShader = newShader.replace('void main', 'out vec4 fragColor;\n  void main' )//在void main前加入这个声明 
+                }  
+                newShader = this.replaceAll(newShader, 'gl_FragDepthEXT','gl_FragDepth')
+            
+                newShader = this.replaceAll(newShader, 'texture2D','texture')
+                newShader = this.replaceAll(newShader, 'textureCube','texture')
+            
+            }
+            
+            newShader = newShader.replace('#extension GL_EXT_frag_depth : enable','') 
+            newShader = this.replaceAll(newShader,'defined(GL_EXT_frag_depth) &&','')
+             
+            
+            otherReplaces.forEach(({oldStr,newStr})=>{
+                newShader = this.replaceAll(newShader, oldStr, newStr)   
+            })
+                
+            return newShader
+        }
+        
+        vs = change(vs,'vs')
+        fs = change(fs,'fs')
+         
+        //console.log('成功替换为webgl2' )
+        return {vs,fs}
+    }//three.js的shaderMaterial也有替换功能,搜 '#define gl_FragDepthEXT gl_FragDepth',
     
 }  
 

+ 4 - 0
src/custom/utils/browser.js

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

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

@@ -424,8 +424,8 @@ var transitions = {
         }
     },
     adjustSpeed: function(e, t) {
-        for (var u = this.getById(e), duration = 0; duration < u.length; duration++) {
-            var r = u[duration];
+        for (var u = this.getById(e), n = 0; n < u.length; n++) {
+            var r = u[n];
             r.duration /= t,
             r.current /= t
         }

+ 179 - 54
src/custom/viewer/ViewerNew.js

@@ -131,13 +131,14 @@ export class Viewer extends ViewerBase{
                 SiteModel,
                 volumeComputer: new VolumeComputer //暂时
             }
-            Potree.settings.useDepthTex = Potree.settings.mergeType2
+            
             
             if(Potree.settings.editType == "pano" ){
-                this.modules.PanoEditor = PanoEditor
+                this.modules.PanoEditor = PanoEditor 
             }else if(Potree.settings.editType == "merge"){
                 this.modules.MergeEditor = MergeEditor 
                 this.modules.CamAniEditor = CamAniEditor
+                Potree.settings.useDepthTex = Potree.settings.mergeType2
             }
             
         }else{
@@ -157,7 +158,9 @@ export class Viewer extends ViewerBase{
             Potree.timeCollect = {
                 //median预置一个中等值,以防止cpu过低的设备首次卡顿  
                 //depthSampler : {minCount:400, median: 1}, //旧的 实时从图片中获取色值的用时,但是现在直接从data中取
-                 depthSamChangeImg:{minCount:200, median: 25} //换图的用时。 需要一开始就start
+                 depthSamChangeImg:{minCount:200, median: 25}, //换图的用时。 需要一开始就start
+                 sortByScore:{minCount:Infinity }, 
+                 //loop :{minCount:Infinity }
             }
             for(let i in Potree.timeCollect){
                 Potree.timeCollect[i].measures = [];
@@ -171,8 +174,17 @@ export class Viewer extends ViewerBase{
                     console.log('timeCollect', Potree.timeCollect.depthSampler.median, Potree.timeCollect.depthSampler.ave, Potree.timeCollect.depthSampler.measures.length)
                 },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)
+            
+        },25000) 
   
         
         this.navigateMode = 'free' // 'panorama'; 'free'自由模式是只显示点云或者未进入到漫游点, 
@@ -586,6 +598,7 @@ export class Viewer extends ViewerBase{
             CursorDeal.init(this, this.mapViewer ? [this, this.mapViewer] : [this])//ADD
             if(Potree.settings.editType == "pano"){
                 this.modules.PanoEditor.init()
+                this.modules.SiteModel.init()  //add
             }else if(Potree.settings.editType == "merge"){
                 this.modules.MergeEditor.init()
             }else{
@@ -759,6 +772,8 @@ export class Viewer extends ViewerBase{
                 this.setDisplay(true) 
             })
              
+             
+            this.createHackMesh() 
         }) 
         
          
@@ -813,10 +828,18 @@ export class Viewer extends ViewerBase{
             
             
         } 
+         
+        this.modules.SiteModel?.bus.addEventListener('FloorChange',(e)=>{
+             if(Potree.settings.editType == 'pano'){
+                 if(this.modules.PanoEditor.entered)return //点位编辑时楼层按钮高亮的是显示的楼层,而不是所在楼层
+                 this.modules.PanoEditor.dispatchEvent({type:'changeFloor', floor:e.currentFloor})
+             }else{
+                 this.updateFpVisiDatasets()
+                 this.updatePanosVisibles(e.currentFloor)       //问:编辑空间模型时,需不需要改为显示当前选择的楼层。因为若所在楼层和选中的不一致,修改选中楼层的轮廓却改不了marker显示很奇怪,尤其刚好在一个建筑内时。
+             }
+        }) 
         
-        
-        
-        if(Potree.settings.editType != 'pano' && Potree.settings.editType != 'merge'){
+        if(Potree.settings.editType != 'pano' &&  Potree.settings.editType != 'merge'){
               
             this.addEventListener('switchFloorplanSelect',(e)=>{//进入平面图设置后 切换选中的数据集
                 this.selectedFloorplan = e.pointcloud;  //绝对显示
@@ -831,15 +854,8 @@ export class Viewer extends ViewerBase{
                 pointclouds && this.mapViewer.fitToDatasets(pointclouds) 
                 
             })
+             
             
-            
-            
-            
-
-            this.modules.SiteModel.bus.addEventListener('FloorChange',(e)=>{
-                 this.updateFpVisiDatasets()
-                 this.updatePanosVisibles(e.currentFloor)       //问:编辑空间模型时,需不需要改为显示当前选择的楼层。因为若所在楼层和选中的不一致,修改选中楼层的轮廓却改不了marker显示很奇怪,尤其刚好在一个建筑内时。
-            }) 
             this.mapViewer.mapLayer.addEventListener('floorplanLoaded',()=>{
                  this.updateCadVisibles(this.fpVisiDatasets, true)   //加载完成后重新更新下
             })
@@ -1071,7 +1087,7 @@ export class Viewer extends ViewerBase{
         let len = shelterHistory.length;
         let waitCloud = []
          
-        let maxTexCount = Common.getBestCount('shelterMaxDepthSample', 1,  50,   1,  13   /*  ,true */    ) 
+        let maxTexCount = Common.getBestCount('shelterMaxDepthSample', 1,  50,   1,  13    /* ,true    */ ) 
         
         
         
@@ -1231,7 +1247,7 @@ export class Viewer extends ViewerBase{
         
         let sheltered = (pano)=>{
             if(/* Potree.settings.displayMode == 'showPanos' && !Potree.Features.EXT_DEPTH.isSupported() && */this.images360.isAtPano() && !this.mainViewport.view.isFlying()){
-                return !this.images360.currentPano.neighbours.includes(pano) && this.images360.currentPano != pano //起初因不支持EXT_DEPTH时无法用depthTex遮住marker, 后为了减少绘制,都判断
+                return !this.images360.isNeighbour(this.images360.currentPano, pano,{dontCompute:true}) //起初因不支持EXT_DEPTH时无法用depthTex遮住marker, 后为了减少绘制,都判断
             } 
         }
         
@@ -3455,6 +3471,7 @@ export class Viewer extends ViewerBase{
     */
 
     renderOverlay(params){   
+    
         viewer.addTimeMark('renderOverlay','start')
         this.renderOverlay1(params)
         this.renderOverlay2(params) 
@@ -3515,6 +3532,53 @@ export class Viewer extends ViewerBase{
     }
     
     
+    createHackMesh(){//为了防止无depthTex的全景在pick点云时画面中仅有一个材质时会黑屏,所以在镜头前再加一个mesh。具体bug表现见bug记录。 
+        if(this.scene.pointclouds.every(e=>e.hasDepthTex) || viewer.images360.panos.length == 0)return 
+        
+        
+        
+        let mesh = new THREE.Mesh(viewer.images360.panos[0].marker.geometry, new THREE.MeshBasicMaterial({color:"#F00",side:2/* ,depthTest:false */}))
+            mesh.visible = false 
+        this.images360.node.add(mesh)
+        
+        
+        let updatePos = ()=>{
+            let dir = this.mainViewport.view.direction
+            let radius = this.images360.cube.scale.length()
+            mesh.position.copy(this.mainViewport.view.position).add(dir.multiplyScalar(radius))//放置skybox之外
+        }
+        
+        
+        this.addEventListener('camera_changed', e => {
+            if(e.viewport.name == 'MainView' && Potree.settings.displayMode == 'showPanos' && !this.images360.currentPano.depthTex
+             && (e.changeInfo?.positionChanged || e.changeInfo?.quaternionChanged)){
+                 
+                updatePos()
+                 
+            } 
+        })
+        
+        this.images360.addEventListener( 'flyToPanoDone', e=>{
+            if(this.images360.currentPano.depthTex){
+                mesh.visible = false
+            }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
+            }
+        })
+        
+
+        //不知道如果用点云计算非当前视角下的block会不会黑闪,如热点遮挡计算
+
+    }
     
     renderOverlay2(params){//渲染剩余部分
         let renderer = params.renderer || this.renderer
@@ -3712,7 +3776,7 @@ export class Viewer extends ViewerBase{
                 dataUrl = await Utils.combineImgs(dataUrls, compressRatio, width, height) 
                  
             }else{
-                dataUrl = await render()
+                dataUrl = await render() 
             }
             
              
@@ -3988,7 +4052,19 @@ export class Viewer extends ViewerBase{
             viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
              
             prisms.forEach(prism=>{ 
-                points.push(...prism.points) 
+                
+                let prismPoints = prism.points.slice()
+                
+                if(prism.baseModel){
+                   let currentBound = prism.baseModel.currentBound || prism.baseModel.boundingBox.clone().applyMatrix4(prism.baseModel.matrixWorld) 
+                   prismPoints = prismPoints.map(e=>e.clone().setZ(Math.max(e.z,  currentBound.max.z)))
+                }
+                
+                points.push(...prismPoints) 
+                
+                
+                
+                
                 prism.setSelected(false)
                 prism.changeStyleForScreenshot(true, {hideLabel: info.type != 'prism2d-all',  showName: info.type == 'prism2d-all' })
                 if(info.type == 'prism2d-single') {
@@ -4030,15 +4106,13 @@ export class Viewer extends ViewerBase{
                     this.waitPointLoad(screenshot, info.maxTimeForPointLoad)
                 }
             }
-        
-            if(info.focusObjectInfo){ 
-                viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
-             
+            viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
+            if(info.focusObjectInfo){  
                 let {promise} = this.focusOnObject( ...info.focusObjectInfo)
                 promise.done(()=>{
                     done()
                 }) 
-            }else{
+            }else{ 
                 done()
             } 
             
@@ -4170,14 +4244,18 @@ export class Viewer extends ViewerBase{
                 
                 if(o.focusBoundCenter){
                      boundOri.getCenter(target)
+                     target.z += 1
                 }
                 let boundSizeOri = boundOri.getSize(new THREE.Vector3)
-                 
-                let boundSizeMap = boundSizeOri.clone().multiplyScalar(o.boundExpandRatio || math.linearClamp(Math.max(boundSizeOri.x, boundSizeOri.y) , [0.5, 30], [2.5, 1.2]))//为了能同时看清测量线和地图,当测量线较短时,扩大margin,防止地图过度放大
+                let expand = o.boundExpandRatio || math.linearClamp(Math.max(boundSizeOri.x, boundSizeOri.y) , [0.5, 30], [2.5, 1.2])//为了能同时看清测量线和地图,当测量线较短时,扩大margin,防止地图过度放大
+                let boundSizeMap = boundSizeOri.clone() 
+                    boundSizeMap.x *= expand,  boundSizeMap.y *= expand 
+                    
                 
                 
                 boundSizeMap.x = Math.max(minBound.x, boundSizeMap.x )
                 boundSizeMap.y = Math.max(minBound.y, boundSizeMap.y )
+            
                 this.mapViewer.moveTo(target.clone(), boundSizeMap, duration, o.margin, null,  done)
             }
         }
@@ -4733,6 +4811,7 @@ export class Viewer extends ViewerBase{
                     if(!measure){ //有出现过手机的depthSamChangeImg得不到measure,可能是因为时间太长在resolveTimings被清除了?   iphone8 plus ios14.1 型号MQ8F2CH/A    
                         return //console.log('未能得到performance.measure',name)
                     }  
+                    record.latestDuration = measure.duration
                     record.measures.push( measure.duration ) 
                     record.sum += measure.duration;
                     record.ave = record.sum / record.measures.length;  
@@ -4757,7 +4836,7 @@ export class Viewer extends ViewerBase{
     }
 
 	resolveTimings(timestamp,log){//打印用时。   注:performance手机的精度只到整数位 。 sidebar中监听update时有高cpu的函数所以不要用demo测
-		 
+		
         if(!this.toggle){
             this.toggle = timestamp;
         }
@@ -4899,13 +4978,22 @@ export class Viewer extends ViewerBase{
          
         this.shelterCount = {byTex:0, byCloud:0,   maxByTex: 100, maxByCloud:0   } //清空 因ifPointBlockedByIntersect可能在任何时候触发,所以需要一开始就定义这个,且每次计算最大可计算次数太麻烦了就定义一个吧。
         
-        
+       
 
         let deltaTime = this.clock.getDelta()
+        
+        
+        
 		this.update(deltaTime, timestamp);
         this.magnifier.render();
+        
+        
+        viewer.addTimeMark('loop2','start')
         this.render(); 
-           
+        viewer.addTimeMark('loop2','end') 
+        
+        
+        
         this.setAllTilesets(model=>model.runtime.update(deltaTime, this.renderer, this.mainViewport.camera))
        
         
@@ -4923,13 +5011,19 @@ export class Viewer extends ViewerBase{
 		
 		Potree.framenumber++;
         
-        
+         
         //-------------
         this.images360.tileDownloader.update() 
-        this.images360.panoRenderer.update()
+        this.images360.panoRenderer.update() 
         this.images360.getNeighbours(this.interacted)
+        
+        
+        
         this.computeShelter()
         //-------------
+        
+        
+        
 		if(this.stats){
 			this.stats.end();
 		}
@@ -4937,11 +5031,27 @@ export class Viewer extends ViewerBase{
        
         viewer.addTimeMark('loop','end')
         viewer.addTimeMark('loopWaitNext','start')
+        this.computeFps() 
 		this.resolveTimings(timestamp, Potree.measureTimings);
         //Potree.measureTimings = 1
 	}
     
-    
+    computeFps(){ 
+        let marks = performance.getEntriesByName("loop-start");
+        if(marks.length){//每次都获取最后一个加入列队。因为marks会定期清除,所以自己记录一个列队
+            
+            let last = marks[marks.length-1]
+            this.fpsCollect.push(last)
+            if(this.fpsCollect.length>50){
+                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以上
+            } 
+        }
+         
+    }
      
 	postError(content, params = {}){
 		let message = this.postMessage(content, params);
@@ -5019,7 +5129,7 @@ export class Viewer extends ViewerBase{
         boundPlane.expandByPoint(this.bound.boundingBox.max.clone().setZ(this.bound.center.z))//最高高度为bound的中心高度
         FirstPersonControls.boundPlane = boundPlane */
         //FirstPersonControls.standardSpeed = THREE.Math.clamp( Math.sqrt(this.bound.boundSize.length() )/ 100 ,   0.02,0.5); //在这个boundPlane中的速度
-        
+        Potree.settings.editType == 'pano' && this.images360.updatePanoBound()
 
         viewer.scene.pointclouds.forEach(e=>{//海拔范围
             e.material.heightMin = this.bound.boundingBox.min.z
@@ -5282,7 +5392,7 @@ export class Viewer extends ViewerBase{
             
             
         }else { 
-            Potree.Utils.setObjectLayers(object, Potree.settings.showObjectsOnMap ? 'bothMapAndScene' : 'model')  
+            Potree.Utils.setObjectLayers(object,'model')  
             
             
             object.traverse( ( child )=>{ 
@@ -5323,7 +5433,7 @@ export class Viewer extends ViewerBase{
         if(fileInfo_.transform){
             let setTransfrom = (name)=>{
                 let value = fileInfo_.transform[name]
-                if(!value)return
+                if(value == void 0)return
                 if(value instanceof Array){
                     object[name].fromArray(value)
                 }else{ 
@@ -5335,7 +5445,7 @@ export class Viewer extends ViewerBase{
             setTransfrom('scale')
               
         }
-        
+         
         
         if(fileInfo_.moveWithPointcloud){
             object.updateMatrix();
@@ -5466,11 +5576,11 @@ export class Viewer extends ViewerBase{
                 }
             });
             tileset.addEventListener('tileLoaded',(e)=>{ //每一个tile加载完要更改透明度
-                let opacity = result.model.parent == this.objs ?  result.model.opacity : result.model.parent.opacity//最多两层
-                MergeEditor.changeOpacity(e.tileContent,  opacity)
-                viewer.images360.judgeModelMat(e.tileContent)
+                let master = result.model.parent == this.objs ? result.model : result.model.parent//最多两层 
+                MergeEditor.changeOpacity(e.tileContent,  master.opacity)
+                if(master.panos) viewer.images360.judgeModelMat(e.tileContent)
                 //set Layers ?
-                Potree.Utils.setObjectLayers(e.tileContent, Potree.settings.showObjectsOnMap ? 'bothMapAndScene' : 'model')  
+                Potree.Utils.setObjectLayers(e.tileContent, 'model')  
            
             })
             
@@ -5512,33 +5622,37 @@ export class Viewer extends ViewerBase{
             
         }else if(fileInfo.fileType == '3dgs'){
              
-            const gsViewer = new GaussianSplats3D.Viewer({ 
+            const gsViewer = new GaussianSplats3D.Viewer({  
+               'dynamicScene': true, //只有加上这个sh光影才会跟随模型位置变化而变化
                 rootElement: this.renderArea,
                 threeScene: this.scene.scene,
                 renderer: this.renderer,
                 camera:  this.mainViewport.camera,
                 useBuiltInControls: false,
                 //dropInMode: true,
-                // sharedMemoryForWorkers:false  //否则 报错 Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': SharedArrayBuffer transfer requires self.crossOriginIsolated. 
-
+                logLevel:10, //console info
+                antialiased: fileInfo.antialiased,
+                sharedMemoryForWorkers: location.host.includes("www.4dmodel.com") ? false : undefined ,   //否则 报错 SharedArrayBuffer transfer requires self.crossOriginIsolated.  本地已经配置所以不会
+                sphericalHarmonicsDegree:  2   //最大支持的sh Level
             });
             //let path = Potree.resourcePath+'/models/gaussian/bonsai.ksplat';
              
             
             gsViewer.addSplatScene(fileInfo.url, {
-                  'streamView': true
+                  'progressiveLoad': true
                 })
-                .then(() => {
-                    gsViewer.start();  
+                .then(() => { 
+                    gsViewer.start(); 
+                    //loadDone(gsViewer.splatMesh)        //放这里才能看到逐步加载的过程,但会导致加载不完整。        
                     gsViewer.splatMesh.updateMatrix()
-                    gsViewer.splatMesh.updateMatrixWorld()
-                 
+                    gsViewer.splatMesh.updateMatrixWorld() 
+                    gsViewer.splatMesh.gsViewer = gsViewer
                     gsViewer.splatMesh.onSplatTreeReadyCallback = ()=>{ 
-                        loadDone(gsViewer.splatMesh)
+                        loadDone(gsViewer.splatMesh) 
                         let {sceneMax,sceneMin} = gsViewer.splatMesh.splatTree.subTrees[0]
                         gsViewer.splatMesh.boundingBox.min.copy(sceneMin)
                         gsViewer.splatMesh.boundingBox.max.copy(sceneMax) 
-                    }
+                    } 
                     let selfRaycaster = new GaussianSplats3D.Raycaster
                     gsViewer.splatMesh.raycast = function(raycaster, intersects ){//重写
                         let hits = []
@@ -5551,21 +5665,32 @@ export class Viewer extends ViewerBase{
                         })
                         intersects.push(...hits)
                     } //问题:感觉normal不准
+                    
+
+                    let updateRegion = ()=>{
+                        gsViewer.update() //load
+                        if(!gsViewer.splatMesh.visibleRegionChanging){
+                            viewer.removeEventListener('update',updateRegion)
+                        }else{
+                            viewer.dispatchEvent('content_changed') 
+                        }
+                    }
+                    viewer.addEventListener('update',updateRegion)
+ 
                 });
             
         
             //window.gsViewer = gsViewer
-          
+            
             viewer.addEventListener('render.begin2',(e)=>{
                 if(e.name == 'scene'){ //gsViewer.selfDrivenUpdate()
                     gsViewer.update()
-                
                     if(gsViewer.initialized && gsViewer.splatRenderReady){
                         
-                    } 
+                    }  
                 }
             })
-        
+            
         }
         
         

+ 21 - 14
src/custom/viewer/map/Map.js

@@ -770,19 +770,23 @@ export class TiledMapOpenStreetMap extends TiledMapBase{
 
     switchStyle(style = 'standard'){
         //if(Potree.settings.mapCompany == 'google')return 
-        if(style == this.style)return
+        if(style == this.style)return 
         
-        
-        
-        
-        if(Potree.settings.mapCompany == 'google'){
-            if(style == 'satellite'){//卫星
-                this.baseUrl = "https://mt2.google.com/vt/lyrs=y@159000000&hl=zh-CN&gl=cn&x=${x}&y=${y}&z=${z}&s=mt1"  /* "http://mt2.google.cn/vt/lyrs=m@177000000&hl=zh-CN&gl=cn&src=app&x=${x}&y=${y}&z=${z}"  */   //最高只到19
-                this.maxDepth = 22     
-            }else{
-                this.baseUrl = "https://mt2.google.com/vt/lyrs=m@159000000&hl=zh-CN&gl=cn&x=${x}&y=${y}&z=${z}&s=mt1"  /* "http://mt2.google.cn/vt/lyrs=m@177000000&hl=zh-CN&gl=cn&src=app&x=${x}&y=${y}&z=${z}"  */   //最高只到19
-                this.maxDepth = 22     
-            }     
+        if(Potree.settings.mapCompany == 'google'){ 
+            let [lang, glPos] = {
+                'zh' : ["zh-CN","cn"],
+                'en' : ["en-US"],  //范围大所以不指定地理位置 。
+                'ja' : ["ja-JP","JP"],  
+                'kr' : ["ko-KR","KR"],
+            }[Potree.settings.language]||[]
+              
+            this.baseUrl = "https://mt2.google.com/vt/lyrs="+ (style == 'satellite'?"y":"m" ) + "@159000000" 
+                            + (lang ? ("&hl=" + lang) : '') 
+                            + (glPos ? ("&gl=" + glPos) : '')
+                            + "&x=${x}&y=${y}&z=${z}&s=mt1"
+                          
+            this.maxDepth = 22
+            
             /* 1)lyrs= 表示的是图层类型,即瓦片类型,具体含义如下: 
             m:路线图 
             t:地形图 
@@ -790,9 +794,12 @@ export class TiledMapOpenStreetMap extends TiledMapBase{
             s:卫星图 
             y:带标签的卫星图 
             h:标签层(路名、地名等)
-            2)& gl=CN 
+            2)& gl=CN    指定地理区域
             谷歌地图针对中国有两套坐标,一套做了偏移,一套没有。经测试在url加入gl=cn地图会有偏移。 
-            Tips:如果谷歌地图和RTK测量的WGS84坐标有偏差,可以尝试在url里去掉& gl=cn。
+            Tips:如果谷歌地图和RTK测量的WGS84坐标有偏差,可以尝试在url里去掉& gl=cn。(摘自网上)
+            
+            通义千问:如果不指定gl参数,Google将使用默认值或基于其他因素(如IP地址)来推断用户的地理区域。这通常不会导致地图数据的不准确性,但可能会影响本地化信息的显示。地图上的标签(如街道名称、城市名称等)可能不会以最合适的语言显示。地址格式和拼写可能不符合当地标准
+            
             5)&hl= 
             设置地图注记文字语言类型,缺省默认为中文。 
             hl=nl 中英双语 

+ 9 - 3
src/custom/viewer/map/MapViewer.js

@@ -474,8 +474,9 @@ export class MapViewer extends ViewerBase{
         this.moveTo(center, size, 200 ) //给duration是为了顺应视口大小改变,缓冲
     }
     
-    moveTo(endPosition, boundSize, duration=0, margin, easeName, callback ){//前两个参数有xy即可
-        endPosition = new THREE.Vector3(endPosition.x,endPosition.y,Potree.config.map.cameraHeight)
+    moveTo(endPosition, boundSize, duration=0, margin, easeName, callback   ){//前两个参数有xy即可
+        let z = Math.max(Potree.config.map.cameraHeight, endPosition.z +  (boundSize?.z ? boundSize.z/2 : 0) ) 
+        endPosition = new THREE.Vector3(endPosition.x, endPosition.y, z)
         this.view.moveOrthoCamera(this.viewports[0],  {endPosition, boundSize, margin, callback},   duration,  easeName)
         
        
@@ -768,7 +769,12 @@ export class MapViewer extends ViewerBase{
        
         
          //绘制其他物体
-        Potree.Utils.setCameraLayers(this.camera, ['mapObjects'  , 'bothMapAndScene', 'light'  ])
+        let layers = ['mapObjects'  , 'bothMapAndScene',    'light'  ]
+        Potree.settings.showObjectsOnMap && layers.push('model')
+        Potree.Utils.setCameraLayers(this.camera, layers)
+        
+        
+        
         viewer.dispatchEvent({type: "render.begin",  viewer: this, viewport:this.viewports[0], params }); 
         
         this.attachedToViewer || this.renderCloud || renderer.render(viewer.scene.scene, this.camera); //类同renderOverlay

+ 3 - 3
src/navigation/FirstPersonControlsNew.js

@@ -503,11 +503,11 @@ export class FirstPersonControls extends THREE.EventDispatcher {
             if(e.viewer.name != 'mainViewer' )return 
             let intersect = e.intersect || e.dragViewport.lastIntersect
             //在数据集外部时绕中心点旋转,在数据集内部时绕intersect点旋转(其他数据集的点也可以) 或者 原地旋转镜头
-            let rotAroundPoint = Potree.settings.rotAroundPoint && e.dragViewport.camera.type != 'OrthographicCamera' &&  (viewer.atDatasets.length == 0 || intersect) && this.canMovePos(viewport) && !viewer.images360.isAtPano() && !this.viewer.inputHandler.pressedKeys[32]
+            let rotAroundPoint = Potree.settings.rotAroundPoint && e.dragViewport.camera.type != 'OrthographicCamera' /* &&  (viewer.atDatasets.length == 0 || intersect) */ && this.canMovePos(viewport) && !viewer.images360.isAtPano() && !this.viewer.inputHandler.pressedKeys[32]
             let rotCenter2d, rotCenter
             if(rotAroundPoint){
-                let pivotType = this.target ? 'target' : viewer.atDatasets.length > 0 ? 'intersect' :  viewer.inputHandler.selection.length ? 'selection' : this.target2 ? 'target2' : 'boundCenter'  
-                rotCenter = pivotType == 'target'? this.target :pivotType == 'intersect' ? intersect.location : pivotType == 'selection' ? viewer.inputHandler.selection[0].position : pivotType == 'target2' ? this.target2 : viewer.bound && viewer.bound.center
+                let pivotType = this.target ? 'target' : (viewer.atDatasets.length > 0 || Potree.settings.editType == 'pano')? 'intersect' :  viewer.inputHandler.selection.length ? 'selection' : this.target2 ? 'target2' : 'boundCenter'  
+                rotCenter = pivotType == 'target'? this.target :pivotType == 'intersect' ? intersect?.location : pivotType == 'selection' ? viewer.inputHandler.selection[0].position : pivotType == 'target2' ? this.target2 : viewer.bound && viewer.bound.center
                 if(rotCenter){
                     rotCenter2d = rotCenter.clone().project(e.dragViewport.camera) //点在屏幕中的位置。   若z>1 则在背面 或 超出far范围 
                 }else{

+ 2 - 1
src/navigation/InputHandlerNew.js

@@ -871,7 +871,7 @@ export class InputHandler extends THREE.EventDispatcher {
                 this.mouse.set(Math.round(viewport.resolution.x/2), Math.round(viewport.resolution.y/2))
             } 
                 
-            intersectPoint = (viewport.noPointcloud || dontIntersect)? null : Utils.getMousePointCloudIntersection(
+            intersectPoint = (viewport.noPointcloud || dontIntersect )? null : Utils.getMousePointCloudIntersection(
                 viewport,
                 this.mouse,
                 this.pointer, 
@@ -881,6 +881,7 @@ export class InputHandler extends THREE.EventDispatcher {
                 {pickClipped: true, measuring: this.measuring.length>0, pickWindowSize, cameraChanged: !!point }  
                 
             );
+
             //恢复
             if(point){
                 viewport.view.applyToCamera(camera)

+ 24 - 7
src/navigation/OrbitControlsNew.js

@@ -152,9 +152,20 @@ export class OrbitControls extends THREE.EventDispatcher{
 		let scroll = (e) => {
             if(!this.enabled)return
 			let resolvedRadius = this.currentViewport.view.radius + this.radiusDelta;
-            if(resolvedRadius < 0.1 && e.delta>0)return; //防止缩放太小,导致很慢
+            
+            /* let model = viewer.inputHandler.intersect?.model || viewer.inputHandler.intersect?.pointcloud || viewer.modules.MergeEditor?.selected;  
+            let min = 0.1
+            if(model){
+                min *= model.scale.x            //有的模型太小。注意:如果没有选中模型且没有intersect会无法前进是会有点怪
+            }
+            if(resolvedRadius < min && e.delta>0)return; //防止缩放太小,导致很慢 */
 			this.radiusDelta += -e.delta * resolvedRadius * 0.1;
             
+            
+            let model = viewer.inputHandler.intersect?.object || viewer.inputHandler.intersect?.pointcloud 
+            model && this.updateRadiusByModel(model, viewer.inputHandler.intersect.location.distanceTo(this.currentViewport.view.position))
+            
+            
 			this.stopTweens();
 		};
 
@@ -364,18 +375,24 @@ export class OrbitControls extends THREE.EventDispatcher{
         if(!I || !object)return;
         
         let dis = this.currentViewport.view.position.distanceTo(I); 
+        let distance = this.updateRadiusByModel(object, dis)
         
-        
-        let bound = object.boundingBox.clone().applyMatrix4(object.matrixWorld)
-        let size = bound.getSize(new THREE.Vector3); 
-        let len = size.length()
          
-        let distance = THREE.Math.clamp(dis, 0.1, Math.max(len * 0.1, 3) );
         
-        minRadius = distance
         viewer.focusOnObject({ position:I }, 'point', null, {distance})
         
     }
+    
+    
+    updateRadiusByModel(object, dis=10){//鼠标到点的距离,如果没有就给个几率最大的距离
+        let bound = object.boundingBox.clone().applyMatrix4(object.matrixWorld)
+        let size = bound.getSize(new THREE.Vector3); 
+        let len = size.length() 
+        let distance = THREE.Math.clamp(dis, 0.8 * object.scale.x , Math.max(len * 0.1 , 3 * object.scale.x) );
+        minRadius = distance
+        //console.log('updateRadiusByModel',distance)
+        return distance
+    }
 
 
 	stopTweens () {

+ 4 - 2
src/viewer/EDLRendererNew.js

@@ -197,7 +197,9 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
          
         const viewer = this.viewer; 
 		let camera = params.camera ? params.camera : viewer.scene.getActiveCamera();
-        let rtEDL = (Potree.settings.pointEnableRT || Potree.settings.displayMode == 'showPanos' || viewer.useEDL) &&  
+        let rtEDL = (Potree.settings.pointEnableRT && Potree.settings.displayMode == 'showPointCloud' 
+                    || Potree.settings.displayMode == 'showPanos'/*  && viewer.images360.currentPano.pointcloud.hasDepthTex  */
+                    || viewer.useEDL) &&  
                     Features.EXT_DEPTH.isSupported() && camera.type != "OrthographicCamera" && !params.dontRenderRtEDL && (params.rtEDL || this.getRtEDL(params.viewport))  // 平面相机不用depthTex直接打开depthTest?且不使用edl
         let useEDL = viewer.useEDL && rtEDL && Potree.settings.displayMode != 'showPanos'
         
@@ -239,7 +241,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
                     }
                     
                 }else{
-                    renderer.render(viewer.scene.scene, camera);
+                    renderer.render(viewer.scene.scene, camera); 
                 }     
                 if(Potree.settings.displayMode == 'showPanos' ){
                     if(Potree.settings.fastTran && viewer.images360.fastTranMaskPass.enabled){

+ 1 - 1
src/viewer/ExtendScene.js

@@ -13,7 +13,7 @@ class ExtendScene extends Scene{
 		delete this.sceneBG;
 		
          
-		this.cameraP = new THREE.PerspectiveCamera(this.fov, 1, Potree.config.view.near, Potree.config.view.near);
+		this.cameraP = new THREE.PerspectiveCamera(this.fov, 1, Potree.config.view.near, Potree.config.view.cameraFar);
 		this.cameraO = new THREE.OrthographicCamera(-1, 1, 1, -1, Potree.config.view.near, Potree.settings.cameraFar);
         this.cameraP.limitFar = true//add
 		this.initializeExtend();