xzw 5 月之前
父節點
當前提交
29898e0e4e
共有 37 個文件被更改,包括 4099 次插入2764 次删除
  1. 2 2
      libs/three.js/3dtiles/three-loader-3dtiles.esm.js
  2. 4 3
      libs/three.js/build/three.module.js
  3. 1555 2368
      libs/three.js/loaders/GLTFLoader.js
  4. 14 1
      src/ExtendPointCloudOctree.js
  5. 1 1
      src/PointCloudOctree.js
  6. 11 1
      src/Potree.js
  7. 6 1
      src/PotreeRendererNew.js
  8. 4 4
      src/custom/materials/BasicMaterial.js
  9. 212 45
      src/custom/materials/ModelTextureMaterial.js
  10. 36 61
      src/custom/mergeStartTest.js
  11. 101 24
      src/custom/modules/mergeModel/MergeEditor.js
  12. 173 7
      src/custom/modules/panos/Images360.js
  13. 131 2
      src/custom/modules/panos/Panorama.js
  14. 2 2
      src/custom/modules/volumeCompute/VolumeComputer.js
  15. 543 0
      src/custom/objects/Monitor.js
  16. 146 0
      src/custom/objects/Overlay.js
  17. 4 36
      src/custom/objects/Tag.js
  18. 20 16
      src/custom/objects/tool/Path.js
  19. 21 14
      src/custom/objects/tool/TransformControls.js
  20. 4 3
      src/custom/potree.shim.js
  21. 19 17
      src/custom/settings.js
  22. 25 9
      src/custom/start.js
  23. 561 4
      src/custom/three.shim.js
  24. 54 6
      src/custom/utils/Common.js
  25. 5 1
      src/custom/utils/math.js
  26. 259 72
      src/custom/viewer/ViewerNew.js
  27. 12 3
      src/loader/BinaryLoader.js
  28. 10 1
      src/materials/ClassificationScheme.js
  29. 36 6
      src/materials/ExtendPointCloudMaterial.js
  30. 13 1
      src/materials/Gradients.js
  31. 4 3
      src/materials/PointCloudMaterial.js
  32. 10 0
      src/materials/shaders/basicTextured.vs
  33. 35 9
      src/materials/shaders/pointcloud_new.vs
  34. 1 1
      src/navigation/InputHandlerNew.js
  35. 8 0
      src/viewer/sidebar1.html
  36. 23 1
      src/viewer/sidebarNew.js
  37. 34 39
      src/workers/BinaryDecoderWorker.js

+ 2 - 2
libs/three.js/3dtiles/three-loader-3dtiles.esm.js

@@ -1,10 +1,10 @@
-import { EventDispatcher, CanvasTexture, LinearFilter, RepeatWrapping, Vector2 as Vector2$1, Frustum, Matrix4 as Matrix4$1, Group, PlaneGeometry, Vector3 as Vector3$1/*,  MeshBasicMaterial */, DoubleSide, Mesh, ArrowHelper, Color, BoxGeometry, EdgesGeometry, LineSegments, LineBasicMaterial, ShaderMaterial, Euler, BufferGeometry, Float32BufferAttribute, Uint8BufferAttribute, BufferAttribute, Points } from '../build/three.module.js';
+import { EventDispatcher, CanvasTexture, LinearFilter, RepeatWrapping, Vector2 as Vector2$1, Frustum, Matrix4 as Matrix4$1, Group, PlaneGeometry, Vector3 as Vector3$1 ,  MeshBasicMaterial  , DoubleSide, Mesh, ArrowHelper, Color, BoxGeometry, EdgesGeometry, LineSegments, LineBasicMaterial, ShaderMaterial, Euler, BufferGeometry, Float32BufferAttribute, Uint8BufferAttribute, BufferAttribute, Points } from '../build/three.module.js';
 import { GLTFLoader as GLTFLoader$1 } from '../loaders/GLTFLoader.js';
 import { DRACOLoader } from '../loaders/DRACOLoader.js';
 import { KTX2Loader } from '../loaders/KTX2Loader.js';
 
 
-import MeshBasicMaterial  from '../../../src/custom/materials/BasicMaterial.js'  // xzw改  原先MeshBasicMaterial贴图发黑,可能和贴图有关
+//import MeshBasicMaterial  from '../../../src/custom/materials/BasicMaterial.js'  // xzw改  原先MeshBasicMaterial贴图发黑,可能和贴图有关
 
 let globalThis = window //add 有的app没有globalThis. 2023.1
 

文件差異過大導致無法顯示
+ 4 - 3
libs/three.js/build/three.module.js


文件差異過大導致無法顯示
+ 1555 - 2368
libs/three.js/loaders/GLTFLoader.js


+ 14 - 1
src/ExtendPointCloudOctree.js

@@ -768,7 +768,20 @@ export class ExtendPointCloudOctree extends PointCloudOctree{
         return Potree.Utils.isIntersectBox(this.pcoGeometry.tightBoundingBox, boxM)     
     }
     
-    
+    updateAttrAuto(){//add
+        let attributes = this.root.geometryNode?.geometry.attributes ||  this.root.geometry?.attributes
+        if(!attributes){
+            console.log('updateAttrAuto unready, delay')
+            return setTimeout(()=>{
+                this.updateAttrAuto()
+            },300)
+        }
+        this.material.activeAttributeName = 
+                Potree.settings.showClass && attributes.classification ? 'classification' :  
+                Potree.settings.showHotTemp && attributes.temp ? 'temp' : 
+                Potree.settings.showHotIr && attributes.ir ? 'ir' : 
+                Potree.settings.cloudAttributeName || 'rgba'
+    }
     
     
     

+ 1 - 1
src/PointCloudOctree.js

@@ -120,7 +120,7 @@ export class PointCloudOctree extends PointCloudTree {//base
 
 			let priorityQueue = ["rgba", "rgb", "intensity", "classification"];
 			let selected = "rgba";
-
+ 
 			for(let attributeName of priorityQueue){
 				let attribute = this.pcoGeometry.pointAttributes.attributes.find(a => a.name === attributeName);
 

+ 11 - 1
src/Potree.js

@@ -719,6 +719,15 @@ export function setLonlat(lon,lat){
         lonlatTo4550 : transform2       // 转大地坐标EPSG:4550  
     } 
     
+    
+    if(window.transform){
+        viewer.transform.lonlatToLocal = window.transform.lonlatToLocal
+    }
+    
+    
+    
+    
+    
     Potree.Log(`setLonlat ${lon}, ${lat}`,{font:{color:"#f49",fontSize:13}} )
     
     
@@ -758,7 +767,8 @@ export function setLonlat(lon,lat){
         }
         for(let f in viewer.transform){
             change(viewer.transform[f]) 
-        }  
+        } 
+        //注意:把几万米转成经纬度再转回来差值会很大
     } 
 }
 

+ 6 - 1
src/PotreeRendererNew.js

@@ -151,6 +151,9 @@ let attributeLocations = {
 	"spacing": {name: "spacing", location: 9},
 	"gps-time":  {name: "gpsTime", location: 10},
 	"aExtra":  {name: "aExtra", location: 11},
+    //add:
+    ir: {name: "ir", location: 12},
+    temp: {name: "temp", location: 13},
 };
 
 class Shader {
@@ -582,7 +585,7 @@ export class Renderer {
 		this.glTypeMapping = new Map();
 		this.glTypeMapping.set(Float32Array, this.gl.FLOAT);
 		this.glTypeMapping.set(Uint8Array, this.gl.UNSIGNED_BYTE);
-		this.glTypeMapping.set(Uint16Array, this.gl.UNSIGNED_SHORT);
+		this.glTypeMapping.set(Uint16Array, this.gl.UNSIGNED_SHORT);//貌似不能用,uint只能是Uint32Array
 
 		this.toggle = 0;
 	}
@@ -750,6 +753,8 @@ export class Renderer {
         shader.setUniform1f("uOpacity", material.usePanoMap ? 1: material.opacity);
         shader.setUniform3f("uColor", material.color.toArray());
         
+        shader.setUniform2f("temperRange", material.uniforms.temperRange.value.toArray());
+        
         let currentTextureBindingPoint = params.currentTextureBindingPoint
         if (material.pointSizeType >= 0/*  && window.needvisibilityTexture */) {
             

+ 4 - 4
src/custom/materials/BasicMaterial.js

@@ -15,14 +15,14 @@ class BasicMaterial  extends THREE.ShaderMaterial{
             uniforms:{
                 color:  {type:'v3',   value: o.color || new THREE.Color("#FFF")} ,
                 map:    {type: 't',    value: o.map },
-                opacity : {type:'f',    value : o.opacity == void 0 ? 1 : o.opacity }
+                opacity : {type:'f',    value : 1}
                  
             },
             vertexShader: Shaders['basicTextured.vs'],   
             fragmentShader: Shaders['basicTextured.fs'], 
             defines:{HasColor:'' }
         },o))
-        //this.name111 = getName()   
+        this.opacity = o.opacity == void 0 ? 1 : o.opacity 
     } 
      
     
@@ -38,7 +38,7 @@ class BasicMaterial  extends THREE.ShaderMaterial{
     
     set opacity(o){
         this.uniforms && (this.uniforms.opacity.value = o)
-         
+        this.transparent = o<1
     }
     get opacity(){
         return this.uniforms.opacity.value  
@@ -57,7 +57,7 @@ class BasicMaterial  extends THREE.ShaderMaterial{
     get map(){
         return this.uniforms.map.value  
     }
-    
+     
   
     
 }

+ 212 - 45
src/custom/materials/ModelTextureMaterial.js

@@ -1,11 +1,24 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import Common from '../utils/Common.js'
-import {Features} from "../../Features.js";
-                                      
+import math from '../utils/math.js' 
+import {Features} from "../../Features.js";                             
+import {ExtendPointCloudMaterial} from "../../materials/ExtendPointCloudMaterial.js";    
+import {Gradients} from "../../materials/Gradients.js";
+
 
 const prefixVertex ="precision highp float;\nprecision highp int;\n\nuniform mat4 modelMatrix;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat4 viewMatrix;\nuniform mat3 normalMatrix;\nuniform vec3 cameraPosition;\n attribute vec3 position;\n attribute vec3 normal;\n attribute vec2 uv;\n"
 const prefixFragment ="#extension GL_EXT_frag_depth : enable \n precision highp float;\nprecision highp int;\n\nuniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"
                         // otherwise error: 'GL_EXT_frag_depth' : extension is disabled
+
+  
+let getClassificationLUT = ()=>{
+    const [width, height] = [256, 1];
+    let data = new Uint8Array(width * 4);
+    let tex = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
+    tex.magFilter = THREE.NearestFilter;
+    tex.needsUpdate = true;
+    return tex
+}
 let shader = {
 	 
 		uniforms: { 
@@ -35,6 +48,31 @@ let shader = {
 				type: "t",
 				value: null
 			}, 
+            pano0ClassMap: {
+				type: "t",
+				value: null
+			}, 
+            pano1ClassMap: {
+				type: "t",
+				value: null
+			}, 
+            pano0TempMap: {
+				type: "t",
+				value: null
+			}, 
+            pano1TempMap: {
+				type: "t",
+				value: null
+			}, 
+            gradient: {
+				type: "t",
+				value: null
+			}, 
+            classificationLUT: {
+				type: "t",
+				value: null
+			}, 
+            
 			pano0Position: {
 				type: "v3",
 				value: new THREE.Vector3
@@ -84,7 +122,10 @@ let shader = {
                 type: "f",
 				value: 2
             },
-             
+            temperRange:{//温度范围
+                type:'vec2',
+                value: new THREE.Vector2(math.getKelvinFromCelsius(15),  math.getKelvinFromCelsius(60)  )  
+            } 
         },
        
         vertexShader: prefixVertex + `
@@ -156,13 +197,28 @@ let shader = {
             uniform float maxDistance;
             uniform float minDistance;
             uniform float minOpa;
-            
-          
-          
+             
             uniform samplerCube pano0Map;
             uniform samplerCube pano1Map;
-          
-            
+            #if defined(useTempMap0) ||  defined(useTempMap1)
+                uniform sampler2D gradient; 
+                uniform vec2 temperRange; 
+            #endif  
+            #if defined(useClassMap0) ||  defined(useClassMap1)
+                uniform sampler2D classificationLUT;
+            #endif
+            #if defined(useTempMap0)
+                uniform sampler2D pano0TempMap;
+            #endif
+            #if defined(useTempMap1)
+                uniform sampler2D pano1TempMap;
+            #endif
+            #if defined(useClassMap0)
+                uniform sampler2D pano0ClassMap;
+            #endif
+            #if defined(useClassMap1)
+                uniform sampler2D pano1ClassMap;
+            #endif
             varying vec2 vUv; 
             varying vec3 vWorldPosition0;
             varying vec3 vWorldPosition1; 
@@ -242,25 +298,111 @@ let shader = {
                 //注:未加载好的话,depth为0,导致第一次漫游过去的时候许多mesh会立刻被遮挡,所以要确保加载完
             #endif
             
+            
+            
+            int getIntFromColor(sampler2D map, vec3 dir){//uint16-> int  0-65536 
+                vec2 uv = getSamplerCoord2(dir.xyz);  
+                uv.x -= 0.25;    
+                vec4 color = texture2D(map, uv); 
+                return int(round(color.r * 255.0)) + (int(round(color.g * 255.0)) << 8)  ;   //some phone needs round,such as vivo x30
+            } 
+            
+            
+            /*vec4 getColorFromMap(sampler2D map, vec3 dir){
+                vec2 uv = getSamplerCoord2(dir.xyz);  
+                uv.x -= 0.25;    
+                vec4 color = texture2D(map, uv); 
+                return color;
+            }*/
+            
+            
+            //ir热成像 
+            #if defined(useTempMap0) ||  defined(useTempMap1)
+            
+                vec4 getTemperature(sampler2D map, vec3 dir){ 
+                    
+                    float temperature = float(getIntFromColor(map, dir))/ 10.0 ; 
+                      
+                    float w = (temperature - temperRange.x) / (temperRange.y - temperRange.x);
+                    w = clamp(w,0.0,1.0);
+                    vec4 color = vec4(texture2D(gradient, vec2(w,1.0-w)).rgb, 1.0);   
+                    return color;
+                    
+                }
+            #endif 
+            
+            #if defined(useClassMap0) ||  defined(useClassMap1)
+              
+                /*vec4 getClassification(sampler2D map, vec3 dir, vec4 originColor){ 
+                    vec4 color = getColorFromMap(map, dir);  
+                    float v = color.r * 255.0;  
+                    if(v<=2.01&&v>=1.99){
+                        color = vec4(1.0,0.0,0.0,1.0);
+                    } else{
+                        color = vec4(0.0,0.0,0.0,1.0);
+                    } 
+                    return color;
+                } */
+                
+                
+                
+                vec4 getClassification(sampler2D map, vec3 dir, vec4 originColor){ 
+                     
+                    int classIndex = getIntFromColor(map, dir); 
+                  
+                    vec2 uv = vec2(float(classIndex) / 255.0, 0.5);         //copy from pointcloud_new.vs
+                    vec4 classColor = texture2D(classificationLUT, uv);
+	
+                    float blendRatio = 0.5;
+                    float classAlpha = classColor.a * blendRatio;  
+                    vec4 color = vec4(classColor.rgb * classAlpha + originColor.rgb * (1.0-classAlpha), 1.0);  //mix with old rgba
+                    return color;
+                }  
+            #endif 
+            
+            
             void main()
-            {
+            { 
+                
+                
                 vec3 vWorldPosition0N = normalize(vWorldPosition0);
                 vec3 vWorldPosition1N = normalize(vWorldPosition1);
                 float progress_ = progress;
                 
                 vec4 colorFromPano0 = vec4(0.0,0.0,0.0,0.0);
                 #if defined(usePanoMap0)
-                    //即progress < 1.0   通常是1
-                    colorFromPano0=textureCube(pano0Map,vWorldPosition0N.xyz);
+                    //即progress < 1.0   通常是1 
+                    
+                    #if defined(useTempMap0) 
+                        colorFromPano0 = getTemperature(pano0TempMap,vWorldPosition0N.xyz); 
+                    #else 
+                        colorFromPano0 = textureCube(pano0Map,vWorldPosition0N.xyz);
+                        #if defined(useClassMap0) 
+                            colorFromPano0 = getClassification(pano0ClassMap,vWorldPosition0N.xyz, colorFromPano0); 
+                        #endif
+                    #endif
+                    
                 #else 
                     progress_ = 1.0;
                 #endif
-                vec4 colorFromPano1=textureCube(pano1Map,vWorldPosition1N.xyz);
+                
+                
+                vec4 colorFromPano1 = vec4(0.0,0.0,0.0,0.0);
+                #if defined(useTempMap1) 
+                    colorFromPano1 = getTemperature(pano1TempMap,vWorldPosition1N.xyz);
+                #else 
+                    colorFromPano1 = textureCube(pano1Map,vWorldPosition1N.xyz);
+                    #if defined(useClassMap1) 
+                        colorFromPano1 = getClassification(pano1ClassMap,vWorldPosition1N.xyz, colorFromPano1); 
+                    #endif
+                #endif
+                 
  
                 gl_FragColor = mix(colorFromPano0,colorFromPano1,progress_);
-              
-              
                 
+                
+                
+                 
               
                 //深度图修改深度
               
@@ -330,9 +472,10 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
 			name: "ModelTextureMaterial",
             defines,  
         })
-    
+         
         this.glslVersion = Potree.settings.isWebgl2 && '300 es'
-            
+        this.uniforms.classificationLUT.value = this.classificationTexture = getClassificationLUT()
+        
         let setSize = (e)=>{ 
             let viewport = e.viewport
             //let viewportOffset = viewport.offset || new Vector2()  
@@ -362,7 +505,18 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
                 //this.uniforms.projectionMatrix.value.copy(e.camera.projectionMatrix) 
                 e.camera && this.uniforms.inverseProjectionMatrix.value.copy(e.camera.projectionMatrixInverse)
             })   
-
+            
+            let setClass = ()=>{
+                this.classification = viewer.classifications
+                ExtendPointCloudMaterial.prototype.recomputeClassification.call(this)
+            }
+            viewer.addEventListener('classifications_changed',setClass)
+            setClass()
+            
+             
+            
+            this._gradient = Gradients.ir  //海拔贴图种类,火灾也是这个
+            this.uniforms.gradient.value = ExtendPointCloudMaterial.generateGradientTexture(this._gradient);  
         } 
 
         let progress = 0
@@ -387,17 +541,8 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
         })
 		//-------------------------------------
 	}
-
-	/**
-	 * 
-	 * @param {Panorama} pano0 
-	 * @param {Panorama} pano1 
-	 * @param {boolean} flag 
-     
-     更新全景图的材质uniforms 
-     
-	 */
-     
+ 
+    
       
      
 	setProjectedPanos(pano0, pano1, progressValue ){
@@ -425,14 +570,16 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
         
         this.updateDepthTex(pano0)  
         this.updateDepthTex(pano1)
-        
-        
+        this.updateTempEnable()
+        this.updateClassEnable()
         //console.log('setProjectedPanos', pano0&&pano0.id, pano1&&pano1.id)
         this.needsUpdate = true;
+        
+        
+        
  	}
     
     
-    
     updateDepthTex(pano, extra){
         if( !Potree.settings.useDepthTex || !pano || !pano.depthTex || pano!=this.pano0 && pano!=this.pano1)return
         //console.log('updateDepthTex', pano.id,  this.pano0 && this.pano0.id,  this.pano1 && this.pano1.id)
@@ -448,24 +595,44 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
             this.uniforms.cameraHeight1.value = this.pano1.floorPosition.distanceTo(this.pano1.position)
             this.uniforms.ceilHeight1.value = this.pano1.getCeilHeight() - this.pano1.position.z  
         } 
-        this.updateDepthTexEnable()
-    }
-    
-    updateDepthTexEnable(){
-        if(this.dontChangeDepth)return
         
-        let hasDepthTex = this.pano0 && this.pano1 && this.pano0.pointcloud.hasDepthTex && this.pano1.pointcloud.hasDepthTex  //暂时不知道一个有图一个没图怎么写所以
         
+        if(this.dontChangeDepth)return
+        let hasDepthTex = this.pano0 && this.pano1 && this.pano0.pointcloud.hasDepthTex && this.pano1.pointcloud.hasDepthTex  //暂时不知道一个有图一个没图怎么写所以
         Potree.Utils.addOrRemoveDefine(this, 'hasDepthTex', hasDepthTex?'add':'remove' )
          
         
     }
     
-    /* EnableDepthTex(){//开启DepthTex
-        if(this.defines['hasDepthTex']){
-            return 
-        }
-        this.defines['hasDepthTex'] = ''
-        this.needsUpdate = true;
-    } */
-}
+    
+    
+    updateTempEnable(){ 
+        let tex0 = Potree.settings.showHotTemp && this.pano0.tempTex || Potree.settings.showHotIr && this.pano0.irTex   
+        let tex1 = Potree.settings.showHotTemp && this.pano1.tempTex || Potree.settings.showHotIr && this.pano1.irTex   
+        this.uniforms.pano0TempMap.value = tex0  
+        this.uniforms.pano1TempMap.value = tex1   
+        
+        Potree.Utils.addOrRemoveDefine(this, 'useTempMap0', tex0?'add':'remove' )
+        Potree.Utils.addOrRemoveDefine(this, 'useTempMap1', tex1?'add':'remove' )
+        viewer.dispatchEvent('content_changed')
+    }
+    updateClassEnable(){ 
+        let hasClassTex0 = Potree.settings.showClass && this.pano0.segTex   
+        let hasClassTex1 = Potree.settings.showClass && this.pano1.segTex  
+        this.uniforms.pano0ClassMap.value = this.pano0.segTex    
+        this.uniforms.pano1ClassMap.value = this.pano1.segTex    
+        
+        Potree.Utils.addOrRemoveDefine(this, 'useClassMap0', hasClassTex0?'add':'remove' )
+        Potree.Utils.addOrRemoveDefine(this, 'useClassMap1', hasClassTex1?'add':'remove' )
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    
+    
+}
+
+
+
+
+ModelTextureMaterial.prototype.setTempRange = ExtendPointCloudMaterial.prototype.setTempRange
+      

+ 36 - 61
src/custom/mergeStartTest.js

@@ -353,11 +353,14 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     ], tileIndex = 0
     
      let glbUrls = [ 
-        `${Potree.resourcePath}/models/glb/animation/dog.glb`, 
-         `${Potree.resourcePath}/models/glb/animation/Soldier.glb`, 
-        {url:`${Potree.resourcePath}/models/glb/animation/man--running.glb`, scale:[100,100,100]}, 
-        `${Potree.resourcePath}/models/glb/animation/man--walk.glb`, 
-        `${Potree.resourcePath}/models/glb/26k.glb`, 
+        {url:`${Potree.resourcePath}/models/glb/animation/Soldier.glb`, angle:Math.PI/2},
+        {url:`${Potree.resourcePath}/models/glb/animation/man.glb`, angle:Math.PI/2}, /* 
+        {url:`${Potree.resourcePath}/models/glb/animation/man--running.glb`, angle:Math.PI/2},
+        {url:`${Potree.resourcePath}/models/glb/animation/man--walk.glb`, angle:Math.PI/2}, */
+        {url:`${Potree.resourcePath}/models/glb/animation/dog.glb`, angle:Math.PI/2},
+        
+        
+        `${Potree.resourcePath}/models/glb/26k.glb` ,
 
         `${Potree.resourcePath}/models/glb/cloud_glb_239.glb`, 
         `${Potree.resourcePath}/models/glb/ModernJPHouseSofa44216499.glb`,
@@ -379,6 +382,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     
     
     let objUrls = [ 
+        Potree.resourcePath+'/models/obj/sphere', 
         Potree.resourcePath+'/models/obj/28M/GW1H',
         //Potree.resourcePath+'/models/obj/496M/Model', 
         Potree.resourcePath+'/models/obj/75M/Tile_+070_+051', 
@@ -459,35 +463,28 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             
             done && done(model)
         }
+         
         
         
-        
-        if(name == 'laser'){
-            Potree.loadDatasets(Potree.loadDatasetsCallback) 
-            viewer.addEventListener('allLoaded',()=>{
-                let pointcloud = modelEditing = viewer.scene.pointclouds[0]; 
-                pointcloud.matrixAutoUpdate  = true
-                pointcloud.initialPosition = pointcloud.position.clone() 
-                pointcloud.pos1MatrixInvert = new THREE.Matrix4().setPosition(pointcloud.initialPosition).invert()
-                    
-                 
-                
-                /* pointcloud.addEventListener('select',(e)=>{
-                    if(Potree.settings.displayMode == 'showPanos')return
-                    console.log('select',e) 
-                    //viewer.setControls(viewer.orbitControls) 
-                    focusOnSelect(pointcloud) 
-                    
-                    viewer.outlinePass.selectedObjects = [pointcloud]
-                    return {stopContinue:true}
-                },1)
-                pointcloud.addEventListener('deselect',(e)=>{
-                    console.log('deselect',e) 
-                    //viewer.setControls(viewer.fpControls)  
-                    viewer.outlinePass.selectedObjects = []
-                }) */
-                loadDone(pointcloud)
-            })
+        if(name == 'laser'){ 
+            if(Potree.browser.urlHasValue('localCloud')){//本地文件
+                let cloudPath = `${Potree.resourcePath}/../data/${Potree.settings.number}/cloud.js` 
+                Potree.loadPointCloud(cloudPath, Potree.settings.number , Potree.settings.number, 0, e => {
+                    viewer.scene.addPointCloud(e.pointcloud);
+                })
+            }else{
+                Potree.loadDatasets(Potree.loadDatasetsCallback) 
+                viewer.addEventListener('allLoaded',()=>{
+                    let pointcloud = modelEditing = viewer.scene.pointclouds[0]; 
+                    pointcloud.matrixAutoUpdate  = true
+                    pointcloud.initialPosition = pointcloud.position.clone() 
+                    pointcloud.pos1MatrixInvert = new THREE.Matrix4().setPosition(pointcloud.initialPosition).invert()
+                         
+                    loadDone(pointcloud)
+                })
+            }
+            
+            
         }else{
             
             let callback = (object)=>{
@@ -495,26 +492,6 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 object.traverse(e=>e.material && (e.material.transparent = true))
                 object.isModel = true
                 object.dataset_id = Date.now() //暂时
-                /* object.addEventListener('select',(e)=>{
-                    if(Potree.settings.displayMode == 'showPanos')return
-                    console.log('select',e) 
-                    viewer.setControls(viewer.orbitControls) 
-                    focusOnSelect(object) 
-                    
-                    viewer.outlinePass.selectedObjects = [object]
-                    return {stopContinue:true}
-                },1)
-                object.addEventListener('deselect',(e)=>{
-                    console.log('deselect',e) 
-                    viewer.setControls(viewer.fpControls)  
-                    viewer.outlinePass.selectedObjects = []
-                }) */
-                
-                /* object.addEventListener('click',(e)=>{
-                    //只是为了能得到hoverElement识别才加这个侦听
-                }) */
-                
-                
                 
                 $('#log').text('')  
 
@@ -531,8 +508,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 let num = 't-yAWONOn'
                 var path = `${Potree.scriptPath}/data/4dkk/${num}/` 
                 
-                viewer.loadModel({
-                    name, 
+                viewer.loadModel({ 
                     objurl: path+'mesh.obj',    //0.6s
                     mtlurl: path+'mesh.mtl',
                     unlit:true,
@@ -541,12 +517,11 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                         position : [0,0,0]  
                     } 
                 },callback,onprogress)
-            }else if(name == 'obj'){
-                  
+            }else if(name == 'obj'){  
+                   
                 let path = objUrls[objIndex++]
                 
-                viewer.loadModel({
-                    name, 
+                viewer.loadModel({ 
                     objurl: path+'.obj',  
                     mtlurl: path+'.mtl',    
                     transform : { 
@@ -571,7 +546,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 
                 let info = glbUrls[glbIndex++]
                 
-                if(info.url){
+                if(info.url){ 
                     angle = info.angle || 0
                     url = info.url
                     scale = info.scale
@@ -580,8 +555,8 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                 }
                 
                 viewer.loadModel({ 
-                    fileType:'glb',
-                    name, 
+                    fileType:'glb', 
+                     unlit:true,
                     url ,
                     transform : { 
                         rotation : [angle,  0,   0],

+ 101 - 24
src/custom/modules/mergeModel/MergeEditor.js

@@ -13,6 +13,8 @@ import {TransformControls} from "../../objects/tool/TransformControls.js";
 import History from "../../utils/History.js"
 import {Box3Helper} from "../../../utils/Box3Helper.js";
  
+import {TextSprite} from '../../objects/TextSprite.js' 
+  
  
 const texLoader = new THREE.TextureLoader() 
       texLoader.crossOrigin = "anonymous" 
@@ -118,18 +120,19 @@ let MergeEditor = {
         {
             
             this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{
-                dontHideWhenFaceCamera: true,
+                //dontHideWhenFaceCamera: true,
             });
             //this.transformControls.space = 'local'//为了在当前方向上平移
             this.transformControls.setSize(1.5)
             viewer.scene.scene.add(this.transformControls)
-            this.transformControls._gizmo.hideAxis = {rotate:['e']}
+            this.transformControls._gizmo.hideAxis = {rotate:['e'] ,scale:['x','y','z' ] }
+            this.transformControls._gizmo.showAxis = {scale:['XYZY']} //仅显示 等于而非包含
             this.transformControls.setRotateMethod(2)
             
             
             //右屏
             this.transformControls2 = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{ 
-                dontHideWhenFaceCamera: true,
+                //dontHideWhenFaceCamera: true,
             }); 
             this.transformControls2.setSize(1.5)
             viewer.scene.scene.add(this.transformControls2) 
@@ -178,7 +181,8 @@ let MergeEditor = {
         
         viewer.addEventListener('global_single_click',(e)=>{
             if(
-                this.noNeedSelection  //如模型查看页
+                e.button != THREE.MOUSE.LEFT
+                || this.noNeedSelection  //如模型查看页
                 || viewer.scene.cameraAnimations.some(c=>c.onUpdate) //正在播放
                 || e.drag && e.drag.notPressMouse   //在加测量线
                 || viewer.mainViewport.view.isFlying() //有其他校准
@@ -637,7 +641,7 @@ let MergeEditor = {
             viewer.outlinePass.edgeStrength = edgeStrengths.glb
         }  
     },*/ 
-    focusOn(objects, duration = 400, fitBound=true, dontLookUp){  
+    focusOn(objects, duration = 400, fitBound=true, dontLookUp, dir){  
         if(!(objects instanceof Array)){
             objects = [objects]
         }
@@ -650,7 +654,7 @@ let MergeEditor = {
         Potree.settings.cameraFar = Math.max( Potree.settings.cameraFar, len*3 )
         
         if(fitBound){
-            viewer.focusOnObject({boundingBox}, 'boundingBox', duration, {dontLookUp, dontChangeCamDir:true}) 
+            viewer.focusOnObject({boundingBox}, 'boundingBox', duration, {dontLookUp, dontChangeCamDir:dir?false:true, dir}) 
         }else{ 
         
             /* 
@@ -681,8 +685,14 @@ let MergeEditor = {
     },
     
     getBoundCenter(model){
-        if(!model.boundCenter) model.boundCenter = new THREE.Vector3
-        model.boundingBox.getCenter(model.boundCenter).applyMatrix4(model.matrixWorld) 
+        if(!model.boundCenter) {
+            model.boundCenter = new THREE.Vector3
+            model.boundSize = new THREE.Vector3
+        }
+        let bound = model.boundingBox.clone().applyMatrix4(model.matrixWorld) 
+        bound.getCenter(model.boundCenter)
+        bound.getSize(model.boundSize)
+        //model.boundingBox.getCenter(model.boundCenter).applyMatrix4(model.matrixWorld) 
     },
     
     setModelBtmHeight(model, z ){  
@@ -690,23 +700,24 @@ let MergeEditor = {
         if(z == void 0) z = model.btmHeight; //维持离地高度
         else model.btmHeight = z;
         
-        if(model.btmHeight == void 0)return
-        
+        if(model.btmHeight == void 0)return 
         model.updateMatrixWorld()
-        let boundingBox2 = model.boundingBox.clone().applyMatrix4(model.matrixWorld)
+        this.getBoundCenter(model) 
+        /* let boundingBox2 = model.boundingBox.clone().applyMatrix4(model.matrixWorld)
         let size = boundingBox2.getSize(new THREE.Vector3);
-        let center = boundingBox2.getCenter(new THREE.Vector3);
-        let hopeZ = z + size.z / 2  
+        let center = boundingBox2.getCenter(new THREE.Vector3); */ 
+        let hopeZ = z + model.boundSize.z / 2  
         //model.position.z = z + size.z / 2 - center.z 
-        model.position.z += (hopeZ - center.z)
+        model.position.z += (hopeZ - model.boundCenter.z)
     },
     
     computeBtmHeight(model){ //位移之后重新计算btmHeight
         model.updateMatrixWorld()
-        let boundingBox2 = model.boundingBox.clone().applyMatrix4(model.matrixWorld)
+        /* let boundingBox2 = model.boundingBox.clone().applyMatrix4(model.matrixWorld)
         let size = boundingBox2.getSize(new THREE.Vector3);
-        let center = boundingBox2.getCenter(new THREE.Vector3);
-        model.btmHeight = center.z - size.z / 2 
+        let center = boundingBox2.getCenter(new THREE.Vector3); */
+        this.getBoundCenter(model)
+        model.btmHeight = model.boundCenter.z - model.boundSize.z / 2 
     },
     
     
@@ -754,6 +765,13 @@ let MergeEditor = {
                 measure.setSelected(false)//隐藏edgelabel  
             }
         }) 
+        viewer.tags.children.forEach(tag=>{
+            if(tag.root == model){
+                tag.titleLabel.updatePose()
+            }
+        })
+        
+        
         
         //反向求transformMatrix  参考Alignment.js   移动漫游点 
         if(model.isPointcloud && model.transformMatrix){ 
@@ -834,9 +852,54 @@ let MergeEditor = {
         
     },
     
+    addTitleForModel(model){ 
+        var titleLabel = new TextSprite({  
+            startClipDis  : 0.5,
+            clipDistance : 1,//消失距离      
+            startOcclusDis: 0.5,
+            occlusionDistance: 0.9,//变为backColor距离 
+            maxOcclusionFactor:0.7,
+            maxClipFactor:1,
+            text:model.name, sizeInfo:{width2d:150},
+            rectBorderThick:1,            
+            borderColor:{r:200,g:200,b:200,a:0.5}, 
+            textColor:{r:255 ,g:255,b:255,a:1.0}, 
+            textshadowColor:'black',
+            backgroundColor:{r: 100,g:100,b:100,a:0.3},
+            borderRadius: 6,   
+            fontsize: 20,  fontWeight:'',//thick
+            renderOrder : Potree.config.renderOrders.tag.label, 
+            pickOrder: Potree.config.renderOrders.tag.label,
+            useDepth : true ,
+            maxLineWidth: 300,
+            transform2Dpercent:{x:0,y:1.3}, //向上移动 
+            textAlign: Potree.settings.isOfficial && 'left'
+        }) 
+        model.titleLabel = titleLabel
+        viewer.scene.scene.add(titleLabel)
+        let updatePos = ()=>{
+            titleLabel.position.copy(model.boundCenter)  
+            titleLabel.position.z += model.boundSize.z / 2          //暂时加载模型顶部,但也可能如果旋转了要在头顶
+            titleLabel.updatePose()
+        }
+        let setVisible = ()=>{
+            Potree.Utils.updateVisible(titleLabel,'followModel', model.visible)
+            viewer.dispatchEvent('content_changed')
+        }
+        model.addEventListener('transformChanged',updatePos)
+        model.addEventListener('isVisible',setVisible)
+        
+        titleLabel.addEventListener('isVisible',()=>{
+            titleLabel.visible && updatePos()
+        })
+        setVisible()
+        updatePos()
+        
+    },
     
+     
     
-    
+     
     
     updateMemoryUsage1(){
         
@@ -913,12 +976,7 @@ let MergeEditor = {
          */
     },
     
-      /* 0.0000621791536865594
-      0.00009005265640549425
-      0.00007493883414610654
-      0.00006503514775640251
-      0.000063906496015 */
-      
+     
       
     
     updateMemoryUsage(){//新  注意 模型即使隐藏也不会降低内存占用,只是会降低卡顿。  未支持3dgs
@@ -1200,6 +1258,12 @@ let MergeEditor = {
         }  
     }
     
+    
+    
+    
+    
+     
+    
 }   
     
     /* 
@@ -1222,6 +1286,19 @@ let MergeEditor = {
 export default MergeEditor  
     
      
+     
+       
+/* class titleLabel extends THREE.Shim.FollowRootObject{
+    constructor(o){ 
+        super(o.root)
+        this.l
+    }
+    
+    
+    
+    
+} */
+        
 /*   
 
 note: 

+ 173 - 7
src/custom/modules/panos/Images360.js

@@ -248,6 +248,48 @@ export class Images360 extends THREE.EventDispatcher{
                     elHide.val(!visi ? "隐藏点云" : "显示点云")  
                 });
                 
+                
+                viewer.addEventListener('allLoaded',()=>{ 
+                    let interval = setInterval(()=>{ 
+                        let attrs = viewer.scene.pointclouds[0].root.geometryNode?.geometry.attributes || viewer.scene.pointclouds[0].root.geometry?.attributes
+                        if(!attrs) return
+                        if(attrs.ir){
+                            let btnHot = $('<button id="hot" style="position: fixed;right: 10px;top: 10px;z-index: 10;">打开热成像</button>')
+                            $('body').append(btnHot)
+                            let onHot = false
+                            btnHot.on('click',()=>{
+                                 onHot = !onHot    
+                                 Potree.settings.showHotIr = onHot
+                                 btnHot.text((btnHot?'关闭':'打开')   +  '热成像')
+                            })
+                        }
+                        if(attrs.temp){
+                            let btn = $('<button id="hot" style="position: fixed;right: 10px;top: 50px;z-index: 10;">打开火调温度</button>')
+                            $('body').append(btn)
+                            let on = false
+                            btn.on('click',()=>{
+                                 on = !on  
+                                 Potree.settings.showHotTemp = on  
+                                 btn.text((on?'关闭':'打开')   +  '火调温度')
+                            }) 
+                        }   
+                        if(attrs.classification){
+                            let btnSeg = $('<button id="hot" style="position: fixed;right: 10px;top: 100px;z-index: 10;">打开分类</button>')
+                            $('body').append(btnSeg)
+                            let onSeg = false
+                            btnSeg.on('click',()=>{
+                                 onSeg = !onSeg    
+                                 Potree.settings.showClass = onSeg  
+                                 btnSeg.text((onSeg?'关闭':'打开')   +  '分类')
+                            }) 
+                        }  
+                        clearInterval(interval)
+                    },100)
+                    
+                                         
+                })
+                
+                
             }
             
             let elDisplayModel = $("<input type='button' value='>>全景'></input>")
@@ -318,7 +360,7 @@ export class Images360 extends THREE.EventDispatcher{
                     }else{
                         config2 = config.transition 
                     }
-
+                    let pano = this.currentPano
                     let changeTileDownload = ()=>{
                         if(config2.showSkybox || config2.showPoint && config2.pointUsePanoTex){
                             this.tileDownloader.start() 
@@ -408,10 +450,20 @@ export class Images360 extends THREE.EventDispatcher{
                             }
                             
                             //this.updateDepthTex()  
-                            if(this.checkAndWaitForPanoLoad(this.currentPano,  this.basePanoSize, wait)){
+                            if(this.checkAndWaitForPanoLoad(pano,  this.basePanoSize, wait)){
                                 //console.log('等待贴图加载2', this.currentPano.id)
                                 return
                             }  
+                            
+                            let types = {'seg':'showClass', 'ir':'showHotIr','temp':'showHotTemp'} 
+                            for(let type in types){
+                                if(Potree.settings[types[type]] && pano['has_'+type] && !pano[type+'Tex']){
+                                    pano.loadTypeImg(type)
+                                    return pano.addEventListener('loaded_'+type, wait, {once:true})    
+                                }
+                            }
+                             
+
                         } 
                         
                          
@@ -522,6 +574,111 @@ export class Images360 extends THREE.EventDispatcher{
             Potree.settings.displayMode =  'showPointCloud'  
         }// 切换模式 end
         
+        
+        { 
+            
+            let showHotTemp = null;  //火调温度 /* --目前仅需点云模式  */
+            Object.defineProperty(Potree.settings, "showHotTemp",{  
+                get: function() {
+                    return showHotTemp
+                },
+                set:  (show)=>{
+                    if(showHotTemp == show)return
+                    showHotTemp = show 
+                    viewer.switchHotType()   
+                    let pano = this.currentPano
+                    if(Potree.settings.displayMode == 'showPanos' && pano.has_temp){ 
+                        
+                        let change = ()=>{
+                            this.cube.material.updateTempEnable()
+                        }
+                         
+                        if(show && !pano.tempTex){
+                            pano.loadTypeImg('temp')
+                            pano.addEventListener('loaded_temp', change, {once:true})   
+                        }else{
+                            change()
+                        } 
+                    }                    
+                } 
+            }) 
+             
+        }
+        
+        
+        { //优先级最低
+            
+            let showHotIr = null;  
+            Object.defineProperty(Potree.settings, "showHotIr",{  
+                get: function() {
+                    return showHotIr
+                },
+                set: (show)=>{
+                    if(showHotIr == show)return
+                    showHotIr = show 
+                    
+                    viewer.switchHotType()   
+                    
+                     
+                    let pano = this.currentPano
+                    
+                    if(Potree.settings.displayMode == 'showPanos' && pano.has_ir){ 
+                        
+                        let change = ()=>{
+                            this.cube.material.updateTempEnable()
+                        }
+                         
+                        if(show && !pano.irTex){
+                            pano.loadTypeImg('ir')
+                            pano.addEventListener('loaded_ir', change, {once:true})   
+                        }else{
+                            change()
+                        } 
+                    }
+                      
+                },
+            })
+              
+        }
+        
+        { //优先级最高
+            
+            let showClass = null;  
+            Object.defineProperty(Potree.settings, "showClass",{  
+                get: function() {
+                    return showClass
+                },
+                set:  (show)=>{
+                    if(showClass == show)return
+                    showClass = show 
+                    let pano = this.currentPano 
+                    viewer.scene.pointclouds.forEach(e=>e.updateAttrAuto())
+                    
+                    
+                    
+                    if(Potree.settings.displayMode == 'showPanos' && pano.has_seg){
+                      
+                        let change = ()=>{
+                            this.cube.material.updateClassEnable()
+                        }
+                        if(show && !pano.segTex){
+                            pano.loadTypeImg('seg')
+                            pano.addEventListener('loaded_seg', change, {once:true})   
+                        }else{
+                            change()
+                        } 
+                    }
+                },
+            }) 
+             
+        }
+        
+        
+        
+        
+        
+        
+        
         {//
             let currentPano = null;  
             Object.defineProperty(this , "currentPano",{  
@@ -821,22 +978,31 @@ export class Images360 extends THREE.EventDispatcher{
                  
             }
             if(!pano.depthTex && pano.pointcloud.hasDepthTex){ //点云模式也要加载depthTex,因获取neighbour需要用到
-                 console.log('等待加载depthtex', pano.id)
+                console.log('等待加载depthtex', pano.id)
                 pano.addEventListener('loadedDepthImg', wait, {once:true})           
                 return pano.loadDepthImg()
             }
+             
             
             if(config.atPano.showSkybox || config.atPano.pointUsePanoTex){ 
+            
+                let types = {'seg':'showClass', 'ir':'showHotIr','temp':'showHotTemp'} 
+                for(let type in types){
+                    if(Potree.settings[types[type]] && pano['has_'+type] && !pano[type+'Tex']){
+                        pano.loadTypeImg(type)
+                        return pano.addEventListener('loaded_'+type, wait, {once:true})    
+                    }
+                }
+            
+            
                 let a = this.updateCube(this.currentPano, toPano.pano)
                 if(a == 'useBound'){
                     toPano.useBound = true
                 }
-                
-              
-                if(this.checkAndWaitForPanoLoad(pano, toPano.basePanoSize || this.basePanoSize,  wait )){
-                    //console.log('等待贴图加载',pano.id)
+                if(this.checkAndWaitForPanoLoad(pano, toPano.basePanoSize || this.basePanoSize,  wait )){ 
                     return
                 }  
+                
             } 
         }
         

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

@@ -144,7 +144,8 @@ class Panorama extends THREE.EventDispatcher{
             this.sid = this.pointcloud.dataset_id + '|' + this.originID  //不会更改的标记
             //全景图和Cube的水平采样起始坐标相差90度 
             
-
+             
+            
             /* if(from4dkk){
                 var qua = o.dataset_orientation 
                 
@@ -265,8 +266,136 @@ class Panorama extends THREE.EventDispatcher{
         
         //平均一张0.75M。2048*1024。 和tile一样加载后永不删除,是否会造成崩溃?
 	}
- 
     
+    
+    /* loadTypeImg(type){ 
+        if(!this['has_'+type] || this[type+'Tex'] || this[type+'Loading'])return
+        
+         
+        let url = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_${type}.png`
+        //let url = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_temp.png`
+        
+        
+        let texture = texLoader.load( url, ()=>{
+            this[type+'Tex'] = texture
+            this.dispatchEvent({type:'loaded_'+type,  loaded:true})
+            this.images360.dispatchEvent({type:'loaded_'+type,  pano:this})
+            this[type+'Loading'] = false
+          
+        },null,(e)=>{//error
+            console.error('load '+type+'Img失败, 数据集sceneCode'+ this.pointcloud.sceneCode,  this.id )
+            this['has_'+type] = false
+            this['loadFailed_'+type] = false
+            this.dispatchEvent({type:'loaded_'+type })
+        });
+        texture.wrapS = THREE.RepeatWrapping;
+        texture.flipY = false 
+        texture.magFilter = THREE.LinearFilter 
+        texture.minFilter = THREE.LinearFilter
+        texture.generateMipmaps = false
+        
+        
+    } */
+    loadTypeImg(type){ 
+        if(!this['has_'+type] || this[type+'Tex'] || this[type+'Loading'])return
+        
+         
+        let url = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_${type}.png`
+        //let url = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_temp.png`
+         
+        let range = {min:Infinity, max:-Infinity, minPixel:null, maxPixel:null}  
+        let pixels 
+        let getRangeFun = (type == 'ir' || type == 'temp') && function(uint16Value, index, pixelCount){ 
+            if(type == 'ir'){
+                pixels || (pixels = new Uint16Array(pixelCount))
+                pixels[index] = uint16Value
+            }
+            
+            
+            if(uint16Value != 0 && uint16Value < range.min){
+                range.min = uint16Value 
+                range.minPixel_ = index
+            }
+            if(uint16Value != 0 && uint16Value > range.max){
+                range.max = uint16Value 
+                range.maxPixel_ = index
+            }
+            
+        }
+        
+        let texture = Potree.Common.load16bitPngTex(url,()=>{
+            this[type+'Tex'] = texture 
+            if(getRangeFun){ 
+                if(type == 'ir'){ 
+                    /* let p = [range.minPixel_, range.maxPixel_].map(index=>{
+                        let row = Math.floor(index / texture.image.width)
+                        let col = index % texture.image.width
+                        return {x:col,y:row}
+                    })
+                    range.minPixel = p[0]
+                    range.maxPixel = p[1]  */
+                    let pixelCount = texture.image.width * texture.image.height
+                    const stopMemberCount = 50;
+                    
+                    
+                    (['min','max']).forEach(name =>{
+                        let value = range[name]
+                        let groups = []
+                        let isNeigh = (A,B)=>{
+                            return (Math.abs(A.x - B.x)<=1 || Math.abs(A.x - B.x) == texture.image.width-1) && Math.abs(A.y - B.y)<=1
+                        }
+                        for(let i=0;i<pixelCount;i++){
+                            if(Math.abs(value - pixels[i]) < 5){
+                                let row = Math.floor(i / texture.image.width)
+                                let col = i % texture.image.width
+                                let data = {x:col, y:row, value: pixels[i]}
+                                Potree.Common.pushToGroupAuto([data], groups, isNeigh/* , recognizeGroup */)
+                                if(groups.some(e=>e.length > stopMemberCount)){
+                                    break
+                                }
+                            }
+                        } 
+                        let groups2 = groups.filter(e=>e.length>1)
+                        if(groups2.length == 0) groups2 = groups
+                        groups2.forEach(group=>{//x的因边界不好写,就只判断y尽量接近中间且范围不大不小即可
+                            group.sort((a,b)=>{return a.y-b.y})
+                            let minY = group[0].y
+                            let maxY = group[group.length-1].y
+                            let centerY = (minY + maxY) / 2
+                            let rectRatio = (maxY - minY) * 1//除非竖的一列,否则一般y范围越大越好  Math.abs(2 - Potree.math.getBaseLog(maxY - minY, group.length)) //范围圆润度, 最好个数是y范围的平方
+                            group.score = group.length * 0.4 + rectRatio - Math.abs(texture.image.height/2 - centerY) * 0.02 //y尽量接近中间
+                            group.center = group[Math.floor(group.length/2)] //忽略是否横向的中间
+                        })
+                        groups2.sort((b,a)=>{return a.score - b.score})
+                        range[name+'Pixel'] = groups2[0]?.center  
+                        //console.log('groups2', this.id, name,groups2)
+                    })
+                    
+                }
+                range.min /= 10, range.max /= 10
+                texture.image.range = range 
+                viewer.gatherTempRange(type,range)
+                
+            }
+            this.dispatchEvent({type:'loaded_'+type,  loaded:true})
+            this.images360.dispatchEvent({type:'loaded_'+type,  pano:this})
+            this[type+'Loading'] = false 
+        },(e)=>{
+            console.error('load '+type+'Img失败, 数据集sceneCode'+ this.pointcloud.sceneCode, e, this.id )
+            this['has_'+type] = false
+            this['loadFailed_'+type] = false
+            this.dispatchEvent({type:'loaded_'+type })
+        }, getRangeFun) 
+        
+        
+        texture.wrapS = THREE.RepeatWrapping;
+        texture.flipY = false 
+        texture.magFilter = THREE.LinearFilter 
+        texture.minFilter = THREE.LinearFilter
+        texture.generateMipmaps = false
+         
+        
+    }
     build(){ 
         
         { // orientation 

+ 2 - 2
src/custom/modules/volumeCompute/VolumeComputer.js

@@ -89,7 +89,7 @@ let horizonZ, zMin, zMax, lowest, highest,  wDelta,  sDelta,  deferred,  interru
 
 
 
-let testPoint = false
+let testPoint_ = false
 
 
 //const asyncTimeout = (delay) => new Promise(resolve => setTimeout(resolve, delay))
@@ -166,7 +166,7 @@ export default class VolumeComputer extends THREE.EventDispatcher{
         
         this.material = new ExtendPointCloudMaterial();
      
-		this.material.activeAttributeName = testPoint ? "rgba" : 'heightCpt' ;// 'rgba' indices prismHeight
+		this.material.activeAttributeName = testPoint_ ? "rgba" : 'heightCpt' ;// 'rgba' indices prismHeight
          
         this.prisms = []
            

+ 543 - 0
src/custom/objects/Monitor.js

@@ -0,0 +1,543 @@
+
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+//import "../../../libs/other/hls.js";
+import BasicMaterial from '../materials/BasicMaterial.js'  
+
+import {TextSprite} from "./TextSprite.js"; 
+
+import math from '@sdk/utils/math'  
+/* import SecurityCamera from '../core/cameras/SecurityCamera'
+import SecurityControls from '../core/controls/SecurityControls' */
+ 
+import {transitions, easing, lerp} from '../utils/transitions.js'
+import CursorDeal from '../utils/CursorDeal.js'
+import FlvVideoPlayerBase from '../utils/media/FlvVideoPlayerBase'
+import H5VideoPlayerBase from '../utils/media/H5VideoPlayerBase'
+
+ 
+const planeGeometry = new THREE.PlaneGeometry(1,1)  
+const testUrl = 'https://newcntv.qcloudcdn.com/asp/hls/4000/0303000a/3/default/d907ef756138493e9fe5b35b4ab69642/4000.m3u8'
+ 
+
+const videoPlayer = Potree.browser.nonsupportH5Video ? new FlvVideoPlayerBase() : new H5VideoPlayerBase()
+const normalColor = new THREE.Color(1, 1, 1)
+const hoverColor = new THREE.Color(2, 2, 2)
+const vec1 = new THREE.Vector3(),
+    vec2 = new THREE.Vector3(),
+    vec3 = new THREE.Vector3(),
+    quat1 = new THREE.Quaternion(),
+    quat2 = new THREE.Quaternion()
+let cameraModel
+
+
+
+//接收4dkk的监控 暂不支持修改   可以直接加到模型上,注意所附模型旋转90度后角度才对。
+export default class Monitor extends THREE.Object3D{
+    constructor(data){
+        super()
+         
+        warnHls()
+        
+        data.video = testUrl
+        
+        this.data = data
+        /* for(let i in data){
+            this[i] = data[i]
+        }  */
+        this.sid = data.sid 
+        this.fov = data.fov 
+        this.cylinderNear = data.near || 0.03
+        this.cylinderFar = data.far || 3
+        
+        
+        
+        
+        this.video = videoPlayer.getVideo(data.video,this)
+        if (data.video) {
+            //console.error('createVideo', this.videoSrc,   this.sid)
+            /* this.video.onloadedmetadata = () => {
+                this.video.canPlayed = true
+                this.video.masters.forEach(v => { //一个video可以对应多个camera,因为它们链接一样
+                     v.dispatchEvent({ type: 'loadedmetadata' })
+                })
+            } */
+
+            let canPlayed = e => {
+                //this.player.$app.Camera.emit('SecurityCamera.videoActive', this.sid)
+                this.videoActive = true 
+                //this.updateInfo(true)
+            }
+
+            if (this.video.canPlayed) {
+                canPlayed()
+            } else {
+                this.addEventListener('loadedmetadata', canPlayed)
+            }
+
+            if (Hls.isSupported() && data.videoType !== 2) {
+                //似乎Hls不支持就无法播放
+                let hls = new Hls()
+                hls.loadSource(data.video)
+                hls.attachMedia(this.video)
+                hls.on(Hls.Events.ERROR, (event, data) => console.log('HLS加载失败', event, data))
+                this.hlsVideoPlayer = hls
+            }   
+            this.play() // ios需要
+            this.pause()
+        }        
+         
+        
+        this.obj3d = new THREE.Group() 
+        this.add(this.obj3d)
+        
+        
+        // 投射体材质
+        this.normalMat = new BasicMaterial({
+            color: 0x00c8af,
+            transparent: true,
+            opacity: 0.1,
+            side: THREE.DoubleSide,
+            depthTest: false,
+        })
+        // 监控视频材质
+        this.videoMat = new BasicMaterial({
+            map: new THREE.VideoTexture(this.video),
+            side: THREE.DoubleSide,
+            depthTest: false,
+            transparent: true,
+            // opacity: 0.5,
+        })
+        // 摄像头
+        if (!cameraModel) {
+            viewer.loadModel({fileType:'glb', url:`${Potree.resourcePath}/models/glb/monitor.glb`},(model)=>{
+                console.log('load monitor glb')
+                this.cameraModel = model.children[0].children[0]
+                this.cameraModel.geometry.translate(30, 50, -10)
+                this.cameraModel.quaternion.setFromEuler(new THREE.Euler(Math.PI / 2, Math.PI, 0))
+                this.obj3d.add(this.cameraModel)
+                model.parent.remove(model)
+                cameraModel = this.cameraModel.clone()
+                this.modelLoaded()
+            })
+        }else{
+            this.cameraModel = cameraModel.clone()
+            this.obj3d.add(this.cameraModel)
+            this.modelLoaded()
+        }
+        this.updateAspect()
+        
+        {
+            this.titleLabel = new TextSprite({
+                text: data.name,
+                backgroundColor: { r: 255, g: 255, b: 255, a: 0 },
+                textColor: { r: 255, g: 255, b: 255, a: 1 },
+                textshadowColor: '#888',
+                borderRadius: 2,
+                fontsize: 34,
+                renderOrder: 5,
+                margin: { x: 12, y: 10 },  
+                sizeInfo: { scale: 0.4, nearBound: 3 },
+            })
+            this.titleLabel.sprite.material.depthTest = this.titleLabel.sprite.material.depthWrite = true
+            this.titleLabel.position.set(0, -0.2, 0.1)
+            this.add(this.titleLabel)
+        }
+        
+        
+        {
+            this.posOri = data.posOri || new THREE.Vector3()
+            this.posOffset = data.posOffset || new THREE.Vector3()
+            // data.position && this.position.copy(data.position)
+            this.position.copy(this.posOri).add(this.posOffset)
+
+        
+            // target的优先级大于rotation
+            if (data.target) {
+                this.target = data.target
+                this.lookAt(data.target)
+            } else {
+                data.rotation && this.quaternion.setFromEuler(data.rotation)
+                this.target = new THREE.Vector3(0, 0, -1).applyQuaternion(this.quaternion).add(this.position)
+            }
+            
+            
+            
+            this.roll = 0
+            data.pitch && (this.pitch = data.pitch)
+            data.yaw && (this.yaw = data.yaw)
+            data.roll && this.setRoll(this.data.roll)
+        }
+        
+        
+
+        
+        //this.updateInfo(true)
+        
+        
+        
+        this.events = {
+            setSize: (width, height) => {
+                if (this.isWatching) {
+                    this.updateAspect()
+                }
+            },
+            update: ()=>{
+                if(!this.video.paused){
+                    viewer.dispatchEvent('content_changed')
+                }
+            }
+            
+        } 
+        viewer.addEventListener('viewerResize', this.events.setSize   )
+        viewer.addEventListener('update', this.events.update   )
+        
+        window.monitor = this
+        Potree.Utils.setObjectLayers(this,'monitor')
+        
+    }
+    
+    modelLoaded(){
+        this.cameraModel.addEventListener('mouseover',()=>{
+            CursorDeal.add('hoverMonitor')
+            this.highlight(true)
+        })    
+        this.cameraModel.addEventListener('mouseleave',()=>{
+            CursorDeal.remove('hoverMonitor')
+            this.highlight(false)
+        })
+        
+        this.cameraModel.addEventListener('click',()=>{
+            this.watch()
+        }) 
+    }
+    
+    
+    watch(){
+        let camera = viewer.mainViewport.camera
+        this.updateAspect()
+        this.isWatching = true
+        //this.target.set(0, 0, -1).applyQuaternion(this.quaternion).add(this.position)
+        viewer.mainViewport.view.cancelFlying()
+        this.oldState = {
+            fov: camera.fov,
+            position: camera.position.clone(),
+            quaternion: camera.quaternion.clone()
+        }
+        this.showVideo(true)
+        this.video.pause()//先显示出画面
+        setTimeout(()=>{
+            viewer.mainViewport.cameraLayers = ['monitor'] //hide others
+        },1000)
+        viewer.mainViewport.view.setRotMode('free')
+        viewer.mainViewport.view.setView({
+            position: this.getWorldPosition(new THREE.Vector3),
+            quaternion: this.getWorldQuaternion(new THREE.Quaternion),
+            onUpdate:(percent)=>{
+                camera.fov = this.data.fov * percent + this.oldState.fov * (1-percent)
+                camera.updateProjectionMatrix()  
+            },
+            callback:()=>{
+                this.video.play()
+                
+            },
+            duration:1500
+        })
+        
+        viewer.renderArea.style['pointer-events'] = 'none'; 
+    }
+    
+        
+    leave(){// 停止观看监控
+        let camera = viewer.mainViewport.camera
+        
+        this.isWatching = false
+        this.cameraModel.material.color.copy(normalColor)
+        viewer.mainViewport.view.cancelFlying()
+        viewer.mainViewport.cameraLayers = null
+        this.video.pause()
+        viewer.mainViewport.view.setView({
+            position: this.oldState.position,
+            quaternion: this.oldState.quaternion,
+            
+            onUpdate:(percent)=>{
+                camera.fov = this.data.fov * (1-percent) + this.oldState.fov * percent
+                camera.updateProjectionMatrix()
+            },
+            callback:()=>{
+                viewer.renderArea.style['pointer-events'] = ''; 
+                this.showVideo(false)
+                viewer.mainViewport.view.setRotMode('standard')
+            },
+            duration:1000
+        })
+    }
+    
+    
+    
+    // 显示监控视频或显示投射体
+    showVideo(isShow) {
+        //console.warn('showVideo', this.info.sid, isShow )
+         
+        if (!this.videoActive) return
+
+        if (isShow) {
+            /* if (browser.detectIOS()) {
+                this.player.domElement.addEventListener('touchstart', this.events.onDomElementTouchStart, true)
+            } */ 
+            this.play()
+            this.normalMat.opacity = 0
+            Potree.Utils.updateVisible(this.cylinder, 'watch', true, 1, 'add')
+            this.cylinder.bottom.material = this.videoMat 
+            //this.cylinder.bottom.renderOrder = RenderOrder.monitorPlane
+        } else {
+            this.pause()
+            this.normalMat.opacity = 0.08
+            Potree.Utils.updateVisible(this.cylinder, 'watch', false, 1, 'cancel')
+            this.cylinder.bottom.material = this.normalMat 
+            //this.cylinder.bottom.renderOrder = RenderOrder.visibleFloor
+        }
+    }
+    
+
+    play() {
+        //console.log('play monitor', this.sid, this.videoSrc)
+        this.shouldPlay = true
+        if (Potree.browser.detectWeixin()) {
+            //用微信平台的 WeixinJSBridge 越过 Autoplay Policy
+            try {
+                top.WeixinJSBridge &&
+                    top.WeixinJSBridge.invoke(
+                        'getNetworkType',
+                        {},
+                        e => {
+                            this.video.play()
+                        },
+                        false
+                    )
+            } catch (error) {
+                this.video.play()
+            }
+        } else {
+            this.video.play()
+            if (this.video.paused) {
+                console.log('播放不了')
+                /* this.player.once('pointerStart', () => { 
+                    this.play()
+                }) */
+            }
+        }
+    }
+
+    pause() {
+        this.shouldPlay = false
+        this.video.pause()
+    }
+
+    
+    
+    
+    
+    
+    
+    
+    dispose(){
+        
+        let index = viewer.scene.monitors.indexOf(this)
+        if(index == -1)return 
+        viewer.scene.monitors.splice(index,1)
+        
+        viewer.removeEventListener("viewerResize", this.events.setSize ) 
+        viewer.removeEventListener("update", this.events.update ) 
+         
+        transitions.cancelById('cameraHighlight_' + this.sid)
+        /* this.tag.geometry.dispose()
+        this.tag.material.dispose()
+        this.cylinder.line.geometry.dispose()
+        this.cylinder.line.material.dispose()
+        this.cylinder.geometry.dispose() */
+        viewer.removeModel(this)
+        this.normalMat.dispose()
+        this.videoMat.dispose()
+
+        
+        this.titleLabel.dispose()
+        
+        this.hlsVideoPlayer && this.hlsVideoPlayer.destroy()
+        
+        
+    
+    }
+    
+    
+    highlight(state) {//相机hover高亮
+        if (this.hightlighted == state) return
+        this.hightlighted = state
+
+        transitions.cancelById('cameraHighlight_' + this.sid)
+
+        transitions.start(lerp.color(this.cameraModel.material.color, state ? hoverColor : normalColor), 100, null, null, null, null, 'cameraHighlight_' + this.sid)
+    }
+    
+    
+ 
+ 
+ 
+ 
+    updateAspect() {
+        //更新aspect且修改geometry
+        let aspect = viewer.mainViewport.camera.aspect
+        if (aspect != this.aspect) {
+            this.aspect = aspect
+            this.updateMesh()
+        }
+    }
+
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+    updateMesh() { 
+        console.log('updateMesh')
+        let nearHeight, nearWidth, farHeight, farWidth
+        nearHeight = Math.tan(THREE.MathUtils.degToRad(this.fov / 2)) * this.cylinderNear
+        nearWidth = nearHeight * this.aspect        //根据canvas比例调整视频面的比例,保持和canvas一致。
+        farHeight = Math.tan(THREE.MathUtils.degToRad(this.fov / 2)) * this.cylinderFar
+        farWidth = farHeight * this.aspect
+       
+        // 投射体(底面以外)
+        let vertices = [],
+            bottomVertices = []
+        vertices.push(-nearWidth, nearHeight, -this.cylinderNear)
+        vertices.push(nearWidth, nearHeight, -this.cylinderNear)
+        vertices.push(nearWidth, -nearHeight, -this.cylinderNear)
+        vertices.push(-nearWidth, -nearHeight, -this.cylinderNear)
+        bottomVertices.push(-farWidth, farHeight, -this.cylinderFar)
+        bottomVertices.push(farWidth, farHeight, -this.cylinderFar)
+        bottomVertices.push(farWidth, -farHeight, -this.cylinderFar)
+        bottomVertices.push(-farWidth, -farHeight, -this.cylinderFar)
+        vertices = vertices.concat(bottomVertices)
+        let firstBuild = !this.cylinder
+        if (firstBuild) {
+            let uvs = []
+            uvs.push(0, 1, 1, 1, 1, 0, 0, 0)
+            uvs.push(0, 1, 1, 1, 1, 0, 0, 0)
+
+            let indexs = []
+            indexs.push(0, 1, 3, 2, 3, 1)
+            indexs.push(0, 1, 4, 5, 4, 1)
+            indexs.push(1, 2, 5, 6, 5, 2)
+            indexs.push(2, 3, 6, 7, 6, 3)
+            indexs.push(3, 0, 7, 4, 7, 0)
+            // indexs.push(4, 7, 5, 6, 5, 7)
+            let cylinderGeo = new THREE.BufferGeometry()
+            cylinderGeo.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))
+            cylinderGeo.setIndex(new THREE.BufferAttribute(new Uint16Array(indexs), 1))
+            this.cylinder = new THREE.Mesh(cylinderGeo, this.normalMat)
+            //if (this.control.player.$app.config.mobile) this.cylinder.rotateZ(-Math.PI / 2)       //用户自己旋转屏幕吧
+            this.obj3d.add(this.cylinder)
+        }
+        this.cylinder.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3))
+
+        // 投射体底面 (视频)
+        const bottomGeo = new THREE.PlaneGeometry(farWidth * 2, farHeight * 2)
+        if (firstBuild) {
+            this.cylinder.bottom = new THREE.Mesh(bottomGeo, this.normalMat)
+            this.cylinder.add(this.cylinder.bottom)
+        } else {
+            this.cylinder.bottom.geometry.dispose()
+            this.cylinder.bottom.geometry = bottomGeo
+        }
+        this.cylinder.bottom.position.set(0, 0, this.cylinderNear - this.cylinderFar)
+
+        // 投射体线框
+        if (firstBuild) {
+            this.cylinder.line = new THREE.LineSegments(new THREE.EdgesGeometry(this.cylinder.geometry), new THREE.LineBasicMaterial({ color: 0xffffff, opacity: 0.6, transparent: true }))
+            this.cylinder.add(this.cylinder.line)
+        } else {
+            this.cylinder.line.geometry.dispose()
+            this.cylinder.line.geometry = new THREE.EdgesGeometry(this.cylinder.geometry)
+        }
+    }
+
+    // 横滚角
+    setRoll(angle) {
+        this.roll = angle % 360
+        this.obj3d.quaternion.setFromAxisAngle(vec1.set(0, 0, -1), THREE.MathUtils.degToRad(angle))
+    }
+ 
+ 
+    get yaw() {
+        // 左右转角
+        let lookAt = vec1.copy(this.cylinder.bottom.position).applyQuaternion(this.quaternion)
+        let lookAtXZ = lookAt.setY(0)
+        let frontDir = vec2.set(0, 0, 1)
+        let frontCross = vec3.set(1, 0, 0)
+        let angle = (THREE.MathUtils.radToDeg(lookAtXZ.angleTo(frontDir) * Math.sign(lookAtXZ.dot(frontCross))) + 180) % 360
+        if (angle > 180) angle = angle - 360
+
+        return angle
+    }
+
+    set yaw(yaw) {
+        let pitch = this.pitch // 要先计算pitch,防止窜数据
+        let upDir = vec1.set(0, 1, 0)
+        let rightDir = vec2.set(1, 0, 0)
+        let yawQuat = quat1.setFromAxisAngle(upDir, THREE.MathUtils.degToRad(yaw))
+        let pitchQuat = quat2.setFromAxisAngle(rightDir, THREE.MathUtils.degToRad(pitch))
+        this.quaternion.multiplyQuaternions(yawQuat, pitchQuat)
+
+        //this.updateTarget()
+    }
+
+    get pitch() {
+        // 上下转角
+        let lookAt = vec1.copy(this.cylinder.bottom.position).applyQuaternion(this.quaternion)
+        let projectVec = vec2.copy(lookAt).projectOnPlane(vec3.set(0, 1, 0))
+        let pitch = THREE.MathUtils.radToDeg(lookAt.angleTo(projectVec) * Math.sign(lookAt.y)) % 180
+        if (pitch > 90) pitch = 90 - pitch
+
+        return pitch
+    }
+
+    set pitch(pitch) {
+        pitch = Math.min(Math.max(pitch, -89.9), 89.9) // 防止万向锁
+
+        let yaw = this.yaw < 0 ? this.yaw + 360 : this.yaw
+        let upDir = vec1.set(0, 1, 0)
+        let rightDir = vec2.set(1, 0, 0) //.applyQuaternion(this.quaternion)
+        let yawQuat = quat1.setFromAxisAngle(upDir, THREE.MathUtils.degToRad(yaw))
+        let pitchQuat = quat2.setFromAxisAngle(rightDir, THREE.MathUtils.degToRad(pitch))
+        this.quaternion.multiplyQuaternions(yawQuat, pitchQuat)
+
+        //this.updateTarget()
+    }
+    
+}
+
+
+
+function warnHls(){
+    if (!window.Hls) {  
+        console.error('没加载Hls.js文件!')  
+    } 
+    if (!window.Hls?.isSupported()) { //在融合页面导入hls文件
+        console.error('Hls is not Supported,  部分监控视频可能不支持') //iphoneX不支持 小米Civi 1S支持。
+    } 
+}
+
+
+
+/* 
+
+
+viewer.addMonitor(undefined, viewer.objs.children[0])
+viewer.scene.monitors[0].leave() 
+
+ */
+
+
+
+

+ 146 - 0
src/custom/objects/Overlay.js

@@ -0,0 +1,146 @@
+
+
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import BasicMaterial from '../materials/BasicMaterial.js'  
+
+const planeGeometry = new THREE.PlaneGeometry(1,1)
+const height4dkk = 0.5 //4dkk的planeGeometry高度
+let texLoader = new THREE.TextureLoader()  
+import FlvVideoPlayerBase from '../utils/media/FlvVideoPlayerBase'
+import H5VideoPlayerBase from '../utils/media/H5VideoPlayerBase'
+
+
+const videoPlayer = Potree.browser.nonsupportH5Video ? new FlvVideoPlayerBase() : new H5VideoPlayerBase()
+
+
+export default class Overlay extends THREE.Object3D {
+    constructor(data){
+        super()
+        this.data = data
+        
+        
+        var plane = new THREE.Mesh(
+            planeGeometry,
+            new BasicMaterial({ 
+                color: '#00c8af',
+                opacity: 0.2,
+                transparent: !0,
+                polygonOffset: true, //是否开启多边形偏移		//ie不开启时blank也不会闪烁
+                polygonOffsetFactor: -0.9, //多边形偏移因子
+                polygonOffsetUnits: -4.0, //多边形偏移单位
+            })
+        )
+        this.add(plane)
+        this.plane = plane
+        
+        this.setContent(data.mediaType, data.url, data.isNew)
+        //this.setScale(data.width, data.height)
+        this.scale.set(data.width||1, data.height||1,  1) //需要把width转化一下吗,还是后端转化?
+        this.addEventListener('dispose',this.dispose.bind(this))
+    } 
+    
+    
+    setContent(type, src, autoSize){
+        this.mediaType = type
+        let loadDone = ()=>{
+            autoSize && this.autoSize()
+            this.plane.material.uniforms.color.value = new THREE.Color('#fff')
+            this.plane.material.opacity = 1
+        }
+        if(type == 'photo'){
+            this.plane.material.map = texLoader.load(src, loadDone)
+                
+           
+            
+        }else{
+            
+            var video = videoPlayer.getVideo(src,this)
+            this.addEventListener('loadedmetadata', loadDone)
+            
+            /* var video = $(`<video  controls="controls" loop   x5-playsinline="" webkit-playsinline="true" playsinline="true" controlslist="nodownload"  preload="meta" ></video>`)[0]
+            video.setAttribute("crossOrigin", 'Anonymous')//要在src设置好前解决跨域
+            video.src = src       
+            video.onloadeddata = loadDone */
+            video.play()
+            this.plane.material.map = new THREE.VideoTexture(video)
+            Potree.Common.mobileAutoPlay(video, ()=>{
+                if(!this.disposed){
+                    video.play()
+                } 
+            })
+        }
+    }
+    
+    
+    autoSize(){//根据素材改变长宽
+        let size = this.getMediaSize()
+        if(size.x > 0 && size.y > 0){
+            let area = this.plane.scale.x * this.plane.scale.y 
+            let r = Math.sqrt(area / (size.x * size.y))
+            this.scale.x = size.x * r
+            this.scale.y = size.y * r
+        }
+        this.dispatchEvent('scale_changed') 
+    }
+    
+    getMediaSize(){
+        let size = new THREE.Vector2;
+        let media = this.plane.material.map.image
+        if(media){
+            if(this.mediaType == 'photo'){
+                size.x = media.width;
+                size.y = media.height; 
+            }else{
+                size.x = media.videoWidth 
+                size.y = media.videoHeight  
+            }
+        }
+        return size
+    }
+    
+    
+    
+    dispose(){
+        //geo和mat已经在mergeEditor里删了,这要删一下video啥的吗?
+        if(this.mediaType == 'video'){
+            let media = this.plane.material.map.image
+            media?.pause()
+            this.disposed = true
+        }
+    }
+    /* save(){ //不在这保存,因为当做模型保存的,只需要比模型多一个url、type的数据
+                也是加入到viewer.objs里
+         let info = {
+            "media": [
+                this.mediaType
+            ],
+            "reverse": false,
+            "limitToOnlyPano": false,
+            "url": "WqXhX412472.png",
+            "sid": "WqXhX412472",
+            "depth": 0,
+            "pos": [
+                0.8304,
+                0.8788,
+                1.2941
+            ],
+            "createTime": 1740641260916,
+            "width": 0.7926,
+            "frameType": "wall_1",
+            "qua": [
+                0,
+                1,
+                0,
+                0
+            ],
+            "poster": "WqXhX412472.png",
+            "floorIndex": 0,
+            "height": 0.6309
+        }
+        return info
+        
+    } */
+}
+
+
+ 

+ 4 - 36
src/custom/objects/Tag.js

@@ -41,17 +41,17 @@ const Vectors = {
 
 
 
-class Tag extends THREE.Object3D{
+class Tag extends THREE.Shim.FollowRootObject{
     constructor(o){
         
-        super()
+        super(o.root)
         
         this.title = o.title 
         this.fontsize = o.fontsize
         this.lineLength = o.lineLength != void 0 ? o.lineLength : defaultLineLength
         this.position.copy(o.position)
         this.normal = o.normal != void 0 ? o.normal : new THREE.Vector3(0,0, 1)
-        this.root = o.root
+         
         this.dragEnable = true 
         this.build(o)
         this.bindEvent()
@@ -339,39 +339,8 @@ class Tag extends THREE.Object3D{
     }
     
     
-    updateMatrixWorld(force){ //重写,只为了将root当做parent
-        
-        
-        this.scale.set(1/this.root.scale.x, 1/this.root.scale.y, 1/this.root.scale.z ) //中和模型缩放。无论模型缩放如何都不能改tag大小
-  
-        this.updateMatrix() 
-        this.matrixWorld.multiplyMatrices( this.root.matrixWorld ,   this.matrix );
-         
-        const children = this.children;
-        for ( let i = 0, l = children.length; i < l; i ++ ) {
-            children[ i ].updateMatrixWorld( force );
-        }  
-    }
     
 
-    updateWorldMatrix( updateParents, updateChildren ) {//重写,只为了将root当做parent
- 
-        if ( updateParents === true && this.root !== null ) {
-            this.root.updateWorldMatrix( true, false );
-        }
-
-        if ( this.matrixAutoUpdate ) this.updateMatrix();
-        this.matrixWorld.multiplyMatrices( this.root.matrixWorld, this.matrix );
-
-        if ( updateChildren === true ) {
-            const children = this.children;
-            for ( let i = 0, l = children.length; i < l; i ++ ) {
-                children[ i ].updateWorldMatrix( false, true );
-            }
-        }
-
-    } 
-
     
     dispose(){
         this.parent.remove(this);
@@ -383,8 +352,7 @@ class Tag extends THREE.Object3D{
      
     
 }
-
-
+ 
 
 
 export default Tag

+ 20 - 16
src/custom/objects/tool/Path.js

@@ -147,6 +147,7 @@ export class Path extends ctrlPolygon{
         this.hoverStates = {}
         this.selectStates = {}
         this.setFadeFar(null)
+        this.geoPoints = []
         {
             let group = new THREE.Object3D;     group.name = 'titleGroup'
             this.titleLabel = new TextSprite(Object.assign({}, depthProps,{
@@ -203,8 +204,8 @@ export class Path extends ctrlPolygon{
         }
         
         {//和measure不同的是它的边是连在一起的一整条
-            this.edge = new THREE.Mesh(voidGeometry,  new DepthBasicMaterial( $.extend({}, depthProps,{color: this.pathColor ,  /* opacity: 0.6,  */side:2,/*  transparent:true,  */fadeFar: this.fadeFar})))
-            //new THREE.MeshBasicMaterial({depthWrite:false, transparent:true, color:this.pathColor || '#fff', opacity:0.5, side:2}))
+            this.edge = new THREE.Mesh(voidGeometry,  new DepthBasicMaterial( $.extend({}, depthProps,{color: this.color ,  /* opacity: 0.6,  */side:2,/*  transparent:true,  */fadeFar: this.fadeFar})))
+            //new THREE.MeshBasicMaterial({depthWrite:false, transparent:true, color:this.color || '#fff', opacity:0.5, side:2}))
             this.edge.material.defines.mapOverlay = true
             this.add(this.edge)
             this.edge.renderOrder = Potree.config.renderOrders.path.edge,  //和tag的一样,但为何遮不住它?好在一般底下有一层地面能遮住
@@ -281,7 +282,7 @@ export class Path extends ctrlPolygon{
         }) 
 
         this.setPathWidth(prop.width || 0.2)
-        this.setPathColor(prop.color || '#fff')
+        this.setColor(prop.color || '#fff')
         
         
         this.setEditEnable(true)
@@ -448,10 +449,13 @@ export class Path extends ctrlPolygon{
         this.hideArrowUntilUpdate()
         
     }
-    setPathColor(color){
-        if(this.pathColor == color)return
-        this.pathColor = color  
+    setColor(color){
+        if(this.color == color)return
+        this.color = color  
+        let c = new THREE.Color().set(this.color) 
+        this.titleLabel.setTextColor({r: c.r*255, g:c.g*255, b:c.b*255, a:0.5})
         this.updateSelectStyle()  //apply color
+        
         viewer.dispatchEvent('content_changed')
     }
     
@@ -666,7 +670,7 @@ export class Path extends ctrlPolygon{
 
     updateEndCaps(){
         let len = this.points.length
-        let pts = this.geoPoints || this.points
+        let pts = this.geoPoints.length>0 ? this.geoPoints : this.points
         let len2 = pts.length
         
         
@@ -816,11 +820,11 @@ export class Path extends ctrlPolygon{
         state == 'click' && (this.selectStates.click = true, byList||this.dispatchEvent({type:'chose', state:true}) )
         state == 'unclick' && (this.selectStates.click = false, byList||this.dispatchEvent({type:'chose', state:false}) ) 
          
-        /* this.arrows?.material.color.set(this.selectStates.click ? '#ffffff' : this.pathColor);
+        /* this.arrows?.material.color.set(this.selectStates.click ? '#ffffff' : this.color);
         
         ([this.edge, this.endCaps[0].children[0]].forEach(e=>{
             e.material.opacity = this.selectStates.click ? 1 : this.selectStates.hover ? 0.8 : 0.6
-            e.material.color.set(this.selectStates.click ? '#00C8AF' : this.pathColor)
+            e.material.color.set(this.selectStates.click ? '#00C8AF' : this.color)
         })) */
         this.updateSelectStyle() 
         
@@ -838,19 +842,19 @@ export class Path extends ctrlPolygon{
     updateSelectStyle(){
         
         
-        let c = new THREE.Color().set(this.pathColor).getHSL({ h: 0, s: 0, l: 0 })
+        let c = new THREE.Color().set(this.color).getHSL({ h: 0, s: 0, l: 0 })
         
-        let pathColor, arrowColor  
+        let color, arrowColor  
         if(this.selectStates.click){
-            pathColor = '#00C8AF'
+            color = '#00C8AF'
             arrowColor = '#ffffff'
         }else if(this.selectStates.hover){  
-            pathColor = new THREE.Color().setHSL(c.h, c.s, c.l - 0.1 )   
+            color = new THREE.Color().setHSL(c.h, c.s, c.l - 0.1 )   
             arrowColor = new THREE.Color().setHSL(c.h, c.s, c.l >= 0.4 ? c.l - 0.3 : c.l + 0.3 )  
         }else{
             arrowColor = new THREE.Color().setHSL(c.h, c.s, c.l >= 0.4 ? c.l - 0.3 : c.l + 0.3 )  
             
-            pathColor = this.pathColor
+            color = this.color
         }
         
         if(this.arrows){
@@ -859,7 +863,7 @@ export class Path extends ctrlPolygon{
             this.edge.material.uniforms.mapColor.value.set(arrowColor)
         }
         ([this.edge, this.endCaps[0].children[0]].forEach(e=>{ 
-            e.material.color.set(pathColor)
+            e.material.color.set(color)
         }))
     }
     
@@ -980,7 +984,7 @@ export class Path extends ctrlPolygon{
      
                         
                     let material = new DepthBasicMaterial(Object.assign({}, depthProps, {
-                         map,  transparent:true, side:2, fadeFar:this.fadeFar , color:this.selectStates.click ? '#ffffff' : this.pathColor   
+                         map,  transparent:true, side:2, fadeFar:this.fadeFar , color:this.selectStates.click ? '#ffffff' : this.color   
                     }))  
                     //let material = new THREE.MeshBasicMaterial({map )})
                     this.arrows = new THREE.InstancedMesh(planeGeo, material, arrowCountMax);  //会自动向shader添加define和instanceMatrix

+ 21 - 14
src/custom/objects/tool/TransformControls.js

@@ -129,8 +129,8 @@ var TransformControls = function ( camera, domElement, options ) {
 		domElement.addEventListener( "touchcancel", onPointerUp, false );
 		domElement.addEventListener( "touchleave", onPointerUp, false );
 
-        let drag = ()=>{
-            if(this.dragging){
+        let drag = (e)=>{
+            if(this.dragging && e.button == 0){
                 this.pointerMove() 
                 return {stopContinue:true}
             }
@@ -700,14 +700,17 @@ var TransformControls = function ( camera, domElement, options ) {
                         let v1 = this.rotateStart.v1
                         
                         rotationAngle = math.getAngle(  v1, v2, rotationAxis_) 
-                        //console.log('rot', rotationAngle,  center.toArray()) 
+                        //console.log('rot', rotationAngle, /*  center.toArray(), */ v1, v2) 
                         
                         if (Number.isNaN(rotationAngle)) {
                             return;
                         }
  
                         this.rotateStart.v1 = v2
-                    } 
+                    }else{
+                        rotationAngle = 0
+                        //console.log('!I', rotationAngle)
+                    }
                 } 
                 
             }else{
@@ -967,7 +970,7 @@ var TransformControlsGizmo = function (options) {
 	this.type = 'TransformControlsGizmo';
 
 	// shared materials
-    this.hideAxis = {}
+    this.hideAxis = {}, this.showAxis = {}
 	var gizmoMaterial = new THREE.MeshBasicMaterial( {
 		depthTest: false,
 		depthWrite: false,
@@ -1038,7 +1041,8 @@ var TransformControlsGizmo = function (options) {
 	matLineYellow.color.set( 0xffff00 );
 
 	var matLineGray = gizmoLineMaterial.clone();
-	matLineGray.color.set( 0x787878 );
+	matLineGray.color.set( 0x999999 ); 
+
 
 	var matLineYellowTransparent = matLineYellow.clone();
 	matLineYellowTransparent.opacity = 0.25;
@@ -1191,7 +1195,7 @@ var TransformControlsGizmo = function (options) {
 			[ new THREE.Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ]]
 		]
 	};
-
+ 
 	var helperRotate = {
 		AXIS: [
 			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
@@ -1199,17 +1203,17 @@ var TransformControlsGizmo = function (options) {
 	};
 
 	var pickerRotate = {
-		X: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
+		X: [    //xzw 由原先全弧度改为一半弧度(Math.PI),否则在背面也会hover到
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.06, 4, 24, Math.PI ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
 		],
 		Y: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.06, 4, 24, Math.PI ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
 		],
 		Z: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.06, 4, 24, Math.PI ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
 		],
 		E: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ]
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1.25, 0.06, 2, 24 ), matInvisible ) ]
 		],
 		XYZE: [
 			[ new THREE.Mesh( new THREE.SphereBufferGeometry( 0.7, 10, 8 ), matInvisible ) ]
@@ -1394,6 +1398,9 @@ var TransformControlsGizmo = function (options) {
 	this.picker[ "rotate" ].visible = false;
 	this.picker[ "scale" ].visible = false;
 
+    
+    options.dontHideWhenFaceCamera || Potree.Utils.updateVisible(this.gizmo[ "rotate" ].children.find(e=>e.name == 'XYZE'), 'decorateCircle', true, 1, 'add')//xzw add 没有环的话不好看。但不要picker
+    
 	// updateMatrixWorld will update transformations and appearance of individual handles
 
 	this.updateMatrixWorld = function () { //TransformControlsGizmo
@@ -1424,9 +1431,9 @@ var TransformControlsGizmo = function (options) {
 
 			var handle = handles[ i ];
             //add
-            if(this.hideAxis[this.mode] && this.hideAxis[this.mode].some(e=>handle.name.includes(e.toUpperCase()))){
+            if(this.hideAxis[this.mode]?.some(e=>handle.name.includes(e.toUpperCase())) && !this.showAxis[this.mode]?.some(e=>handle.name == e.toUpperCase() )){
                 Potree.Utils.updateVisible(handle, 'hidden', false)
-                continue 
+                if(!handle.visible)  continue  //XYZE还是要作为装饰显示
             }   
             
             let visible = true

+ 4 - 3
src/custom/potree.shim.js

@@ -855,7 +855,7 @@ Utils.addOrRemoveDefine = function(material, defineName, type, value=''){
         if(defines[defineName] != void 0 && defines[defineName] == value)return
         defines[defineName] = value
     }else{
-        if(defines[defineName] != void 0)return;
+        if(defines[defineName] == void 0)return;
         delete defines[defineName] 
     }
     material.needsUpdate = true;
@@ -873,7 +873,7 @@ Utils.makeTexDontResize = function(map){//避免贴图因非2的次方而缩小
 } 
 
 
-Utils.updateVisible = function(object, reason, ifShow, level=0, type){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的
+Utils.updateVisible = function(object, reason, ifShow, level=0, type, needRender=true){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的
     if(!object.unvisibleReasons) object.unvisibleReasons = []; //如果length>0代表不可见
     if(!object.visibleReasons) object.visibleReasons = []; //在同级时,优先可见
     
@@ -897,6 +897,7 @@ Utils.updateVisible = function(object, reason, ifShow, level=0, type){//当所
                 visible: shouldVisi,
                 reason,
             }) 
+            needRender && viewer.dispatchEvent('content_changed')
         }
         
         
@@ -2079,7 +2080,7 @@ PointCloudOctreeGeometryNode.prototype.loadHierachyThenPoints = function(pointcl
 PointCloudOctreeGeometryNode.prototype.loadPoints = function(){
     let name = this.name
     this.pcoGeometry.loader.load(this, ()=>{//callback
-        viewer.dispatchEvent('pointcloud_changed')  
+        viewer.dispatchEvent('pointcloud_changed' )  
         //console.log('loadPoints success   ', name) 
     });
 } 

+ 19 - 17
src/custom/settings.js

@@ -260,34 +260,35 @@ const config = {//配置参数   不可修改
     skyboxBgWidth : 100 , 
     
     renderLayers:{//渲染层,方便分批渲染管理,替代scene的创建。数字不代表顺序。(数字不能太大)
-        bg: 20, 
-        bg2: 21,
+        sceneObjects:0,//default
+                
         
         skybox: 1,
-        pointcloud: 11,
-        sceneObjects:0,//default
-        model : 2,   
-        light: 15, 
+        pointcloud: 2, 
+        model : 3,  
+        monitor:4,        
+        light: 5, 
         //measure:4, 
         //tags:5,        
         magnifier:6, 
-        magnifierContent:16,
-        volume:7,
-        transformationTool:8,
+        magnifierContent:7,
+        volume:8,
+        transformationTool:9,
        
-        map:9,
-        mapObjects:10,//default
+        map:10,
+        mapObjects:11,//default
         
          
-        bothMapAndScene: 3,
-        
-        onlyMapVisi:12,//只能mapViewer可见
-        mapUnvisi:13,//只有mapViewer不可见
-        sideVisi:14,//只有侧面可见
+        bothMapAndScene: 12,
         
+        onlyMapVisi:13,//只能mapViewer可见
+        mapUnvisi:14,//只有mapViewer不可见
+        sideVisi:15,//只有侧面可见
         
+        bg: 16, 
+        bg2: 17,
         layer1: 18,// 备用1
-        layer2: 17,// 备用2
+        layer2: 19,// 备用2
     },
     
     renderOrders:{ //会影响到绘制、pick时的顺序。
@@ -545,6 +546,7 @@ let settings = {//设置   可修改
     
     //fastTran: isTest
     pathSmooth : true,// window.location.href.includes('192.168.0.59')  //true //smooth曲线, 非折线
+    maxClipFadeTime: 0.6
 }
  
 

+ 25 - 9
src/custom/start.js

@@ -326,6 +326,7 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
                     pointcloud.datasetData = dataset
                     pointcloud.dataset_id = dataset.id;//供漫游点找到属于的dataset点云
                     pointcloud.hasDepthTex = Potree.settings.useDepthTex && (!!dataset.has_depth  ||  Potree.settings.isLocalhost && Potree.settings.number == 'SS-t-7DUfWAUZ3V') //test   
+                    pointcloud.hasTempTex = !!dataset.has_ir
                     material.minSize =  config.minSize
                     material.maxSize =  config.maxSize   
                     material.pointSizeType =/*   Potree.settings.isOfficial ?   */ config.pointSizeType    /* : 'ADAPTIVE'    */   //Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED
@@ -352,15 +353,30 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
                         //console.log('loadPanos',dataset.sceneCode, dataset.id, data)
                         viewer.images360.addPanoData(data, pointcloud)
                         panosLoaded ++; 
-                        if(panosLoaded == datasetLength){
-                            
-                            Potree.loadImgVersion((e={})=>{ 
-                                Potree.settings.panoVersion = e.imgVersion//全景图被替换后
-                                panosLoadDone() 
+                        let f = ()=>{
+                            if(panosLoaded == datasetLength){ 
+                                Potree.loadImgVersion((e={})=>{ 
+                                    Potree.settings.panoVersion = e.imgVersion//全景图被替换后
+                                    panosLoadDone() 
+                                })  
+                            } 
+                        }
+                        if(dataset.has_ir){//红外热成像漫游点数据
+                            let panoIrUrl = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${pointcloud.sceneCode}/imagemap/types.json`
+                            Potree.loadFile(panoIrUrl,null,(e)=>{
+                                e.imagemap_types.forEach(o=>{
+                                    o.uuids.forEach(uuid=>{
+                                        //if(o.property == 'temp')return //只要ir
+                                        let pano = viewer.images360.getPano(`${pointcloud.dataset_id}|${uuid}`,'sid') 
+                                        pano['has_'+o.property] = true
+                                    }) 
+                                })
+                                f()                                
                             })
-                            
-                            
-                        } 
+                        }else{
+                            f()
+                        }
+                        
                     })
                 })
             }else{
@@ -1009,7 +1025,7 @@ export function mergeEditStart(dom, mapDom){
                     updateBound()
                     model.dispatchEvent('transformChanged')  
                 }
-                model.addEventListener('position_changed', ()=>{
+                model.addEventListener('position_changed', ()=>{//要先发送position_changed再其他
                     updateBound()
                     MergeEditor.getBoundCenter(model);//更新boundcenter
             

+ 561 - 4
src/custom/three.shim.js

@@ -220,7 +220,18 @@ THREE.EventDispatcher.prototype.traverse = function(callback){
     }
 }
 
-
+THREE.Quaternion.prototype.toObject = function(e){
+    return {
+        x: this.x, y:this.y, z:this.z, w:this.w
+    }
+}
+THREE.Euler.prototype.toObject = function(addOrder){
+    let euler = {
+        x: this.x, y:this.y, z:this.z
+    }
+    addOrder && (euler.order = this.order)
+    return euler
+}
 
 THREE.Object3D.prototype.traverse = function ( callback, lastResult ) {
 
@@ -236,7 +247,65 @@ THREE.Object3D.prototype.traverse = function ( callback, lastResult ) {
     } 
 }   
 
+THREE.Shim.FollowRootObject = function(root){
+    THREE.Object3D.call(this)
+    this.root = root
+}  
+ 
+THREE.Shim.FollowRootObject.prototype  = Object.assign( Object.create(THREE.Object3D.prototype), {  
+    constructor: THREE.Shim.FollowRootObject,     
+  
+    updateMatrixWorld(force){ //重写,只为了将root当做parent
+ 
+        this.scale.set(1/this.root.scale.x, 1/this.root.scale.y, 1/this.root.scale.z ) //中和模型缩放。无论模型缩放如何都不能改tag大小
+  
+        this.updateMatrix() 
+        this.matrixWorld.multiplyMatrices( this.root.matrixWorld ,   this.matrix );
+         
+        const children = this.children;
+        for ( let i = 0, l = children.length; i < l; i ++ ) {
+            children[ i ].updateMatrixWorld( force );
+        }  
+    },
+
+    
+    updateWorldMatrix( updateParents, updateChildren ) {//重写,只为了将root当做parent
+ 
+        if ( updateParents === true && this.root !== null ) {
+            this.root.updateWorldMatrix( true, false );
+        }
+
+        if ( this.matrixAutoUpdate ) this.updateMatrix();
+        this.matrixWorld.multiplyMatrices( this.root.matrixWorld, this.matrix );
+
+        if ( updateChildren === true ) {
+            const children = this.children;
+            for ( let i = 0, l = children.length; i < l; i ++ ) {
+                children[ i ].updateWorldMatrix( false, true );
+            }
+        }
+
+    }  
+}) 
+
+        
+        
+        
+       
 
+    
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
 
 THREE.Material.prototype.setValues = function ( values ) {
 
@@ -374,9 +443,16 @@ THREE.Object3D.prototype.realVisible = function(){
     }   
     return v                                
 }
-
-
-
+THREE.Object3D.prototype.isChildOf = function(rootParent ){//是否后代, 包括自身
+    let parent = this 
+    while(parent){
+        if(parent == rootParent){
+            return true
+            break
+        } 
+        parent = parent.parent 
+    }
+}
 THREE.Curve.prototype.getPointAt = function ( u, optionalTarget ) { 
     const t = this.getUtoTmapping( u );
     this.UtoTMapArr && this.UtoTMapArr.push(t) //add
@@ -384,3 +460,484 @@ THREE.Curve.prototype.getPointAt = function ( u, optionalTarget ) {
 
 } 
 
+
+const _inverseMatrix$3 = /*@__PURE__*/ new THREE.Matrix4();
+const _ray$3 = /*@__PURE__*/ new THREE.Ray();
+const _sphere$6 = /*@__PURE__*/ new THREE.Sphere();
+const _sphereHitAt = /*@__PURE__*/ new THREE.Vector3();
+
+const _vA$1 = /*@__PURE__*/ new THREE.Vector3();
+const _vB$1 = /*@__PURE__*/ new THREE.Vector3();
+const _vC$1 = /*@__PURE__*/ new THREE.Vector3();
+
+const _tempA = /*@__PURE__*/ new THREE.Vector3();
+const _morphA = /*@__PURE__*/ new THREE.Vector3();
+
+const _intersectionPoint = /*@__PURE__*/ new THREE.Vector3();
+const _intersectionPointWorld = /*@__PURE__*/ new THREE.Vector3();
+
+
+const _basePosition = /*@__PURE__*/ new THREE.Vector3();
+
+const _skinIndex = /*@__PURE__*/ new THREE.Vector4();
+const _skinWeight = /*@__PURE__*/ new THREE.Vector4();
+
+const _vector3 = /*@__PURE__*/ new THREE.Vector3();
+const _matrix4 = /*@__PURE__*/ new THREE.Matrix4();
+const _vertex = /*@__PURE__*/ new THREE.Vector3();
+const _sphere$5 = /*@__PURE__*/ new THREE.Sphere();
+const _inverseMatrix$2 = /*@__PURE__*/ new THREE.Matrix4();
+const _ray$2 = /*@__PURE__*/ new THREE.Ray();
+
+
+
+
+THREE.SkinnedMesh.prototype.computeBoundingSphere = function(){
+
+    const geometry = this.geometry;
+
+    if ( !this.boundingSphere ) {
+
+        this.boundingSphere = new THREE.Sphere();
+
+    }
+
+    this.boundingSphere.makeEmpty();
+
+    const positionAttribute = geometry.getAttribute( 'position' );
+
+    for ( let i = 0; i < positionAttribute.count; i ++ ) {
+
+        this.getVertexPosition( i, _vertex );
+        this.boundingSphere.expandByPoint( _vertex );
+
+    }
+
+}
+THREE.SkinnedMesh.prototype.computeBoundingBox = function(){
+
+    const geometry = this.geometry;
+
+    if ( !this.boundingBox  ) {
+
+        this.boundingBox = new THREE.Box3();
+
+    }
+
+    this.boundingBox.makeEmpty();
+
+    const positionAttribute = geometry.getAttribute( 'position' );
+
+    for ( let i = 0; i < positionAttribute.count; i ++ ) {
+
+        this.getVertexPosition( i, _vertex );
+        this.boundingBox.expandByPoint( _vertex );
+
+    }
+
+}
+THREE.SkinnedMesh.prototype.raycast = function( raycaster, intersects ) {//根据r173重写,因模型大小跟着骨骼走 
+    const material = this.material;
+    const matrixWorld = this.matrixWorld;
+
+    if ( material === undefined ) return;
+
+    // test with bounding sphere in world space
+
+    if ( !this.boundingSphere ) this.computeBoundingSphere();
+
+    _sphere$5.copy( this.boundingSphere );
+    _sphere$5.applyMatrix4( matrixWorld );
+
+    if ( raycaster.ray.intersectsSphere( _sphere$5 ) === false ) return;
+
+    // convert ray to local space of skinned mesh
+
+    _inverseMatrix$2.copy( matrixWorld ).invert();
+    _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 );
+
+    // test with bounding box in local space
+
+    if ( this.boundingBox ) {
+
+        if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return;
+
+    }
+
+    // test for intersections with geometry
+
+    this._computeIntersections( raycaster, intersects, _ray$2 );
+}
+
+THREE.SkinnedMesh.prototype.getVertexPosition = function( index, target ) {
+
+    //super.getVertexPosition( index, target );
+    
+
+    const geometry = this.geometry;
+    const position = geometry.attributes.position;
+    const morphPosition = geometry.morphAttributes.position;
+    const morphTargetsRelative = geometry.morphTargetsRelative;
+
+    target.fromBufferAttribute( position, index );
+
+    const morphInfluences = this.morphTargetInfluences;
+
+    if ( morphPosition && morphInfluences ) {
+
+        _morphA.set( 0, 0, 0 );
+
+        for ( let i = 0, il = morphPosition.length; i < il; i ++ ) {
+
+            const influence = morphInfluences[ i ];
+            const morphAttribute = morphPosition[ i ];
+
+            if ( influence === 0 ) continue;
+
+            _tempA.fromBufferAttribute( morphAttribute, index );
+
+            if ( morphTargetsRelative ) {
+
+                _morphA.addScaledVector( _tempA, influence );
+
+            } else {
+
+                _morphA.addScaledVector( _tempA.sub( target ), influence );
+
+            }
+
+        }
+
+        target.add( _morphA );
+
+    }
+
+    
+    this.applyBoneTransform( index, target );
+
+    return target;
+
+}
+
+
+
+THREE.SkinnedMesh.prototype.applyBoneTransform = function( index, vector ) {
+
+    const skeleton = this.skeleton;
+    const geometry = this.geometry;
+
+    _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
+    _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
+
+    _basePosition.copy( vector ).applyMatrix4( this.bindMatrix );
+
+    vector.set( 0, 0, 0 );
+
+    for ( let i = 0; i < 4; i ++ ) {
+
+        const weight = _skinWeight.getComponent( i );
+
+        if ( weight !== 0 ) {
+
+            const boneIndex = _skinIndex.getComponent( i );
+
+            _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
+
+            vector.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight );
+
+        }
+
+    }
+
+    return vector.applyMatrix4( this.bindMatrixInverse );
+
+}
+
+ 
+THREE.SkinnedMesh.prototype._computeIntersections = function( raycaster, intersects, rayLocalSpace ) {
+
+    let intersection;
+
+    const geometry = this.geometry;
+    const material = this.material;
+
+    const index = geometry.index;
+    const position = geometry.attributes.position;
+    const uv = geometry.attributes.uv;
+    const uv1 = geometry.attributes.uv1;
+    const normal = geometry.attributes.normal;
+    const groups = geometry.groups;
+    const drawRange = geometry.drawRange;
+
+    if ( index !== null ) {
+
+        // indexed buffer geometry
+
+        if ( Array.isArray( material ) ) {
+
+            for ( let i = 0, il = groups.length; i < il; i ++ ) {
+
+                const group = groups[ i ];
+                const groupMaterial = material[ group.materialIndex ];
+
+                const start = Math.max( group.start, drawRange.start );
+                const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) );
+
+                for ( let j = start, jl = end; j < jl; j += 3 ) {
+
+                    const a = index.getX( j );
+                    const b = index.getX( j + 1 );
+                    const c = index.getX( j + 2 );
+
+                    intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c );
+
+                    if ( intersection ) {
+
+                        intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics
+                        intersection.face.materialIndex = group.materialIndex;
+                        intersects.push( intersection );
+
+                    }
+
+                }
+
+            }
+
+        } else {
+
+            const start = Math.max( 0, drawRange.start );
+            const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
+
+            for ( let i = start, il = end; i < il; i += 3 ) {
+
+                const a = index.getX( i );
+                const b = index.getX( i + 1 );
+                const c = index.getX( i + 2 );
+
+                intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c );
+
+                if ( intersection ) {
+
+                    intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
+                    intersects.push( intersection );
+
+                }
+
+            }
+
+        }
+
+    } else if ( position !== undefined ) {
+
+        // non-indexed buffer geometry
+
+        if ( Array.isArray( material ) ) {
+
+            for ( let i = 0, il = groups.length; i < il; i ++ ) {
+
+                const group = groups[ i ];
+                const groupMaterial = material[ group.materialIndex ];
+
+                const start = Math.max( group.start, drawRange.start );
+                const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) );
+
+                for ( let j = start, jl = end; j < jl; j += 3 ) {
+
+                    const a = j;
+                    const b = j + 1;
+                    const c = j + 2;
+
+                    intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c );
+
+                    if ( intersection ) {
+
+                        intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics
+                        intersection.face.materialIndex = group.materialIndex;
+                        intersects.push( intersection );
+
+                    }
+
+                }
+
+            }
+
+        } else {
+
+            const start = Math.max( 0, drawRange.start );
+            const end = Math.min( position.count, ( drawRange.start + drawRange.count ) );
+
+            for ( let i = start, il = end; i < il; i += 3 ) {
+
+                const a = i;
+                const b = i + 1;
+                const c = i + 2;
+
+                intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c );
+
+                if ( intersection ) {
+
+                    intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics
+                    intersects.push( intersection );
+
+                }
+
+            }
+
+        }
+
+    }
+
+}
+
+const _v40 = /*@__PURE__*/ new THREE.Vector4();
+const _v41 = /*@__PURE__*/ new THREE.Vector4();
+const _v42 = /*@__PURE__*/ new THREE.Vector4();
+function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) {
+
+	object.getVertexPosition( a, _vA$1 );
+	object.getVertexPosition( b, _vB$1 );
+	object.getVertexPosition( c, _vC$1 );
+
+	const intersection = checkIntersection$1( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint );
+
+	if ( intersection ) {
+
+		const barycoord = new THREE.Vector3();
+		THREE.Triangle.getBarycoord( _intersectionPoint, _vA$1, _vB$1, _vC$1, barycoord );
+
+		if ( uv ) {
+
+			intersection.uv = THREE.Triangle.getInterpolatedAttribute( uv, a, b, c, barycoord, new THREE.Vector2() );
+
+		}
+
+		if ( uv1 ) {
+
+			intersection.uv1 = THREE.Triangle.getInterpolatedAttribute( uv1, a, b, c, barycoord, new THREE.Vector2() );
+
+		}
+
+		if ( normal ) {
+
+			intersection.normal = THREE.Triangle.getInterpolatedAttribute( normal, a, b, c, barycoord, new THREE.Vector3() );
+
+			if ( intersection.normal.dot( ray.direction ) > 0 ) {
+
+				intersection.normal.multiplyScalar( - 1 );
+
+			}
+
+		}
+
+		const face = {
+			a: a,
+			b: b,
+			c: c,
+			normal: new THREE.Vector3(),
+			materialIndex: 0
+		};
+
+		THREE.Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal );
+
+		intersection.face = face;
+		intersection.barycoord = barycoord;
+
+	}
+
+	return intersection;
+
+}
+function checkIntersection$1( object, material, raycaster, ray, pA, pB, pC, point ) {
+
+	let intersect;
+
+	if ( material.side === THREE.BackSide ) {
+
+		intersect = ray.intersectTriangle( pC, pB, pA, true, point );
+
+	} else {
+
+		intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === THREE.FrontSide ), point );
+
+	}
+
+	if ( intersect === null ) return null;
+
+	_intersectionPointWorld.copy( point );
+	_intersectionPointWorld.applyMatrix4( object.matrixWorld );
+
+	const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld );
+
+	if ( distance < raycaster.near || distance > raycaster.far ) return null;
+
+	return {
+		distance: distance,
+		point: _intersectionPointWorld.clone(),
+		object: object
+	};
+
+}
+ 
+const _v1$6 = /*@__PURE__*/ new THREE.Vector3();
+const _v2$3 = /*@__PURE__*/ new THREE.Vector3();
+THREE.Sphere.prototype.expandByPoint = function( point ) {
+
+    if ( this.isEmpty() ) {
+
+        this.center.copy( point );
+
+        this.radius = 0;
+
+        return this;
+
+    }
+
+    _v1$6.subVectors( point, this.center );
+
+    const lengthSq = _v1$6.lengthSq();
+
+    if ( lengthSq > ( this.radius * this.radius ) ) {
+
+        // calculate the minimal sphere
+
+        const length = Math.sqrt( lengthSq );
+
+        const delta = ( length - this.radius ) * 0.5;
+
+        this.center.addScaledVector( _v1$6, delta / length );
+
+        this.radius += delta;
+
+    }
+
+    return this;
+
+}
+
+
+
+
+/* THREE.Triangle.prototype.getBarycoord( point, target ) {
+
+    return Triangle.getBarycoord( point, this.a, this.b, this.c, target );
+
+} */
+
+THREE.Triangle.getInterpolatedAttribute = function( attr, i1, i2, i3, barycoord, target ) {
+
+    _v40.setScalar( 0 );
+    _v41.setScalar( 0 );
+    _v42.setScalar( 0 );
+
+    _v40.fromBufferAttribute( attr, i1 );
+    _v41.fromBufferAttribute( attr, i2 );
+    _v42.fromBufferAttribute( attr, i3 );
+
+    target.setScalar( 0 );
+    target.addScaledVector( _v40, barycoord.x );
+    target.addScaledVector( _v41, barycoord.y );
+    target.addScaledVector( _v42, barycoord.z );
+
+    return target;
+
+}

+ 54 - 6
src/custom/utils/Common.js

@@ -2,9 +2,9 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import math from './math.js'
-
-
-//THREE.Vector2.name2 = 'Common'
+import '../../../libs/other/UPNG.js'
+ 
+ 
 
 var Common = {
      
@@ -265,10 +265,12 @@ var Common = {
     },
     
     
-    getNameFromURL(url){
+    getNameFromURL(url, removePostfix){
         if(!url)return ''
         let get = (e)=>{
-            return e.split('/').pop()
+            let a = e.split('/').pop()
+            if(removePostfix) a = a.split('.')[0]
+            return a
         }
         if(url instanceof Array){
             return url.map(e=>get(e))
@@ -680,8 +682,54 @@ var Common = {
          
         //console.log('成功替换为webgl2' )
         return {vs,fs}
-    }//three.js的shaderMaterial也有替换功能,搜 '#define gl_FragDepthEXT gl_FragDepth',
+    },//three.js的shaderMaterial也有替换功能,搜 '#define gl_FragDepthEXT gl_FragDepth',
     
+    
+    
+    load16bitPngTex(src,onLoad,onError, pixelFun){//单通道无符号16位的png (正常图片是32位一个像素)
+        const texture = new THREE.DataTexture
+        fetch(src)
+            .then(response => { 
+                if(!response.ok){
+                    console.log('loadFile失败' , src )
+                    return onError && onError()
+                } 
+                return response.arrayBuffer() 
+            }).then(arrayBuffer => { //文件数据
+                const png = UPNG.decode(arrayBuffer); //解析出像素数据
+                
+                let pixelCount = png.width * png.height
+                let data = new Uint8Array(pixelCount * 4)
+                
+                
+                let view = new DataView(data.buffer);
+                for(let i=0;i<pixelCount;i++){ //将uint16的只有两个byte的扩充到四个byte,因为贴图只支持4个byte的
+                    let r = png.data[i*2]
+                    let g = png.data[i*2+1]
+                    data[i*4+0] = g //反了
+                    data[i*4+1] = r 
+                    data[i*4+2] = data[i*4+3] = 255; //无用值填充
+                    
+                    if(pixelFun){
+                        pixelFun(view.getUint16(i*4,true), i , pixelCount) //原值
+                    }
+                    
+                }
+                texture.image.data = data
+                texture.image.width = png.width;
+                texture.image.height = png.height; 
+                texture.image.src = src 
+                
+                texture.needsUpdate = true;
+                 
+                onLoad && onLoad()
+        })
+        .catch(error => { 
+            onError && onError(error)
+        });
+        
+        return texture
+    }
 }  
 
 Potree.Common = Common

+ 5 - 1
src/custom/utils/math.js

@@ -658,7 +658,11 @@ var math = {
         return value
         
          
-    } 
+    },
+    
+    getKelvinFromCelsius(c){ //摄氏度->开尔文
+        return c + 273.1 //273.15   算法的值是整数然后除以10所以只保留一位小数
+    }
 };
 
 /* 

+ 259 - 72
src/custom/viewer/ViewerNew.js

@@ -17,9 +17,10 @@ import {TagTool} from "../objects/tool/TagTool.js";
 import Compass from "../objects/tool/Compass.js";
 import {FloorCompass} from "../objects/tool/FloorCompass.js";
 import AxisViewer from "../objects/tool/AxisViewer.js";
- 
+import Monitor from "../objects/Monitor.js";
     
 
+
 import {ExtendScene} from '../../viewer/ExtendScene.js' 
 import {transitions, easing, lerp} from '../utils/transitions.js' 
 import {Renderer} from "../../PotreeRendererNew.js"; 
@@ -77,7 +78,7 @@ import {ViewerBase} from "../viewer/viewerBase.js"
 
 
     
-   
+import {Gradients} from "../../materials/Gradients.js";   
 import SSAARenderPass from "../materials/postprocessing/SSAARenderPass.js"
 import EffectComposer from '../materials/postprocessing/EffectComposer.js'
 import {ShaderPass} from '../materials/postprocessing/ShaderPass.js'
@@ -135,7 +136,15 @@ export class Viewer extends ViewerBase{
         
         this.setLoaders()
         
-          
+        this.tempRange = {
+            ir:{min:Infinity, max:-Infinity},//红外
+            temp:{min:Infinity, max:-Infinity}//火灾
+        }
+        
+        /* if(Potree.settings.number == 'SS-t-g45LdhXGdYv'){
+            this.tempRange.temp = {min:473.1,   max:1273.1} 
+        } */
+        
         
         if(this.renderer.capabilities.isWebGL2){
             Potree.settings.isWebgl2 = true  //是否启用webgl2
@@ -155,8 +164,10 @@ export class Viewer extends ViewerBase{
             }else if(Potree.settings.editType == "merge"){
                 this.modules.MergeEditor = MergeEditor 
                 this.modules.CamAniEditor = CamAniEditor
+                this.modules.AnimationEditor = new AnimationEditor
+                
                 Potree.settings.useDepthTex = Potree.settings.mergeType2
-                //this.modules.AnimationEditor = new AnimationEditor
+                
             }
             
         }else{
@@ -360,9 +371,9 @@ export class Viewer extends ViewerBase{
             this.edlOpacity = 1.0;
             this.useEDL = false;
             this.description = "";
-
-            this.classifications = ClassificationScheme.DEFAULT;
-
+ 
+            this.setClassifications(ClassificationScheme['user'])
+            
             this.moveSpeed = 1;
 
             this.lengthUnit = LengthUnits.METER;
@@ -909,7 +920,14 @@ export class Viewer extends ViewerBase{
             /* this.modules.Clip.bus.addEventListener('updateSelectedDatasets',()=>{
                  this.updateFpVisiDatasets()
             }) */
-                
+            
+            let count = 0
+            viewer.addEventListener('pointcloud_changed',(e)=>{
+                if(count > 5 && !Potree.settings.showHotIr && !Potree.settings.showHotTemp)return;
+                Common.intervalTool.isWaiting('gatherTempRange', (e)=>{
+                    if(this.gatherTempRange() && Potree.visibleNodes?.length) count ++ //成功获取
+                } , THREE.Math.clamp(200,  count * 200, 1200 ) )
+            })            
             
         }
         
@@ -1318,10 +1336,13 @@ export class Viewer extends ViewerBase{
         let inEntity = currentFloor 
         
         //console.error('updatePanosVisibles', currentFloor)
-        
+        let SiteModel = this.modules.SiteModel
         viewer.images360.panos.forEach(pano=>{
             let visible = inEntity ? inEntity.panos.includes(pano) : this.atDatasets.some(e=>e.panos.includes(pano))
-            
+            /* if(!visible && inEntity && SiteModel.noEditBefore && !SiteModel.entities.some(e=>e.panos.includes(pano))){
+                 //没编辑过的空间模型很有错的可能,如果pano不在任何楼里就显示2025.3  for https://laser.4dkankan.com/?lang=zh&m=SG-y2wVWYmrM7u#/
+                 visible = true
+            } */
             Potree.Utils.updateVisible(pano, 'buildingChange', visible, 2)  
         })
         this.dispatchEvent('content_changed')
@@ -1958,8 +1979,17 @@ export class Viewer extends ViewerBase{
 	}
 
 	setClassifications(classifications){
-		this.classifications = classifications;
-
+        for(let i in classifications){
+            let color = classifications[i].color
+            if(typeof color == 'string'){
+                color = new THREE.Color(color)
+                color = [color.r, color.g, color.b, 1]
+                classifications[i].color = color
+            }
+        }
+        
+        
+		this.classifications = classifications; 
 		this.dispatchEvent({'type': 'classifications_changed', 'viewer': this});
 	}
 
@@ -2890,9 +2920,10 @@ export class Viewer extends ViewerBase{
 			material.uniforms.uFilterNumberOfReturnsRange.value = this.filterNumberOfReturnsRange;
 			material.uniforms.uFilterGPSTimeClipRange.value = this.filterGPSTimeRange;
 			material.uniforms.uFilterPointSourceIDClipRange.value = this.filterPointSourceIDRange;
-
-			material.classification = this.classifications;
-			material.recomputeClassification();
+             
+            //material.classificationName = this.classificationName
+			//material.classification = this.classifications;
+			//material.recomputeClassification();
 
 			this.updateMaterialDefaults(pointcloud);
 		}
@@ -3604,7 +3635,7 @@ export class Viewer extends ViewerBase{
         else{
             if(params.viewport.name == "mapViewport" )cameraLayers = ['bothMapAndScene', 'light']
             else {
-                cameraLayers = ['sceneObjects', 'light', 'bothMapAndScene' ];
+                cameraLayers = ['sceneObjects', 'light', 'bothMapAndScene', 'monitor' ];
                 if(!params.useModelOnRT){
                      cameraLayers.push('model')
                 } 
@@ -5082,14 +5113,16 @@ export class Viewer extends ViewerBase{
         
         this.setAllTilesets(model=>model.runtime.update(deltaTime, this.renderer, this.mainViewport.camera))
         
-        let hasAnimation
-        this.objs.children.forEach(model=>{
-            if(model.aniMixer){
-                hasAnimation = true
-                model.aniMixer.update(deltaTime)
-            }
-        })
-        hasAnimation && (this.dispatchEvent('content_changed'))
+        {
+            let hasAnimation
+            window.pauseAni || this.objs.children.forEach(model=>{
+                if(model.mixer && model.mixer._nActiveActions){ 
+                    hasAnimation = true
+                    model.mixer.update(deltaTime)
+                }
+            }) //以后有空的话用frust判断是否在画面内,不在的话即使有动画也不要 update 和 render, 如果paused的话是不是也可以不update
+            hasAnimation && (this.dispatchEvent('content_changed'))
+        }
         
         
         
@@ -5553,12 +5586,12 @@ export class Viewer extends ViewerBase{
         };
 
         // 设置加载失败的回调函数(可选)
-        this.fileManager.onError = (url) => {
+        /* this.fileManager.onError = (url) => {
             console.error(`Failed to load resource: ${url}`);
-        };
+        }; */
         
         loaders = {
-            objLoader : new OBJLoader( this.fileManager ),
+            objLoaders : [],//new OBJLoader( this.fileManager ),
             mtlLoader : new MTLLoader( this.fileManager ),
             glbLoader : new GLTFLoader(undefined, this.renderer, Potree.settings.libsUrl ),
             plyLoader : new PLYLoader( this.fileManager ),
@@ -5567,6 +5600,18 @@ export class Viewer extends ViewerBase{
         }
     }
 
+
+    getObjLoader(){
+        let loader = loaders.objLoaders.find(e=>!e.inUse)
+        if(!loader){
+            loader = new OBJLoader( this.fileManager ) 
+            loader.inUse = true 
+            loaders.objLoaders.push(loader)
+        }
+        return loader 
+    }
+
+
     modelLoaded(object, fileInfo_={}, done){//普通模型加载完以后
         object.isModel = true
         let boundingBox = new THREE.Box3()
@@ -5581,10 +5626,10 @@ export class Viewer extends ViewerBase{
             }   
         }
         
-        object.name = fileInfo_.name != void 0 ? fileInfo_.name : fileInfo_.fileType
+        object.name = fileInfo_.name != void 0 ? fileInfo_.name :  Potree.Common.getNameFromURL(fileInfo_.url,true)  // fileInfo_.fileType
         object.fileType = fileInfo_.fileType
         object.boundingBox = boundingBox  //未乘上matrixWorld的本地boundingBox
-        //object.scale.set(1,1,1);//先获取原始的大小时的boundingBox
+        //fileInfo_.parentInfo || object.scale.set(1,1,1);//先获取原始的大小时的boundingBox 
         object.opacity = 1 //初始化 记录
         object.updateMatrixWorld()
         
@@ -5673,29 +5718,35 @@ export class Viewer extends ViewerBase{
             object.traverse( ( child )=>{ 
                 let is = child.isMesh || child instanceof THREE.Points || child.isLine
                 
-                if (is){
-                    
-                    child.renderOrder = Potree.config.renderOrders.model;
-                    //if(Potree.settings.boundAddObjs){
+                if (is){ 
+                    child.renderOrder = Potree.config.renderOrders.model; 
+                    let boundingBox_  
+                    if(child instanceof THREE.SkinnedMesh){//animation
+                        child.computeBoundingBox();
+                        boundingBox_  = child.boundingBox
+                    }else{
                         child.geometry.computeBoundingBox()
-                        //console.log(child.matrixWorld.clone())
-                        boundingBox.union(child.geometry.boundingBox.clone().applyMatrix4(child.matrixWorld)) //但感觉如果最外层object大小不为1,要还原下scale再乘
-                    //}//获取在scale为1时,表现出的大小
-                    //Potree.Utils.makeTexDontResize(child.material.map) 
-                    //console.log(child.name, 'roughness',child.material.roughness,'metalness',child.material.metalness)
+                        boundingBox_ = child.geometry.boundingBox
+                    }
+                    
+                    //获取在scale为1时,表现出的大小
+                    boundingBox.union(boundingBox_.clone().applyMatrix4(child.matrixWorld)) //但感觉如果最外层object大小不为1,要还原下scale再乘
+                 
                     
                     let changeMat = (oldMat)=>{
                         let mat = oldMat
-                        if(fileInfo_.unlit && (!(oldMat instanceof BasicMaterial) /* || object.fileType == 'glb' */)){ //注释掉是因为已经写入到loader文件里了
-                            //mat = new THREE.MeshBasicMaterial({map:oldMat.map})
-                            mat = new BasicMaterial({map : oldMat.map, opacity: oldMat.opacity, color: oldMat.color})  //很奇怪glb的图会使原本的MeshBasicMaterial 会偏暗,所以自己重新写
-                            //oldMat.dispose() 
-                        } 
+                        if(fileInfo_.unlit && (!(oldMat instanceof THREE.MeshBasicMaterial) /* || object.fileType == 'glb' */)){ //注释掉是因为已经写入到loader文件里了
+                            mat = new THREE.MeshBasicMaterial({name:oldMat.name, map : oldMat.map, opacity: oldMat.opacity, color: oldMat.color, skinning:oldMat.skinning})  
+                        }  
+                        
                         if(fileInfo_.useStandandMat && !(oldMat instanceof THREE.MeshStandardMaterial)){
                             mat = new THREE.MeshStandardMaterial()
                             mat.roughness = 0.7
                             mat.metalness = 0.5
-                        }  
+                        }   
+                        fileInfo_.metalness != void 0 && (mat.metalness = fileInfo_.metalness)
+                        fileInfo_.roughness != void 0 && (mat.roughness = fileInfo_.roughness)
+                        if(mat != oldMat)oldMat.dispose()
                         //纯色的还是不能用BasicMaterial
                         return mat
                     }
@@ -5738,9 +5789,10 @@ export class Viewer extends ViewerBase{
             object.matrixWorldNeedsUpdate = true 
         }
         object.updateMatrixWorld()
-        MergeEditor.getBoundCenter(object) //初始化       
+        MergeEditor.getBoundCenter(object) //初始化  
+        fileInfo_.objLoader && (fileInfo_.objLoader.inUse = false)        
         done && done(object, fileInfo_)
-            
+        
         this.dispatchEvent({type:'modelLoaded',model:object})
         
         
@@ -5812,41 +5864,52 @@ export class Viewer extends ViewerBase{
     
     
         if(fileInfo.fileType == 'obj'){ //暂时不支持数组
+            let objLoader = fileInfo.objLoader = this.getObjLoader()
+            let loadobj = ()=>{
+                objLoader.load(fileInfo.objurl, (object, total)=>{  
+                    loadDone(object/* , total, fileInfo.objurl */)
+                }, onProgress,  onError )
+            }
             if(fileInfo.mtlurl){ 
                 loaders.mtlLoader.load( fileInfo.mtlurl , (materials)=>{ 
+                     
+                    objLoader.setMaterials( materials )  //因为这句所以不同obj不能用同一个objLoader,否则材质紊乱,模型变白
                     materials.preload(); 
-            
-                    loaders.objLoader.setMaterials( materials ).load(fileInfo.objurl, (object, total)=>{  
-                        loadDone(object/* , total, fileInfo.objurl */)
-                    }, onProgress,  onError )
-                } , onProgress,  onError  );  
+                    loadobj() 
+                                                                                                          
+                                                                      
+                                             
+                } , onProgress,  ()=>{
+                    //console.log('mtl load failed, load obj directly')
+                    loadobj()
+                });  
             }else{
-                loaders.objLoader.load(fileInfo.objurl, (object, total)=>{  
-                    loadDone(object)
-                }, onProgress,  onError)
-            }
+                loadobj()
+                                    
+                                        
+            } 
         }else if(fileInfo.fileType == 'glb'){ 
             loaders.glbLoader.unlitMat = true//!!fileInfo.unlit
             loaders.glbLoader.load(fileInfo.url,  ( gltf, total )=>{    
-                console.log('loadGLTF', gltf)
+                console.log('loadGLTF', gltf, 'aniCount:',gltf.animations.length)
                 let model = gltf.scene
                 if(gltf.animations.length){
-                    //let skeleton = new THREE.SkeletonHelper( model );
-					//skeleton.visible = true;
-					//model.add( skeleton ); 
-                    /* viewer.scene.scene.add(skeleton)
+                    let skeleton = new THREE.SkeletonHelper( model );
+					//skeleton.visible = false; 
+                    viewer.scene.scene.add(skeleton)
                     model.skeletonHelper = skeleton //注意:不能覆盖model.skeleton,因其另有 */
-                    
+                    skeleton.material.opacity = 0.1
                     let mixer = new THREE.AnimationMixer( model);
-                   
+                    model.actions = []
                     gltf.animations.forEach(ani=>{
-                        let action = mixer.clipAction( ani ); 
+                        if(ani.tracks.filter(e=>e instanceof THREE.QuaternionKeyframeTrack).length > 1){ //>一帧的
+                            model.actions.push(mixer.clipAction( ani )); 
+                        } 
                     })
-                    model.aniMixer = mixer
+                    model.mixer = mixer
                 }
                 model.gltf = gltf
-                
-                
+                  
                 
                 loadDone(model) 
             }, onProgress, onError)
@@ -6022,7 +6085,7 @@ export class Viewer extends ViewerBase{
     }
     
     removeModel(model){
-        this.objs.remove(model);
+        model.parent.remove(model)
         let dispose = (e)=>{
             e.geometry && e.geometry.dispose() 
             e.material && e.material.dispose()
@@ -6031,7 +6094,7 @@ export class Viewer extends ViewerBase{
             dispose(e) 
         })
            
-        if(Potree.settings.boundAddObjs){
+        if(Potree.settings.boundAddObjs && model.isChildOf(viewer.objs)){
             this.updateModelBound() 
         }
     }
@@ -6273,14 +6336,14 @@ export class Viewer extends ViewerBase{
                     video.play();
                     video.currentTime = 0
                     Potree.settings.zoom.enabled = false
-                    
+                     
                     transitions.start(lerp.property(plane.material, "opacity", 1 ) , e.toPano.duration*0.4 , ()=>{
               
                     }, 0,   easing['easeInOutQuad'])  
                 },  e.toPano.duration*0.6)  //时间上不能和消失的重叠 延迟
+                  
                 
-                
-            }
+            } 
             
             //消失
             transitions.start(lerp.property(plane.material, "opacity", 0,  ) , e.toPano.duration*0.4, ()=>{
@@ -6389,7 +6452,37 @@ export class Viewer extends ViewerBase{
         
         
     }  */
-    
+    addMonitor(data, model){
+        if(!model){
+            return console.warn('必须指定第二个参数为所附模型')
+        }
+        data = data || {
+            "sid": "gSMVdb159299",
+            "name": "1",
+            "panoId": "0",
+            "video": "https://newcntv.qcloudcdn.com/asp/hls/4000/0303000a/3/default/d907ef756138493e9fe5b35b4ab69642/4000.m3u8",
+            "posOri": {
+                "x": 0.75,
+                "y": 0.07,
+                "z": 1.5
+            },
+            "posOffset": {
+                "x": 0,
+                "y": 0,
+                "z": 0
+            },
+            "fov": 70,
+            "far": 3,
+            "yaw": 24,
+            "roll": 0,
+            "pitch": -1,
+            "videoType": 1
+        }
+        let monitor = new Monitor(data)
+        model.add(monitor)
+        this.scene.monitors.push(monitor)
+        this.dispatchEvent('content_changed')
+    }
     
     addTube(datas){//加水管 自动生成
         
@@ -6487,8 +6580,102 @@ export class Viewer extends ViewerBase{
         return sprite  
     }
     
+    
+    switchHotType(){//   add
+        let type = Potree.settings.showHotTemp ? 'temp' : Potree.settings.showHotIr ? 'ir' : null 
+        let _gradient = Gradients[type ? 'ir' : 'RAINBOW'] //e.material._gradient = Gradients[type == 'ir' ? 'ir' : 'RAINBOW']//渐变色阶
+        let gradientTex = Potree.ExtendPointCloudMaterial.generateGradientTexture(_gradient);
+        let min,max
+        if(type){ 
+            min = this.tempRange[type].min 
+            max = this.tempRange[type].max 
+            if(min > 0){
+                let expand = (max - min) * 0.015 //微扩大,因为比min还小的是0, 0是未探测到温度,要区别出0和min,否则这俩将是一个颜色(但不能扩太多,因为界面标记了最低值,否则0就是最低值了)
+                min-=expand  
+            }else if(max < 0){
+                let expand = (max - min) * 0.015 
+                max-=expand 
+            }
+        }
+
+        
+        let objects = [viewer.images360.cube].concat(viewer.scene.pointclouds)
+        objects.forEach(e=>{ 
+            e.material.uniforms.gradient.value = e.material.gradientTexture = gradientTex 
+            type && e.material.uniforms.temperRange.value.set(min, max ) 
+        })   
+        
+        viewer.scene.pointclouds.forEach(e=>e.updateAttrAuto())
+        
+        
+    }
+    
+    
+    
+    gatherTempRange(type, range){  //收集温度范围
+        let oldRange = Common.CloneObject(this.tempRange)
+        
+        if(range){ //全景模式时
+            let {min,max} = this.tempRange[type]
+            min = Math.min(range.min, min)    
+            max = Math.max(range.max, max) 
+            this.tempRange[type] = {min,max} 
+            
+        }else{//点云模式随着加载而扩充温度范围
+            viewer.scene.pointclouds.forEach(pointcloud=>{
+                let rootAttr = pointcloud.root.geometryNode?.geometry.attributes || pointcloud.root.geometry?.attributes
+                if(!rootAttr)return false
+                let types = ['temp','ir'].filter(e=>rootAttr[e])    
+                if(types.length == 0)return true
+                 
+                let nodesAttr = pointcloud.visibleNodes.map(e=>e.geometryNode.geometry.attributes) 
+                nodesAttr = [rootAttr].concat(nodesAttr).filter(e=>!e[types[0]].tempRangeVisited_)
+                nodesAttr.forEach(attributes=>{ 
+                    types.forEach(type_=>{
+                        let {min,max} = this.tempRange[type_]
+                        let range = attributes[type_].tempRange 
+                        min = Math.min(range[0], min)    
+                        max = Math.max(range[1], max) 
+                        this.tempRange[type_] = {min,max} 
+                        attributes[type_].tempRangeVisited_ = true
+                    }) 
+                    
+                })
+                
+                 
+                /* pointcloud.visibleNodes.forEach(e=>{ 
+                    if(e.tempRangeVisited)return 
+                    types.forEach(type_=>{
+                        let {min,max} = this.tempRange[type_]
+                        let range = e.geometryNode.geometry.attributes[type_].tempRange 
+                        min = Math.min(range[0], min)    
+                        max = Math.max(range[1], max) 
+                        this.tempRange[type_] = {min,max} 
+                    }) 
+                    e.tempRangeVisited = true
+                }) */ 
+            })
+        }
+        if(!Common.ifSame(oldRange, this.tempRange)){
+            this.switchHotType() //update  
+            console.log('tempRangeChanged', Common.CloneObject(this.tempRange))
+            this.dispatchEvent('tempRangeChanged')
+        }
+        return true
+        //其实可以只识别低nodeLevel的range,会快一些吗,甚至只root的就行,不知道后面的会不会超过root的范围
+       
+        
+    }
+    
+    
+    
+    
+     
+    
 };
 
+
+ 
 //------  CLIP  默认clipTask都是clipInside ----------------------
 /* 
 并集相当于加法,交集相当于乘法。 所有结果都能展开成多个乘积相加。

+ 12 - 3
src/loader/BinaryLoader.js

@@ -103,11 +103,20 @@ export class BinaryLoader{
 					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 1);
 					geometry.setAttribute('spacing', bufferAttribute);
                   
-				} else if (property === "covs") {//add
+				}else if (property === "IR"  || property === "TEMP"  ) {//温度 
+                    let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 1);
+                    bufferAttribute.tempRange = batchAttribute.range
+                    geometry.setAttribute(property.toLowerCase(), bufferAttribute); 
+                  
+				}else if (property === "SEG") {//分类
+					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 1); 
+					geometry.setAttribute('classification', bufferAttribute);
+                  
+				}else if (property === "covs") {//add
 					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 6);
 					geometry.setAttribute('covs', bufferAttribute);
                   
-				} else if(property != 'GS3D'){//改
+				} /* else if(property != 'GS3D'){//改
                     //geometry.setAttribute("rgba", new THREE.BufferAttribute(new Uint8Array(buffer), 4, true));
 					const bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 1);
  
@@ -128,7 +137,7 @@ export class BinaryLoader{
 						attribute.initialRange = batchAttribute.range;
 					}  
 
-				} 
+				}  */
 			}  
 
 			tightBoundingBox.max.sub(tightBoundingBox.min);

+ 10 - 1
src/materials/ClassificationScheme.js

@@ -16,7 +16,16 @@ export const ClassificationScheme = {
 		9:       { visible: true, name: 'water'             , color: [0.0,  0.0,  1.0,  1.0] },
 		12:      { visible: true, name: 'overlap'           , color: [1.0,  1.0,  0.0,  1.0] },
 		DEFAULT: { visible: true, name: 'default'           , color: [0.3,  0.6,  0.6,  0.5] },
-	}
+	},
+    user:{//add on 2025
+        1:       { visible: true, name: 'concrete'  , color: '#CCCCCC' }, //混凝土
+		2:       { visible: true, name: 'wood'      , color: '#47FF53' },
+		3:       { visible: true, name: 'metal'     , color: '#FFD633' },
+		4:       { visible: true, name: 'glass'     , color: '#1C6BFF' },
+		5:       { visible: true, name: 'plastic'   , color: '#47FFFF'},
+		6:       { visible: true, name: 'electric_wire'  , color: '#FF3939' },
+		 
+    }
 };
 
 Object.defineProperty(ClassificationScheme, 'RANDOM', {

+ 36 - 6
src/materials/ExtendPointCloudMaterial.js

@@ -8,7 +8,7 @@ import {PointSizeType, PointShape, TreeType, ElevationGradientRepeat} from "../d
 import {Features} from "../Features.js";
 import {PointCloudMaterial} from './PointCloudMaterial.js'
 import Common from "../custom/utils/Common.js";
-
+import math from "../custom/utils/math.js";
 
 //
 // how to calculate the radius of a projected sphere in screen space
@@ -32,9 +32,9 @@ export class ExtendPointCloudMaterial extends PointCloudMaterial {
  
 		let maxSize = getValid(parameters.maxSize, 200.0);
         let orthoMaxSize = getValid(parameters.orthoMaxSize, 3.0);
-		this._gradient = Gradients.RAINBOW//Gradients.SPECTRAL;//海拔贴图种类
+		this._gradient = Gradients.ir   //Gradients.SPECTRAL;//海拔贴图种类,火灾也是这个
 		this.gradientTexture = ExtendPointCloudMaterial.generateGradientTexture(this._gradient); 
-		//this.matcapTexture = ExtendPointCloudMaterial.generateMatcapTexture(this._matcap);
+        this.classificationName = null
         
         delete this.uniforms.screenWidth
         delete this.uniforms.screenHeight
@@ -112,7 +112,10 @@ export class ExtendPointCloudMaterial extends PointCloudMaterial {
                 type:'vec4',
                 value:new THREE.Vector4
             },
-            
+            temperRange:{//温度范围
+                type:'vec2',
+                value: new THREE.Vector2(math.getKelvinFromCelsius(99),  math.getKelvinFromCelsius(1500)  ) //火场温度
+            }
         })
         
         delete this.clipBoxes;
@@ -125,12 +128,34 @@ export class ExtendPointCloudMaterial extends PointCloudMaterial {
         let {vs, fs} = Common.changeShaderToWebgl2(Shaders['pointcloud_new.vs'],Shaders['pointcloud_new.fs'], 'selfBuild' )
         Shaders['pointcloud_new.vs'] = vs
         Shaders['pointcloud_new.fs'] = fs
+        {
+            let setClass = ()=>{
+                this.classification = viewer.classifications
+                this.recomputeClassification()
+            }
+            viewer.addEventListener('classifications_changed',setClass)
+            viewer.addEventListener('classification_visibility_changed',setClass)
+            setClass()
+        }
+        
+        
+        
+        
         
 		this.updateShaderSource();
 	}
-
- 
+    
+    /* get classificationName(){
+        return this.classificationName_
+    }
  
+    set classificationName(name){//add
+        if(this.classificationName_ != name){
+            this.classificationName_ = name
+            this.classification = ClassificationScheme[name]
+            this.recomputeClassification();
+        }
+    } */
 
 	updateShaderSource () {  
 
@@ -411,5 +436,10 @@ export class ExtendPointCloudMaterial extends PointCloudMaterial {
         this.highlightBoxes = highlightBoxes
         this.prisms = prismPolygons
 	}
+    
+    setTempRange(min,max){//温度范围
+        this.uniforms.temperRange.value.set(math.getKelvinFromCelsius(min),math.getKelvinFromCelsius(max)) 
+    }
 
+    
 }

+ 13 - 1
src/materials/Gradients.js

@@ -128,7 +128,7 @@ let Gradients = {
 		[0.93, new THREE.Color(0.81608, 0.18462, 0.01809)],
 		[1.00, new THREE.Color(0.66449, 0.08436, 0.00424)],
 	],
-	RAINBOW: [
+	RAINBOW: [ // 火灾
 		[0, new THREE.Color(0.278, 0, 0.714)],
 		[1 / 6, new THREE.Color(0, 0, 1)],
 		[2 / 6, new THREE.Color(0, 1, 1)],
@@ -143,6 +143,18 @@ let Gradients = {
 		[0.04, new THREE.Color(1, 1, 1)],
 		[1.00, new THREE.Color(1, 1, 1)]
 	],
+   
+    
+    ir:[ //红外
+        [0,     new THREE.Color('#000000')],
+        [1 / 5, new THREE.Color('#230463')],
+        [2 / 5, new THREE.Color('#80068B')],
+        [3 / 5, new THREE.Color('#F49107')],
+        [4 / 5, new THREE.Color('#FAD725')],
+        [1,     new THREE.Color('#FFFFFF')],
+    ]
+    
+    
 };
 
 

+ 4 - 3
src/materials/PointCloudMaterial.js

@@ -364,9 +364,9 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {//base
 	recomputeClassification () {
 		const classification = this.classification;
 		const data = this.classificationTexture.image.data;
-
+        if(!classification)return
 		let width = 256;
-		const black = [1, 1, 1, 1];
+		const black = [1, 1, 1, 1];  //1111
 
 		let valuesChanged = false;
 
@@ -387,7 +387,7 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {//base
 			}else{
 				color = black;
 			}
-
+            
 			const r = parseInt(255 * color[0]);
 			const g = parseInt(255 * color[1]);
 			const b = parseInt(255 * color[2]);
@@ -561,6 +561,7 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {//base
 
 	set activeAttributeName(value){
 		if (this._activeAttributeName !== value) {
+            //console.error('_activeAttributeName!!!!!!!',value)
 			this._activeAttributeName = value;
 
 			this.updateShaderSource();

+ 10 - 0
src/materials/shaders/basicTextured.vs

@@ -1,6 +1,16 @@
+ 
+//#include <skinning_pars_vertex>
+ 
 varying vec2 vUv;
+
+
+
 void main() {
   vUv = uv;
   gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+  
+  /*#include <begin_vertex>  
+  #include <skinbase_vertex> 
+  #include <skinning_vertex>*/
 } 
   

+ 35 - 9
src/materials/shaders/pointcloud_new.vs

@@ -4,7 +4,6 @@ precision highp int;
  
 #define PI 3.141592653589793
 
- 
 
 
 
@@ -41,7 +40,7 @@ precision highp int;
 attribute vec3 position;
 attribute vec3 color;
 attribute float intensity;
-attribute float classification;
+attribute float classification;  //attribute float classification;
 attribute float returnNumber;
 attribute float numberOfReturns;
 attribute float pointSourceID;
@@ -51,6 +50,9 @@ attribute float gpsTime;
 attribute vec3 normal;
 attribute float aExtra;
 
+attribute float ir; //红外温度 开尔文 
+attribute float temp; //火灾AI温度 开尔文 
+
 uniform mat4 modelMatrix;
 uniform mat4 modelViewMatrix;
 uniform mat4 projectionMatrix;
@@ -186,8 +188,9 @@ uniform sampler2D uShadowMap[num_shadowmaps];
 uniform mat4 uShadowWorldView[num_shadowmaps];
 uniform mat4 uShadowProj[num_shadowmaps];
 #endif
-
-
+ 
+uniform vec2 temperRange;
+ 
 
 varying vec3	vColor;
 varying float	vLogDepth;
@@ -195,6 +198,7 @@ varying vec3	vViewPosition;
 varying float 	vRadius;
 varying float 	vPointSize;
 
+  
 
 float Round(float number){
 	return floor(number + 0.5);
@@ -503,8 +507,25 @@ vec3 getElevation(vec4 world){
 	return cElevation;
 }
 
+
+vec3 getTemperature(){  //xzw add 
+
+    float temperature = 0.0;
+    
+    #if defined color_type_ir 
+        temperature = ir;
+    #else
+        temperature = temp;
+    #endif
+	float w = (temperature - temperRange.x) / (temperRange.y - temperRange.x);
+    w = clamp(w,0.0,1.0);
+	vec3 c = texture2D(gradient, vec2(w,1.0-w)).rgb;
+	return c;
+}
+
+
 vec4 getClassification(){
-	vec2 uv = vec2(classification / 255.0, 0.5);
+	vec2 uv = vec2(float(classification) / 255.0, 0.5);
 	vec4 classColor = texture2D(classificationLUT, uv);
 	
 	return classColor;
@@ -677,6 +698,8 @@ vec3 getColor(vec4 world){
 	#elif defined color_type_rgb_height
 		vec3 cHeight = getElevation();
 		color = (1.0 - uTransition) * getRGB() + uTransition * cHeight;
+    #elif defined color_type_ir || defined color_type_temp // add
+        color = getTemperature();
 	#elif defined color_type_depth
 		float linearDepth = gl_Position.w;
 		float expDepth = (gl_Position.z / gl_Position.w) * 0.5 + 0.5;
@@ -698,9 +721,12 @@ vec3 getColor(vec4 world){
 		color = texture2D(gradient, vec2(w,1.0-w)).rgb;
 	#elif defined color_type_indices
 		color = indices.rgb;
-	#elif defined color_type_classification
+	#elif defined color_type_classification  //xzw change   
 		vec4 cl = getClassification(); 
-		color = cl.rgb;
+        vec3 originC = getRGB();
+        float blendRatio = 0.5;
+        float classAlpha = cl.a * blendRatio;  
+		color = cl.rgb * classAlpha + originC * (1.0-classAlpha);  //mix with old rgba
 	#elif defined color_type_return_number
 		color = getReturnNumber();
 	#elif defined color_type_returns
@@ -895,14 +921,14 @@ bool insideBox(mat4 clipBox, vec4 worldPos){//add
 
 void doClipping(vec4 world){
 
-	{
+	/*{ 
 		vec4 cl = getClassification(); 
 		if(cl.a == 0.0){
 			gl_Position = vec4(100.0, 100.0, 100.0, 0.0);
 			
 			return;
 		}
-	}
+	}*/
 
 	#if defined(clip_return_number_enabled)
 	{ // return number filter

+ 1 - 1
src/navigation/InputHandlerNew.js

@@ -964,7 +964,7 @@ export class InputHandler extends THREE.EventDispatcher {
                             normal = localNormal.clone().applyQuaternion(quaternion) 
                             
                             allElements[0].object.matrix.decompose( new THREE.Vector3, quaternion, new THREE.Vector3 ) 
-                            localNormal = normal.applyQuaternion(quaternion.invert()) //改为在最外层model上无旋转时的normal
+                            localNormal = normal.clone().applyQuaternion(quaternion.invert()) //改为在最外层model上无旋转时的normal
                         }
                     }
                     

+ 8 - 0
src/viewer/sidebar1.html

@@ -124,6 +124,14 @@
 
 			<ul id="classificationList" class="pv-menu-list"></ul>
 
+            <div class="divider"><span>温度(摄氏度)</span></div> 
+            <div id="temperature">
+                <ul class="pv-menu-list"> 
+                    <li><span> tempRange</span> : <span id="tempRange"> </span> <div id="sldTempRange"></div></li>
+                </ul>
+            </div>
+            
+            
 			<div class="divider"><span>Returns</span></div>
 
 			<div id="return_filter_panel">

+ 23 - 1
src/viewer/sidebarNew.js

@@ -1445,9 +1445,31 @@ export class Sidebar{
 		this.initReturnFilters();
 		this.initGPSTimeFilters();
 		this.initPointSourceIDFilters();
-
+        this.initTempFilters();
 	}
 
+    initTempFilters(){//xzw add
+        let temperPanel = $('#temperature');
+        let rangeSlider = temperPanel.find('#sldTempRange');
+        rangeSlider.slider({
+            range: true,
+            min: 10, max: 200, step: 1,
+            values: [20, 50],
+            slide: (event, ui) => {
+                viewer.scene.pointclouds.forEach(e=>e.material.setTempRange(ui.values[0], ui.values[1]))
+                viewer.images360.cube.material.setTempRange(ui.values[0], ui.values[1])
+                viewer.dispatchEvent('content_changed')
+                temperPanel.find('#tempRange')[0].innerHTML = `${ui.values[0]} to ${ui.values[1]}`;
+            }  
+        });
+        
+        
+         
+        
+        
+        
+    }
+
 	initReturnFilters(){
 		let elReturnFilterPanel = $('#return_filter_panel');
 

+ 34 - 39
src/workers/BinaryDecoderWorker.js

@@ -264,7 +264,39 @@ onmessage = function (event) {
 			}
 
 			attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
-		} else if (pointAttribute.name === "GS3D") {//add  见inriav1plyparser.js
+		}else if (pointAttribute.name === "IR"   || pointAttribute.name === "TEMP" ) {  //温度   ir是热成像的温度 temp是AI识别的温度
+                
+            let buff = new ArrayBuffer(numPoints * 4);  //原本数据是uint16但是shader没办法接收,只能转了
+			let f32 = new Float32Array(buff); //according to pointAttribute.type.name, 取值范围为 0 到 65535。
+            let min = Infinity, max = -Infinity, v
+            for (let j = 0; j < numPoints; j++) {//仿照position的写法 填入数据 
+                f32[j] = v = view.getUint16(inOffset + j * pointAttributes.byteSize, true) / 10 //开尔文 
+                v != 0 && (min = Math.min(min, v)) //==0可能是没探测到所以跳过
+                v != 0 && (max = Math.max(max, v))
+			}//ir or temp 里面的值除以10就是开尔文温度 
+            attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
+            pointAttribute.range = [min, max];  //但每个geo范围不一样
+
+        }else if (pointAttribute.name === "SEG"){  //类型
+            /* 
+            1: concrete
+            2: wood
+            3: metal
+            4: glass
+            5: plastic
+            6: electric_wire 
+             */
+            let buff = new ArrayBuffer(numPoints * 4);
+			let f32 = new Float32Array(buff); //according to pointAttribute.type.name, 取值范围为 0 到 65535。
+
+            for (let j = 0; j < numPoints; j++) {//仿照position的写法 填入数据 
+                f32[j] = view.getUint16(inOffset + j * pointAttributes.byteSize, true) 
+                    
+			}//ir or temp 里面的值除以10就是开尔文温度哈
+            attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
+
+
+        } else if (pointAttribute.name === "GS3D") {//add  见inriav1plyparser.js
             //////////////////////////////////////////////////////////
             let buff = new ArrayBuffer(numPoints * pointAttribute.byteSize);
 			let f32 = new Float32Array(buff);
@@ -303,44 +335,7 @@ onmessage = function (event) {
 			}
 
 			attributeBuffers['rgba'] = { buffer: buff2, attribute: pointAttribute };
-            
-            
-          
-            
-            
-            //scale 
-            /* let buff4 = new ArrayBuffer(numPoints * 12);
-			let scale = new Float32Array(buff4);
-            const offset_scale = gs3dProplist.indexOf('scale_0') //第49个数开始是      
-            let getScale = (index)=>{
-                return Math.exp(f32[index+offset_scale]); 
-            }
-			for (let j = 0; j < numPoints; j++) {
-				scale[3 * j + 0] = getScale(j * pointAttribute.numElements + 0)   
-				scale[3 * j + 1] = getScale(j * pointAttribute.numElements + 1) 
-				scale[3 * j + 2] = getScale(j * pointAttribute.numElements + 2) 
-			}
-
-			attributeBuffers['scale'] = { buffer: buff4, attribute: pointAttribute };
-            
-            
-            //rotation
-            let buff5 = new ArrayBuffer(numPoints * 16);
-			let qua = new Float32Array(buff5);
-            const offset_rot = gs3dProplist.indexOf('rot_0')        
-            let getRot = (index)=>{
-                return f32[index+offset_rot] ; 
-            }
-			for (let j = 0; j < numPoints; j++) {
-				qua[4 * j + 0] = getRot(j * pointAttribute.numElements + 0)   
-				qua[4 * j + 1] = getRot(j * pointAttribute.numElements + 1) 
-				qua[4 * j + 2] = getRot(j * pointAttribute.numElements + 2) 
-                qua[4 * j + 2] = getRot(j * pointAttribute.numElements + 3) 
-			}
-
-			attributeBuffers['qua'] = { buffer: buff5, attribute: pointAttribute }; */
-            
-            
+             
             
             //compute cov:
             let buff3 = new ArrayBuffer(numPoints * 24);