Selaa lähdekoodia

feat: 同步meger分支

bill 2 vuotta sitten
vanhempi
commit
6105e76391
100 muutettua tiedostoa jossa 15370 lisäystä ja 4963 poistoa
  1. 79 0
      examples/pano.html
  2. 2 0
      gulpfile.js
  3. 105 82
      libs/three.js/build/three.module.js
  4. 7 12
      libs/three.js/lines/Line2.js
  5. 22 40
      libs/three.js/lines/LineGeometry.js
  6. 560 241
      libs/three.js/lines/LineMaterial.js
  7. 274 128
      libs/three.js/lines/LineSegments2.js
  8. 76 95
      libs/three.js/lines/LineSegmentsGeometry.js
  9. 1 1
      libs/three.js/loaders/DDSLoader.js
  10. 117 170
      libs/three.js/loaders/DRACOLoader.js
  11. 92 35
      libs/three.js/loaders/GLTFLoader.js
  12. 481 499
      libs/three.js/loaders/KTX2Loader.js
  13. 9 8
      libs/three.js/loaders/MTLLoader.js
  14. 2 2
      libs/three.js/loaders/OBJLoader.js
  15. 52 0
      libs/three.js/loaders/draco/draco_decoder.js
  16. BIN
      libs/three.js/loaders/draco/draco_decoder.wasm
  17. 104 0
      libs/three.js/loaders/draco/draco_wasm_wrapper.js
  18. 21 0
      libs/three.js/loaders/ktx/basis_transcoder.js
  19. BIN
      libs/three.js/loaders/ktx/basis_transcoder.wasm
  20. BIN
      note/images360.updateCube笔记.jpg
  21. BIN
      resources/textures/explode.png
  22. BIN
      resources/textures/fire.png
  23. BIN
      resources/textures/icon-explode.png
  24. BIN
      resources/textures/icon-fire.png
  25. BIN
      resources/textures/icon-smoke.png
  26. BIN
      resources/textures/pic_point_s32.png
  27. BIN
      resources/textures/smokeparticle.png
  28. 3 3
      src/Actions.js
  29. 2 3
      src/Annotation.js
  30. 22 6
      src/EventDispatcher.js
  31. 21 0
      src/Features.js
  32. 2 1
      src/KeyCodes.js
  33. 7 1
      src/LRU.js
  34. 944 63
      src/PointCloudOctree.js
  35. 6 6
      src/PointCloudOctreeGeometry.js
  36. 3 4
      src/PointCloudTree.js
  37. 117 55
      src/Potree.js
  38. 101 16
      src/PotreeRenderer.js
  39. 10 4
      src/Potree_update_visibility.js
  40. 2 2
      src/defines.js
  41. 1 1
      src/exporter/DXFExporter.js
  42. 1 1
      src/exporter/GeoJSONExporter.js
  43. 19 2
      src/loader/BinaryLoader.js
  44. 1 1
      src/loader/GeoPackageLoader.js
  45. 4 2
      src/loader/POCLoader.js
  46. 1 1
      src/loader/ShapefileLoader.js
  47. 43 0
      src/materials/BasicMaterial.js
  48. 72 46
      src/materials/DepthBasicMaterial.js
  49. 4 3
      src/materials/EyeDomeLightingMaterial.js
  50. 395 0
      src/materials/ModelTextureMaterial.js
  51. 33 12
      src/materials/PointCloudMaterial.js
  52. 10 7
      src/materials/shaders/depthBasic.fs
  53. 23 12
      src/materials/shaders/edl.fs
  54. 1 1
      src/materials/shaders/pointcloud.fs
  55. 43 16
      src/materials/shaders/pointcloud.vs
  56. 521 399
      src/modules/CameraAnimation/CameraAnimation.js
  57. 1777 437
      src/modules/Images360/Images360.js
  58. 0 207
      src/modules/Images360/ModelTextureMaterial.js
  59. 381 140
      src/modules/Images360/Panorama.js
  60. 85 44
      src/modules/Images360/tile/PanoRenderer.js
  61. 4 4
      src/modules/Images360/tile/QualityManager.js
  62. 135 127
      src/modules/Images360/tile/TileDownloader.js
  63. 64 32
      src/modules/Images360/tile/TilePrioritizer.js
  64. 3 2
      src/modules/Images360/tile/TileUtils.js
  65. 1 2
      src/modules/OrientedImages/OrientedImageControls.js
  66. 2 3
      src/modules/OrientedImages/OrientedImages.js
  67. 253 0
      src/modules/Particles/ParticleEditor.js
  68. 198 24
      src/modules/clipModel/Clip.js
  69. 259 48
      src/modules/datasetAlignment/Alignment.js
  70. 616 0
      src/modules/mergeModel/MergeEditor.js
  71. 1227 0
      src/modules/panoEdit/panoEditor.js
  72. 229 74
      src/navigation/RouteGuider.js
  73. 660 132
      src/modules/siteModel/BuildingBox.js
  74. 1001 294
      src/modules/siteModel/SiteModel.js
  75. 2 3
      src/navigation/DeviceOrientationControls.js
  76. 23 24
      src/navigation/EarthControls.js
  77. 344 109
      src/navigation/FirstPersonControls.js
  78. 846 447
      src/navigation/InputHandler.js
  79. 220 36
      src/navigation/OrbitControls.js
  80. 0 628
      src/navigation/PanoramaControls.js
  81. 0 107
      src/navigation/Reticule.js
  82. 4 5
      src/navigation/VRControls.js
  83. 3 3
      src/viewer/Axis.js
  84. 141 0
      src/objects/InfiniteGridHelper.js
  85. 21 7
      src/utils/Label.js
  86. 350 0
      src/objects/Magnifier.js
  87. 271 0
      src/objects/Reticule.js
  88. 67 24
      src/viewer/Sprite.js
  89. 161 0
      src/objects/Tag.js
  90. 24 19
      src/TextSprite.js
  91. 475 0
      src/objects/fireParticle/explode/ExplodeParticle.js
  92. 57 0
      src/objects/fireParticle/explode/Particle.js
  93. 18 0
      src/objects/fireParticle/explode/Util.js
  94. 4 0
      src/objects/fireParticle/explode/const.js
  95. 40 0
      src/objects/fireParticle/explode/shader.js
  96. 274 0
      src/objects/fireParticle/fire/FireParticle.js
  97. 63 0
      src/objects/fireParticle/fire/shader.js
  98. 57 0
      src/objects/fireParticle/smoke/Particle.js
  99. 594 0
      src/objects/fireParticle/smoke/SmokeParticle.js
  100. 0 0
      src/objects/fireParticle/smoke/shader.js

+ 79 - 0
examples/pano.html

@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<meta name="description" content="">
+	<meta name="author" content="">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+	<title>Potree Viewer</title>
+
+	<link rel="stylesheet" type="text/css" href="../../build/potree/potree.css">
+	<link rel="stylesheet" type="text/css" href="../../libs/jquery-ui/jquery-ui.min.css">
+	<link rel="stylesheet" type="text/css" href="../../libs/openlayers3/ol.css">
+	<link rel="stylesheet" type="text/css" href="../../libs/spectrum/spectrum.css">
+	<link rel="stylesheet" type="text/css" href="../../libs/jstree/themes/mixed/style.css">
+</head>
+
+<body>
+	<script src="../../libs/jquery/jquery-3.1.1.min.js"></script>
+	<script src="../../libs/spectrum/spectrum.js"></script>
+	<script src="../../libs/jquery-ui/jquery-ui.min.js"></script>
+	<script src="../../libs/other/BinaryHeap.js"></script>
+	<script src="../../libs/tween/tween.min.js"></script>
+	<script src="../../libs/d3/d3.js"></script>
+	<script src="../../libs/proj4/proj4.js"></script> 
+	
+    
+	<script src="../../libs/openlayers3/ol.js"></script>
+	<script src="../../libs/i18next/i18next.js"></script>
+	<script src="../../libs/jstree/jstree.js"></script>
+	<script src="../../build/potree/potree.js"></script>
+	<script src="../../libs/plasio/js/laslaz.js"></script>
+	
+	
+	<div class="potree_container" style="position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; ">
+		<div id="potree_render_area" style="background-image: url('../../build/potree/resources/images/background.jpg');">
+            
+		</div>
+		<div id="potree_sidebar_container"> </div>
+	</div>
+	
+	<script type="module">
+
+	import * as THREE from "../libs/three.js/build/three.module.js";
+    import browser from '../src/utils/browser.js' //这里必须加.js
+     
+        /*var number = window.location.href.substring(window.location.href.indexOf("=") + 1);
+        if (number.indexOf("&") != -1) {
+            number = number.substring(0, number.indexOf("&"));
+        }
+        if (number.indexOf("#") != -1) {
+            number = number.substring(0, number.indexOf("#"));
+        }*/
+        
+        var number = browser.urlHasValue('m',true);
+        console.log(number)
+        Potree.panoEditStart(document.getElementById("potree_render_area"),null, number);
+        
+          
+		/*
+        数据集校准 平移后
+        单个数据集:
+        
+        点云的本地位置是一样的 。说明:单个数据集时点云最终平移量为0,同理漫游点也是 
+        多个数据集时,参照为第一个dataset,见GeoTransformationService.setOffsetFromGlobal。所以第一个数据集的位置为000,其他的不是。因此本地坐标是不固定的,只有指定了参考数据集才能确定,如果去掉第一个数据集下一次显示的坐标就不同了,但是不影响相对位置所以看起来一样。
+        (注意:navvis平移后要刷新location才生效。)
+        
+        
+        var view = window.IV.getMainView() 
+        view.ImageService.images.forEach(e=>console.log(e.id + ": "+e.location.toArray()))
+        
+        
+        images360.panos.forEach(e=>console.log(e.id + ": "+e.position.toArray()))
+        */        
+		
+	</script>
+	
+	
+  </body>
+</html>

+ 2 - 0
gulpfile.js

@@ -22,6 +22,7 @@ let paths = {
 	html: [
 		"src/viewer/potree.css",
 		"src/viewer/sidebar.html",
+        "src/viewer/sidebar2.html",
 		"src/viewer/profile.html"
 	],
 	resources: [
@@ -89,6 +90,7 @@ let shaders = [
 gulp.task('webserver', gulp.series(async function() {
 	server = connect.server({
 		port: 1234,
+        host:'192.168.0.113',
 		https: false,
 	});
 }));

+ 105 - 82
libs/three.js/build/three.module.js

@@ -208,7 +208,7 @@ function EventDispatcher() {}
 
 Object.assign( EventDispatcher.prototype, {
 
-	addEventListener: function ( type, listener ) {
+	addEventListener: function ( type, listener, importance=0 ) {//add importance
 
 		if ( this._listeners === undefined ) this._listeners = {};
 
@@ -220,10 +220,10 @@ Object.assign( EventDispatcher.prototype, {
 
 		}
 
-		if ( listeners[ type ].indexOf( listener ) === - 1 ) {
-
-			listeners[ type ].push( listener );
-
+		if ( !listeners[ type ].some(e=>e.listener == listener )  ) { 
+			//listeners[ type ].push( listener );
+            listeners[type].push({ listener,  importance});
+            listeners[type] = listeners[type].sort((e,a)=> a.importance - e.importance)//add
 		}
 
 	},
@@ -234,7 +234,7 @@ Object.assign( EventDispatcher.prototype, {
 
 		const listeners = this._listeners;
 
-		return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
+		return listeners[ type ] !== undefined &&  listeners[ type ].some(e=>e.listener == listener )    
 
 	},
 
@@ -247,20 +247,36 @@ Object.assign( EventDispatcher.prototype, {
 
 		if ( listenerArray !== undefined ) {
 
-			const index = listenerArray.indexOf( listener );
+			/* const index = listenerArray.indexOf( listener );
 
 			if ( index !== - 1 ) {
 
 				listenerArray.splice( index, 1 );
 
-			}
+			} */
+
+            let item = listenerArray.find(e=>e.listener == listener)
+            item && listenerArray.splice(listenerArray.indexOf(item), 1);
 
 		}
 
 	},
-
-	dispatchEvent: function ( event ) {
-
+    removeEventListeners(type){//add
+		if(this._listeners && this._listeners[type] !== undefined){
+			delete this._listeners[type];
+		}
+	} ,
+    removeAllListeners(){ //add
+        this._listeners = {};
+        
+    },
+    
+    
+    
+	dispatchEvent: function ( event ) { 
+        if(typeof event == 'string'){//add
+            event = {type:event}
+        }
 		if ( this._listeners === undefined ) return;
 
 		const listeners = this._listeners;
@@ -271,18 +287,18 @@ Object.assign( EventDispatcher.prototype, {
 			event.target = this;
 
 			// Make a copy, in case listeners are removed while iterating.
-			const array = listenerArray.slice( 0 );
-
-			for ( let i = 0, l = array.length; i < l; i ++ ) {
-
-				array[ i ].call( this, event );
-
+			 
+            for(let {listener} of listenerArray.slice(0)){
+				let result = listener.call(this, event);   //add stopContinue
+                if(result && result.stopContinue){
+                    break
+                }
 			}
 
 		}
 
 	}
-
+    
 } );
 
 const _lut = [];
@@ -6829,8 +6845,11 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 	traverse: function ( callback ) {
 
-		callback( this );
-
+		let result = callback( this );
+        if(result && result.stopContinue){//xzw add
+            return 
+        }
+             
 		const children = this.children;
 
 		for ( let i = 0, l = children.length; i < l; i ++ ) {
@@ -8524,7 +8543,7 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 			if ( currentValue === undefined ) {
 
-				console.warn( 'THREE.' + this.type + ': \'' + key + '\' is not a property of this material.' );
+				//console.warn( 'THREE.' + this.type + ': \'' + key + '\' is not a property of this material.' );
 				continue;
 
 			}
@@ -8696,7 +8715,7 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 		if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor;
 		if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits;
 
-		if ( this.linewidth && this.linewidth !== 1 ) data.linewidth = this.linewidth;
+		if ( this.lineWidth && this.lineWidth !== 1 ) data.lineWidth = this.lineWidth;
 		if ( this.dashSize !== undefined ) data.dashSize = this.dashSize;
 		if ( this.gapSize !== undefined ) data.gapSize = this.gapSize;
 		if ( this.scale !== undefined ) data.scale = this.scale;
@@ -8707,7 +8726,7 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 		if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha;
 
 		if ( this.wireframe === true ) data.wireframe = this.wireframe;
-		if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth;
+		if ( this.wireframelineWidth > 1 ) data.wireframelineWidth = this.wireframelineWidth;
 		if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap;
 		if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin;
 
@@ -8881,7 +8900,7 @@ Object.defineProperty( Material.prototype, 'needsUpdate', {
  *  depthWrite: <bool>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>,
+ *  wireframelineWidth: <float>,
  *
  *  skinning: <bool>,
  *  morphTargets: <bool>
@@ -8914,7 +8933,7 @@ function MeshBasicMaterial( parameters ) {
 	this.refractionRatio = 0.98;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 	this.wireframeLinecap = 'round';
 	this.wireframeLinejoin = 'round';
 
@@ -8954,7 +8973,7 @@ MeshBasicMaterial.prototype.copy = function ( source ) {
 	this.refractionRatio = source.refractionRatio;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 	this.wireframeLinecap = source.wireframeLinecap;
 	this.wireframeLinejoin = source.wireframeLinejoin;
 
@@ -11700,7 +11719,7 @@ var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0
  *  vertexShader: <string>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>,
+ *  wireframelineWidth: <float>,
  *
  *  lights: <bool>,
  *
@@ -11722,10 +11741,10 @@ function ShaderMaterial( parameters ) {
 	this.vertexShader = default_vertex;
 	this.fragmentShader = default_fragment;
 
-	this.linewidth = 1;
+	this.lineWidth = 1;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 
 	this.fog = false; // set to use scene fog
 	this.lights = false; // set to use scene lights
@@ -11786,7 +11805,7 @@ ShaderMaterial.prototype.copy = function ( source ) {
 	this.defines = Object.assign( {}, source.defines );
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 
 	this.lights = source.lights;
 	this.clipping = source.clipping;
@@ -18877,7 +18896,7 @@ function WebGLRenderStates( extensions, capabilities ) {
  *  displacementBias: <float>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>
+ *  wireframelineWidth: <float>
  * }
  */
 
@@ -18901,7 +18920,7 @@ function MeshDepthMaterial( parameters ) {
 	this.displacementBias = 0;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 
 	this.fog = false;
 
@@ -18932,7 +18951,7 @@ MeshDepthMaterial.prototype.copy = function ( source ) {
 	this.displacementBias = source.displacementBias;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 
 	return this;
 
@@ -19377,8 +19396,8 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 		result.clippingPlanes = material.clippingPlanes;
 		result.clipIntersection = material.clipIntersection;
 
-		result.wireframeLinewidth = material.wireframeLinewidth;
-		result.linewidth = material.linewidth;
+		result.wireframelineWidth = material.wireframelineWidth;
+		result.lineWidth = material.lineWidth;
 
 		if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
 
@@ -19780,7 +19799,7 @@ function WebGLState( gl, extensions, capabilities ) {
 	let currentFlipSided = null;
 	let currentCullFace = null;
 
-	let currentLineWidth = null;
+	let currentlineWidth = null;
 
 	let currentPolygonOffsetFactor = null;
 	let currentPolygonOffsetUnits = null;
@@ -20149,13 +20168,13 @@ function WebGLState( gl, extensions, capabilities ) {
 
 	}
 
-	function setLineWidth( width ) {
+	function setlineWidth( width ) {
 
-		if ( width !== currentLineWidth ) {
+		if ( width !== currentlineWidth ) {
 
 			if ( lineWidthAvailable ) gl.lineWidth( width );
 
-			currentLineWidth = width;
+			currentlineWidth = width;
 
 		}
 
@@ -20346,7 +20365,7 @@ function WebGLState( gl, extensions, capabilities ) {
 		currentFlipSided = null;
 		currentCullFace = null;
 
-		currentLineWidth = null;
+		currentlineWidth = null;
 
 		currentPolygonOffsetFactor = null;
 		currentPolygonOffsetUnits = null;
@@ -20376,7 +20395,7 @@ function WebGLState( gl, extensions, capabilities ) {
 		setFlipSided: setFlipSided,
 		setCullFace: setCullFace,
 
-		setLineWidth: setLineWidth,
+		setlineWidth: setlineWidth,
 		setPolygonOffset: setPolygonOffset,
 
 		setScissorTest: setScissorTest,
@@ -23684,19 +23703,23 @@ function WebGLRenderer( parameters ) {
 
 		_width = width;
 		_height = height;
+        
+        //if(!window.unableSetSize){ 
+            _canvas.width = Math.floor( width * _pixelRatio );
+            _canvas.height = Math.floor( height * _pixelRatio );
+        
+        
+        
+            if ( updateStyle !== false ) {
 
-		_canvas.width = Math.floor( width * _pixelRatio );
-		_canvas.height = Math.floor( height * _pixelRatio );
-
-		if ( updateStyle !== false ) {
-
-			_canvas.style.width = width + 'px';
-			_canvas.style.height = height + 'px';
-
-		}
-
-		this.setViewport( 0, 0, width, height );
+                _canvas.style.width = width + 'px';
+                _canvas.style.height = height + 'px';
 
+            }
+            
+            
+            this.setViewport( 0, 0, width, height );
+        //}
 	};
 
 	this.getDrawingBufferSize = function ( target ) {
@@ -24102,7 +24125,7 @@ function WebGLRenderer( parameters ) {
 
 			if ( material.wireframe === true ) {
 
-				state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
+				state.setlineWidth( material.wireframelineWidth * getTargetPixelRatio() );
 				renderer.setMode( 1 );
 
 			} else {
@@ -24113,11 +24136,11 @@ function WebGLRenderer( parameters ) {
 
 		} else if ( object.isLine ) {
 
-			let lineWidth = material.linewidth;
+			let lineWidth = material.lineWidth;
 
 			if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
 
-			state.setLineWidth( lineWidth * getTargetPixelRatio() );
+			state.setlineWidth( lineWidth * getTargetPixelRatio() );
 
 			if ( object.isLineSegments ) {
 
@@ -26784,7 +26807,7 @@ InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
  *  color: <hex>,
  *  opacity: <float>,
  *
- *  linewidth: <float>,
+ *  lineWidth: <float>,
  *  linecap: "round",
  *  linejoin: "round"
  * }
@@ -26798,7 +26821,7 @@ function LineBasicMaterial( parameters ) {
 
 	this.color = new Color( 0xffffff );
 
-	this.linewidth = 1;
+	this.lineWidth = 1;
 	this.linecap = 'round';
 	this.linejoin = 'round';
 
@@ -26819,7 +26842,7 @@ LineBasicMaterial.prototype.copy = function ( source ) {
 
 	this.color.copy( source.color );
 
-	this.linewidth = source.linewidth;
+	this.lineWidth = source.lineWidth;
 	this.linecap = source.linecap;
 	this.linejoin = source.linejoin;
 
@@ -33485,7 +33508,7 @@ RawShaderMaterial.prototype.isRawShaderMaterial = true;
  *  refractionRatio: <float>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>,
+ *  wireframelineWidth: <float>,
  *
  *  skinning: <bool>,
  *  morphTargets: <bool>,
@@ -33540,7 +33563,7 @@ function MeshStandardMaterial( parameters ) {
 	this.refractionRatio = 0.98;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 	this.wireframeLinecap = 'round';
 	this.wireframeLinejoin = 'round';
 
@@ -33604,7 +33627,7 @@ MeshStandardMaterial.prototype.copy = function ( source ) {
 	this.refractionRatio = source.refractionRatio;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 	this.wireframeLinecap = source.wireframeLinecap;
 	this.wireframeLinejoin = source.wireframeLinejoin;
 
@@ -33763,7 +33786,7 @@ MeshPhysicalMaterial.prototype.copy = function ( source ) {
  *  refractionRatio: <float>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>,
+ *  wireframelineWidth: <float>,
  *
  *  skinning: <bool>,
  *  morphTargets: <bool>,
@@ -33814,7 +33837,7 @@ function MeshPhongMaterial( parameters ) {
 	this.refractionRatio = 0.98;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 	this.wireframeLinecap = 'round';
 	this.wireframeLinejoin = 'round';
 
@@ -33872,7 +33895,7 @@ MeshPhongMaterial.prototype.copy = function ( source ) {
 	this.refractionRatio = source.refractionRatio;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 	this.wireframeLinecap = source.wireframeLinecap;
 	this.wireframeLinejoin = source.wireframeLinejoin;
 
@@ -33915,7 +33938,7 @@ MeshPhongMaterial.prototype.copy = function ( source ) {
  *  alphaMap: new THREE.Texture( <Image> ),
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>,
+ *  wireframelineWidth: <float>,
  *
  *  skinning: <bool>,
  *  morphTargets: <bool>,
@@ -33960,7 +33983,7 @@ function MeshToonMaterial( parameters ) {
 	this.alphaMap = null;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 	this.wireframeLinecap = 'round';
 	this.wireframeLinejoin = 'round';
 
@@ -34010,7 +34033,7 @@ MeshToonMaterial.prototype.copy = function ( source ) {
 	this.alphaMap = source.alphaMap;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 	this.wireframeLinecap = source.wireframeLinecap;
 	this.wireframeLinejoin = source.wireframeLinejoin;
 
@@ -34038,7 +34061,7 @@ MeshToonMaterial.prototype.copy = function ( source ) {
  *  displacementBias: <float>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>
+ *  wireframelineWidth: <float>
  *
  *  skinning: <bool>,
  *  morphTargets: <bool>,
@@ -34064,7 +34087,7 @@ function MeshNormalMaterial( parameters ) {
 	this.displacementBias = 0;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 
 	this.fog = false;
 
@@ -34097,7 +34120,7 @@ MeshNormalMaterial.prototype.copy = function ( source ) {
 	this.displacementBias = source.displacementBias;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 
 	this.skinning = source.skinning;
 	this.morphTargets = source.morphTargets;
@@ -34134,7 +34157,7 @@ MeshNormalMaterial.prototype.copy = function ( source ) {
  *  refractionRatio: <float>,
  *
  *  wireframe: <boolean>,
- *  wireframeLinewidth: <float>,
+ *  wireframelineWidth: <float>,
  *
  *  skinning: <bool>,
  *  morphTargets: <bool>,
@@ -34172,7 +34195,7 @@ function MeshLambertMaterial( parameters ) {
 	this.refractionRatio = 0.98;
 
 	this.wireframe = false;
-	this.wireframeLinewidth = 1;
+	this.wireframelineWidth = 1;
 	this.wireframeLinecap = 'round';
 	this.wireframeLinejoin = 'round';
 
@@ -34217,7 +34240,7 @@ MeshLambertMaterial.prototype.copy = function ( source ) {
 	this.refractionRatio = source.refractionRatio;
 
 	this.wireframe = source.wireframe;
-	this.wireframeLinewidth = source.wireframeLinewidth;
+	this.wireframelineWidth = source.wireframelineWidth;
 	this.wireframeLinecap = source.wireframeLinecap;
 	this.wireframeLinejoin = source.wireframeLinejoin;
 
@@ -34335,7 +34358,7 @@ MeshMatcapMaterial.prototype.copy = function ( source ) {
  *  color: <hex>,
  *  opacity: <float>,
  *
- *  linewidth: <float>,
+ *  lineWidth: <float>,
  *
  *  scale: <float>,
  *  dashSize: <float>,
@@ -36754,8 +36777,8 @@ FileLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 					for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
 						const callback = callbacks[ i ];
-						if ( callback.onLoad ) callback.onLoad( response );
-
+						if ( callback.onLoad ) callback.onLoad( response, event.total); //xzw add event.total
+  
 					}
 
 					scope.manager.itemEnd( url );
@@ -40379,13 +40402,13 @@ MaterialLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 		if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass;
 
 		if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;
-		if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth;
+		if ( json.wireframelineWidth !== undefined ) material.wireframelineWidth = json.wireframelineWidth;
 		if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap;
 		if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin;
 
 		if ( json.rotation !== undefined ) material.rotation = json.rotation;
 
-		if ( json.linewidth !== 1 ) material.linewidth = json.linewidth;
+		if ( json.lineWidth !== 1 ) material.lineWidth = json.lineWidth;
 		if ( json.dashSize !== undefined ) material.dashSize = json.dashSize;
 		if ( json.gapSize !== undefined ) material.gapSize = json.gapSize;
 		if ( json.scale !== undefined ) material.scale = json.scale;
@@ -42097,7 +42120,7 @@ ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 			return res.blob();
 
 		} ).then( function ( blob ) {
-
+            //console.log('getBlob', url   )
 			return createImageBitmap( blob, scope.options );
 
 		} ).then( function ( imageBitmap ) {
@@ -42108,9 +42131,9 @@ ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 			scope.manager.itemEnd( url );
 
-		} ).catch( function ( e ) {
-
-			if ( onError ) onError( e );
+		} ).catch( function ( e ) { 
+            //console.log('error', url, e)
+			if ( onError ) onError( e, url );
 
 			scope.manager.itemError( url );
 			scope.manager.itemEnd( url );

+ 7 - 12
libs/three.js/lines/Line2.js

@@ -2,23 +2,18 @@ import { LineSegments2 } from '../lines/LineSegments2.js';
 import { LineGeometry } from '../lines/LineGeometry.js';
 import { LineMaterial } from '../lines/LineMaterial.js';
 
-var Line2 = function ( geometry, material ) {
+class Line2 extends LineSegments2 {
 
-	if ( geometry === undefined ) geometry = new LineGeometry();
-	if ( material === undefined ) material = new LineMaterial( { color: Math.random() * 0xffffff } );
+	constructor( geometry = new LineGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) {
 
-	LineSegments2.call( this, geometry, material );
+		super( geometry, material );
 
-	this.type = 'Line2';
+		this.isLine2 = true;
 
-};
+		this.type = 'Line2';
 
-Line2.prototype = Object.assign( Object.create( LineSegments2.prototype ), {
+	}
 
-	constructor: Line2,
-
-	isLine2: true
-
-} );
+}
 
 export { Line2 };

+ 22 - 40
libs/three.js/lines/LineGeometry.js

@@ -1,27 +1,25 @@
 import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js';
 
-var LineGeometry = function () {
+class LineGeometry extends LineSegmentsGeometry {
 
-	LineSegmentsGeometry.call( this );
+	constructor() {
 
-	this.type = 'LineGeometry';
+		super();
 
-};
+		this.isLineGeometry = true;
 
-LineGeometry.prototype = Object.assign( Object.create( LineSegmentsGeometry.prototype ), {
+		this.type = 'LineGeometry';
 
-	constructor: LineGeometry,
-
-	isLineGeometry: true,
+	}
 
-	setPositions: function ( array ) {
+	setPositions( array ) {
 
 		// converts [ x1, y1, z1,  x2, y2, z2, ... ] to pairs format
 
-		var length = array.length - 3;
-		var points = new Float32Array( 2 * length );
+		const length = array.length - 3;
+		const points = new Float32Array( 2 * length );
 
-		for ( var i = 0; i < length; i += 3 ) {
+		for ( let i = 0; i < length; i += 3 ) {
 
 			points[ 2 * i ] = array[ i ];
 			points[ 2 * i + 1 ] = array[ i + 1 ];
@@ -33,20 +31,20 @@ LineGeometry.prototype = Object.assign( Object.create( LineSegmentsGeometry.prot
 
 		}
 
-		LineSegmentsGeometry.prototype.setPositions.call( this, points );
+		super.setPositions( points );
 
 		return this;
 
-	},
+	}
 
-	setColors: function ( array ) {
+	setColors( array ) {
 
 		// converts [ r1, g1, b1,  r2, g2, b2, ... ] to pairs format
 
-		var length = array.length - 3;
-		var colors = new Float32Array( 2 * length );
+		const length = array.length - 3;
+		const colors = new Float32Array( 2 * length );
 
-		for ( var i = 0; i < length; i += 3 ) {
+		for ( let i = 0; i < length; i += 3 ) {
 
 			colors[ 2 * i ] = array[ i ];
 			colors[ 2 * i + 1 ] = array[ i + 1 ];
@@ -58,40 +56,24 @@ LineGeometry.prototype = Object.assign( Object.create( LineSegmentsGeometry.prot
 
 		}
 
-		LineSegmentsGeometry.prototype.setColors.call( this, colors );
+		super.setColors( colors );
 
 		return this;
 
-	},
-
-	fromLine: function ( line ) {
-
-		var geometry = line.geometry;
-
-		if ( geometry.isGeometry ) {
-
-			this.setPositions( geometry.vertices );
+	}
 
-		} else if ( geometry.isBufferGeometry ) {
+	fromLine( line ) {
 
-			this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
+		const geometry = line.geometry;
 
-		}
+		this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
 
 		// set colors, maybe
 
 		return this;
 
-	},
-
-	copy: function ( /* source */ ) {
-
-		// todo
-
-		return this;
-
 	}
 
-} );
+}
 
 export { LineGeometry };

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 560 - 241
libs/three.js/lines/LineMaterial.js


+ 274 - 128
libs/three.js/lines/LineSegments2.js

@@ -1,209 +1,355 @@
 import {
+	Box3,
 	InstancedInterleavedBuffer,
 	InterleavedBufferAttribute,
 	Line3,
 	MathUtils,
 	Matrix4,
 	Mesh,
+	Sphere,
 	Vector3,
 	Vector4
-} from '../build/three.module.js';
+} from '../build/three.module.js'; 
 import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js';
 import { LineMaterial } from '../lines/LineMaterial.js';
 
-var LineSegments2 = function ( geometry, material ) {
+const _start = new Vector3();
+const _end = new Vector3();
 
-	if ( geometry === undefined ) geometry = new LineSegmentsGeometry();
-	if ( material === undefined ) material = new LineMaterial( { color: Math.random() * 0xffffff } );
+const _start4 = new Vector4();
+const _end4 = new Vector4();
 
-	Mesh.call( this, geometry, material );
+const _ssOrigin = new Vector4();
+const _ssOrigin3 = new Vector3();
+const _mvMatrix = new Matrix4();
+const _line = new Line3();
+const _closestPoint = new Vector3();
 
-	this.type = 'LineSegments2';
+const _box = new Box3();
+const _sphere = new Sphere();
+const _clipToWorldVector = new Vector4();
 
-};
+let _ray, _instanceStart, _instanceEnd, _lineWidth;
 
-LineSegments2.prototype = Object.assign( Object.create( Mesh.prototype ), {
+// Returns the margin required to expand by in world space given the distance from the camera,
+// line width, resolution, and camera projection
+function getWorldSpaceHalfWidth( camera, distance, resolution ) {
 
-	constructor: LineSegments2,
+	// transform into clip space, adjust the x and y values by the pixel width offset, then
+	// transform back into world space to get world offset. Note clip space is [-1, 1] so full
+	// width does not need to be halved.
+	_clipToWorldVector.set( 0, 0, - distance, 1.0 ).applyMatrix4( camera.projectionMatrix );
+	_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
+	_clipToWorldVector.x = _lineWidth / resolution.width;
+	_clipToWorldVector.y = _lineWidth / resolution.height;
+	_clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse );
+	_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
 
-	isLineSegments2: true,
+	return Math.abs( Math.max( _clipToWorldVector.x, _clipToWorldVector.y ) );
 
-	computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
+}
 
-		var start = new Vector3();
-		var end = new Vector3();
+function raycastWorldUnits( lineSegments, intersects ) {
 
-		return function computeLineDistances() {
+	for ( let i = 0, l = _instanceStart.count; i < l; i ++ ) {
 
-			var geometry = this.geometry;
+		_line.start.fromBufferAttribute( _instanceStart, i );
+		_line.end.fromBufferAttribute( _instanceEnd, i );
 
-			var instanceStart = geometry.attributes.instanceStart;
-			var instanceEnd = geometry.attributes.instanceEnd;
-			var lineDistances = new Float32Array( 2 * instanceStart.data.count );
+		const pointOnLine = new Vector3();
+		const point = new Vector3();
 
-			for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
+		_ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
+		const isInside = point.distanceTo( pointOnLine ) < _lineWidth * 0.5;
 
-				start.fromBufferAttribute( instanceStart, i );
-				end.fromBufferAttribute( instanceEnd, i );
+		if ( isInside ) {
 
-				lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
-				lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
+			intersects.push( {
+				point,
+				pointOnLine,
+				distance: _ray.origin.distanceTo( point ),
+				object: lineSegments,
+				face: null,
+				faceIndex: i,
+				uv: null,
+				uv2: null,
+			} );
 
-			}
+		}
 
-			var instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
+	}
 
-			geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
-			geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
+}
 
-			return this;
+function raycastScreenSpace( lineSegments, camera, intersects ) {
 
-		};
+	const projectionMatrix = camera.projectionMatrix;
+	const material = lineSegments.material;
+	const resolution = material.resolution;
+	const matrixWorld = lineSegments.matrixWorld;
 
-	}() ),
+	const geometry = lineSegments.geometry;
+	const instanceStart = geometry.attributes.instanceStart;
+	const instanceEnd = geometry.attributes.instanceEnd;
 
-	raycast: ( function () {
+	const near = - camera.near;
 
-		var start = new Vector4();
-		var end = new Vector4();
+	//
 
-		var ssOrigin = new Vector4();
-		var ssOrigin3 = new Vector3();
-		var mvMatrix = new Matrix4();
-		var line = new Line3();
-		var closestPoint = new Vector3();
+	// pick a point 1 unit out along the ray to avoid the ray origin
+	// sitting at the camera origin which will cause "w" to be 0 when
+	// applying the projection matrix.
+	_ray.at( 1, _ssOrigin );
 
-		return function raycast( raycaster, intersects ) {
+	// ndc space [ - 1.0, 1.0 ]
+	_ssOrigin.w = 1;
+	_ssOrigin.applyMatrix4( camera.matrixWorldInverse );
+	_ssOrigin.applyMatrix4( projectionMatrix );
+	_ssOrigin.multiplyScalar( 1 / _ssOrigin.w );
 
-			if ( raycaster.camera === null ) {
+	// screen space
+	_ssOrigin.x *= resolution.x / 2;
+	_ssOrigin.y *= resolution.y / 2;
+	_ssOrigin.z = 0;
 
-				console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
+	_ssOrigin3.copy( _ssOrigin );
 
-			}
+	_mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
 
-			var threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
+	for ( let i = 0, l = instanceStart.count; i < l; i ++ ) {
 
-			var ray = raycaster.ray;
-			var camera = raycaster.camera;
-			var projectionMatrix = camera.projectionMatrix;
+		_start4.fromBufferAttribute( instanceStart, i );
+		_end4.fromBufferAttribute( instanceEnd, i );
 
-			var geometry = this.geometry;
-			var material = this.material;
-			var resolution = material.resolution;
-			var lineWidth = material.linewidth + threshold;
+		_start4.w = 1;
+		_end4.w = 1;
 
-			var instanceStart = geometry.attributes.instanceStart;
-			var instanceEnd = geometry.attributes.instanceEnd;
+		// camera space
+		_start4.applyMatrix4( _mvMatrix );
+		_end4.applyMatrix4( _mvMatrix );
 
-			// pick a point 1 unit out along the ray to avoid the ray origin
-			// sitting at the camera origin which will cause "w" to be 0 when
-			// applying the projection matrix.
-			ray.at( 1, ssOrigin );
+		// skip the segment if it's entirely behind the camera
+		const isBehindCameraNear = _start4.z > near && _end4.z > near;
+		if ( isBehindCameraNear ) {
 
-			// ndc space [ - 1.0, 1.0 ]
-			ssOrigin.w = 1;
-			ssOrigin.applyMatrix4( camera.matrixWorldInverse );
-			ssOrigin.applyMatrix4( projectionMatrix );
-			ssOrigin.multiplyScalar( 1 / ssOrigin.w );
+			continue;
 
-			// screen space
-			ssOrigin.x *= resolution.x / 2;
-			ssOrigin.y *= resolution.y / 2;
-			ssOrigin.z = 0;
+		}
 
-			ssOrigin3.copy( ssOrigin );
+		// trim the segment if it extends behind camera near
+		if ( _start4.z > near ) {
 
-			var matrixWorld = this.matrixWorld;
-			mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
+			const deltaDist = _start4.z - _end4.z;
+			const t = ( _start4.z - near ) / deltaDist;
+			_start4.lerp( _end4, t );
 
-			for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {
+		} else if ( _end4.z > near ) {
 
-				start.fromBufferAttribute( instanceStart, i );
-				end.fromBufferAttribute( instanceEnd, i );
+			const deltaDist = _end4.z - _start4.z;
+			const t = ( _end4.z - near ) / deltaDist;
+			_end4.lerp( _start4, t );
 
-				start.w = 1;
-				end.w = 1;
+		}
 
-				// camera space
-				start.applyMatrix4( mvMatrix );
-				end.applyMatrix4( mvMatrix );
+		// clip space
+		_start4.applyMatrix4( projectionMatrix );
+		_end4.applyMatrix4( projectionMatrix );
 
-				// clip space
-				start.applyMatrix4( projectionMatrix );
-				end.applyMatrix4( projectionMatrix );
+		// ndc space [ - 1.0, 1.0 ]
+		_start4.multiplyScalar( 1 / _start4.w );
+		_end4.multiplyScalar( 1 / _end4.w );
 
-				// ndc space [ - 1.0, 1.0 ]
-				start.multiplyScalar( 1 / start.w );
-				end.multiplyScalar( 1 / end.w );
+		// screen space
+		_start4.x *= resolution.x / 2;
+		_start4.y *= resolution.y / 2;
 
-				// skip the segment if it's outside the camera near and far planes
-				var isBehindCameraNear = start.z < - 1 && end.z < - 1;
-				var isPastCameraFar = start.z > 1 && end.z > 1;
-				if ( isBehindCameraNear || isPastCameraFar ) {
+		_end4.x *= resolution.x / 2;
+		_end4.y *= resolution.y / 2;
 
-					continue;
+		// create 2d segment
+		_line.start.copy( _start4 );
+		_line.start.z = 0;
 
-				}
+		_line.end.copy( _end4 );
+		_line.end.z = 0;
 
-				// screen space
-				start.x *= resolution.x / 2;
-				start.y *= resolution.y / 2;
+		// get closest point on ray to segment
+		const param = _line.closestPointToPointParameter( _ssOrigin3, true );
+		_line.at( param, _closestPoint );
 
-				end.x *= resolution.x / 2;
-				end.y *= resolution.y / 2;
+		// check if the intersection point is within clip space
+		const zPos = MathUtils.lerp( _start4.z, _end4.z, param );
+		const isInClipSpace = zPos >= - 1 && zPos <= 1;
 
-				// create 2d segment
-				line.start.copy( start );
-				line.start.z = 0;
+		const isInside = _ssOrigin3.distanceTo( _closestPoint ) < _lineWidth * 0.5;
 
-				line.end.copy( end );
-				line.end.z = 0;
+		if ( isInClipSpace && isInside ) {
 
-				// get closest point on ray to segment
-				var param = line.closestPointToPointParameter( ssOrigin3, true );
-				line.at( param, closestPoint );
+			_line.start.fromBufferAttribute( instanceStart, i );
+			_line.end.fromBufferAttribute( instanceEnd, i );
 
-				// check if the intersection point is within clip space
-				var zPos = MathUtils.lerp( start.z, end.z, param );
-				var isInClipSpace = zPos >= - 1 && zPos <= 1;
+			_line.start.applyMatrix4( matrixWorld );
+			_line.end.applyMatrix4( matrixWorld );
 
-				var isInside = ssOrigin3.distanceTo( closestPoint ) < lineWidth * 0.5;
+			const pointOnLine = new Vector3();
+			const point = new Vector3();
 
-				if ( isInClipSpace && isInside ) {
+			_ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
 
-					line.start.fromBufferAttribute( instanceStart, i );
-					line.end.fromBufferAttribute( instanceEnd, i );
+			intersects.push( {
+				point: point,
+				pointOnLine: pointOnLine,
+				distance: _ray.origin.distanceTo( point ),
+				object: lineSegments,
+				face: null,
+				faceIndex: i,
+				uv: null,
+				uv2: null,
+			} );
 
-					line.start.applyMatrix4( matrixWorld );
-					line.end.applyMatrix4( matrixWorld );
+		}
 
-					var pointOnLine = new Vector3();
-					var point = new Vector3();
+	}
 
-					ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );
+}
 
-					intersects.push( {
+class LineSegments2 extends Mesh {
 
-						point: point,
-						pointOnLine: pointOnLine,
-						distance: ray.origin.distanceTo( point ),
+	constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) {
 
-						object: this,
-						face: null,
-						faceIndex: i,
-						uv: null,
-						uv2: null,
+		super( geometry, material );
 
-					} );
+		this.isLineSegments2 = true;
 
-				}
+		this.type = 'LineSegments2';
 
-			}
+	}
 
-		};
+	// for backwards-compatibility, but could be a method of LineSegmentsGeometry...
 
-	}() )
+	computeLineDistances() {
 
-} );
+		const geometry = this.geometry;
+
+		const instanceStart = geometry.attributes.instanceStart;
+		const instanceEnd = geometry.attributes.instanceEnd;
+		const lineDistances = new Float32Array( 2 * instanceStart.count );
+
+		for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
+
+			_start.fromBufferAttribute( instanceStart, i );
+			_end.fromBufferAttribute( instanceEnd, i );
+
+			lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
+			lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end );
+
+		}
+
+		const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
+
+		geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
+		geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
+
+		return this;
+
+	}
+
+	raycast( raycaster, intersects ) {
+
+		const worldUnits = this.material.worldUnits;
+		const camera = raycaster.camera;
+
+		if ( camera === null && ! worldUnits ) {
+
+			console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2 while worldUnits is set to false.' );
+
+		}
+
+		const threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
+
+		_ray = raycaster.ray;
+
+		const matrixWorld = this.matrixWorld;
+		const geometry = this.geometry;
+		const material = this.material;
+
+		_lineWidth = material.lineWidth + threshold;
+
+		_instanceStart = geometry.attributes.instanceStart;
+		_instanceEnd = geometry.attributes.instanceEnd;
+
+		// check if we intersect the sphere bounds
+		if ( geometry.boundingSphere === null ) {
+
+			geometry.computeBoundingSphere();
+
+		}
+
+		_sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld );
+
+		// increase the sphere bounds by the worst case line screen space width
+		let sphereMargin;
+		if ( worldUnits ) {
+
+			sphereMargin = _lineWidth * 0.5;
+
+		} else {
+
+			const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( _ray.origin ) );
+			sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, material.resolution );
+
+		}
+
+		_sphere.radius += sphereMargin;
+
+		if ( _ray.intersectsSphere( _sphere ) === false ) {
+
+			return;
+
+		}
+
+		// check if we intersect the box bounds
+		if ( geometry.boundingBox === null ) {
+
+			geometry.computeBoundingBox();
+
+		}
+
+		_box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
+
+		// increase the box bounds by the worst case line width
+		let boxMargin;
+		if ( worldUnits ) {
+
+			boxMargin = _lineWidth * 0.5;
+
+		} else {
+
+			const distanceToBox = Math.max( camera.near, _box.distanceToPoint( _ray.origin ) );
+			boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, material.resolution );
+
+		}
+
+		_box.expandByScalar( boxMargin );
+
+		if ( _ray.intersectsBox( _box ) === false ) {
+
+			return;
+
+		}
+
+		if ( worldUnits ) {
+
+			raycastWorldUnits( this, intersects );
+
+		} else {
+
+			raycastScreenSpace( this, camera, intersects );
+
+		}
+
+	}
+
+}
 
 export { LineSegments2 };

+ 76 - 95
libs/three.js/lines/LineSegmentsGeometry.js

@@ -9,32 +9,33 @@ import {
 	WireframeGeometry
 } from '../build/three.module.js';
 
-var LineSegmentsGeometry = function () {
+const _box = new Box3();
+const _vector = new Vector3();
 
-	InstancedBufferGeometry.call( this );
+class LineSegmentsGeometry extends InstancedBufferGeometry {
 
-	this.type = 'LineSegmentsGeometry';
+	constructor() {
 
-	var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
-	var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
-	var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
+		super();
 
-	this.setIndex( index );
-	this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
-	this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+		this.isLineSegmentsGeometry = true;
 
-};
+		this.type = 'LineSegmentsGeometry';
 
-LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGeometry.prototype ), {
+		const positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
+		const uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
+		const index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
 
-	constructor: LineSegmentsGeometry,
+		this.setIndex( index );
+		this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
+		this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
 
-	isLineSegmentsGeometry: true,
+	}
 
-	applyMatrix4: function ( matrix ) {
+	applyMatrix4( matrix ) {
 
-		var start = this.attributes.instanceStart;
-		var end = this.attributes.instanceEnd;
+		const start = this.attributes.instanceStart;
+		const end = this.attributes.instanceEnd;
 
 		if ( start !== undefined ) {
 
@@ -60,11 +61,11 @@ LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGe
 
 		return this;
 
-	},
+	}
 
-	setPositions: function ( array ) {
+	setPositions( array ) {
 
-		var lineSegments;
+		let lineSegments;
 
 		if ( array instanceof Float32Array ) {
 
@@ -76,7 +77,7 @@ LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGe
 
 		}
 
-		var instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
+		const instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
 
 		this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
 		this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
@@ -88,11 +89,11 @@ LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGe
 
 		return this;
 
-	},
+	}
 
-	setColors: function ( array ) {
+	setColors( array ) {
 
-		var colors;
+		let colors;
 
 		if ( array instanceof Float32Array ) {
 
@@ -104,32 +105,32 @@ LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGe
 
 		}
 
-		var instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
+		const instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
 
 		this.setAttribute( 'instanceColorStart', new InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
 		this.setAttribute( 'instanceColorEnd', new InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
 
 		return this;
 
-	},
+	}
 
-	fromWireframeGeometry: function ( geometry ) {
+	fromWireframeGeometry( geometry ) {
 
 		this.setPositions( geometry.attributes.position.array );
 
 		return this;
 
-	},
+	}
 
-	fromEdgesGeometry: function ( geometry ) {
+	fromEdgesGeometry( geometry ) {
 
 		this.setPositions( geometry.attributes.position.array );
 
 		return this;
 
-	},
+	}
 
-	fromMesh: function ( mesh ) {
+	fromMesh( mesh ) {
 
 		this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) );
 
@@ -137,117 +138,97 @@ LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGe
 
 		return this;
 
-	},
-
-	fromLineSegments: function ( lineSegments ) {
-
-		var geometry = lineSegments.geometry;
-
-		if ( geometry.isGeometry ) {
-
-			this.setPositions( geometry.vertices );
+	}
 
-		} else if ( geometry.isBufferGeometry ) {
+	fromLineSegments( lineSegments ) {
 
-			this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
+		const geometry = lineSegments.geometry;
 
-		}
+		this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
 
 		// set colors, maybe
 
 		return this;
 
-	},
-
-	computeBoundingBox: function () {
-
-		var box = new Box3();
-
-		return function computeBoundingBox() {
-
-			if ( this.boundingBox === null ) {
-
-				this.boundingBox = new Box3();
-
-			}
+	}
 
-			var start = this.attributes.instanceStart;
-			var end = this.attributes.instanceEnd;
+	computeBoundingBox() {
 
-			if ( start !== undefined && end !== undefined ) {
+		if ( this.boundingBox === null ) {
 
-				this.boundingBox.setFromBufferAttribute( start );
+			this.boundingBox = new Box3();
 
-				box.setFromBufferAttribute( end );
+		}
 
-				this.boundingBox.union( box );
+		const start = this.attributes.instanceStart;
+		const end = this.attributes.instanceEnd;
 
-			}
+		if ( start !== undefined && end !== undefined ) {
 
-		};
+			this.boundingBox.setFromBufferAttribute( start );
 
-	}(),
+			_box.setFromBufferAttribute( end );
 
-	computeBoundingSphere: function () {
+			this.boundingBox.union( _box );
 
-		var vector = new Vector3();
+		}
 
-		return function computeBoundingSphere() {
+	}
 
-			if ( this.boundingSphere === null ) {
+	computeBoundingSphere() {
 
-				this.boundingSphere = new Sphere();
+		if ( this.boundingSphere === null ) {
 
-			}
+			this.boundingSphere = new Sphere();
 
-			if ( this.boundingBox === null ) {
+		}
 
-				this.computeBoundingBox();
+		if ( this.boundingBox === null ) {
 
-			}
+			this.computeBoundingBox();
 
-			var start = this.attributes.instanceStart;
-			var end = this.attributes.instanceEnd;
+		}
 
-			if ( start !== undefined && end !== undefined ) {
+		const start = this.attributes.instanceStart;
+		const end = this.attributes.instanceEnd;
 
-				var center = this.boundingSphere.center;
+		if ( start !== undefined && end !== undefined ) {
 
-				this.boundingBox.getCenter( center );
+			const center = this.boundingSphere.center;
 
-				var maxRadiusSq = 0;
+			this.boundingBox.getCenter( center );
 
-				for ( var i = 0, il = start.count; i < il; i ++ ) {
+			let maxRadiusSq = 0;
 
-					vector.fromBufferAttribute( start, i );
-					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+			for ( let i = 0, il = start.count; i < il; i ++ ) {
 
-					vector.fromBufferAttribute( end, i );
-					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+				_vector.fromBufferAttribute( start, i );
+				maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
 
-				}
+				_vector.fromBufferAttribute( end, i );
+				maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
 
-				this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+			}
 
-				if ( isNaN( this.boundingSphere.radius ) ) {
+			this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
 
-					console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
+			if ( isNaN( this.boundingSphere.radius ) ) {
 
-				}
+				console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
 
 			}
 
-		};
+		}
 
-	}(),
+	}
 
-	toJSON: function () {
+	toJSON() {
 
 		// todo
 
-	},
+	}
 
-	applyMatrix: function ( matrix ) {
+	applyMatrix( matrix ) {
 
 		console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' );
 
@@ -255,6 +236,6 @@ LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGe
 
 	}
 
-} );
+}
 
 export { LineSegmentsGeometry };

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

@@ -5,7 +5,7 @@ import {
 	RGBA_S3TC_DXT5_Format,
 	RGB_ETC1_Format,
 	RGB_S3TC_DXT1_Format
-} from '../../../build/three.module.js';
+} from '../build/three.module.js';
 
 var DDSLoader = function ( manager ) {
 

+ 117 - 170
libs/three.js/loaders/DRACOLoader.js

@@ -3,89 +3,68 @@ import {
 	BufferGeometry,
 	FileLoader,
 	Loader
-} from '../../../build/three.module.js';
+} from '../build/three.module.js';
 
-var DRACOLoader = function ( manager ) {
+const _taskCache = new WeakMap();
 
-	Loader.call( this, manager );
+class DRACOLoader extends Loader {
 
-	this.decoderPath = '';
-	this.decoderConfig = {};
-	this.decoderBinary = null;
-	this.decoderPending = null;
+	constructor( manager ) {
 
-	this.workerLimit = 4;
-	this.workerPool = [];
-	this.workerNextTaskID = 1;
-	this.workerSourceURL = '';
+		super( manager );
 
-	this.defaultAttributeIDs = {
-		position: 'POSITION',
-		normal: 'NORMAL',
-		color: 'COLOR',
-		uv: 'TEX_COORD'
-	};
-	this.defaultAttributeTypes = {
-		position: 'Float32Array',
-		normal: 'Float32Array',
-		color: 'Float32Array',
-		uv: 'Float32Array'
-	};
+		this.decoderPath = '';
+		this.decoderConfig = {};
+		this.decoderBinary = null;
+		this.decoderPending = null;
 
-};
+		this.workerLimit = 4;
+		this.workerPool = [];
+		this.workerNextTaskID = 1;
+		this.workerSourceURL = '';
 
-DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+		this.defaultAttributeIDs = {
+			position: 'POSITION',
+			normal: 'NORMAL',
+			color: 'COLOR',
+			uv: 'TEX_COORD'
+		};
+		this.defaultAttributeTypes = {
+			position: 'Float32Array',
+			normal: 'Float32Array',
+			color: 'Float32Array',
+			uv: 'Float32Array'
+		};
 
-	constructor: DRACOLoader,
+	}
 
-	setDecoderPath: function ( path ) {
+	setDecoderPath( path ) {
 
 		this.decoderPath = path;
 
 		return this;
 
-	},
+	}
 
-	setDecoderConfig: function ( config ) {
+	setDecoderConfig( config ) {
 
 		this.decoderConfig = config;
 
 		return this;
 
-	},
+	}
 
-	setWorkerLimit: function ( workerLimit ) {
+	setWorkerLimit( workerLimit ) {
 
 		this.workerLimit = workerLimit;
 
 		return this;
 
-	},
-
-	/** @deprecated */
-	setVerbosity: function () {
-
-		console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' );
-
-	},
-
-	/** @deprecated */
-	setDrawMode: function () {
-
-		console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' );
-
-	},
-
-	/** @deprecated */
-	setSkipDequantization: function () {
-
-		console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' );
-
-	},
+	}
 
-	load: function ( url, onLoad, onProgress, onError ) {
+	load( url, onLoad, onProgress, onError ) {
 
-		var loader = new FileLoader( this.manager );
+		const loader = new FileLoader( this.manager );
 
 		loader.setPath( this.path );
 		loader.setResponseType( 'arraybuffer' );
@@ -94,7 +73,7 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		loader.load( url, ( buffer ) => {
 
-			var taskConfig = {
+			const taskConfig = {
 				attributeIDs: this.defaultAttributeIDs,
 				attributeTypes: this.defaultAttributeTypes,
 				useUniqueIDs: false
@@ -106,12 +85,12 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		}, onProgress, onError );
 
-	},
+	}
 
 	/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
-	decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) {
+	decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) {
 
-		var taskConfig = {
+		const taskConfig = {
 			attributeIDs: attributeIDs || this.defaultAttributeIDs,
 			attributeTypes: attributeTypes || this.defaultAttributeTypes,
 			useUniqueIDs: !! attributeIDs
@@ -119,16 +98,16 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		this.decodeGeometry( buffer, taskConfig ).then( callback );
 
-	},
+	}
 
-	decodeGeometry: function ( buffer, taskConfig ) {
+	decodeGeometry( buffer, taskConfig ) {
 
 		// TODO: For backward-compatibility, support 'attributeTypes' objects containing
 		// references (rather than names) to typed array constructors. These must be
 		// serialized before sending them to the worker.
-		for ( var attribute in taskConfig.attributeTypes ) {
+		for ( const attribute in taskConfig.attributeTypes ) {
 
-			var type = taskConfig.attributeTypes[ attribute ];
+			const type = taskConfig.attributeTypes[ attribute ];
 
 			if ( type.BYTES_PER_ELEMENT !== undefined ) {
 
@@ -140,13 +119,13 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		//
 
-		var taskKey = JSON.stringify( taskConfig );
+		const taskKey = JSON.stringify( taskConfig );
 
 		// Check for an existing task using this buffer. A transferred buffer cannot be transferred
 		// again from this thread.
-		if ( DRACOLoader.taskCache.has( buffer ) ) {
+		if ( _taskCache.has( buffer ) ) {
 
-			var cachedTask = DRACOLoader.taskCache.get( buffer );
+			const cachedTask = _taskCache.get( buffer );
 
 			if ( cachedTask.key === taskKey ) {
 
@@ -171,13 +150,13 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		//
 
-		var worker;
-		var taskID = this.workerNextTaskID ++;
-		var taskCost = buffer.byteLength;
+		let worker;
+		const taskID = this.workerNextTaskID ++;
+		const taskCost = buffer.byteLength;
 
 		// Obtain a worker and assign a task, and construct a geometry instance
 		// when the task completes.
-		var geometryPending = this._getWorker( taskID, taskCost )
+		const geometryPending = this._getWorker( taskID, taskCost )
 			.then( ( _worker ) => {
 
 				worker = _worker;
@@ -212,7 +191,7 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 			} );
 
 		// Cache the task result.
-		DRACOLoader.taskCache.set( buffer, {
+		_taskCache.set( buffer, {
 
 			key: taskKey,
 			promise: geometryPending
@@ -221,11 +200,11 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		return geometryPending;
 
-	},
+	}
 
-	_createGeometry: function ( geometryData ) {
+	_createGeometry( geometryData ) {
 
-		var geometry = new BufferGeometry();
+		const geometry = new BufferGeometry();
 
 		if ( geometryData.index ) {
 
@@ -233,12 +212,12 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		}
 
-		for ( var i = 0; i < geometryData.attributes.length; i ++ ) {
+		for ( let i = 0; i < geometryData.attributes.length; i ++ ) {
 
-			var attribute = geometryData.attributes[ i ];
-			var name = attribute.name;
-			var array = attribute.array;
-			var itemSize = attribute.itemSize;
+			const attribute = geometryData.attributes[ i ];
+			const name = attribute.name;
+			const array = attribute.array;
+			const itemSize = attribute.itemSize;
 
 			geometry.setAttribute( name, new BufferAttribute( array, itemSize ) );
 
@@ -246,11 +225,11 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		return geometry;
 
-	},
+	}
 
-	_loadLibrary: function ( url, responseType ) {
+	_loadLibrary( url, responseType ) {
 
-		var loader = new FileLoader( this.manager );
+		const loader = new FileLoader( this.manager );
 		loader.setPath( this.decoderPath );
 		loader.setResponseType( responseType );
 		loader.setWithCredentials( this.withCredentials );
@@ -261,22 +240,22 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		} );
 
-	},
+	}
 
-	preload: function () {
+	preload() {
 
 		this._initDecoder();
 
 		return this;
 
-	},
+	}
 
-	_initDecoder: function () {
+	_initDecoder() {
 
 		if ( this.decoderPending ) return this.decoderPending;
 
-		var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
-		var librariesPending = [];
+		const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
+		const librariesPending = [];
 
 		if ( useJS ) {
 
@@ -292,7 +271,7 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 		this.decoderPending = Promise.all( librariesPending )
 			.then( ( libraries ) => {
 
-				var jsContent = libraries[ 0 ];
+				const jsContent = libraries[ 0 ];
 
 				if ( ! useJS ) {
 
@@ -300,9 +279,9 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 				}
 
-				var fn = DRACOLoader.DRACOWorker.toString();
+				const fn = DRACOWorker.toString();
 
-				var body = [
+				const body = [
 					'/* draco decoder */',
 					jsContent,
 					'',
@@ -316,15 +295,15 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		return this.decoderPending;
 
-	},
+	}
 
-	_getWorker: function ( taskID, taskCost ) {
+	_getWorker( taskID, taskCost ) {
 
 		return this._initDecoder().then( () => {
 
 			if ( this.workerPool.length < this.workerLimit ) {
 
-				var worker = new Worker( this.workerSourceURL );
+				const worker = new Worker( this.workerSourceURL );
 
 				worker._callbacks = {};
 				worker._taskCosts = {};
@@ -334,7 +313,7 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 				worker.onmessage = function ( e ) {
 
-					var message = e.data;
+					const message = e.data;
 
 					switch ( message.type ) {
 
@@ -365,32 +344,32 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 			}
 
-			var worker = this.workerPool[ this.workerPool.length - 1 ];
+			const worker = this.workerPool[ this.workerPool.length - 1 ];
 			worker._taskCosts[ taskID ] = taskCost;
 			worker._taskLoad += taskCost;
 			return worker;
 
 		} );
 
-	},
+	}
 
-	_releaseTask: function ( worker, taskID ) {
+	_releaseTask( worker, taskID ) {
 
 		worker._taskLoad -= worker._taskCosts[ taskID ];
 		delete worker._callbacks[ taskID ];
 		delete worker._taskCosts[ taskID ];
 
-	},
+	}
 
-	debug: function () {
+	debug() {
 
 		console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
 
-	},
+	}
 
-	dispose: function () {
+	dispose() {
 
-		for ( var i = 0; i < this.workerPool.length; ++ i ) {
+		for ( let i = 0; i < this.workerPool.length; ++ i ) {
 
 			this.workerPool[ i ].terminate();
 
@@ -402,18 +381,18 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 	}
 
-} );
+}
 
 /* WEB WORKER */
 
-DRACOLoader.DRACOWorker = function () {
+function DRACOWorker() {
 
-	var decoderConfig;
-	var decoderPending;
+	let decoderConfig;
+	let decoderPending;
 
 	onmessage = function ( e ) {
 
-		var message = e.data;
+		const message = e.data;
 
 		switch ( message.type ) {
 
@@ -434,20 +413,20 @@ DRACOLoader.DRACOWorker = function () {
 				break;
 
 			case 'decode':
-				var buffer = message.buffer;
-				var taskConfig = message.taskConfig;
+				const buffer = message.buffer;
+				const taskConfig = message.taskConfig;
 				decoderPending.then( ( module ) => {
 
-					var draco = module.draco;
-					var decoder = new draco.Decoder();
-					var decoderBuffer = new draco.DecoderBuffer();
+					const draco = module.draco;
+					const decoder = new draco.Decoder();
+					const decoderBuffer = new draco.DecoderBuffer();
 					decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
 
 					try {
 
-						var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
+						const geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
 
-						var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
+						const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
 
 						if ( geometry.index ) buffers.push( geometry.index.array.buffer );
 
@@ -475,13 +454,13 @@ DRACOLoader.DRACOWorker = function () {
 
 	function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
 
-		var attributeIDs = taskConfig.attributeIDs;
-		var attributeTypes = taskConfig.attributeTypes;
+		const attributeIDs = taskConfig.attributeIDs;
+		const attributeTypes = taskConfig.attributeTypes;
 
-		var dracoGeometry;
-		var decodingStatus;
+		let dracoGeometry;
+		let decodingStatus;
 
-		var geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
+		const geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
 
 		if ( geometryType === draco.TRIANGULAR_MESH ) {
 
@@ -505,15 +484,15 @@ DRACOLoader.DRACOWorker = function () {
 
 		}
 
-		var geometry = { index: null, attributes: [] };
+		const geometry = { index: null, attributes: [] };
 
 		// Gather all vertex attributes.
-		for ( var attributeName in attributeIDs ) {
+		for ( const attributeName in attributeIDs ) {
 
-			var attributeType = self[ attributeTypes[ attributeName ] ];
+			const attributeType = self[ attributeTypes[ attributeName ] ];
 
-			var attribute;
-			var attributeID;
+			let attribute;
+			let attributeID;
 
 			// A Draco file may be created with default vertex attributes, whose attribute IDs
 			// are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
@@ -553,13 +532,13 @@ DRACOLoader.DRACOWorker = function () {
 
 	function decodeIndex( draco, decoder, dracoGeometry ) {
 
-		var numFaces = dracoGeometry.num_faces();
-		var numIndices = numFaces * 3;
-		var byteLength = numIndices * 4;
+		const numFaces = dracoGeometry.num_faces();
+		const numIndices = numFaces * 3;
+		const byteLength = numIndices * 4;
 
-		var ptr = draco._malloc( byteLength );
+		const ptr = draco._malloc( byteLength );
 		decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
-		var index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
+		const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
 		draco._free( ptr );
 
 		return { array: index, itemSize: 1 };
@@ -568,15 +547,15 @@ DRACOLoader.DRACOWorker = function () {
 
 	function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
 
-		var numComponents = attribute.num_components();
-		var numPoints = dracoGeometry.num_points();
-		var numValues = numPoints * numComponents;
-		var byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
-		var dataType = getDracoDataType( draco, attributeType );
+		const numComponents = attribute.num_components();
+		const numPoints = dracoGeometry.num_points();
+		const numValues = numPoints * numComponents;
+		const byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
+		const dataType = getDracoDataType( draco, attributeType );
 
-		var ptr = draco._malloc( byteLength );
+		const ptr = draco._malloc( byteLength );
 		decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
-		var array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
+		const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
 		draco._free( ptr );
 
 		return {
@@ -603,38 +582,6 @@ DRACOLoader.DRACOWorker = function () {
 
 	}
 
-};
-
-DRACOLoader.taskCache = new WeakMap();
-
-/** Deprecated static methods */
-
-/** @deprecated */
-DRACOLoader.setDecoderPath = function () {
-
-	console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' );
-
-};
-
-/** @deprecated */
-DRACOLoader.setDecoderConfig = function () {
-
-	console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' );
-
-};
-
-/** @deprecated */
-DRACOLoader.releaseDecoderModule = function () {
-
-	console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' );
-
-};
-
-/** @deprecated */
-DRACOLoader.getDecoderModule = function () {
-
-	console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' );
-
-};
+}
 
 export { DRACOLoader };

+ 92 - 35
libs/three.js/loaders/GLTFLoader.js

@@ -62,17 +62,38 @@ import {
 	VectorKeyframeTrack,
 	sRGBEncoding
 } from '../build/three.module.js';
+ 
+
+import {DRACOLoader} from './DRACOLoader.js'
+import {KTX2Loader} from './KTX2Loader.js'
+import { MeshoptDecoder } from '../libs/meshopt_decoder.module.js';
+import {DDSLoader} from './DDSLoader.js'
 
-var GLTFLoader = ( function () {
 
-	function GLTFLoader( manager ) {
 
+var GLTFLoader = ( function () {
+
+	function GLTFLoader( manager, renderer, urlPrefix) {
+ 
 		Loader.call( this, manager );
 
-		this.dracoLoader = null;
+		/* this.dracoLoader = null;
 		this.ddsLoader = null;
 		this.ktx2Loader = null;
-		this.meshoptDecoder = null;
+		this.meshoptDecoder = null; */
+        
+        //xzw add:
+        this.dracoLoader = new DRACOLoader;
+        this.ktx2Loader = new KTX2Loader
+        this.meshoptDecoder = MeshoptDecoder;
+        this.ddsLoader = new DDSLoader //这个没测过
+        
+        //路径相对于index.html  
+        this.dracoLoader.setDecoderPath( urlPrefix + 'three.js/loaders/draco/'  /*or 'https://unpkg.com/three@0.144.0/examples/js/libs/draco/gltf/' 版本可升级 */) //这两个路径可以自己改。在laser的环境也要放一份这个路径
+        this.ktx2Loader.setTranscoderPath(urlPrefix + 'three.js/loaders/ktx/' /*or 'https://unpkg.com/three@0.144.0/examples/js/libs/basis/' 版本可升级  */).detectSupport( renderer )
+            
+        //------------
+
 
 		this.pluginCallbacks = [];
 
@@ -118,7 +139,7 @@ var GLTFLoader = ( function () {
 
 		constructor: GLTFLoader,
 
-		load: function ( url, onLoad, onProgress, onError ) {
+		load: function ( url, onLoad, onProgress, onError   ) {
 
 			var scope = this;
 
@@ -167,23 +188,27 @@ var GLTFLoader = ( function () {
 			loader.setRequestHeader( this.requestHeader );
 			loader.setWithCredentials( this.withCredentials );
 
-			loader.load( url, function ( data ) {
+			loader.load( url, function ( data, total ) {// xzw add total 
+                console.log('数据加载成功', url.split('/').pop(), '  ,total: '+total)
+                let f = ()=>{
+                    try {
 
-				try {
+                        scope.parse( data, resourcePath, function ( gltf ) {
 
-					scope.parse( data, resourcePath, function ( gltf ) {
+                            onLoad( gltf, total );
 
-						onLoad( gltf );
+                            scope.manager.itemEnd( url );
 
-						scope.manager.itemEnd( url );
+                        }, _onError );
 
-					}, _onError );
+                    } catch ( e ) {
 
-				} catch ( e ) {
+                        _onError( e );
 
-					_onError( e );
-
-				}
+                    }
+                }
+                f()
+                //setTimeout(f,5000)/////////////////test
 
 			}, onProgress, _onError );
 
@@ -293,10 +318,9 @@ var GLTFLoader = ( function () {
 				crossOrigin: this.crossOrigin,
 				manager: this.manager,
 				ktx2Loader: this.ktx2Loader,
-				meshoptDecoder: this.meshoptDecoder
-
+				meshoptDecoder: this.meshoptDecoder, 
 			} );
-
+            parser.unlitMat = this.unlitMat//add
 			parser.fileLoader.setRequestHeader( this.requestHeader );
 
 			for ( var i = 0; i < this.pluginCallbacks.length; i ++ ) {
@@ -329,7 +353,7 @@ var GLTFLoader = ( function () {
 							extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
 							break;
 
-						case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
+						case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: 
 							extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
 							break;
 
@@ -1009,7 +1033,7 @@ var GLTFLoader = ( function () {
 	function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
 
 		if ( ! dracoLoader ) {
-
+                
 			throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
 
 		}
@@ -1910,15 +1934,17 @@ var GLTFLoader = ( function () {
 
 		// Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the
 		// expensive work of uploading a texture to the GPU off the main thread.
-		if ( typeof createImageBitmap !== 'undefined' && /Firefox/.test( navigator.userAgent ) === false ) {
+		/* if ( typeof createImageBitmap !== 'undefined' && /Firefox/.test( navigator.userAgent ) === false ) {
 
 			this.textureLoader = new ImageBitmapLoader( this.options.manager );
 
-		} else {
+		} else { */
+        //为了防止chrome出现报错  The source image could not be decoded. 导致reject,改用TextureLoader  by xzw
 
 			this.textureLoader = new TextureLoader( this.options.manager );
 
-		}
+		//}
+ 
 
 		this.textureLoader.setCrossOrigin( this.options.crossOrigin );
 
@@ -2465,7 +2491,7 @@ var GLTFLoader = ( function () {
 	};
 
 	GLTFParser.prototype.loadTextureImage = function ( textureIndex, source, loader ) {
-
+         
 		var parser = this;
 		var json = this.json;
 		var options = this.options;
@@ -2512,26 +2538,57 @@ var GLTFLoader = ( function () {
 
 			return new Promise( function ( resolve, reject ) {
 
-				var onLoad = resolve;
-
-				if ( loader.isImageBitmapLoader === true ) {
-
-					onLoad = function ( imageBitmap ) {
+				/* var onLoad = resolve;
 
+				if ( loader.isImageBitmapLoader === true ) { 
+                    onLoad = function ( imageBitmap ) {
+                        //console.log('resolveURL onLoad',textureIndex )
 						resolve( new CanvasTexture( imageBitmap ) );
 
 					};
-
 				}
-
-				loader.load( resolveURL( sourceURI, options.path ), onLoad, undefined, reject );
+                */
+
+                //为了防止chrome出现报错  The source image could not be decoded. 导致reject,重新写贴图加载方式:
+
+                parser.textureLoader.load(sourceURI, (tex)=>{
+                    tex.minFilter = THREE.LinearMipmapLinearFilter //原本:NearestMipMapNearestFilter 闪烁
+                    //tex.needsUpdate
+                    resolve(tex) 
+                })  
+               
+                return;
+
+				loader.load( resolveURL( sourceURI, options.path ), onLoad, undefined,   (e,url)=>{
+                    console.log('reject',textureIndex, arguments)
+                    if ( loader.isImageBitmapLoader === true ) {
+                        let img = new Image;
+                        img.setAttribute('crossOrigin', 'Anonymous')
+                        img.src = url; 
+                        resolve(new THREE.Texture(img))
+                        
+                        
+                        /* img.onload = ()=>{
+                            let canvas = document.createElement('canvas')
+                            let context = canvas.getContext('2d')
+                            context.canvas.width = img.width
+                            context.canvas.height = img.height
+                            context.drawImage(img, 0, 0, img.width, img.height)
+                            resolve( new CanvasTexture( canvas ) );   
+                            console.log('自己绘制 onload', textureIndex)
+                        } */
+                    }else{
+                        reject.apply(this,arguments)
+                    }
+                     
+                }  /*  ,  reject  */  );
 
 			} );
 
 		} ).then( function ( texture ) {
-
+            
 			// Clean up resources and configure Texture.
-
+            //console.log('texture', textureIndex, texture.image)
 			if ( isObjectURL === true ) {
 
 				URL.revokeObjectURL( sourceURI );
@@ -2728,7 +2785,7 @@ var GLTFLoader = ( function () {
 
 	GLTFParser.prototype.getMaterialType = function ( /* materialIndex */ ) {
 
-		return MeshStandardMaterial;
+		return this.unlitMat ? MeshBasicMaterial : MeshStandardMaterial;  //xzw add unlitMat
 
 	};
 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 481 - 499
libs/three.js/loaders/KTX2Loader.js


+ 9 - 8
libs/three.js/loaders/MTLLoader.js

@@ -5,12 +5,12 @@ import {
 	FrontSide,
 	Loader,
 	LoaderUtils,
-	MeshPhongMaterial,
+	MeshStandardMaterial,//MeshPhongMaterial, 用MeshStandardMaterial好调些,因为遵循能量守恒
 	RepeatWrapping,
 	TextureLoader,
 	Vector2
-} from '../../../build/three.module.js';
-
+} from '../build/three.module.js';
+ 
 /**
  * Loads a Wavefront .mtl file specifying materials
  */
@@ -388,8 +388,8 @@ MTLLoader.MaterialCreator.prototype = {
 				case 'ks':
 
 					// Specular color (color when light is reflected from shiny surface) using RGB values
-					params.specular = new Color().fromArray( value );
-
+					//params.specular = new Color().fromArray( value );
+                    //console.log('specular',value)
 					break;
 
 				case 'ke':
@@ -452,8 +452,9 @@ MTLLoader.MaterialCreator.prototype = {
 					// The specular exponent (defines the focus of the specular highlight)
 					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
 
-					params.shininess = parseFloat( value );
-
+					//params.shininess = parseFloat( value );
+                    //console.log('shininess',value)
+                    
 					break;
 
 				case 'd':
@@ -489,7 +490,7 @@ MTLLoader.MaterialCreator.prototype = {
 
 		}
 
-		this.materials[ materialName ] = new MeshPhongMaterial( params );
+		this.materials[ materialName ] = new MeshStandardMaterial( params )//MeshPhongMaterial( params );
 		return this.materials[ materialName ];
 
 	},

+ 2 - 2
libs/three.js/loaders/OBJLoader.js

@@ -451,11 +451,11 @@ var OBJLoader = ( function () {
 			loader.setPath( this.path );
 			loader.setRequestHeader( this.requestHeader );
 			loader.setWithCredentials( this.withCredentials );
-			loader.load( url, function ( text ) {
+			loader.load( url, function ( text , total ) {// xzw add total 
 
 				try {
 
-					onLoad( scope.parse( text ) );
+					onLoad( scope.parse( text ) , total );
 
 				} catch ( e ) {
 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 52 - 0
libs/three.js/loaders/draco/draco_decoder.js


BIN
libs/three.js/loaders/draco/draco_decoder.wasm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 104 - 0
libs/three.js/loaders/draco/draco_wasm_wrapper.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 21 - 0
libs/three.js/loaders/ktx/basis_transcoder.js


BIN
libs/three.js/loaders/ktx/basis_transcoder.wasm


BIN
note/images360.updateCube笔记.jpg


BIN
resources/textures/explode.png


BIN
resources/textures/fire.png


BIN
resources/textures/icon-explode.png


BIN
resources/textures/icon-fire.png


BIN
resources/textures/icon-smoke.png


BIN
resources/textures/pic_point_s32.png


BIN
resources/textures/smokeparticle.png


+ 3 - 3
src/Actions.js

@@ -1,8 +1,8 @@
 
+import * as THREE from "../libs/three.js/build/three.module.js";
+ 
 
-import {EventDispatcher} from "./EventDispatcher.js";
-
-export class Action extends EventDispatcher {
+export class Action extends THREE.EventDispatcher {
 	constructor (args = {}) {
 		super();
 

+ 2 - 3
src/Annotation.js

@@ -2,10 +2,9 @@
 
 import * as THREE from "../libs/three.js/build/three.module.js";
 import {Action} from "./Actions.js";
-import {Utils} from "./utils.js";
-import {EventDispatcher} from "./EventDispatcher.js";
+import {Utils} from "./utils.js"; 
 
-export class Annotation extends EventDispatcher {
+export class Annotation extends THREE.EventDispatcher {
 	constructor (args = {}) {
 		super();
 

+ 22 - 6
src/EventDispatcher.js

@@ -28,9 +28,9 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 
+ 
 
-
-
+ 
 
 export class EventDispatcher{
 
@@ -84,9 +84,15 @@ export class EventDispatcher{
 			delete this._listeners[type];
 		}
 	};
+    
+    
+    
 
 	dispatchEvent(event){
-
+        if(typeof event == 'string'){//add
+            event = {type:event}
+        }
+        
 		let listeners = this._listeners;
 		let listenerArray = listeners[event.type];
 
@@ -105,14 +111,18 @@ export class EventDispatcher{
     
     
     //add 
-    emit(type){ 
+    /* emit(type){ 
         this.dispatchEvent({type, arguments: Array.from(arguments).slice(1, arguments.length) })
     }
-    on(type, fun){
+    on(type, fun){  
         this.addEventListener(type,(ev)=>{
             fun.apply(this, ev.arguments)
         })
-    }  
+    } 
+    off(type, fun){
+        this.removeEventListener(type,)
+    }
+    
     once(type, fun) {
         function callback() {
             this.removeEventListener(type, callback),
@@ -122,6 +132,12 @@ export class EventDispatcher{
         return callback.listener = fun,
             this.on(type, callback),
             this
+    }  */
+    
+    removeAllListeners(){
+        
+        this._listeners = {};
+        
     }
      
 }

+ 21 - 0
src/Features.js

@@ -1,4 +1,8 @@
 
+import browser from './utils/browser.js'
+
+
+
 let ftCanvas = document.createElement('canvas');
 
 export const Features = (function () {
@@ -67,6 +71,23 @@ export const Features = (function () {
 			}
 
 		},
+        //add:
+        EXT_DEPTH:{
+            isSupported: function () { 
+                if(browser.detectIOS()){
+                    let {major,minor,patch} = browser.iosVersion()
+                    if(major == 15 && minor == 4 && patch == 1){
+                        console.warn('检测到是ios15.4.1, 关闭EXT_frag_depth')//该版本ext_depth有问题,导致clear错乱。没有解决办法先关闭。
+                        return false
+                    }
+                }
+
+                return  gl.getExtension('EXT_frag_depth'); //shader中的GL_EXT_frag_depth需要判断一下detectIOS吗。。
+            }
+        },
+        
+         
+        
 		//WEBGL2: {
 		//	isSupported: function(){
 		//		return gl instanceof WebGL2RenderingContext;

+ 2 - 1
src/KeyCodes.js

@@ -7,7 +7,8 @@ export const KeyCodes = {
 	RIGHT: 39,
 	BOTTOM: 40,
 	DELETE: 46,
-
+    BACKSPACE:8,
+    
 	A: 'A'.charCodeAt(0),
 	S: 'S'.charCodeAt(0),
 	D: 'D'.charCodeAt(0),

+ 7 - 1
src/LRU.js

@@ -147,7 +147,13 @@ class LRU{
 		} */ 
         
         //改成navvis的,使用pointBudget,否则四屏点云闪烁。
-        for (; this.numPoints > viewer.viewports.length * 2 * Potree.pointBudget;  ) {//要根据屏幕数量来增加pointBudget
+        
+        
+        let max = /* this.pageVisible ?  */viewer.viewports.length * 2 * Potree.pointBudget// : 1000
+        
+        
+        
+        for (; this.numPoints > max;  ) {//要根据屏幕数量来增加pointBudget
             var node = this.getLRUItem();
             node && this.disposeSubtree(node)
         }

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 944 - 63
src/PointCloudOctree.js


+ 6 - 6
src/PointCloudOctreeGeometry.js

@@ -1,11 +1,10 @@
-
-import { EventDispatcher } from "./EventDispatcher.js";
+ 
 import * as THREE from "../libs/three.js/build/three.module.js";
 import {PointCloudTreeNode} from "./PointCloudTree.js";
 import {XHRFactory} from "./XHRFactory.js";
 import {Utils} from "./utils.js";
 
-export class PointCloudOctreeGeometry extends EventDispatcher{
+export class PointCloudOctreeGeometry extends THREE.EventDispatcher{
 
 	constructor(){
         super()
@@ -93,7 +92,7 @@ export class PointCloudOctreeGeometryNode extends PointCloudTreeNode{
 		} else if (version.upTo('1.3')) {
 			url = this.pcoGeometry.octreeDir + '/' + this.name;
 		}
-
+       
 		return url;
 	}
 
@@ -200,7 +199,7 @@ export class PointCloudOctreeGeometryNode extends PointCloudTreeNode{
 				let parentName = name.substring(0, name.length - 1);
 				let parentNode = nodes[parentName];
 				let level = name.length - 1;
-                pco.emit('updateNodeMaxLevel',level);//add
+                pco.dispatchEvent({type:'updateNodeMaxLevel',level});//add
                 
 				let boundingBox = Utils.createChildAABB(parentNode.boundingBox, index);
 
@@ -224,7 +223,8 @@ export class PointCloudOctreeGeometryNode extends PointCloudTreeNode{
 		if ((node.level % node.pcoGeometry.hierarchyStepSize) === 0) {
 			// let hurl = node.pcoGeometry.octreeDir + "/../hierarchy/" + node.name + ".hrc";
 			let hurl = node.pcoGeometry.octreeDir + '/' + node.getHierarchyPath() + '/' + node.name + '.hrc';
-
+            hurl += '?m='+node.pcoGeometry.timeStamp //add
+            
 			let xhr = XHRFactory.createXMLHttpRequest();
 			xhr.open('GET', hurl, true);
 			xhr.responseType = 'arraybuffer';

+ 3 - 4
src/PointCloudTree.js

@@ -1,9 +1,7 @@
 
-import * as THREE from "../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "./EventDispatcher.js";
+import * as THREE from "../libs/three.js/build/three.module.js"; 
 
-
-export class PointCloudTreeNode extends EventDispatcher{
+export class PointCloudTreeNode extends THREE.EventDispatcher{
 
 	constructor(){
 		super();
@@ -42,6 +40,7 @@ export class PointCloudTreeNode extends EventDispatcher{
 export class PointCloudTree extends THREE.Object3D {
 	constructor () {
 		super();
+        //this.spriteGroup = new THREE.Object3D  //add
 	}
 
 	initialized () {

+ 117 - 55
src/Potree.js

@@ -1,3 +1,6 @@
+export {config, settings} from "./settings.js";
+export * from "./start.js";
+ 
 
 export * from "./Actions.js";
 export * from "./AnimationPath.js";
@@ -16,7 +19,7 @@ export * from "./Points.js";
 export * from "./Potree_update_visibility.js";
 export * from "./PotreeRenderer.js";
 export * from "./ProfileRequest.js";
-export * from "./TextSprite.js";
+//export * from "./TextSprite.js";
 export * from "./utils.js";
 export * from "./Version.js";
 export * from "./WorkerPool.js";
@@ -41,23 +44,23 @@ export * from "./loader/PointAttributes.js";
 export * from "./loader/ShapefileLoader.js";
 export * from "./loader/GeoPackageLoader.js";
 
-export * from "./utils/Box3Helper.js";
-export * from "./utils/ClippingTool.js";
-export * from "./utils/ClipVolume.js";
+export * from "./objects/tool/Box3Helper.js";
+export * from "./objects/tool/ClippingTool.js";
+export * from "./objects/tool/ClipVolume.js";
 export * from "./utils/GeoTIFF.js";
-export * from "./utils/Measure.js";
-export * from "./utils/MeasuringTool.js";
+export * from "./objects/tool/Measure.js";
+export * from "./objects/tool/MeasuringTool.js";
 export * from "./utils/Message.js";
 export * from "./utils/PointCloudSM.js";
-export * from "./utils/PolygonClipVolume.js";
-export * from "./utils/Profile.js";
-export * from "./utils/ProfileTool.js";
-export * from "./utils/ScreenBoxSelectTool.js";
-export * from "./utils/SpotLightHelper.js";
-export * from "./utils/TransformationTool.js";
-export * from "./utils/Volume.js";
-export * from "./utils/VolumeTool.js";
-export * from "./utils/Compass.js";
+export * from "./objects/tool/PolygonClipVolume.js";
+export * from "./objects/tool/Profile.js";
+export * from "./objects/tool/ProfileTool.js";
+export * from "./objects/tool/ScreenBoxSelectTool.js";
+export * from "./objects/tool/SpotLightHelper.js";
+export * from "./objects/tool/TransformationTool.js";
+export * from "./objects/tool/Volume.js";
+export * from "./objects/tool/VolumeTool.js";
+export * from "./objects/tool/Compass.js";
 
 export * from "./viewer/viewer.js";
 export * from "./viewer/Scene.js";
@@ -78,8 +81,6 @@ export {VRControls} from "./navigation/VRControls.js";
 
 //add:
 export {Alignment} from "./modules/datasetAlignment/Alignment.js";
-export {config, settings} from "./settings.js";
-export {start} from "./start.js";
 
 
 
@@ -142,73 +143,131 @@ export {scriptPath, resourcePath};
 
 //add: 
 
+ 
 
 
-export async function loadFile(path, callback){
+export async function loadFile(path, callback, onError){
     if(Potree.fileServer){
-        Potree.fileServer.get(path).then(data=>{
+         
+        Potree.fileServer.get(path).then(data=>{ 
+            if(data.data)data = data.data
+            if(data.data)data = data.data //融合页面getdataset需要查找两次data
             callback && callback(data)
-        })
+        }).catch(onError) 
     }else{
-        let response = await fetch(path); 
-        let text = await response.text();
-        var data = JSON.parse(text)
-        callback && callback(data) 
-        return data
+        try{
+            let response = await fetch(path); 
+            let text = await response.text();
+            var data = JSON.parse(text)
+            if(data.data) data = data.data
+            callback && callback(data)    
+            return data 
+        }catch(e){
+            onError && onError(e)
+        }
+          
     }
     
     //查询: http://192.168.0.26:8080/doc.html#/default/filter-%E6%BC%AB%E6%B8%B8%E7%82%B9/filterUsingGET    
 }
 
-export async function loadDatasets(callback){//之后直接把path写进来
-    var path 
+export async function loadDatasets(callback,sceneCode,onError){//之后直接把path写进来
+    let path 
+    sceneCode = sceneCode || Potree.settings.number
     if(Potree.fileServer){
-        path = `/laser/dataset/${Potree.settings.number}/getDataSet` 
+        path = `/laser/dataset/${sceneCode}/getDataSet` 
     }else{
-        path = `https://${Potree.config.urls.prefix2}/indoor/${Potree.settings.number}/api/datasets`
-          
+        
+        //path = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/datasets`
+        //现在只能加载得了本地的了
+        path = `${Potree.settings.urls.prefix}/laser/dataset/${sceneCode}/getDataSet`
+        //path = `${Potree.scriptPath}/data/${sceneCode}/getDataSet.json`
+        
     }
-    return loadFile(path, callback)
+    return loadFile(path, callback,onError)
     
 }
-export async function loadMapEntity(){
-    var path 
-    let callback = (data)=>{
-        var map = viewer.mapViewer.mapLayer.maps.find(e => e.name == 'floorplan')
-        if(map){
+
+
+//目前上传平面图后如果不点击保存按钮,数据还是旧的不生效
+export async function loadMapEntity(datasetId, force){ 
+    if(!Potree.settings.floorplanEnable && !force && Potree.fileServer  )return /* 等待平面图类型定义好会加载 */
+     
+    
+    let loaded = 0
+    
+    let needLoads = datasetId == 'all' ? viewer.scene.pointclouds.map(e=>e.dataset_id) : [datasetId]
+    
+    
+    let callback = (dataset_id, floorplanType, data  )=>{
+        //要防止旧的比新的先获取到导致覆盖新的,因为两种type随时可能切换
+        if(floorplanType != Potree.settings.floorplanType[dataset_id]) return //如果请求的floorplanType不是当前最新的floorplanType就返回
+        
+        var map = viewer.mapViewer.mapLayer.maps.find(e => e.name == 'floorplan_'+ dataset_id)
+        if(map){  
             viewer.mapViewer.mapLayer.removeMap(map)
+        } 
+        
+        var mapNew = viewer.mapViewer.mapLayer.addMapEntity(data.data || data,  dataset_id)
+        if(map){
+            mapNew.visibleReasons = map.visibleReasons 
+            mapNew.unvisibleReasons = map.unvisibleReasons 
+        }
+        loaded ++; 
+    } 
+    
+    needLoads.forEach(dataset_id=>{
+        let floorplanType = Potree.settings.floorplanType[dataset_id],  prefix = ''
+        if(!Potree.fileServer){   
+            prefix = Potree.settings.urls.prefix
+        }
+        if(!floorplanType)return
+        var path 
+        /* if(Potree.fileServer){ 
+            path = `/laser/tiledMap/${Potree.settings.number}/tiledMap/${floorplanType}/${dataset_id}` 
         }else{
+            path = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/tiled_maps`
             
-        }
-        
-        viewer.mapViewer.mapLayer.addMapEntity(data.data || data )
-    }
-    if(Potree.fileServer){
-        if(!Potree.settings.floorplanType || !Potree.settings.floorplanEnable)return /* 等待平面图类型定义好会加载 */
-        path = `/laser/tiledMap/${Potree.settings.number}/tiledMap/${Potree.settings.floorplanType}` 
-    }else{
-        path = `https://${Potree.config.urls.prefix2}/indoor/${Potree.settings.number}/api/tiled_maps`
+        } */
+        path = `${prefix}/laser/tiledMap/${Potree.settings.number}/tiledMap/${floorplanType}/${dataset_id}` 
         
-    }
-    return loadFile(path, callback)
+        Potree.settings.floorplanRequests[dataset_id] = true //开始加载了
+        return loadFile(path, callback.bind(this,  dataset_id, floorplanType)  )
+    })
+    
      
     
 }
  
-export async function loadPanos(center, callback){
+export async function loadPanos(datasetId, callback){
     var path 
-    let query = ''//`?lat=${center.lat}&lon=${center.lon}&radius=200000`
+    let query = `?datasetId=${datasetId}`                  //`?lat=${center.lat}&lon=${center.lon}&radius=200000`
     if(Potree.fileServer){
         path = `/laser/filter/${Potree.settings.number}/query` + query
-    }else{ 
-        path = `https://${Potree.config.urls.prefix2}/indoor/${Potree.settings.number}/api/images/filter` + query
-             
+    }else{
+        //path = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/images/filter` + query
+        //path = `${Potree.scriptPath}/data/${Potree.settings.number}/panos-${datasetId}.json`
+        path = `${Potree.settings.urls.prefix}/laser/filter/${Potree.settings.number}/query` + query
+       
+         
     }
     return loadFile(path, callback) 
     
 }
 
 
+export async function loadPanosInfo(callback){ 
+    var path 
+    if(Potree.fileServer){
+        
+    }else{
+        path = `${Potree.scriptPath}/data/panoEdit/vision_edit.txt`
+          
+    }
+    return loadFile(path, callback)
+    
+}
+
 
 
 //site_model
@@ -258,7 +317,7 @@ export async function loadPanos(center, callback){
 
 
 
-export function Log(value, color, fontSize){
+export function Log(value, color, fontSize){ 
     color = color || '#13f'
     fontSize = fontSize || 14
     console.warn(`%c${value}`, `color:${color};font-size:${fontSize}px`) 
@@ -266,9 +325,11 @@ export function Log(value, color, fontSize){
 
  
 
-export function loadPointCloud(path, name, callback){
+export function loadPointCloud(path, name, sceneCode, timeStamp, callback, onError){
 	let loaded = function(e){
 		e.pointcloud.name = name;
+        e.pointcloud.sceneCode = sceneCode //对应4dkk的场景码
+        
 		callback(e);
 	};
 
@@ -289,10 +350,11 @@ export function loadPointCloud(path, name, callback){
 				}
 			});
 		} else if (path.indexOf('cloud.js') > 0) {
-			POCLoader.load(path, function (geometry) {
+			POCLoader.load(path, timeStamp, function (geometry) {
 				if (!geometry) {
 					//callback({type: 'loading_failed'});
 					console.error(new Error(`failed to load point cloud from URL: ${path}`));
+                    onError && onError()
 				} else {
 					let pointcloud = new PointCloudOctree(geometry);
 					// loaded(pointcloud);

+ 101 - 16
src/PotreeRenderer.js

@@ -156,7 +156,7 @@ let attributeLocations = {
 class Shader {
 
 	constructor(gl, name, vsSource, fsSource) {
-		this.gl = gl;
+		this.gl = gl; 
 		this.name = name;
 		this.vsSource = vsSource;
 		this.fsSource = fsSource;
@@ -225,11 +225,18 @@ class Shader {
 
 			this.vs = gl.createShader(gl.VERTEX_SHADER);
 			this.fs = gl.createShader(gl.FRAGMENT_SHADER);
+            
+            
+            
+            
 			this.program = gl.createProgram();
             
             if(  !gl.isProgram(this.program  )){//创建失败  开启多个页面可能会,原因是webglcontextlost
                 //console.error('创建program失败');
-                viewer.emit('webglError', 'potreeRenderer创建program失败')
+                viewer.dispatchEvent('webglError', {msg: 'potreeRenderer创建program失败'})
+                console.log(this.vs)
+                console.log(this.fs)
+                
                 return;
             }
 
@@ -553,7 +560,7 @@ export class Renderer {
 	constructor(threeRenderer) {
 		this.threeRenderer = threeRenderer;
 		this.gl = this.threeRenderer.getContext();
-
+  
 		this.buffers = new Map();
 		this.shaders = new Map();
 		this.textures = new Map();
@@ -684,7 +691,7 @@ export class Renderer {
 
 			let node = stack.pop();
 
-			if (node instanceof PointCloudTree) {
+			if (node instanceof PointCloudTree) { 
 				octrees.push(node);
 				continue;
 			}
@@ -1035,8 +1042,11 @@ export class Renderer {
 			}
 
 			let numPoints = webglBuffer.numElements;
-			gl.drawArrays(gl.POINTS, 0, numPoints);
-
+			 
+            gl.drawArrays(gl.POINTS, 0, numPoints); 
+            //gl.drawArrays(gl.TRIANGLES, 0, numPoints);
+            
+            
 			i++;
 		}
 
@@ -1047,6 +1057,10 @@ export class Renderer {
 			performance.measure("render.renderNodes", "renderNodes-start", "renderNodes-end");
 		}
 	}
+    
+    
+    
+    
 
 	renderOctree(octree, nodes, camera, target, params = {}){
 
@@ -1120,7 +1134,7 @@ export class Renderer {
                 
                 if(material.useFilterByNormal){
                     defines.push("#define use_filter_by_normal");
-                    defines.push("#define attenuated_opacity");
+                    //Potree.settings.editType == 'pano' ? defines.push("#define attenuated_opacity2") : defines.push("#define attenuated_opacity");
                     
                 }
 
@@ -1213,9 +1227,18 @@ export class Renderer {
 
 		if (transparent){
 			gl.enable(gl.BLEND);
-			gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
-			gl.depthMask(false);
-			gl.disable(gl.DEPTH_TEST);
+            
+            if(params.notAdditiveBlending){
+                gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); //NormalBlending 
+                gl.enable(gl.DEPTH_TEST);
+                gl.depthMask(true); //如果不开启depthWrite,深度会错乱。 
+            }else{
+                gl.blendFunc(gl.SRC_ALPHA, gl.ONE); //AdditiveBlending   原本
+                gl.disable(gl.DEPTH_TEST);
+                gl.depthMask(false);
+            } 
+			
+			
 		} else {
 			gl.disable(gl.BLEND);
 			gl.depthMask(true);
@@ -1251,11 +1274,15 @@ export class Renderer {
 			shader.setUniformMatrix4("uViewInv", viewInv);
 			shader.setUniformMatrix4("uProjInv", projInv);
 
-			let screenWidth = target ? target.width : material.screenWidth;
+			/* let screenWidth = target ? target.width : material.screenWidth;
 			let screenHeight = target ? target.height : material.screenHeight;
 
 			shader.setUniform1f("uScreenWidth", screenWidth);
-			shader.setUniform1f("uScreenHeight", screenHeight);
+			shader.setUniform1f("uScreenHeight", screenHeight); */
+            
+            shader.setUniform2f('resolution', material.resolution.toArray())
+            
+            
 			shader.setUniform1f("fov", Math.PI * camera.fov / 180);
 			shader.setUniform1f("near", camera.near);
 			shader.setUniform1f("far", camera.far);
@@ -1316,7 +1343,7 @@ export class Renderer {
 			}
 
 
-			shader.setUniform1f("size", material.usePanoMap ? Potree.config.material.absolutePanoramaSize : material.size);//usePanoMap时控制在不大不小的范围内感觉较好,考虑到有的点云稀疏,用大一点的点
+			shader.setUniform1f("size", material.usePanoMap ? Potree.config.material.absolutePanoramaSize * Math.min(window.devicePixelRatio,2) : material.size);//usePanoMap时控制在不大不小的范围内感觉较好,考虑到有的点云稀疏,用大一点的点
 			shader.setUniform1f("maxSize", material.uniforms.maxSize.value);
 			shader.setUniform1f("minSize", material.uniforms.minSize.value);
 
@@ -1512,10 +1539,37 @@ export class Renderer {
 		gl.activeTexture(gl.TEXTURE2);
 		gl.bindTexture(gl.TEXTURE_2D, null);
 		gl.activeTexture(gl.TEXTURE0);
+        
+        
+        
+        //gl.bindTexture(gl.TEXTURE_2D, null); //add
+        
+        
+        
+        
+        //add  恢复为不透明(否则renderToCubeMap时的贴图会被渲染成高亮的颜色)
+        gl.disable(gl.BLEND);
+        gl.depthMask(true);
+        gl.enable(gl.DEPTH_TEST);
+            //DEPTH_TEST等需要恢复吗
+         
+        
 	}
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
 
-	render(scene, camera, target = null, params = {}) {
 
+	render(scene, camera, target = null, params = {}) {
+ 
 		const gl = this.gl;
 
 		// PREPARE 
@@ -1527,11 +1581,42 @@ export class Renderer {
 		// camera.matrixWorldInverse.invert(camera.matrixWorld);
 
 		const traversalResult = this.traverse(scene);
-
-
+        
+        //排序
+        if(Potree.settings.notAdditiveBlending){//add 
+            
+            traversalResult.octrees.forEach(tree=>{
+                if(tree.material.opacity==1){
+                    tree._z = Infinity //不透明的先渲染
+                }else{
+                    let center = tree.boundCenter ? tree.boundCenter.clone() : tree.boundingBox.getCenter(tree.boundCenter).applyMatrix4(tree.matrixWorld) 
+                    center.project(camera) 
+                    tree._z = center.z 
+                }
+            })   
+                 
+            traversalResult.octrees.sort((tree1,tree2)=>{ 
+                return tree2._z - tree1._z //降序  (-1 朝外)。 离屏幕近的后渲染
+            })
+        }
+        
+        
+        
+        
 		// RENDER
 		for (const octree of traversalResult.octrees) {
 			let nodes = octree.visibleNodes;
+            
+            
+            
+            /* nodes.sort((node1,node2)=>{//姑且
+                
+                let center = node.getBoundingSphere().center.clone().applyMatrix4(octree.matrixWorld)
+                return  
+                
+                
+            }) */
+            
 			this.renderOctree(octree, nodes, camera, target, params);
 		}
 

+ 10 - 4
src/Potree_update_visibility.js

@@ -1,7 +1,7 @@
 
 import * as THREE from "../libs/three.js/build/three.module.js";
 import {ClipTask, ClipMethod} from "./defines.js";
-import {Box3Helper} from "./utils/Box3Helper.js";
+import {Box3Helper} from "./objects/tool/Box3Helper.js";
 
 export function updatePointClouds(pointclouds,camera, areaSize /* renderer */){
  
@@ -75,8 +75,11 @@ export function updateVisibilityStructures(pointclouds, camera, areaSize) {
 		let camMatrixObject = new THREE.Matrix4().multiply(worldI).multiply(view);
 		let camObjPos = new THREE.Vector3().setFromMatrixPosition(camMatrixObject);
 		camObjPositions.push(camObjPos);
-
-		if ( viewer.getObjVisiByReason(pointcloud, 'datasetSelection') &&  pointcloud.root !== null) {//改 visible -> 
+        
+        // 因漫游模式而隐藏的话 依旧需要加入visibleNodes,因为pick需要
+        
+                                                    /*  viewer.getObjVisiByReason(pointcloud, 'datasetSelection') */
+		if (pointcloud.visible || pointcloud.unvisibleReasons && pointcloud.unvisibleReasons.length == 1 && pointcloud.unvisibleReasons[0].reason == 'displayMode'   &&  pointcloud.root !== null) {//改 visible -> 
 			priorityQueue.push({pointcloud: i, node: pointcloud.root, weight: Number.MAX_VALUE});
 		}
 
@@ -322,8 +325,11 @@ export function updateVisibility(pointclouds, camera, areaSize){
 			let transformVersion = pointcloudTransformVersion.get(pointcloud);
 			if(node._transformVersion !== transformVersion.number){
 				node.sceneNode.updateMatrix();
+				//node.sceneNode.matrixWorld.multiplyMatrices(pointcloud.matrixWorld, node.sceneNode.matrix);	
 				node.sceneNode.matrixWorld.multiplyMatrices(pointcloud.matrixWorld, node.sceneNode.matrix);	
-				node._transformVersion = transformVersion.number;
+				
+                node._transformVersion = transformVersion.number;
+                               
 			}
 
 			if (pointcloud.showBoundingBox && !node.boundingBoxNode && node.getBoundingBox) {

+ 2 - 2
src/defines.js

@@ -1,6 +1,6 @@
 import * as THREE from "../libs/three.js/build/three.module.js";
 import {Enum} from "./Enum.js";
-import math from "./utils/math";
+import math from "./utils/math.js";
 
 export const CameraMode = {
 	ORTHOGRAPHIC: 0,
@@ -26,7 +26,7 @@ export const ElevationGradientRepeat = {
 	MIRRORED_REPEAT: 2,
 };
 
-export const MOUSE = {// MouseEvent.buttons
+export const Buttons = {// MouseEvent.buttons
     //buttons,设置按下了鼠标哪些键,是一个3个比特位的二进制值,默认为0。1表示按下主键(通常是左键),2表示按下次要键(通常是右键),4表示按下辅助键(通常是中间的键)。
 	NONE:0,//add
     

+ 1 - 1
src/exporter/DXFExporter.js

@@ -7,7 +7,7 @@
  */
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
-import {Measure} from "../utils/Measure.js";
+import {Measure} from "../objects/tool/Measure.js";
 
 export class DXFExporter {
 

+ 1 - 1
src/exporter/GeoJSONExporter.js

@@ -6,7 +6,7 @@
  *
  */
 
-import {Measure} from "../utils/Measure.js";
+import {Measure} from "../objects/tool/Measure.js";
 
 export class GeoJSONExporter{
 

+ 19 - 2
src/loader/BinaryLoader.js

@@ -5,6 +5,9 @@ import {Version} from "../Version.js";
 import {XHRFactory} from "../XHRFactory.js";
 
 
+
+
+//加载 解析点云
 export class BinaryLoader{
 
 	constructor(version, boundingBox, scale){
@@ -28,7 +31,8 @@ export class BinaryLoader{
 		if (this.version.equalOrHigher('1.4')) {
 			url += '.bin';
 		}
-
+        url += '?m='+node.pcoGeometry.timeStamp //add
+        
 		let xhr = XHRFactory.createXMLHttpRequest();
 		xhr.open('GET', url, true);
 		xhr.responseType = 'arraybuffer';
@@ -52,7 +56,7 @@ export class BinaryLoader{
 		}
 	};
 
-	parse(node, buffer){
+	parse(node, buffer){ //解析点云
 		let pointAttributes = node.pcoGeometry.pointAttributes;
 		let numPoints = buffer.byteLength / node.pcoGeometry.pointAttributes.byteSize;
 
@@ -133,7 +137,20 @@ export class BinaryLoader{
 			node.loading = false;
 			node.estimatedSpacing = data.estimatedSpacing;
 			Potree.numNodesLoading--;
+            
+            
+            
+             
+            
+            
+            
+            
+            
+            
+            
 		};
+ 
+
 
 		let message = {
 			buffer: buffer,

+ 1 - 1
src/loader/GeoPackageLoader.js

@@ -100,7 +100,7 @@ export class GeoPackageLoader{
 
 				const matLine = new LineMaterial( {
 					color: new THREE.Color().setRGB(...getColor(table)),
-					linewidth: 2, 
+					lineWidth: 2, 
 					resolution:  new THREE.Vector2(1000, 1000),
 					dashed: false
 				} );

+ 4 - 2
src/loader/POCLoader.js

@@ -185,12 +185,14 @@ function lasLazAttributes(fMno){
 
 export class POCLoader {
 
-	static load(url, callback){
+	static load(url, timeStamp, callback){ //add timeStamp
 		try {
 			let pco = new PointCloudOctreeGeometry();
+            pco.timeStamp = timeStamp
+            
 			pco.url = url;
 			let xhr = XHRFactory.createXMLHttpRequest();
-			xhr.open('GET', url, true);
+			xhr.open('GET', url+'?m='+timeStamp, true);  
 
 			xhr.onreadystatechange = function () {
 				if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) {

+ 1 - 1
src/loader/ShapefileLoader.js

@@ -14,7 +14,7 @@ export class ShapefileLoader{
 
 		const matLine = new LineMaterial( {
 			color: 0xff0000,
-			linewidth: 3, // in pixels
+			lineWidth: 3, // in pixels
 			resolution:  new THREE.Vector2(1000, 1000),
 			dashed: false
 		} );

+ 43 - 0
src/materials/BasicMaterial.js

@@ -0,0 +1,43 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import {Shaders} from "../../build/shaders/shaders.js";
+ 
+
+class BasicMaterial  extends THREE.ShaderMaterial{ 
+    constructor(o={}){
+        
+       super( Object.assign({},{ 
+            uniforms:{
+                tDiffuse:    { type: 't',  value: o.map },
+                alpha : {type:'f', value : 1 }
+            },
+            vertexShader: Shaders['basicTextured.vs'],   
+            fragmentShader: Shaders['basicTextured.fs']  
+        },o))
+        
+        
+         
+    }
+    set opacity(o){
+        this.uniforms && (this.uniforms.alpha.value = o)
+         
+    }
+    get opacity(){
+        return this.uniforms.alpha.value  
+    }
+    
+    set map(o){
+        this.uniforms.tDiffuse.value = o
+         
+    }
+    get map(){
+        return this.uniforms.tDiffuse.value  
+    }
+    
+  
+    
+}
+
+
+
+export default BasicMaterial

+ 72 - 46
src/materials/DepthBasicMaterial.js

@@ -1,10 +1,10 @@
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Shaders} from "../../build/shaders/shaders.js";
-
-
+import {Features} from "../Features.js";
+    
  
-export default class DepthBasicMaterial extends THREE.ShaderMaterial{
+export default class DepthBasicMaterial extends THREE.ShaderMaterial{ 
     constructor(o={}){
         let {width, height} = viewer.renderer.getSize(new THREE.Vector2());
         
@@ -14,16 +14,22 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
 			nearPlane:     { type: 'f', 	value: 0.1 },
 			farPlane:      { type: 'f', 	value: 10000 }, 
 			depthTexture:   { type: 't', 	value: null }, 
-			opacity:        { type: 'f',	value: o.opacity == void 0 ? 1 : o.opacity },
+			opacity:        { type: 'f',	value: 1  },
 			map:             { type: 't', 	value: o.map }, 
-            baseColor:     {type:'v3',      value: o.color ?  new THREE.Color(o.color) :  new THREE.Color("#ffffff"),
-             
-		}};  
+            baseColor:     {type:'v3',      value: o.color ?  new THREE.Color(o.color) :  new THREE.Color("#ffffff")},
+            backColor:     {type:'v3',      value: o.backColor ?  new THREE.Color(o.backColor) :  new THREE.Color("#ddd")},
+            clipDistance :     { type: 'f', 	value:o.clipDistance || 4}, //消失距离
+            occlusionDistance :     { type: 'f', 	value: o.occlusionDistance || 1 }, //变为backColor距离
+            maxClipFactor :  { type: 'f', 	value: o.maxClipFactor || 1 },  //0-1
+      
+
+		}  
         
         let defines = {};
-        if(o.useDepth)defines.useDepth = ''
-        if(o.map)defines.use_map = ''
         
+        let useDepth = o.useDepth && Features.EXT_DEPTH.isSupported()/*  && Potree.settings.matUseDepth */
+        if(useDepth )defines.useDepth = ''
+        if(o.map)defines.use_map = '' 
         super({ 
             uniforms,
             vertexShader: Shaders['depthBasic.vs'],   
@@ -33,67 +39,80 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
             transparent: o.transparent == void 0 ?  true : o.transparent,
             side: o.side || 0 /* THREE.DoubleSide */,
             defines, 
-        } )
-        
-        if(o.useDepth) this.useDepth_ = true
-        
-         
-         
-        let setSize = (e)=>{//如果出现横条状的异常,往往是viewportOffset出错 
-            let viewport = e.viewport
-            let viewportOffset = viewport.offset || new THREE.Vector2() 
-            this.uniforms.resolution.value.copy(viewport.resolution2) 
-            this.uniforms.viewportOffset.value.copy(viewportOffset)
-            
-            //console.log('depth '+viewportOffset.toArray())
+        })
+        if(o.opacity != void 0){
+            this.opacity = o.opacity
         }
+       
+        if(useDepth) this.useDepth_ = true
         
-        let viewport = viewer.mainViewport;
-         
-        setSize( {viewport} )
         
-        viewer.addEventListener('resize',(e)=>{
-            if(!e.viewport || e.viewport.name != 'mapViewport'){//地图不需要
-                setSize(e)
-                //console.log(this.name +  viewportOffset.toArray())     
-            } 
-        })  
+      
         
+        if(this.useDepth){  
         
-        viewer.addEventListener('camera_changed', (e)=>{
-            if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) 
-        }) 
-    
+            let setSize = (e)=>{//如果出现横条状的异常,往往是viewportOffset出错 
+                let viewport = e.viewport
+                let viewportOffset = viewport.offset || new THREE.Vector2() 
+                this.uniforms.resolution.value.copy(viewport.resolution2) 
+                this.uniforms.viewportOffset.value.copy(viewportOffset)
+                
+                //console.log('depth '+viewportOffset.toArray())
+            }
+            
+            let viewport = viewer.mainViewport;
+                 
+            setSize( {viewport} )
+            
+            viewer.addEventListener('resize',(e)=>{ 
+                if(!this.useDepth || !e.viewport || e.viewport.camera.isPerspectiveCamera){//地图不需要
+                    setSize(e) 
+                } 
+            })  
         
-        /* viewer.addEventListener("render.begin", (e)=>{//before render  如果有大于两个viewport的话可能要
-            if(e.viewport.name != 'mapViewport') this.updateDepthParams({camera:e.viewport.camera})
-        }) */
         
-        this.updateDepthParams()
+            
+            /* viewer.addEventListener('camera_changed', (e)=>{
+                if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) 
+            }) */ 
+         
+            viewer.addEventListener("render.begin", (e)=>{//before render  如果有大于两个viewport的话,不同viewport用不同的depthTex
+                if(e.viewport.camera.isPerspectiveCamera) this.updateDepthParams(e)
+            })
+            
+            this.updateDepthParams()
+        }
+         
+        
         //点云变化时要一直触发updateDepthParams??
         //viewer.once("render.pass.end",this.updateDepthParams.bind(this))
     }
     
     updateDepthParams(e={}){//主要用于点云遮住mesh
         if(this.useDepth){ 
-            var camera = e.camera || viewer.scene.getActiveCamera(); 
-            this.uniforms.depthTexture.value = viewer.getPRenderer().rtEDL.depthTexture   //其实只赋值一次就行
+            var viewport = e.viewport || viewer.mainViewport;
+            var camera = viewport.camera;
+            /* if(Potree.settings.displayMode == 'showPanos' && viewer.images360.currentPano.depthTex){
+                this.uniforms.depthTexture.value = viewer.images360.currentPano.depthTex
+            }else{ */
+                this.uniforms.depthTexture.value = viewer.getPRenderer().getRtEDL(viewport).depthTexture   //其实只赋值一次就行
+            //}
             this.uniforms.nearPlane.value = camera.near;
             this.uniforms.farPlane.value = camera.far;
-             
+            
         }            
-    }
+    } 
     set map(map){
         this.uniforms.map.value = map; 
     }
     
     get useDepth(){
         return this.useDepth_
-    }
+    } 
     
-    set useDepth(value){
+    set useDepth(value){//如果不支持 EXT_DEPTH 的话会失效
         if(this.useDepth_ != value){
-            if(value){
+            if(value && Features.EXT_DEPTH.isSupported()){
                 this.defines.useDepth = ''
                 this.updateDepthParams()
             }else{
@@ -105,6 +124,13 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
     } 
     
     
+    get opacity(){
+        return this.uniforms.opacity.value
+    }
+    set opacity(o){
+        this.uniforms && (this.uniforms.opacity.value = o)
+    }
+    
     /* dispose(){ 
         super.dispose()
         viewer.depthBasic

+ 4 - 3
src/materials/EyeDomeLightingMaterial.js

@@ -17,8 +17,9 @@ export class EyeDomeLightingMaterial extends THREE.RawShaderMaterial{
 		super();
 
 		let uniforms = {
-			screenWidth:    { type: 'f', 	value: 0 },
-			screenHeight:   { type: 'f', 	value: 0 },
+			/* screenWidth:    { type: 'f', 	value: 0 },
+			screenHeight:   { type: 'f', 	value: 0 }, */ 
+            resolution:    { type: 'v2',  value: new THREE.Vector2() },
 			edlStrength:    { type: 'f', 	value: 1.0 },
 			uNear:          { type: 'f', 	value: 1.0 },
 			uFar:           { type: 'f', 	value: 1.0 },
@@ -72,7 +73,7 @@ export class EyeDomeLightingMaterial extends THREE.RawShaderMaterial{
 	}
 
 	set neighbourCount(value){
-		if (this._neighbourCount !== value) {
+		if (this._neighbourCount !== value) { //周围八个格子
 			this._neighbourCount = value;
 			this.neighbours = new Float32Array(this._neighbourCount * 2);
 			for (let c = 0; c < this._neighbourCount; c++) {

+ 395 - 0
src/materials/ModelTextureMaterial.js

@@ -0,0 +1,395 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import Common from '../utils/Common.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;\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\n"
+const prefixFragment ="precision highp float;\nprecision highp int;\n\nuniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"
+ 
+let shader = {
+	 
+		uniforms: { 
+			
+			opacity: {
+				type: "f",
+				value: 1
+			},
+			progress: {
+				type: "f",
+				value: 0
+			},
+			
+			pano0Map: {
+				type: "t",
+				value: null
+			}, 
+            pano1Map: {
+				type: "t",
+				value: null
+			}, 
+            depthMap0: {
+				type: "t",
+				value: null
+			},
+            depthMap1: {
+				type: "t",
+				value: null
+			}, 
+			pano0Position: {
+				type: "v3",
+				value: new THREE.Vector3
+			},
+			pano0Matrix: {
+				type: "m4",
+				value: new THREE.Matrix4
+			},
+			
+			pano1Position: {
+				type: "v3",
+				value: new THREE.Vector3
+			},
+			pano1Matrix: {
+				type: "m4",
+				value: new THREE.Matrix4
+            },
+			/* pano1Matrix2: {
+				type: "m4",
+				value: new THREE.Matrix4
+            },
+            */
+            
+            inverseProjectionMatrix: {
+                value: new THREE.Matrix4
+            },  
+            /* projectionMatrix:{//需要再写一遍吗
+                value: new THREE.Matrix4
+            }, */
+            viewport: {
+                value: new THREE.Vector4
+            },
+            //如     {x: 0, y: 0, z: 428, w: 969}  xy应该是offset, zw是宽高 
+            cameraHeight0: {
+				type: "f",
+				value: 1
+			},
+            cameraHeight1: {
+				type: "f",
+				value: 1
+			},
+            
+        },
+       
+        vertexShader: prefixVertex + `
+
+            uniform vec3 pano0Position;
+            uniform mat4 pano0Matrix;
+            
+            uniform vec3 pano1Position;
+            uniform mat4 pano1Matrix;
+            //uniform mat4 pano1Matrix2;
+
+           
+            varying vec2 vUv; 
+            varying vec3 vWorldPosition0;
+            varying vec3 vWorldPosition1;
+            varying vec3 vWorldPosition12;
+            
+            vec3 transformAxis( vec3 direction ) //navvis->4dkk
+            {
+                float y = direction.y;
+                direction.y = direction.z;
+                direction.z = -y;
+                return  direction;
+            }
+             
+            
+            void main() {
+            
+                vUv = uv;
+                vec4 worldPosition = modelMatrix * vec4(position, 1.0);
+                
+                
+            
+                vec3 positionLocalToPanoCenter0 = worldPosition.xyz - pano0Position;
+                vWorldPosition0 = (vec4(positionLocalToPanoCenter0, 1.0) * pano0Matrix).xyz;
+                vWorldPosition0.x *= -1.0;
+                vWorldPosition0 = transformAxis(vWorldPosition0);
+                
+                vec3 positionLocalToPanoCenter1 = worldPosition.xyz - pano1Position;
+                vWorldPosition1 = (vec4(positionLocalToPanoCenter1, 1.0) * pano1Matrix).xyz;
+                vWorldPosition1.x *= -1.0;
+                vWorldPosition1 = transformAxis(vWorldPosition1);
+                
+                /* 
+                vec3 positionLocalToPanoCenter12 = worldPosition.xyz - pano1Position;
+                vWorldPosition12 = (vec4(positionLocalToPanoCenter12, 1.0) * pano1Matrix2).xyz;
+                vWorldPosition12.x *= -1.0;
+                vWorldPosition12 = transformAxis(vWorldPosition12);
+                 */
+                
+                
+                
+                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+            
+            }
+
+        `,
+        fragmentShader: prefixFragment + `
+             
+            #define PI 3.141592653 
+            
+             
+            uniform float modelAlpha;
+            uniform float opacity;
+            uniform float progress;
+            uniform int blackout;
+            uniform vec3 pano0Position;
+            uniform vec3 pano1Position;
+            uniform float maxDistance;
+            uniform float minDistance;
+            uniform float minOpa;
+            
+            uniform float cameraHeight0;
+            uniform float cameraHeight1;
+
+          
+       
+            /* uniform sampler2D pano0Map;
+            uniform sampler2D pano1Map;    */   
+            uniform samplerCube pano0Map;
+            uniform samplerCube pano1Map;
+          
+            
+            varying vec2 vUv; 
+            varying vec3 vWorldPosition0;
+            varying vec3 vWorldPosition1;
+            //varying vec3 vWorldPosition12;
+          
+            /* vec2 getSamplerCoord( vec3 direction ) 
+            {
+                direction = normalize(direction);
+                float tx=atan(direction.x,-direction.y)/(PI*2.0)+0.5;
+                float ty=acos(direction.z)/PI;
+
+                return vec2(tx,ty);
+            } */
+
+            vec2 getSamplerCoord2( vec3 direction ) 
+            { 
+                direction = normalize(direction);
+                float tx=atan(direction.x,direction.z)/(PI*2.0)+0.5;
+                float ty=acos(direction.y)/PI;
+
+                return vec2(tx,ty); 
+            }
+            
+            #extension GL_EXT_frag_depth : enable
+            #if defined(GL_EXT_frag_depth) && defined(hasDepthTex)  
+                uniform sampler2D depthMap0;
+                uniform sampler2D depthMap1;
+                uniform mat4 inverseProjectionMatrix;
+                uniform mat4 projectionMatrix;
+                uniform vec4 viewport; 
+            
+                vec2 getDepth(vec3 dir, sampler2D depthMap, float height, vec4 eyePos){
+                    vec2 depthValue = vec2(0.0, 0.0);
+                    vec2 uv2 = getSamplerCoord2(/* vWorldPosition12 */dir.xyz);  //暂时只用基于目标漫游点的方向
+                    uv2.x -= 0.25;    //全景图和Cube的水平采样起始坐标相差90度,这里矫正 0.25 个采样偏移
+                    vec4 depth = texture2D(depthMap, uv2);
+                    //float distance = depth.r + 256. * (depth.g + 256. * depth.b);
+                    //distance *= 255. * .001;           // distance is now in meters
+                    
+                    //更改
+                    float distance = (depth.g + depth.r / 256.) * 255.;  //为什么要乘以255 
+                    
+                    if(distance == 0.0){//漫游点底部识别不到的区域,给一个地板高度 
+                         if(uv2.y > 0.75)distance = height / dir.y; 
+                         else distance = 100000.0;//给个超级远的值
+                    } 
+                    depthValue.x = distance;
+                    
+                   // return  r[1] + r[0] / 256  
+                    distance += .1;          // add a safety margin
+
+                    vec4 eyePos2 = vec4(normalize(eyePos.xyz) * distance, 1.);
+                    vec4 clipPos2 = projectionMatrix * eyePos2;
+                    vec4 ndcPos2 = clipPos2 * 1. / clipPos2.w;
+
+                    
+                    depthValue.y = 0.5 * ((gl_DepthRange.far - gl_DepthRange.near) * ndcPos2.z
+                            + gl_DepthRange.near + gl_DepthRange.far); 
+                    return depthValue;      
+                }
+                //注:未加载好的话,depth为0,导致第一次漫游过去的时候许多mesh会立刻被遮挡,所以要确保加载完
+            #endif
+            
+            void main()
+            {
+                
+                /* vec2 samplerCoord0 = getSamplerCoord(vWorldPosition0.xyz);
+                vec2 samplerCoord1 = getSamplerCoord(vWorldPosition1.xyz);  
+                vec4 colorFromPano0=texture2D(pano0Map,samplerCoord0);
+                vec4 colorFromPano1=texture2D(pano1Map,samplerCoord1); */
+                
+                vec4 colorFromPano0 = vec4(0.0,0.0,0.0,0.0);
+                if(progress < 1.0){//通常是1
+                    colorFromPano0=textureCube(pano0Map,vWorldPosition0.xyz);
+                }
+                vec4 colorFromPano1=textureCube(pano1Map,vWorldPosition1.xyz);
+ 
+                gl_FragColor=mix(colorFromPano0,colorFromPano1,progress);
+              
+              
+                
+              
+                //深度图修改深度
+              
+                #if defined(GL_EXT_frag_depth) && defined(hasDepthTex)  
+                    vec4 ndcPos;
+                    ndcPos.xy = ((2.0 * gl_FragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1.;
+                    ndcPos.z = (2.0 * gl_FragCoord.z - gl_DepthRange.near - gl_DepthRange.far) /
+                        (gl_DepthRange.far - gl_DepthRange.near);
+                    ndcPos.w = 1.0;
+
+                    vec4 clipPos = ndcPos / gl_FragCoord.w;
+                    vec4 eyePos = inverseProjectionMatrix * clipPos;
+                    vec2 depth0 = vec2(0.0,0.0); 
+                    if(progress < 1.0){
+                        depth0 = getDepth(vWorldPosition0, depthMap0, cameraHeight0, eyePos);
+                    }
+                    vec2 depth1 = getDepth(vWorldPosition1, depthMap1, cameraHeight1, eyePos);
+                    
+                    /* if(progress < 1.0 && depth1.x == 0.0 && depth0.x > 0.0){
+                        gl_FragDepthEXT = depth0.y; 
+                    }else{ */
+                        gl_FragDepthEXT = mix(depth0.y,depth1.y,progress);
+                    //}
+                    
+                    
+
+                #endif
+
+                
+            }
+        `
+    }
+            
+            
+
+export default class ModelTextureMaterial extends THREE.RawShaderMaterial { 
+	constructor( ){ 
+    
+        let defines = {}
+         
+       
+        
+        super({
+            fragmentShader: shader.fragmentShader,
+			vertexShader: shader.vertexShader,
+			uniforms: THREE.UniformsUtils.clone(shader.uniforms),
+            side:THREE.DoubleSide,
+			name: "ModelTextureMaterial",
+            defines
+        })
+    
+        
+            
+        let setSize = (e)=>{ 
+            let viewport = e.viewport
+            let viewportOffset = viewport.offset || new Vector2()  
+            let resolution = viewport.resolution2 
+            this.uniforms.viewport.value.set(viewportOffset.x, viewportOffset.y, resolution.x, resolution.y) 
+        }
+        let viewport = viewer.mainViewport;
+         
+        setSize({viewport})
+
+
+        viewer.addEventListener('resize',(e)=>{
+            setSize(e)     
+        }) 
+        
+        
+        //var supportExtDepth = !!Features.EXT_DEPTH.isSupported()  
+        {
+         
+            //add
+            
+            viewer.addEventListener('camera_changed', (e)=>{
+                //this.uniforms.projectionMatrix.value.copy(e.camera.projectionMatrix) 
+                this.uniforms.inverseProjectionMatrix.value.copy(e.camera.projectionMatrixInverse)
+            })   
+
+        } 
+
+        
+		//-------------------------------------
+	}
+
+	/**
+	 * 
+	 * @param {Panorama} pano0 
+	 * @param {Panorama} pano1 
+	 * @param {boolean} flag 
+     
+     更新全景图的材质uniforms 
+     
+	 */
+     
+      
+     
+	setProjectedPanos(pano0, pano1, progressValue ){
+        
+ 		progressValue!=void 0 && (this.uniforms.progress.value = progressValue);
+		//pano0.ensureSkyboxReadyForRender();
+        
+        
+        if(pano0){
+            this.uniforms.pano0Map.value = pano0.getSkyboxTexture();//pano0.texture
+            this.uniforms.pano0Position.value.copy(pano0.position)
+            this.uniforms.pano0Matrix.value.copy(pano0.panoMatrix/* pano0.mesh.matrixWorld */ );
+            this.uniforms.cameraHeight0.value = pano0.floorPosition.distanceTo(pano0.position)
+            
+            //pano1.ensureSkyboxReadyForRender();
+        }
+        
+		
+		this.uniforms.pano1Map.value = pano1.getSkyboxTexture()//pano1.texture;
+		this.uniforms.pano1Position.value.copy(pano1.position)
+		this.uniforms.pano1Matrix.value.copy(pano1.panoMatrix /* pano1.mesh.matrixWorld */ );
+        this.uniforms.cameraHeight1.value = pano1.floorPosition.distanceTo(pano1.position)
+        this.pano0 = pano0
+        this.pano1 = pano1
+        
+        this.updateDepthTex(pano0)  
+        this.updateDepthTex(pano1)
+        
+        
+        //console.log('setProjectedPanos', pano0&&pano0.id, pano1&&pano1.id)
+        this.needsUpdate = true;
+ 	}
+    
+    
+    
+    updateDepthTex(pano){
+        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)
+        this.uniforms.depthMap0.value = this.pano0 && this.pano0.depthTex 
+        this.uniforms.depthMap1.value = this.pano1 && this.pano1.depthTex 
+        this.updateDepthTexEnable()
+    }
+    
+    updateDepthTexEnable(){
+        let hasDepthTex = this.pano0 && this.pano1 && this.pano0.pointcloud.hasDepthTex && this.pano1.pointcloud.hasDepthTex  //暂时不知道一个有图一个没图怎么写所以
+        
+        Common.addOrRemoveDefine(this, 'hasDepthTex', hasDepthTex?'add':'remove' )
+        
+        
+    }
+    
+    /* EnableDepthTex(){//开启DepthTex
+        if(this.defines['hasDepthTex']){
+            return 
+        }
+        this.defines['hasDepthTex'] = ''
+        this.needsUpdate = true;
+    } */
+}

+ 33 - 12
src/materials/PointCloudMaterial.js

@@ -5,7 +5,7 @@ import {Gradients} from "./Gradients.js";
 import {Shaders} from "../../build/shaders/shaders.js";
 import {ClassificationScheme} from "./ClassificationScheme.js";
 import {PointSizeType, PointShape, TreeType, ElevationGradientRepeat} from "../defines.js";
-
+import {Features} from "../Features.js";
 //
 // how to calculate the radius of a projected sphere in screen space
 // http://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space
@@ -90,8 +90,9 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
 			blendHardness:		{ type: "f", value: 2.0 },
 			blendDepthSupplement:	{ type: "f", value: 0.0 },
 			fov:				{ type: "f", value: 1.0 },
-			screenWidth:		{ type: "f", value: 1.0 },
-			screenHeight:		{ type: "f", value: 1.0 },
+			/* screenWidth:		{ type: "f", value: 1.0 },
+			screenHeight:		{ type: "f", value: 1.0 }, */
+            resolution:    { type: 'v2',  value: new THREE.Vector2() },
 			near:				{ type: "f", value: 0.1 },
 			far:				{ type: "f", value: 1.0 },
 			uColor:				{ type: "c", value: new THREE.Color( 0xffffff ) },
@@ -280,14 +281,20 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
 		} else if (this.pointSizeType === PointSizeType.ADAPTIVE) {
 			defines.push('#define adaptive_point_size');
 		}
-
+        
+        if(!Features.EXT_DEPTH.isSupported() && this.shape === PointShape.PARABOLOID){
+            this.shape = PointShape.SQUARE ;//强行替换
+        }
+        
+        
 		if (this.shape === PointShape.SQUARE) {
 			defines.push('#define square_point_shape');
 		} else if (this.shape === PointShape.CIRCLE) {
 			defines.push('#define circle_point_shape');
-		} else if (this.shape === PointShape.PARABOLOID) {
+		}  else if (this.shape === PointShape.PARABOLOID) { 
 			defines.push('#define paraboloid_point_shape');
-		}
+		}  
+        //console.log('this.shape PARABOLOID', this.shape, this.shape === PointShape.PARABOLOID)
 
 		if (this._useEDL || this.fakeEDL) {
 			defines.push('#define use_edl');
@@ -533,7 +540,7 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
 		}
 	}
 
-	get screenWidth () {
+	/* get screenWidth () {
 		return this.uniforms.screenWidth.value;
 	}
 
@@ -542,7 +549,7 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
 			this.uniforms.screenWidth.value = value;
 			// this.updateShaderSource();
 		}
-	}
+	} 
 
 	get screenHeight () {
 		return this.uniforms.screenHeight.value;
@@ -553,8 +560,19 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
 			this.uniforms.screenHeight.value = value;
 			// this.updateShaderSource();
 		}
-	}
-
+	}*/
+    
+    //add--------------
+    get resolution(){
+        return this.uniforms.resolution.value
+    }
+    set resolution(value){
+        this.uniforms.resolution.value.copy(value);
+    }
+    //--------------
+    
+    
+    
 	get near () {
 		return this.uniforms.near.value;
 	}
@@ -622,9 +640,12 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
 	}
 
 	set pointSizeType (value) {
+        
+        if(typeof value == 'string' )value = PointSizeType[value]
+        
 		if (this._pointSizeType !== value) {
 			this._pointSizeType = value;
-			this.updateShaderSource();
+			this.updateShaderSource();              //这句表明这个属性频繁更改会卡顿
 			this.dispatchEvent({
 				type: 'point_size_type_changed',
 				target: this
@@ -668,7 +689,7 @@ export class PointCloudMaterial extends THREE.RawShaderMaterial {
         if(typeof value == 'string') {
             var colorArr = Potree.config.colors[value]  
             if(!colorArr){ 
-                console.warn('没找到该颜色值'+ value)
+                //console.warn('没找到该颜色值'+ value)
             }else{
                 color = new THREE.Color().fromArray(colorArr).multiplyScalar(1/255)
             }                

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

@@ -2,6 +2,10 @@ varying vec2 vUv;
 uniform float opacity;
 
 uniform vec3 baseColor;
+uniform vec3 backColor;
+uniform float occlusionDistance;
+uniform float clipDistance;
+uniform float maxClipFactor;
 
 
 #if defined use_map
@@ -45,24 +49,23 @@ void main() {
         float fragDepth = convertToLinear(gl_FragCoord.z);
 
         // The coordinates of the current fragment in the depth texture
-        vec2 depthTxtCoords = vec2(gl_FragCoord.x-viewportOffset.x,  gl_FragCoord.y) / resolution;
+        vec2 depthTxtCoords = vec2(gl_FragCoord.x-viewportOffset.x,  gl_FragCoord.y - viewportOffset.y) / resolution;
      
         // The linear depth value of the pixel occupied by this fragment in the depth buffer
         float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
 
         // The difference between the two depths
-        float delta = textureDepth - fragDepth;
+        float delta =  fragDepth - textureDepth;
 
-        if (delta < 0.0)//差距
+        if (delta > 0.0)//差距
         {
             // occlusionDistance and clipDistance define the width of the respective zones and
             // mixFactor and clipFactor express the interpolation between the two colors depending on the position
             // of the current fragment withing those zones.
             
-            float occlusionDistance = - 1.0; //1米
-            float clipDistance = - 4.0;
+            
             mixFactor = clamp(delta / occlusionDistance, 0.0, 1.0);
-            clipFactor = clamp(delta / clipDistance, 0.0, 1.0);
+            clipFactor = clamp(delta / clipDistance, 0.0, maxClipFactor);
         }
         
         // If the fragment is totally transparent, don't bother drawing it
@@ -75,8 +78,8 @@ void main() {
                 color = texture2D(map, vUv) * color; 
             #endif
            
-            vec3 backColor = vec3(0.8,0.8,0.8); 
             
+             
             color = vec4(mix(color.rgb, backColor, mixFactor), color.a * (1.0 - clipFactor));
         }
          

+ 23 - 12
src/materials/shaders/edl.fs

@@ -9,8 +9,11 @@
 precision mediump float;
 precision mediump int;
 
-uniform float screenWidth;
-uniform float screenHeight;
+//uniform float screenWidth;
+//uniform float screenHeight;
+uniform vec2 resolution;
+
+
 uniform vec2 neighbours[NEIGHBOUR_COUNT];
 uniform float edlStrength;
 uniform float radius;
@@ -29,22 +32,22 @@ varying vec2 vUv;
 uniform int useEDL;
 
 float response(float depth){
-	vec2 uvRadius = radius / vec2(screenWidth, screenHeight);
+	vec2 uvRadius = radius / resolution;             //vec2(screenWidth, screenHeight);
 	
 	float sum = 0.0;
 	
 	for(int i = 0; i < NEIGHBOUR_COUNT; i++){
 		vec2 uvNeighbor = vUv + uvRadius * neighbours[i];
-		
+		//获取周围八个格子的值
 		float neighbourDepth = texture2D(uEDLColor, uvNeighbor).a;
 		neighbourDepth = (neighbourDepth == 1.0) ? 0.0 : neighbourDepth;
 
 		if(neighbourDepth != 0.0){
-			if(depth == 0.0){
-				sum += 100.0;
-			}else{
-				sum += max(0.0, depth - neighbourDepth);
-			}
+			//if(depth == 0.0){
+			//	sum += 100.0;
+			//}else{
+				sum += max(0.0, depth - neighbourDepth);  //获取差值
+			//}
 		}
 	}
 	
@@ -57,17 +60,25 @@ void main(){
 	float depth = cEDL.a;
 	depth = (depth == 1.0) ? 0.0 : depth;
     
-    if(depth == 0.0){
+    if(depth == 0.0){ //去掉这句就能在无点云像素的地方渲染outline,但会遮住其他mesh
 		discard;
 	}
     
     
     if(useEDL == 1){
         float res = response(depth);
-        float shade = exp(-res * 300.0 * edlStrength);
+        
+        //if(depth == 0.0 && res == 0.0){   //test
+        //    discard;
+        //}
+         
+        float shade = exp(-res * 300.0 * edlStrength); //自然常数e为底的指数函数
 
         gl_FragColor = vec4(cEDL.rgb * shade, opacity); 
-    }else{//加  不改颜色的情况
+        
+        //const vec3 outlineColor = vec3(1.0,0.0,0.0);//test -outline
+        //gl_FragColor = vec4(mix(cEDL.rgb, outlineColor, -res), opacity );
+    }else{//加  不改颜色的情况 
         gl_FragColor = vec4(cEDL.rgb, opacity);
     } 
     

+ 1 - 1
src/materials/shaders/pointcloud.fs

@@ -130,7 +130,7 @@ void main() {
 	
  
     #if defined color_type_indices    //pick point recognize
-		gl_FragColor = vec4(color, uPCIndex / 255.0);
+		gl_FragColor = vec4(color, uPCIndex / 255.0); //uPCIndex : node Index
 	#else
 		gl_FragColor = vec4(color, vOpacity);
 	#endif

+ 43 - 16
src/materials/shaders/pointcloud.vs

@@ -46,7 +46,7 @@ attribute float classification;
 attribute float returnNumber;
 attribute float numberOfReturns;
 attribute float pointSourceID;
-attribute vec4 indices;
+attribute vec4 indices;    //每个点的index
 attribute float spacing;
 attribute float gpsTime;
 attribute vec3 normal;
@@ -58,8 +58,11 @@ uniform mat4 projectionMatrix;
 uniform mat4 viewMatrix;
 uniform mat4 uViewInv;
 
-uniform float uScreenWidth;
-uniform float uScreenHeight;
+//uniform float uScreenWidth;
+//uniform float uScreenHeight;
+uniform vec2 resolution;
+
+
 uniform float fov;
 uniform float near;
 uniform float far;
@@ -714,7 +717,10 @@ float getPointSize(){
 	float pointSize = 1.0;
 	
 	float slope = tan(fov / 2.0);
-	float projFactor = -0.5 * uScreenHeight / (slope * vViewPosition.z);
+	float projFactor = -0.5 * resolution.y / (slope * vViewPosition.z);
+    
+    
+    
     /*
 	float scale = length(
 		modelViewMatrix * vec4(0, 0, 0, 1) - 
@@ -733,7 +739,7 @@ float getPointSize(){
 		pointSize = size;
 	#elif defined attenuated_point_size
 		if(uUseOrthographicCamera){
-			pointSize = size;
+			pointSize = size * 10.0;  //加个乘数
 		}else{  //近大远小,模拟真实mesh,边缘放大
 			//pointSize = size * spacing * projFactor;  //spacing是attribute  为空  如果有这个值就能更自适应填补
             //pointSize = size * uOctreeSpacing * projFactor / 18.0; //直接用cloud的spacing里,不过因为都一样所以可能没有什么意义
@@ -743,7 +749,7 @@ float getPointSize(){
 	#elif defined adaptive_point_size
 		if(uUseOrthographicCamera) {
 			float worldSpaceSize = 1.0 * size * r / getPointSizeAttenuation();
-			pointSize = (worldSpaceSize / uOrthoWidth) * uScreenWidth;
+			pointSize = (worldSpaceSize / uOrthoWidth) * resolution.x;    //uScreenWidth;
 		} else {
 			float worldSpaceSize = 1.0 * size * r / getPointSizeAttenuation();
 			pointSize = worldSpaceSize * projFactor;
@@ -932,21 +938,29 @@ vec3 transformAxis( vec3 direction ) //navvis->4dkk
 }
 
 void main() {
-    
+    //bool filtered_by_normal = false; 
+    float normalZ = 0.0;
+
+
+
     #ifdef use_filter_by_normal
-        if(abs(getNormal().z) > 0.3) { //ufilterByNormalThreshold 暂定0.3
+        /*if(abs(getNormal().z) > 0.4) { //ufilterByNormalThreshold 暂定 3
 			// Move point outside clip space space to discard it.
-			gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
-            return;
-		}
+			//gl_Position = vec4(0.0, 0.0, 2.0, 1.0);   //gl_Position的可视区域是 x,y,z都是[-1,1]  
+            //return;
+            //filtered_by_normal = true; //标记一下。不直接不绘制,因为有的法线都是垂直向上
+             
+		}*/
+        
+        normalZ = abs(getNormal().z);
     #endif
      
     vec4 mvPosition = modelViewMatrix * vec4(position, 1.0 );
     vViewPosition = mvPosition.xyz;
     gl_Position = projectionMatrix * mvPosition;
     vLogDepth = log2(-mvPosition.z);
-
-
+    
+    
      
     // COLOR
     //加-------------------
@@ -999,12 +1013,25 @@ void main() {
    
    
     //-------------------        
-
-    //数据集校准时,相机拉远后随着点云密集需降低透明度 
+ 
     #ifdef attenuated_opacity  
-        vOpacity = uOpacity * exp(-length(-mvPosition.xyz) / 1000.0);  //opacityAttenuation = 1000
+        //zoom不会改变z 所以这并不是用在分屏时候的
+        //vOpacity = uOpacity * exp(-length(-mvPosition.xyz) / 1000.0);  // e为底的指数函数  opacityAttenuation = 1000
+        vOpacity = uOpacity  * exp(gl_Position.z/50.0); 
+        vOpacity = clamp(vOpacity, 0.001, 1.0);          
+        /*if(filtered_by_normal){//垂直朝相机时降低透明度 
+            vOpacity *= 0.2; 
+            vOpacity = clamp(vOpacity, 0.0001, 0.1);    
+        } */  
     #else
         vOpacity = uOpacity;
+        /*if(filtered_by_normal){//垂直朝相机时降低透明度 
+        /*if(filtered_by_normal){//垂直朝相机时降低透明度 
+            vOpacity *= 0.3; 
+            vOpacity = clamp(vOpacity, 0.0001, 0.1);    
+        }*/ 
+        
+        vOpacity *= max(0.1,  (1.0 - normalZ));
     #endif
 	 
 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 521 - 399
src/modules/CameraAnimation/CameraAnimation.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1777 - 437
src/modules/Images360/Images360.js


+ 0 - 207
src/modules/Images360/ModelTextureMaterial.js

@@ -1,207 +0,0 @@
-import * as THREE from "../../../libs/three.js/build/three.module.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;\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\n"
-const prefixFragment ="precision highp float;\nprecision highp int;\n\nuniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"
- 
-let shader = {
-	 
-		uniforms: { 
-			
-			opacity: {
-				type: "f",
-				value: 1
-			},
-			progress: {
-				type: "f",
-				value: 0
-			},
-			
-			pano0Map: {
-				type: "t",
-				value: null
-			},
-			pano0Position: {
-				type: "v3",
-				value: new THREE.Vector3
-			},
-			pano0Matrix: {
-				type: "m4",
-				value: new THREE.Matrix4
-			},
-			pano1Map: {
-				type: "t",
-				value: null
-			},
-			pano1Position: {
-				type: "v3",
-				value: new THREE.Vector3
-			},
-			pano1Matrix: {
-				type: "m4",
-				value: new THREE.Matrix4
-            } 
-            
-        },
-       
-        vertexShader: prefixVertex + `
-
-            uniform vec3 pano0Position;
-            uniform mat4 pano0Matrix;
-            
-            uniform vec3 pano1Position;
-            uniform mat4 pano1Matrix;
-
-           
-            varying vec2 vUv;
-            varying vec3 vWorldPosition0;
-            varying vec3 vWorldPosition1;
-            
-            vec3 transformAxis( vec3 direction ) //navvis->4dkk
-            {
-                float y = direction.y;
-                direction.y = direction.z;
-                direction.z = -y;
-                return  direction;
-            }  
-            
-            
-            
-            void main() {
-            
-                vUv = uv;
-                vec4 worldPosition = modelMatrix * vec4(position, 1.0);
-                
-                
-            
-                vec3 positionLocalToPanoCenter0 = worldPosition.xyz - pano0Position;
-                vWorldPosition0 = (vec4(positionLocalToPanoCenter0, 1.0) * pano0Matrix).xyz;
-                vWorldPosition0.x *= -1.0;
-                vWorldPosition0 = transformAxis(vWorldPosition0);
-                
-                vec3 positionLocalToPanoCenter1 = worldPosition.xyz - pano1Position;
-                vWorldPosition1 = (vec4(positionLocalToPanoCenter1, 1.0) * pano1Matrix).xyz;
-                vWorldPosition1.x *= -1.0;
-                vWorldPosition1 = transformAxis(vWorldPosition1);
-                
-                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-            
-            }
-
-        `,
-        fragmentShader: prefixFragment + `
-
-            #define PI 3.141592653 
-            
-             
-            uniform float modelAlpha;
-            uniform float opacity;
-            uniform float progress;
-            uniform int blackout;
-            uniform vec3 pano0Position;
-            uniform vec3 pano1Position;
-            uniform float maxDistance;
-            uniform float minDistance;
-            uniform float minOpa;
-            
-          
-       
-            /* uniform sampler2D pano0Map;
-            uniform sampler2D pano1Map;    */   
-            uniform samplerCube pano0Map;
-            uniform samplerCube pano1Map;
-          
-            
-            varying vec2 vUv;
-            varying vec3 vWorldPosition0;
-            varying vec3 vWorldPosition1;
-
-          
-            vec2 getSamplerCoord( vec3 direction ) 
-            {
-                direction = normalize(direction);
-                float tx=atan(direction.x,-direction.y)/(PI*2.0)+0.5;
-                float ty=acos(direction.z)/PI;
-
-                return vec2(tx,ty);
-            }  
-            void main()
-            {
-                
-                /* vec2 samplerCoord0 = getSamplerCoord(vWorldPosition0.xyz);
-                vec2 samplerCoord1 = getSamplerCoord(vWorldPosition1.xyz);  
-                vec4 colorFromPano0=texture2D(pano0Map,samplerCoord0);
-                vec4 colorFromPano1=texture2D(pano1Map,samplerCoord1); */
-                
-                vec4 colorFromPano0=textureCube(pano0Map,vWorldPosition0.xyz);
-                vec4 colorFromPano1=textureCube(pano1Map,vWorldPosition1.xyz);
-
-                
-
-
-
-
-                gl_FragColor=mix(colorFromPano0,colorFromPano1,progress);
-              
-                
-            }
-        `
-    }
-            
-            
-
-export default class ModelTextureMaterial extends THREE.RawShaderMaterial { 
-	constructor( ){ 
-        
-        super({
-            fragmentShader: shader.fragmentShader,
-			vertexShader: shader.vertexShader,
-			uniforms: THREE.UniformsUtils.clone(shader.uniforms),
-            side:THREE.DoubleSide,
-			name: "ModelTextureMaterial"
-            
-        })
-        
-        
-        /* var defines = parameters.defines || {}
-        defines.Not_Cube = "";
-        parameters.defines = defines; 
-		
- 
-		super(common.extendObject({
-			fragmentShader: shaders[matName].fragmentShader,
-			vertexShader: shaders[matName].vertexShader,
-			uniforms: THREE.UniformsUtils.clone(shaders[matName].uniforms),
-			name: "ModelTextureMaterial"
-		}, parameters));*/
-        
-        
-		//-------------------------------------
-	}
-
-	/**
-	 * 
-	 * @param {Panorama} pano0 
-	 * @param {Panorama} pano1 
-	 * @param {boolean} flag 
-     
-     更新全景图的材质uniforms 
-     
-	 */
-	setProjectedPanos(pano0, pano1, progressValue ){
-        
- 		progressValue!=void 0 && (this.uniforms.progress.value = progressValue);
-		//pano0.ensureSkyboxReadyForRender();
-		this.uniforms.pano0Map.value = pano0.getSkyboxTexture();//pano0.texture
-		this.uniforms.pano0Position.value.copy(pano0.position)
-		this.uniforms.pano0Matrix.value.copy(pano0.panoMatrix/* pano0.mesh.matrixWorld */ );
-		//pano1.ensureSkyboxReadyForRender();
- 
-        
-		
-		this.uniforms.pano1Map.value = pano1.getSkyboxTexture()//pano1.texture;
-		this.uniforms.pano1Position.value.copy(pano1.position)
-		this.uniforms.pano1Matrix.value.copy(pano1.panoMatrix /* pano1.mesh.matrixWorld */ );
-        
-        
-        this.needsUpdate = true;
- 	}
-}

+ 381 - 140
src/modules/Images360/Panorama.js

@@ -1,27 +1,41 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {transitions, easing, lerp} from '../../utils/transitions.js'
-import TileUtils from './tile/TileUtils'
-import { PanoRendererEvents, PanoramaEvents, PanoSizeClass} from '../../defines'
-import math from '../../utils/math'
-import { EventDispatcher } from "../../EventDispatcher.js";
-import {TextSprite} from '../../TextSprite'
+import TileUtils from './tile/TileUtils.js'
+import { PanoRendererEvents, PanoramaEvents, PanoSizeClass} from '../../defines.js'
+import math from '../../utils/math.js' 
+import {TextSprite} from '../../objects/TextSprite.js'
+import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js";
+
 
 var texLoader = new THREE.TextureLoader()
 
 const labelProp = {
-    sizeInfo: {minSize : 120 ,  maxSize : 200,   nearBound : 0.8, farBound : 10},
-    backgroundColor:{r: 255, g: 255, b: 255, a: 0.2 },
+    sizeInfo: {minSize : 200 ,  maxSize : 250,   nearBound : 0.8, farBound : 10},
+    backgroundColor:{r: 255, g: 255, b: 255, a: 0.4 },
+    textColor:{r: 0, g: 0, b: 0, a: 1 }, 
     borderRadius: 15,
+    renderOrder:10
 }
 
 let standardMarkerMat 
+let markerTex
 let getMarerMat = function(){
-    if(!standardMarkerMat) {
-        let map = texLoader.load( Potree.resourcePath+'/textures/marker.png' )
-        map.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊
-        standardMarkerMat = new THREE.MeshBasicMaterial({opacity:0.7, side: THREE.DoubleSide , map ,transparent:true, depthTest:false})//总是被点云遮住,所以depthTest:false
+    if(!markerTex) {
+        markerTex = {
+            default:texLoader.load( Potree.resourcePath+'/textures/marker.png' ),
+            ring:texLoader.load( Potree.resourcePath+'/textures/marker2.png' )
+        }
+        markerTex.default.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊
+        markerTex.ring.anisotropy = 4  
+        //有可能被点云遮住吗。 
+     
     }
-    return standardMarkerMat.clone()
+    return  new DepthBasicMaterial({opacity:0.7, side: THREE.DoubleSide , map:markerTex.default ,transparent:true, 
+        clipDistance: 2,  occlusionDistance:1,  //不能设置太短,因为过渡时深度不准确
+        //depthTest: !!Potree.settings.useDepthTex,
+        useDepth:  !!Potree.settings.useDepthTex
+        //改为DepthBasicMaterial是因为原Basic的材质过渡时会先隐藏后出现
+    })    
 }
 //显示全景图时marker没有被遮挡,如果需要,要换成depthBasicMaterial  或者直接把skybox的深度修改(拿到深度贴图后更如此)
 let planeGeo = new THREE.PlaneBufferGeometry(0.2,0.2);
@@ -50,78 +64,130 @@ var a = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.
  */
 
 //暂时直接用4dkkconsole输出的数据 
-class Panorama extends EventDispatcher{
+class Panorama extends THREE.EventDispatcher{
 
-	constructor(o, transform, images360){//file, time, longitude, latitude, altitude, course, pitch, roll
+	constructor(o,  images360){//file, time, longitude, latitude, altitude, course, pitch, roll
         super()
-        this.id = o.id;
+        this.id = o.id; //唯一标识
         this.images360 = images360
-        this.transform =  transform
         this.visible = true  //for viewer updateVisible
-         
-        this.originPosition = new THREE.Vector3().fromArray(o.dataset_location) 
-        this.originFloorPosition = new THREE.Vector3().fromArray(o.dataset_floor_location)
-        
-        this.originID = parseInt(o.file_id)//"file_id":"00022"对应是原本的4dkk的id --来自vision.txt
-        
-        
-        
-        this.pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == o.dataset_id) || viewer.scene.pointclouds[0]
-        this.pointcloud.panos.push(this)
-        this.pointcloud.addEventListener('isVisible',(e)=>{ 
-            /* if(!e.visible){//数据集隐藏时漫游点也隐藏
-                e.reason == 'datasetSelection' && viewer.updateVisible(this, 'pointcloudVisi', false) 
-            }else{
-                console.log('pointcloudVisi 1')
-                viewer.updateVisible(this, 'pointcloudVisi', true) 
-            } */
-            e.reason == 'datasetSelection' && viewer.updateVisible(this, 'pointcloudVisi', e.visible) 
-            
-        })
+        this.enabled = true//是否可以走
         this.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走)
-            this.marker.visible = e.visible
+            //console.log('pano isVisible', this.id, e.visible) 
+            viewer.updateVisible(this.marker, 'panoVisi', e.visible)
             Potree.settings.showPanoMesh && (this.mesh.visible = e.visible)
             if(e.reason == 'screenshot' || e.visible){
                 this.label && (this.label.visible = e.visible)//截图时隐藏下
             }
+            viewer.updateVisible(this.label2, 'panoVisi', e.visible)
         })
+        /*  
+        漫游点可见性:旧
+            level       reason                           类型
+            2(最高)buildingChange(不在此楼层)        unvisible   
+            1       modeIsShowPanos(漫游模式)          visible    //不记得为什么加这个了,所以重写
+            0       pointcloudVisi(隐藏了数据集)       unvisible
+         */
+         
+         /* 
+        漫游点可见性:新
+            level       reason                           类型
+            2(最高)buildingChange(不在此楼层)        unvisible   
+            1       ifShowMarker(marker显示开关)       unvisible 
+            0       pointcloudVisi(隐藏了数据集)       unvisible
+         */ 
+         
         
-        
-        //全景图和Cube的水平采样起始坐标相差90度 
-        
-
-        /* if(from4dkk){
-            var qua = o.dataset_orientation 
+        if(Potree.settings.editType == 'pano'){//漫游点拼合编辑
+            this.uuid = o.uuid  //因为有多个数据集 所以会重复
+            this.index = o.index  //下标, 用于visibles
+            this.pointcloud = viewer.scene.pointclouds.find(e=>e.panoUuid == o.uuid) 
+            this.pointcloud.panos.push(this)
+            this.sid = this.pointcloud.dataset_id + '|' + this.uuid  //不会更改的标记  用于entity.panos里的标记
             
-            var quaternion = new THREE.Quaternion().fromArray(qua)
-                quaternion = new THREE.Quaternion().multiplyQuaternions(quaternion,  rot901);//整张球幕图要旋转下  因为在4dkk里转过,还原。如果是tiles的不用
-            this.quaternion = new THREE.Quaternion(quaternion.x, -quaternion.z, quaternion.y, quaternion.w) //转化坐标
-                
-        }else{ */
-        
             
-            var qua = o.dataset_orientation 
-            qua = [qua[1], qua[2], qua[3], qua[0]] 
-            this.quaternion = new THREE.Quaternion().fromArray(qua)
-            this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
+            this.panosData = o
             
-            this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion,  rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度
+            //数据中原本的位置朝向
+            this.dataPosition = new THREE.Vector3().copy(o.pose.translation) 
+            this.dataQuaternion = new THREE.Quaternion().copy(o.pose.rotation) 
+            this.dataRotation = new THREE.Euler().setFromQuaternion(this.dataQuaternion) 
             
-            this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk)
-            this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion) 
-         //}
-         
-         
-            //this.quaternion1 = Potree.Utils.QuaternionFactory.fromArray(o.dataset_orientation)
-            //同quaternion
+            
+            //因为位置朝向随着点云位置改变,所以直接运用到点云上,这里清零
+            this.originPosition = new THREE.Vector3()   //{x: 0, y: 0, z: 0}
+            this.quaternion = new THREE.Quaternion()  //{w: 0, x: 0, y: 0, z: 1}
+            //this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
+            this.visibles = o.visibles 
+            
+            
+            const height = 1.4; //相机高度
+            this.originFloorPosition = this.originPosition.clone()
+            this.originFloorPosition.z -= height
+            
+            
+            /* this.originPosition = new THREE.Vector3().copy(o.pose.translation)  //{x: 0, y: 0, z: 0}
+            this.quaternion = new THREE.Quaternion().copy(o.pose.rotation) //{w: 0, x: 0, y: 0, z: 1}
+            //this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
+            this.visibles = o.visibles 
+            this.pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == o.uuid) 
+            this.pointcloud.panos.push(this)
+            
+            const height = 1.5; //相机高度
+            this.originFloorPosition = this.originPosition.clone()
+            this.originFloorPosition.z -= height
+             */
+            
+            
+        }else{
+            this.originPosition = new THREE.Vector3().fromArray(o.dataset_location) 
+            this.originFloorPosition = new THREE.Vector3().fromArray(o.dataset_floor_location)
+            
+            this.originID = parseInt(o.file_id)//"file_id":"00022"对应是原本的4dkk的id --来自vision.txt
+             
+            this.pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == o.dataset_id) || viewer.scene.pointclouds[0]
+            this.pointcloud.panos.push(this)
+            
+            //this.sid = this.pointcloud.sceneCode + '|' + this.originID  //不会更改的标记
+            this.sid = this.pointcloud.dataset_id + '|' + this.originID  //不会更改的标记
+            //全景图和Cube的水平采样起始坐标相差90度 
+            
+
+            /* if(from4dkk){
+                var qua = o.dataset_orientation 
+                
+                var quaternion = new THREE.Quaternion().fromArray(qua)
+                    quaternion = new THREE.Quaternion().multiplyQuaternions(quaternion,  rot901);//整张球幕图要旋转下  因为在4dkk里转过,还原。如果是tiles的不用
+                this.quaternion = new THREE.Quaternion(quaternion.x, -quaternion.z, quaternion.y, quaternion.w) //转化坐标
+                    
+            }else{ */
+            
+                
+                var qua = o.dataset_orientation 
+                qua = [qua[1], qua[2], qua[3], qua[0]] 
+                this.quaternion = new THREE.Quaternion().fromArray(qua)
+                this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
+                this.quaternion2 = this.quaternion.clone()
+                this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion,  rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度
+                
+                this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk)
+                
+             //}
+             
+             
+                //this.quaternion1 = Potree.Utils.QuaternionFactory.fromArray(o.dataset_orientation)
+                //同quaternion
 
-      
-        //let xy = this.transform.forward([this.longitude, this.latitude]);  
-        this.file = `https://4dkk.4dage.com/images/images${Potree.settings.number}/pan/high/${this.id}.jpg`
+          
+            //let xy = this.transform.forward([this.longitude, this.latitude]);  
+            this.file = `https://4dkk.4dage.com/images/images${Potree.settings.number}/pan/high/${this.id}.jpg`
+            
+             
+            
+        }
+        this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion) 
         this.build()
         this.transformByPointcloud() //初始化位移
-         
-        
         
         {//tile
             this.minimumTiledPanoLoaded = !1;
@@ -135,22 +201,22 @@ class Panorama extends EventDispatcher{
             
             
            
-            images360.panoRenderer.on(PanoRendererEvents.TileRenderSuccess, this.onTileRendered.bind(this));
-            images360.panoRenderer.on(PanoRendererEvents.PanoRenderComplete, this.onPanoRendered.bind(this));
-            images360.panoRenderer.on(PanoRendererEvents.TileRenderFailure, this.onTileRenderFail.bind(this));
-            images360.panoRenderer.on(PanoRendererEvents.UploadAttemptedForAllTiles, this.onUploadAttemptedForAllTiles.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderSuccess, this.onTileRendered.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.PanoRenderComplete, this.onPanoRendered.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderFailure, this.onTileRenderFail.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.UploadAttemptedForAllTiles, this.onUploadAttemptedForAllTiles.bind(this));
             
         }
         
         
         
-        this.on('hoverOn', (e)=>{//from Map
+        this.addEventListener('hoverOn', (e)=>{//from Map
             if(!e.byMainView){ 
                 this.hoverOn(e) 
             } 
         })
         
-        this.on('hoverOff', (e)=>{
+        this.addEventListener('hoverOff', (e)=>{
             if(!e.byMainView){
                 this.hoverOff(e) 
             } 
@@ -159,15 +225,38 @@ class Panorama extends EventDispatcher{
     
 
 
+    setEnable(enable){//是否可以走
+        viewer.updateVisible(this, 'isEnabled', enable) //令所有marker不可见
 
+        this.enabled = enable 
+        //如果当前在全景模式且在这个点,需要切换显示吗? 目前用不到 
+    }
 
-
-
-
+ 
+    loadDepthImg(){ 
+        if(!this.pointcloud.hasDepthTex || this.depthTex || this.depthTexLoading)return
+        this.depthTexLoading = true
+        let src = Potree.settings.number == 'SS-t-7DUfWAUZ3V' ?  `${Potree.scriptPath}/data/${Potree.settings.number}/depthMap/${this.originID}.png`
+                : `https://laser-oss.4dkankan.com/${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png`
+        let texture = texLoader.load( src, ()=>{
+            this.depthTex = texture
+            this.images360.dispatchEvent({type:'loadedDepthImg', pano:this, loaded:true})
+            this.depthTexLoading = false
+        },(e)=>{//error
+            console.error('loadDepthImg失败, 数据集sceneCode'+ this.pointcloud.sceneCode,  this.id )
+            this.pointcloud.hasDepthTex = false
+            this.images360.dispatchEvent({type:'loadedDepthImg', pano:this, })
+        });
+        texture.wrapS = THREE.RepeatWrapping;
+        texture.flipY = false 
+        texture.magFilter = THREE.LinearFilter
+        texture.minFilter = THREE.LinearFilter
+	}
+ 
     
     build(){
           
-        let mesh = new THREE.Mesh(sg, sm); 
+        /* let mesh = new THREE.Mesh(sg, sm); 
         mesh.scale.set(1, 1, 1);
         mesh.material.transparent = true;
         mesh.material.opacity = 0.75;
@@ -181,7 +270,12 @@ class Panorama extends EventDispatcher{
         })
         mesh.addEventListener('click',(e)=>{
             this.images360.focusPano(this)
-        })
+        }) 
+        this.mesh = mesh;
+        if(!Potree.settings.showPanoMesh) mesh.visible = false
+        this.images360.node.add(mesh)
+        */
+        
         { // orientation
             //var {course, pitch, roll} = this;
             //mesh.quaternion.copy(this.quaternion) 
@@ -191,49 +285,98 @@ class Panorama extends EventDispatcher{
             //quaternion.premultiply(rot90)
             this.panoMatrix = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion) 
             this.oriPanoMatrix = this.panoMatrix.clone()
+            
+            if(this.quaternion2)this.oriPanoMatrix2 = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion2) 
+        
+            
             //console.log(this.quaternion)
             //this.quaternion = quaternion
         } 
-        this.mesh = mesh;
-        if(!Potree.settings.showPanoMesh) mesh.visible = false
+        
+        
          
         let marker = new THREE.Mesh(planeGeo, getMarerMat() ) 
+            marker.name = 'marker_'+this.id
             marker.up.set(0,0,1)
             marker.lookAt(marker.up) 
             marker.scale.set(2,2,2) 
-            
+        this.addEventListener('changeMarkerTex',(e)=>{
+            marker.material.map = markerTex[e.name]  
+        })    
              
         this.marker = marker 
+        if(Potree.settings.editType == 'pano'){
+            viewer.updateVisible(marker, 'panoEdit', false, 4)
+        }
         
-        this.images360.node.add(mesh)
         this.images360.node.add(marker)
         Potree.settings.isTest && this.createTextLabel()
+        this.createTextLabel2() 
         
+        /* let mouseover = (e)=>{ 
+            if(!e.byMap){
+                pano.mapMarker.material = panoMarkerMats.selected
+                if(!e.byMainView) pano.dispatchEvent({type: "hoverOn", byMap:true})
+                this.needRender = true    
+            }
+        }
+        
+        let mouseleave = (e)=>{
+            if(!e.byMap){
+                pano.mapMarker.material = panoMarkerMats.default
+                if(!e.byMainView) pano.dispatchEvent({type: "hoverOff", byMap:true})
+                this.needRender = true
+            }
+        } */
+         
+        
+        marker.addEventListener('mouseover', this.hoverOn.bind(this));  
+        marker.addEventListener('mouseleave', this.hoverOff.bind(this)); 
     }
     
     
     
     
     transformByPointcloud(){
-         
-        let position = this.originPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);
+        
+        let position = this.originPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);//也可以用datasetPosTransform算
         let floorPosition = this.originFloorPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);
         this.setPosition(position, floorPosition) 
         this.panoMatrix = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix  ) 
-    
-        //quaternion也变下  
+        //this.panoMatrix2 =  Potree.Utils.datasetRotTransform({fromDataset:true, pointcloud:this.pointcloud,  matrix:this.oriPanoMatrix, getMatrix:true}) //和上一行结果一样
+        //quaternion也变下
+        if(this.oriPanoMatrix2){ 
+            this.panoMatrix2 = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix2  )//供DepthImageSampler使用 
+            this.panoMatrix2Inverse = this.panoMatrix2.clone().invert(); 
+        }        
+        this.dispatchEvent('rePos')
     }
     
     setPosition(position, floorPosition){
         this.position = position
         this.floorPosition = floorPosition
-        this.mesh.position.copy(this.position)
+        //this.mesh.position.copy(this.position)
         this.marker.position.copy(this.floorPosition) 
-        this.marker.position.z+=0.1//会被点云遮住
-        if(this.label){
-            this.label.position.copy(this.floorPosition), this.label.position.z+=0.2
+        this.marker.position.z+=0.04//会被点云遮住
+        if(this.label){ 
+            if(Potree.settings.editType == 'pano'){
+                this.label.position.copy(this.position)
+            }else{
+                this.label.position.copy(this.floorPosition)
+            } 
+            this.label.position.z+=0.14
             this.label.update()
         }
+        
+        if(this.label2){
+            if(Potree.settings.editType == 'pano'){
+                this.label2.position.copy(this.position)
+            }else{
+                this.label2.position.copy(this.floorPosition)
+            }
+            this.label2.position.copy(this.marker.position)
+            this.label2.update()
+        }
           
     }
     
@@ -247,7 +390,8 @@ class Panorama extends EventDispatcher{
     hoverOn(e={}) { 
         //console.log("hoverOn  " + this.id  )
         transitions.start(lerp.property(this.marker.material, "opacity", 1), 250)  
-		if(!e.byMap) this.emit('hoverOn', {byMainView:true})
+		if(!e.byMap) this.dispatchEvent({type:'hoverOn', byMainView:true})
+        if(!e.byImages360) this.images360.dispatchEvent({type:'markerHover', hovered:true, pano:this})
     }
 
  
@@ -256,7 +400,8 @@ class Panorama extends EventDispatcher{
     hoverOff(e={}){
         //console.log("hoverOff  " + this.id  )
         transitions.start(lerp.property(this.marker.material, "opacity", 0.5), 250) 
-        if(!e.byMap) this.emit('hoverOff', {byMainView:true})
+        if(!e.byMap) this.dispatchEvent({type:'hoverOff',  byMainView:true})
+        if(!e.byImages360) this.images360.dispatchEvent({type:'markerHover', hovered:false, pano:this})
     }
     
     
@@ -264,14 +409,14 @@ class Panorama extends EventDispatcher{
     setZoomed(zoomed){
         this.zoomed = zoomed;
         Potree.settings.displayMode == 'showPanos' && this.updateSkyboxForZoomLevel(); //放大后换成zoomTarget贴图
-        viewer.emit('panoSetZoom', this, zoomed)
+        viewer.dispatchEvent({type:'panoSetZoom', zoomed})
         
     }
     
     
     enter(){ 
         this.setZoomed(!1),
-        viewer.emit(PanoramaEvents.Enter,  {oldPano:old, newPano:this  }  )
+        viewer.dispatchEvent({type:PanoramaEvents.Enter,  oldPano:old, newPano:this  }  )
         old = this 
         //console.log("enter pano "+ this.id)
     } 
@@ -296,7 +441,7 @@ class Panorama extends EventDispatcher{
         
         //console.log("exit pano "+ this.id)
         
-        viewer.emit(PanoramaEvents.Exit, this); 
+        viewer.dispatchEvent({type:PanoramaEvents.Exit, pano:this}); 
     }
     
     
@@ -331,15 +476,11 @@ class Panorama extends EventDispatcher{
     
    
     
-    isLoaded(e){
-        //if (this.tiled) {
-            if (e && "string" == typeof e)
-                console.error("Wrong panoSize given to Panorama.isLoaded(); a tiled pano uses PanoSizeClass");
-            return !!this.minimumTiledPanoLoaded && (!e || this.highestPartialTileRenderOpCompleted >= e)
-        //}
-        /* if (e && "number" == typeof e)
-            throw new BasicException("Wrong panoSize given to Panorama.isLoaded(); a non-tiled pano uses high/low.");
-        return !!this.solidSkybox.high || e in this.solidSkybox */
+    isLoaded(e){ 
+        if (e && "string" == typeof e)
+            console.error("Wrong panoSize given to Panorama.isLoaded(); a tiled pano uses PanoSizeClass"); 
+        return !!this.minimumTiledPanoLoaded && (!e || this.highestFullTileRenderOpCompleted >= e)//改:原本是:this.highestPartialTileRenderOpCompleted >= e, 希望这代表全部加载完
+     
     }
 
     getWaitDeferred(size){//获取不同size的tile贴图的promiss 
@@ -370,30 +511,33 @@ class Panorama extends EventDispatcher{
         t.active = !1;
         t.deferred = $.Deferred();
     }
-    onTileRendered(e, t, i, n){
-        e === this.id && this.dispatchEvent({type:PanoramaEvents.TileLoaded,  size:t, index:i, count:n});
+    onTileRendered(ev){  
+        ev.id === this.id && this.dispatchEvent({
+            type:PanoramaEvents.TileLoaded, 
+            size:ev.panoSize, index:ev.tileIndex, count:ev.totalTiles
+        });
     }
 
-    onPanoRendered(e, t, i, n) {
-        if(e === this.id)
+    onPanoRendered(ev) { 
+        if(ev.id === this.id)  
         {
             this.minimumTiledPanoLoaded = !0;
             this.updateSkyboxForZoomLevel();//更新贴图 setProjected
-            t > this.highestPartialTileRenderOpCompleted && (this.highestPartialTileRenderOpCompleted = t);//应该是更新最高获取到的Partial size
-            !n && t > this.highestFullTileRenderOpCompleted && (this.highestFullTileRenderOpCompleted = t); //应该是更新最高获取到的Full size
-            //this.emit("load", t);
+            ev.panoSize > this.highestPartialTileRenderOpCompleted && (this.highestPartialTileRenderOpCompleted = ev.panoSize);//应该是更新最高获取到的Partial size
+            ev.updateFullComplete && ev.panoSize > this.highestFullTileRenderOpCompleted && (this.highestFullTileRenderOpCompleted = ev.panoSize); //应该是更新最高获取到的Full size
+            //this.dispatchEvent("load", ev.panoSize);
             viewer.ifAllLoaded( this);
-            this.dispatchEvent({type:PanoramaEvents.LoadComplete, size:t, count:i});
+            this.dispatchEvent({type:PanoramaEvents.LoadComplete, size:ev.panoSize, count:ev.totalTiles});
         }
     }
  
-    onTileRenderFail(e, t, i) {
-        e === this.id && this.dispatchEvent({type:PanoramaEvents.LoadFailed, t});
+    onTileRenderFail(ev) { 
+        ev.id === this.id && this.dispatchEvent({type:PanoramaEvents.LoadFailed   });
     }
-    onUploadAttemptedForAllTiles(e, t, i) {
-        if (e === this.id) {
+    onUploadAttemptedForAllTiles(ev) { 
+        if (ev.id === this.id) {        
             var n = this.images360.qualityManager.getPanoSize(PanoSizeClass.BASE);
-            if(t === n && this.shouldRedrawOnBaseLoaded)
+            if(ev.panoSize === n && this.shouldRedrawOnBaseLoaded) //shouldRedrawOnBaseLoaded一直是false。在4dkk里只有初始点在quickstart后变为true。
             {
                 this.shouldRedrawOnBaseLoaded = !1;
                 this.panoRenderer.resetRenderStatus(this.id, !0, !1);
@@ -406,58 +550,119 @@ class Panorama extends EventDispatcher{
     
     createTextLabel(){
         this.removeTextLabel()
-        this.label = new TextSprite($.extend(
-           labelProp, {text: this.id}) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`}
-        );
-
+        this.label = new TextSprite(Object.assign({},
+           labelProp, {text: this.id }) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`}
+        ); 
         this.images360.node.add(this.label);
         this.floorPosition && this.label.position.copy(this.floorPosition)
     }
     
+    createTextLabel2(){ 
+        let labelProp2 = {
+            //sizeInfo: {minSize : 200 ,  maxSize : 250,   nearBound : 0.8, farBound : 10},
+            backgroundColor:{r: 255, g: 255, b: 255, a: 0 },
+            textColor:{r:255 , g: 255, b: 255, a: 1 }, 
+            textBorderColor:{r:30 , g:30, b: 30, a: 1 }, 
+            textBorderThick:3,
+            dontFixOrient:true,
+            renderOrder:10,
+            fontsize:30,
+        } 
+        this.label2 = new TextSprite(Object.assign({},
+           labelProp2, {text: /* this.originID  */   parseInt(this.id)+1   }) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`}
+        ); 
+        this.images360.node.add(this.label2);
+        this.floorPosition && this.label2.position.copy(this.floorPosition)
+        let s = 0.4
+        this.label2.scale.set(s,s,s)
+        viewer.updateVisible(this.label2, 'notDisplay', false)
+    }
+    
     removeTextLabel(){
-        if(this.label){
-            
+        if(this.label){ 
             this.label.parent.remove(this.label);
         }
     }
     
-    
+    dispose(){
+        
+        let i = viewer.images360.panos.indexOf(this);
+        if(i==-1)return
+        
+        this.marker.parent.remove(this.marker)
+        
+        
+        this.removeTextLabel()
+        if(this.depthTex) this.depthTex.dispose()
+        viewer.images360.panos.splice(i,1);
+        
+        this.dispatchEvent('dispose')
+        //删除tile贴图、depthTex等以后再写
+    }
     
 };
 
 
-
+ 
 
 Panorama.prototype.loadTiledPano = function() {
-    var downloads = []  , t = [];
-    
+    //var downloads = []  , t = [];
+    var downloaded = {}  , eventAdded = {}, latestPartialRequest = {}; //每个pano对应一组这些
+         
     return function(size, dirs, fov, o, a, download) {
-        var dir = dirs.find(e=>e.datasetId == this.pointcloud.dataset_id).direction;
+        var dir = dirs.datasetsLocal.find(e=>e.datasetId == this.pointcloud.dataset_id).direction;
         //var dir = dirs
-        
+         
         
         null !== o && void 0 !== o || (o = !0),
         null !== a && void 0 !== a || (a = !0);
         var l = this.getWaitDeferred(size)
           , c = l.deferred
           , h = null
-          , u = null;
+          , u = null; 
         fov && ("number" == typeof fov ? h = fov : (h = fov.hFov, u = fov.vFov))  
+        
         if (!this.isLoaded(size)) {
+            //console.log('loadTiledPano', this.id, size, fov)
             if (!l.active) {
                 l.active = !0 
+                let name = this.id + ":" + size
+                downloaded[name] = downloaded[name] || []
+                /* 
+                this.downloaded = downloaded
+                this.latestPartialRequest = latestPartialRequest 
+                 */
+                latestPartialRequest[name] = null
+                     
                 if (fov) {
-                    var d = TileUtils.matchingTilesInDirection(this, size, dir, h, u);
-                    downloads[this.id + ":" + size] = {
-                        tileCount: 0,
-                        targetTileCount: d
-                    } 
+                    let tileArr = []//add 
+                    var d = TileUtils.matchingTilesInDirection(this, size, dir, h, u, tileArr);
+                    
+                    latestPartialRequest[name] = tileArr
+                    downloaded[name].forEach((e)=>{
+                         let item = latestPartialRequest[name].find(a=>e.faceTileIndex == a.faceTileIndex && e.face == a.face)  
+                         if(item){
+                             item.loaded = true
+                         }
+                    })
+                    if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的全部加载成功
+                        //let total = TileUtils.getTileCountForSize(size)
+                        //this.onPanoRendered(this.id, size, total, !0);
+                        c.resolve(size/* , total */);
+                        this.resetWaitDeferred(size)
+                        //console.log('该部分早已经加载好了'+size, this.id)
+                        latestPartialRequest[name] = null
+                    }
+                     
                     //console.log("Loading partial pano: " + this.id + " with " + d + " tiles")
                 }
-                if(!t[this.id]) {
-                    t[this.id] = !0 
+                if(!eventAdded[this.id]) {
+                    eventAdded[this.id] = !0 
                     
                     this.addEventListener(PanoramaEvents.LoadComplete, function(ev/* e, t */) {//本次任务全部加载完毕 
+                        
+                        //console.warn('点位(可能部分)下载完成 ', 'id:'+this.id,  'size:'+ev.size ) 
+                        
                         var i = this.getWaitDeferred(ev.size).deferred;//"pending"为还未完成
                         i && "pending" === i.state() && this.highestPartialTileRenderOpCompleted >= ev.size && (i.resolve(ev.size, ev.count),
                         this.resetWaitDeferred(ev.size))//恢复active为false
@@ -470,26 +675,62 @@ Panorama.prototype.loadTiledPano = function() {
                     }.bind(this)) 
                     
                     this.addEventListener(PanoramaEvents.TileLoaded, function(ev/* t, i, n */) {//每张加载完时
-                        var r = this.getWaitDeferred(ev.size).deferred;
+                        
+                        //console.log('tileLoaded', 'id:'+this.id,  'size:'+ev.size, 'tileIndex:'+ev.index )
+                        let tileIndex = ev.index
+                        let total = ev.count
+                        let size = ev.size
+                        let name = this.id + ":" + size 
+                        downloaded[name] = downloaded[name] || [] //不是所有的加载都是从loadTiledPano获取的所以会有未定义的情况
+                        
+                        let {faceTileIndex,face} = TileUtils.getTileLocation(size, tileIndex, {}) 
+                        downloaded[name].push({faceTileIndex,face})    
+                        var r = this.getWaitDeferred(size).deferred;
+                        if (r && "pending" === r.state()) { 
+                            r.notify(size, tileIndex, total);
+                            if(latestPartialRequest[name]){
+                                let item = latestPartialRequest[name].find(e=>e.faceTileIndex == faceTileIndex && e.face == face)    
+                                item && (item.loaded = true ) 
+                                
+                                if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的局部tiles全部加载成功
+                                    this.onPanoRendered(this.id, size, total, !0); //onPanoRendered还会触发 PanoramaEvents.LoadComplete   
+                                    r.resolve(size, total);
+                                    this.resetWaitDeferred(size)
+                                    //console.log('该部分加载好了'+size, this.id)
+                                    latestPartialRequest[name] = null
+                                }
+                                
+                            } 
+                        } 
+
+
+    
+                        
+                        /* var r = this.getWaitDeferred(ev.size).deferred;
                         if (r && "pending" === r.state()) {
                             r.notify(ev.size, ev.index, ev.count);
+                             
                             var o = downloads[this.id + ":" + ev.size];
                             if(o){//如果有规定下载哪些tile,只需要下载这些tile则LoadComplete
                                 o.tileCount++ 
+                                
                                 if(o.tileCount === o.targetTileCount){//达到下载目标数
                                     this.onPanoRendered(this.id, ev.size, ev.count, !0);
                                     r.resolve(ev.size, ev.count);
                                     this.resetWaitDeferred(ev.size)
                                 }
                             }
-                        }
+                        } */
                     }.bind(this))
                 }
             }
             this.images360.tileDownloader.clearForceQueue(),
-            this.images360.tileDownloader.forceQueueTilesForPano(this, size, dir, h, u, download),
-            this.tiledPanoRenderTarget = this.images360.panoRenderer.activateTiledPano(this, this.images360.qualityManager.getMaxNavPanoSize(), o),
+            this.images360.tileDownloader.forceQueueTilesForPano(this, size, dir, h, u, download) 
+            this.tiledPanoRenderTarget = this.images360.panoRenderer.activateTiledPano(this, this.images360.qualityManager.getMaxNavPanoSize(), o) 
             this.images360.panoRenderer.renderPanoTiles(this.id, dirs, a)
+        }else{
+            //console.log('早已经全加载好了' +size, this.id)
+            c.resolve(size)
         }
         return c.promise()
     }

+ 85 - 44
src/modules/Images360/tile/PanoRenderer.js

@@ -1,15 +1,14 @@
  
-import {PanoSizeClass, PanoRendererEvents , Vectors,SceneRendererEvents,TileDownloaderEvents, GLCubeFaces} from '../../../defines'
+import {PanoSizeClass, PanoRendererEvents , Vectors,SceneRendererEvents,TileDownloaderEvents, GLCubeFaces} from '../../../defines.js'
  
 import {Shaders} from "../../../../build/shaders/shaders.js";
-import TileTree from './TileTree'
-import TilePrioritizer from './TilePrioritizer'
+import TileTree from './TileTree.js'
+import TilePrioritizer from './TilePrioritizer.js'
 import TileUtils from './TileUtils' 
-import {settings,config} from '../../../settings'
+import {settings,config} from '../../../settings.js'
 /* import config from '../../config' */
-import * as THREE from "../../../../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "../../../EventDispatcher.js";
-import math from '../../../utils/math' 
+import * as THREE from "../../../../libs/three.js/build/three.module.js"; 
+import math from '../../../utils/math.js' 
 
 function createDescriptor() {
     var e = {
@@ -48,7 +47,7 @@ var b = !1,
     }/* ,
     M = []; */
 
-class PanoRenderer extends EventDispatcher{
+class PanoRenderer extends THREE.EventDispatcher{
     constructor(viewer, tileDownloader, qualityManager) {
         super()
 		this.tileDirectory = {};
@@ -311,7 +310,7 @@ class PanoRenderer extends EventDispatcher{
             if(i){
                 this.uploadTile(e, !1)//提交
             }else{
-                if(this.shoulPushToFrontOfQueue(e)){
+                if(this.shoulPushToFrontOfQueue(e)){//如果是512的优先
                     this.forceQueue.push(e)
                 }else{
                     if(t && this.direction){
@@ -321,12 +320,7 @@ class PanoRenderer extends EventDispatcher{
                 e.uploadQueued = !0 
                 this.uploadInterval || this.uploadIntervalCancelled || this.refreshUploadInterval(0)
             }
-            
-            
-            
-            /* i ? this.uploadTile(e, !1) : (this.shoulPushToFrontOfQueue(e) ? this.forceQueue.push(e) : t && this.direction ? TilePrioritizer.insertSortedPanoTile(r, e, n.pano, this.direction) : r.push(e),
-                e.uploadQueued = !0,
-                this.uploadInterval || this.uploadIntervalCancelled || this.refreshUploadInterval(0)) */
+             
         }
     }
 
@@ -387,17 +381,21 @@ class PanoRenderer extends EventDispatcher{
             (!i || i && i === r.tile.panoId) && r.level >= t ? (r.uploadQueued = !1,
                 e.splice(n, 1)) : n++
         }
+        //若报错, r.tile.panoId改为 r.panoId
     }
 
-
-    updateUploadQueue(e, t) {
-        e || (e = 1);
+        
+    updateUploadQueue(maxNPF,maxPF/* e, t */) {//参数是 maxNonBaseUploadsPerFrame and maxBaseUploadsPerFrame, 优先上传512
+        maxNPF || (maxNPF = 1);
         for (var i = 0, n = 0;;) {
-            if (n >= t || i >= e)
+            let old = this.forceQueue.slice(0)
+           
+            if (n >= maxPF || i >= maxNPF)
                 break;
             var r = this.getNextFromUploadQueue();
             if (!r)
                 break;
+            //r.panoSize <2048 && console.log('panoId', r.panoId, 'panoSize', r.panoSize , old)
             0 !== r.level ? i++ : n++
             if (!(r.panoSize > this.qualityManager.getMaxNavPanoSize()) || this.zoomingActive) {
                 var o = this.getActiveRenderTargetDescriptor(r.panoId);
@@ -485,7 +483,8 @@ class PanoRenderer extends EventDispatcher{
             if (t.hasOwnProperty(i)) {
                 var n = t[i];
                 n.uploadCount = 0,
-                    n.uploadAttempts = 0
+                n.uploadAttempts = 0  
+                n.uploaded = [] 
             }
     }
 
@@ -494,7 +493,8 @@ class PanoRenderer extends EventDispatcher{
             n = i[t];
         return n || (n = {
                     uploadCount: 0,
-                    uploadAttempts: 0
+                    uploadAttempts: 0, 
+                    uploaded:[],//add
                 },
                 i[t] = n),
             n
@@ -797,12 +797,12 @@ PanoRenderer.prototype.renderPanoTiles = function () {
         for (var a = 0; a < TileUtils.FACES_PER_PANO; a++) {
             var s = this.getTileTree(panoId, a);
             e.length = 0 
-            s.breadthFirst({//获取所有node?
+            s.breadthFirst({//获取所有node?  85个
                 saveVisited: e
             });
             for (var l = 0; l < e.length; l++) {
                 var c = e[l];
-                this.queueTileUpload(c.tile, !1, r || 0 === l && n)//???
+                this.queueTileUpload(c.tile, !1, r || 0 === l && n)//为什么第0个会直接uploadTile??
             }
         }
         this.updateDirection(i)
@@ -853,7 +853,7 @@ PanoRenderer.prototype.uploadTile = function () {//重写
     var collection = {},
         overlayStyle = config.tiling.overlayStyle;
         
-        
+    var failHistory = {};    
         
     return function (info, n) {
         
@@ -867,6 +867,7 @@ PanoRenderer.prototype.uploadTile = function () {//重写
             tileY = info.tileY,
             p = !0,
             g = !1,
+            ignore = false, //add   
             LodDescripor = (this.getPanoDescriptor(id), this.getPanoLODDescriptor(id, panoSize)),
             activeDescripor = this.getActiveRenderTargetDescriptor(id),
             renderTarget = activeDescripor.renderTarget,
@@ -878,15 +879,37 @@ PanoRenderer.prototype.uploadTile = function () {//重写
             size =  this.zoomRenderTarget.width   //this.qualityManager.getMaxZoomPanoSize(); //放大后可能2048或4096
         } 
         
+        let done = ()=>{ 
+            if(!LodDescripor.uploaded.includes(tileIndex)){//已经upload过(本来这时候直接返回,但发现缩放后这不会归零,导致清晰度不更新,所以还是redraw且emit吧)
+                //console.log('try to reupload and return',tileIndex) 
+                LodDescripor.uploaded.push(tileIndex)
+                LodDescripor.uploadCount++;
+            }   
+            this.dispatchEvent({type:PanoRendererEvents.TileRenderSuccess, id, panoSize, tileIndex, totalTiles});
+            LodDescripor.uploadCount === totalTiles && this.dispatchEvent({type:PanoRendererEvents.PanoRenderComplete, id, panoSize, totalTiles, updateFullComplete:true});
+            this.setUploaded(info, !0);
+            this.addCoverageForNode(info.node);
+        }
+        
         
         
+        {//已经uploadTile过了不再uploadTile
+            if(!this.isRenderTargetDescriptorValid(activeDescripor)){
+                p = !1; g = !1
+            } 
+            if(!n){
+                this.anyUploaded(info.node) && (p = !1, g = !0,ignore = true  ) //包括子集也uploadTile了
+                this.isTileUploaded(info) && (p = !1, g = !1,ignore = true ) //当前tile uploadTile了 
+            }
+        }
         
         
-        this.isRenderTargetDescriptorValid(activeDescripor) || (p = !1, g = !1),
-            n || (this.anyUploaded(info.node) && (p = !1, g = !0),
-                this.isTileUploaded(info) && (p = !1, g = !1));
         if (p) {
-              
+             
+            /*if(failHistory[id+':'+ panoSize+ ':' +tileIndex]){
+                console.log('uploadTile retry',id, panoSize, tileIndex)
+            } 
+            console.log('uploadTile 成功', id, panoSize, tileIndex) */  
 
             var C = tileX * tileSize,
                 I = tileY * tileSize,
@@ -916,18 +939,29 @@ PanoRenderer.prototype.uploadTile = function () {//重写
                     this.renderToCubeMap(tex, renderTarget, tileSize, tileSize, 0, 0, tileSize, tileSize, b, w, E, E, info.cubeFace);
                 }
             }
+            done()
+           
+        }else if(ignore){ 
+            //console.log('finish because anyUploaded',id,panoSize,tileIndex)
+            done() //改: 如果因为这部分更高清的贴图已加载所以才不绘制的话,直接完成
             
-            LodDescripor.uploadCount++;
-            this.emit(PanoRendererEvents.TileRenderSuccess, id, panoSize, tileIndex, totalTiles);
-            LodDescripor.uploadCount === totalTiles && this.emit(PanoRendererEvents.PanoRenderComplete, id, panoSize, totalTiles);
-            this.setUploaded(info, !0);
-            this.addCoverageForNode(info.node);
-        } else {
-            this.setUploaded(info, !1);
+        }else{
+            //console.log('uploadTile  失败', id, panoSize, tileIndex)
+            if(panoSize == 512){
+                //console.log("!!!!!!!!!!!!!")
+            } 
+            
+            failHistory[id+':'+ panoSize+ ':' +tileIndex] = true;   
+            this.setUploaded(info, !1); 
         }
-        info.uploadAttempted || (LodDescripor.uploadAttempts++, this.emit(PanoRendererEvents.TileUploadAttempted, id, panoSize, tileIndex, totalTiles)),
-            info.uploadAttempted = !0;
-        LodDescripor.uploadAttempts === totalTiles && this.emit(PanoRendererEvents.UploadAttemptedForAllTiles, id, panoSize, totalTiles);
+
+
+
+
+
+        info.uploadAttempted || (LodDescripor.uploadAttempts++, this.dispatchEvent({type:PanoRendererEvents.TileUploadAttempted, id, panoSize, tileIndex, totalTiles})),
+        info.uploadAttempted = !0;
+        LodDescripor.uploadAttempts === totalTiles && this.dispatchEvent({type:PanoRendererEvents.UploadAttemptedForAllTiles, id, panoSize, totalTiles});
         return g;
     }
 }()
@@ -954,7 +988,7 @@ PanoRenderer.prototype.renderToCubeMap = function() {
         plane = null,
         l = 1;
     return function(texture, renderTarget, tileWidth, tileHeight, startXinTile, startYinTile, widthinTile, heightinTile, startX, startY, width, height, cubeFace, E, b, w) {
-        
+         
           
         var renderer =  this.viewer.renderer; 
         
@@ -1030,7 +1064,8 @@ PanoRenderer.prototype.renderToCubeMap = function() {
         renderer.properties.get(scene); 
         material.uniforms.tDiffuse.value = texture;
         material.blending = E || THREE.NoBlending,
-        material.transparent = !!b,
+        material.transparent = !!b 
+        
         void 0 !== w && null !== w || (w = 1),
         material.uniforms.alpha.value = w,
         material.needUpdate = !0  
@@ -1047,6 +1082,10 @@ PanoRenderer.prototype.renderToCubeMap = function() {
         var oldTarget = renderer.getRenderTarget();
         renderer.autoClear = !1
         
+        
+         
+         
+        
         renderer.setRenderTarget(renderTarget, cubeFace);
         renderer.render(scene, camera/* , renderTarget, !1 */);  
         renderer.setRenderTarget(oldTarget)
@@ -1147,10 +1186,12 @@ PanoRenderer.prototype.copyCubeMap = function() {//将texture渲染到zoomRender
 
 
 
-
-
-
-
+/* 
+forceQueue在tiledownloader和panorenderer都有。
+forceQueue 的应该就是优先下载的吧,按理说一定要先下载完才能下载后面的在 
+在panoRenderer中 uploadQueues 是候补队列 ,在tileDownloader里priorityQueue是候补队列
+tiledownloader下载完成会触发tiledownloader开始uploadTile.
+ */
 
 
 export default PanoRenderer

+ 4 - 4
src/modules/Images360/tile/QualityManager.js

@@ -1,9 +1,9 @@
 
 import * as THREE from "../../../../libs/three.js/build/three.module.js";
  
-import browser from '../../../utils/browser' 
-import {settings,config} from '../../../settings'
-import {ModelManagerEvents,PanoSizeClass} from '../../../defines'
+import browser from '../../../utils/browser.js' 
+import {settings,config} from '../../../settings.js'
+import {ModelManagerEvents,PanoSizeClass} from '../../../defines.js'
 
 
 
@@ -116,7 +116,7 @@ export default class QualityManager {
             return PanoSizeClass.STANDARD
         }
         return PanoSizeClass.HIGH  */
-        switch(config.navTileClass){  
+        switch(Potree.settings.navTileClass){  
             case '1k':
                 return PanoSizeClass.STANDARD;
                 break;

+ 135 - 127
src/modules/Images360/tile/TileDownloader.js

@@ -1,17 +1,24 @@
-import {TileDownloaderEvents, DownloadStatus} from '../../../defines'
+import {TileDownloaderEvents, DownloadStatus} from '../../../defines.js'
 import * as THREE from "../../../../libs/three.js/build/three.module.js";
  
-import TilePrioritizer from './TilePrioritizer'
-import TileUtils from './TileUtils'
+import TilePrioritizer from './TilePrioritizer.js'
+import TileUtils from './TileUtils.js'
  
  
-import {settings, config} from '../../../settings' 
+import {settings, config} from '../../../settings.js' 
 import {
     http
-} from '../../../utils/request'
-import { EventDispatcher } from "../../../EventDispatcher.js";
+} from '../../../utils/request.js' 
 
-class TileDownloader extends EventDispatcher{
+
+
+window.downloaded = {}
+window.startdownloads = [];
+
+
+
+
+class TileDownloader extends THREE.EventDispatcher{
     constructor( ) {
         super()
         this.panos = null;
@@ -32,7 +39,14 @@ class TileDownloader extends EventDispatcher{
             Success: 2,
             Fail: 3
         });
-
+        
+        this.visible = true //add   借用viewer.updateVisible来判断是否start
+         
+        viewer.addEventListener('pageVisible', (e)=>{//不可见时不refreshUpdateInterval 
+            //console.log('visibilitychange:', state)
+            viewer.updateVisible(this,  'pageVisible', e.v) 
+            this.judgeStart() 
+        }) 
          
     }
 
@@ -41,15 +55,36 @@ class TileDownloader extends EventDispatcher{
         this.imagePanos = t 
           //  this.panoGroupId = i
     }
-
-    start() {
-        this.started = true //add
-        this.refreshUpdateInterval(0)
+  
+    start() { 
+        this.downloadCubeTex = true 
+        if(!Potree.settings.useDepthTex){
+            viewer.updateVisible(this,'pano', true )
+            this.judgeStart()            
+        }else{
+            this.refreshInterval || this.judgeStart()
+        }
     }
 
     stop() {
-        this.started = false
-        window.clearTimeout(this.refreshInterval)
+        this.downloadCubeTex = false
+        if(!Potree.settings.useDepthTex){
+            viewer.updateVisible(this,'pano', false )
+            this.judgeStart()
+        } 
+    }
+
+    judgeStart(){//add
+        if(this.visible){
+            //console.log('judgeStart true')
+            this.started = true 
+            this.refreshUpdateInterval(0)
+        }else{
+            //console.log('judgeStart false')
+            this.started = false
+            window.clearTimeout(this.refreshInterval)
+        }
+        
     }
 
     refreshUpdateInterval(e) {
@@ -61,15 +96,20 @@ class TileDownloader extends EventDispatcher{
                 .bind(this), e)
     }
 
-    update() {
-        var e = this.forceQueue.length > 0;
-        this.processQueueForDownloading(this.forceQueue);
-        if (this.processPriorityQueue) {
-            this.queuePrioritizedTilesForPanos(this.panos);
-            this.priorityQueue.length > 0 && (e = !0);
-            this.processQueueForDownloading(this.priorityQueue);
+    update() { 
+        if(this.downloadCubeTex){ //可以下载贴图
+            var e = this.forceQueue.length > 0;
+            this.processQueueForDownloading(this.forceQueue);
+            if (this.processPriorityQueue) {
+                this.queuePrioritizedTilesForPanos(this.panos);
+                this.priorityQueue.length > 0 && (e = !0);
+                this.processQueueForDownloading(this.priorityQueue);
+            }
+            return e 
+        }else{//仅下载depthTex
+            this.tilePrioritizer.filterDepthTex(this.panos)
         }
-        return e
+        
     }
 
     
@@ -134,12 +174,27 @@ class TileDownloader extends EventDispatcher{
 
             for (var n = 0, r = 0; n < i && e.length > 0; r++) {
                 var o = e.shift();
-                o && (this.startDownload(o),
-                    n++)
+                
+                
+                if(o){
+                    //add 为了防止1024的在512前下载完,这里强行等待512下载完毕再开始下载
+                    if(o.panoSize > 512 && !this.isPanoDownloaded(o.pano, 512) ){
+                        //console.log('512的还没下载好呢!')
+                        e.push(o)
+                        break;//一般512的都是连续下载的,所以后面就都不是512了直接中断 
+                    } 
+                    
+                    this.startDownload(o)
+                    n++
+                }
+                
             }
         }
-    }
- 
+    } 
+    
+    
+    
+    
     testDownload(panoSize, tileSize, callback) {
         var n = this.downloadTestResults[panoSize];
         if (n)
@@ -161,6 +216,10 @@ class TileDownloader extends EventDispatcher{
     }
 
     startDownload(e) {//开始下载啦
+        //console.log('startDownload')
+        
+        startdownloads.push(e)
+        
         e.status = DownloadStatus.Downloading;
         var t = this.getTileUrl(e/* e.pano.id, e.panoSize, e.tileSize, e.tileIndex, e.pano.alignmentType */);//xzw add alignmentType
         if(!t)return;
@@ -188,15 +247,25 @@ class TileDownloader extends EventDispatcher{
                 tileY: e.tileY,
                 direction: e.direction
             };
+            
+            downloaded[e.pano.id] || (downloaded[e.pano.id]={512:[],1024:[],2048:[]})
+            downloaded[e.pano.id][e.panoSize] || (downloaded[e.pano.id][e.panoSize] = [])
+            downloaded[e.pano.id][e.panoSize].push(e)
+            if(e.panoSize != 512 && downloaded[e.pano.id][512].length<6){
+                console.warn('没下完')
+            }
+            
+            
+            
             e.image = t,
-                this.dispatchEvent({type:TileDownloaderEvents.TileDownloadSuccess, desc:n} ),
-                this.isPanoDownloaded(e.pano, e.panoSize) && (n = {
-                        panoId: e.pano.id,
-                        tileSize: e.tileSize,
-                        panoSize: e.panoSize
-                    },
-                    this.dispatchEvent({type:TileDownloaderEvents.PanoDownloadComplete, desc:n}),
-                    i && i.onLoad && i.onLoad(e.pano, e.panoSize))
+            this.dispatchEvent({type:TileDownloaderEvents.TileDownloadSuccess, desc:n} ) 
+            this.isPanoDownloaded(e.pano, e.panoSize) && (n = {
+                    panoId: e.pano.id,
+                    tileSize: e.tileSize,
+                    panoSize: e.panoSize
+                },
+                this.dispatchEvent({type:TileDownloaderEvents.PanoDownloadComplete, desc:n}),
+                i && i.onLoad && i.onLoad(e.pano, e.panoSize))
         //}
     }
 
@@ -282,7 +351,11 @@ class TileDownloader extends EventDispatcher{
     
 
     getTiles(d, sceneNum){
-        return `https://4dkk.4dage.com/images/images${sceneNum}/${d}`    
+        if(Potree.settings.isLocal2){//新的地址  scene_view_data/场景码/images/tiles
+            return `${Potree.settings.urls.prefix3}/scene_view_data/${sceneNum}/images/${d}`    
+        }
+        
+        return `${Potree.settings.urls.prefix3}/images/images${sceneNum}/${d}`    
     }
 
     loadImage(e, t, i, n) {
@@ -305,16 +378,24 @@ TileDownloader.prototype.forceQueueTilesForPano = function() {//根据条件开
             TilePrioritizer.sortPanoTiles(e, pano, dir) //按最佳方向排序e
             t.length = 0 
             TileUtils.matchingTilesInDirection(pano, size, dir, hFov, vFov, t);//得到在符合视野标准的集合t
+            
+            
+            
             for (var f = 0, g = function(e) {
-                    return e.face === m.face && e.faceTileIndex === m.faceTileIndex
-                }; f < e.length;) {
+                return e.face === m.face && e.faceTileIndex === m.faceTileIndex
+            }; f < e.length;) {  //过滤掉不符合角度要求的
                 var m = e[f],
                     v = t.findIndex(g);
                 v < 0 ? e.splice(f, 1) : f++
             }
         }
-        for (var A = 0; A < e.length; A++)
+        for (var A = 0; A < e.length; A++){
             this.forceQueue.push(e[A]);         //装载
+        }
+        /* if(e.length){
+            console.log(e)
+        } */
+        
         this.setStatusForAllDescriptors(this.forceQueue, DownloadStatus.ForceQueued);
         this.clearFromQueue(this.priorityQueue, DownloadStatus.ForceQueued, !1);
         download && this.processQueueForDownloading(this.forceQueue, !0);
@@ -354,7 +435,7 @@ TileDownloader.prototype.getTileUrl = function() {
             panoSize = o.panoSize,
             tileSize = o.tileSize,
             tileIndex = o.tileIndex,
-            datasetName = o.pano.pointcloud.name
+            sceneCode = o.pano.pointcloud.sceneCode
         var metadata = {sceneScheme:10}  
         
         
@@ -364,9 +445,17 @@ TileDownloader.prototype.getTileUrl = function() {
             h = Math.floor(tileIndex / l),
             u = "",
             d = '',  g = '';
-        1 === config.tiling.customCompression && (u = "_" + config.tiling["q" + e[panoSize]]);
-         
-        /* if (metadata.sceneScheme == 10)  */{//阿里云oss的规则
+        
+        
+        
+        if(Potree.settings.isLocal){//原始规则
+            //1 === config.tiling.customCompression && (u = "_" + config.tiling["q" + e[panoSize]]);
+            //1 === o.tiling.customCompression && (u = "_" + o.tiling["q" + e[n]]);
+            d = "tiles/" + id + "/" + e[panoSize] + u + "_face" + h + "_" + t.tileX + "_" + t.tileY + ".jpg" 
+            d =  this.getTiles(d, sceneCode);
+            g = "?"  
+          
+        }else{//阿里云oss的规则   if (metadata.sceneScheme == 10) 
             
             d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
             if (e[panoSize] == '512') {
@@ -379,7 +468,7 @@ TileDownloader.prototype.getTileUrl = function() {
                     d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
                 }
                 //起始位置
-                /* if (t.tileX == 0) {
+                if (t.tileX == 0) {
                     d += 'x_0,';
                 } else {
                     d += 'x_' + (512 * t.tileX - 1) + ',';
@@ -389,95 +478,14 @@ TileDownloader.prototype.getTileUrl = function() {
                     d += 'y_0';
                 } else {
                     d += 'y_' + (512 * t.tileY - 1);
-                } */
-                
-                if (t.tileX == 0) {
-                    d += 'x_1,';
-                } else {
-                    d += 'x_' + (512 * t.tileX) + ',';
-                }
-
-                if (t.tileY == 0) {
-                    d += 'y_1';
-                } else {
-                    d += 'y_' + (512 * t.tileY);
-                }
-                
-                
-                
+                } 
             }
             
-            d = this.getTiles(d, datasetName);
+            d = this.getTiles(d, sceneCode);
             g = "&" 
         } 
         
-         
-        
-        /* //8目
-        else if (metadata.sceneScheme == 11) {
-            //阿里云oss的规则 
-            d = 'tiles/2k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
-            if (e[panoSize] == '512') {
-                d += 'image/resize,h_512';
-            } else {
-                //移动端是1k,pc端是2k
-                if (e[panoSize] == '1k' || e[panoSize] == '2k') {
-                    //https://4dkk.4dage.com/images/imagesx4iqYDG3/tiles/4k/122_skybox0.jpg?x-oss-process=image/resize,m_lfit,w_1024/crop,w_512,h_512,x_511,y_0
-                    d += 'image/resize,m_lfit,w_' + panoSize + '/crop,w_512,h_512,';
-                } else {
-                    d = 'tiles/2k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
-                }
-
-                if (t.tileX == 0) {
-                    d += 'x_0,';
-                } else {
-                    d += 'x_' + (512 * t.tileX - 1) + ',';
-                }
-
-                if (t.tileY == 0) {
-                    d += 'y_0';
-                } else {
-                    d += 'y_' + (512 * t.tileY - 1);
-                }
-            } 
-            d = this.getTiles(d, datasetName); 
-            g = "&" 
-        }
-        //双目,随心装等
-        else if (metadata.sceneScheme == 12) {
-            //阿里云oss的规则 
-            d = 'tiles/1k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
-            if (e[panoSize] == '512') {
-                d += 'image/resize,h_512';
-            } else {
-                d = 'tiles/1k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
-                if (t.tileX == 0) {
-                    d += 'x_0,';
-                } else {
-                    d += 'x_' + (512 * t.tileX - 1) + ',';
-                }
-
-                if (t.tileY == 0) {
-                    d += 'y_0';
-                } else {
-                    d += 'y_' + (512 * t.tileY - 1);
-                }
-            } 
-            d = this.getTiles(d, datasetName); 
-            g = "&" 
-        }
-        else {//国际版 
-            //var d = this.getTiles("tiles/" + id + "/" + e[panoSize] + u + "_face" + h + "_" + t.tileX + "_" + t.tileY + ".jpg");
-            d = this.getTiles("tiles/" + id + "/" + e[panoSize]  + "_face" + h + "_" + t.tileX + "_" + t.tileY + ".jpg", datasetName);
-            //return d = ab.changeIfTileGenerating(d)
-            g = "?"  
-            
-        }
-          */
-          
-        /* if(typeof(this.store.getters['scene/metadata'].imagesVersion)!='undefined'){
-            d+= g +'imagesVersion='+this.store.getters['scene/metadata'].imagesVersion
-        }  */
+        d += g + 'time='+o.pano.pointcloud.timeStamp  //加后缀
          
         return d;
     }

+ 64 - 32
src/modules/Images360/tile/TilePrioritizer.js

@@ -1,10 +1,10 @@
  
-import {DownloadStatus} from '../../../defines'
-import {Images360} from '../Images360'
-import TileUtils from './TileUtils'
-import cameraLight from '../../../utils/cameraLight'
-import math from '../../../utils/math'
-import Common from '../../../utils/Common' 
+import {DownloadStatus} from '../../../defines.js'
+import {Images360} from '../Images360.js'
+import TileUtils from './TileUtils.js'
+import cameraLight from '../../../utils/cameraLight.js'
+import math from '../../../utils/math.js'
+import Common from '../../../utils/Common.js' 
 import * as THREE from "../../../../libs/three.js/build/three.module.js";
 
 
@@ -171,7 +171,7 @@ TilePrioritizer.appendQueue = function (e, t) {
 };
 
 TilePrioritizer.sortPanoTiles = function (descriptors, pano, dir) {
-    if(dir instanceof Array)  dir = dir.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
+    if(dir.datasetsLocal)  dir = dir.datasetsLocal.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
     u._panoSpaceDir.copy(dir) 
     TileUtils.getRelativeDirection(pano.quaternion4dkk, u._panoSpaceDir) //应该是将dir根据quaternion转化下
     u._fovThresholdNarrow = math.getFOVDotThreshold(TilePrioritizer.DIRECTIONAL_FOV_NARROW)
@@ -180,7 +180,7 @@ TilePrioritizer.sortPanoTiles = function (descriptors, pano, dir) {
 };
 
 TilePrioritizer.insertSortedPanoTile = function (e, t, pano, dir) {
-    if(dir instanceof Array)  dir = dir.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
+    if(dir.datasetsLocal)  dir = dir.datasetsLocal.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
     u._panoSpaceDir.copy(dir),
         TileUtils.getRelativeDirection(pano.quaternion4dkk, u._panoSpaceDir),
         u._fovThresholdNarrow = math.getFOVDotThreshold(TilePrioritizer.DIRECTIONAL_FOV_NARROW),
@@ -202,23 +202,45 @@ TilePrioritizer.insertSortedPanoTile = function (e, t, pano, dir) {
 };
 
 
-TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先加载的  (有点复杂,没看很懂)
+
+TilePrioritizer.prototype.filterDepthTex = function (panos ) {//仅下载depthTex
+    if(!Potree.settings.useDepthTex || !this.priorityCriteria.pano)return
+    
+    let cameraDirLocals = this.priorityCriteria.cameraDirs.vectorForward
+    let t = [] 
+    //获得视野范围内的邻近点位序列t
+    this.populateScoredPanos(this.priorityCriteria.pano, panos, t, cameraDirLocals , TilePrioritizer.MAX_SCORED_PANOS_TOCONSIDER);
+    
+    t.forEach(p=>p.loadDepthImg()) 
+}
+
+
+
+TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先加载的 pano和tile (有点复杂,没看很懂)
     var e = [],
         t = [],
         i = [];
     return function (queue, panos, tileDownloader) {
         //this.populateNeighborPanos(this.priorityCriteria.pano, panos, e);
-        
          
-        let cameraDirLocals = this.priorityCriteria.cameraDirs.map(e=>{ //add
+        /* let cameraDirLocals = this.priorityCriteria.cameraDirs.map(e=>{ //add
+            var dataset = viewer.scene.pointclouds.find(u=>u.dataset_id == e.datasetId)
+            var matrix = new THREE.Matrix4().copy(dataset.rotateMatrix)
+            var direction = math.convertVector.YupToZup(e.direction)  
+        
+        
             return {
                 datasetId:e.datasetId,
-                direction: math.convertVector.YupToZup(e.direction)
+                direction: direction.clone().applyMatrix4(matrix)
             }
-        }) 
-        //let cameraDirLocals = math.convertVector.YupToZup(this.priorityCriteria.cameraDirs)
-        
+        }) */
+        let cameraDirLocals = this.priorityCriteria.cameraDirs.vectorForward
+         
+        //获得视野范围内的邻近点位序列t
         this.populateScoredPanos(this.priorityCriteria.pano, panos, t, cameraDirLocals , TilePrioritizer.MAX_SCORED_PANOS_TOCONSIDER);
+        
+        t.forEach(p=>p.loadDepthImg()) //add
+        
         var s = this.baseSize //512
             ,
             l = this.standardSize //1024
@@ -226,49 +248,59 @@ TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先
             c = this.highSize //2048
             ,
             h = this.ultraHighSize; //4096
-        this.queueTilesForPano(queue, tileDownloader, this.priorityCriteria.pano, s);
-        if (this.priorityCriteria.upcomingPanos) {//即将走到的,之前用于导览路线
+            
+            
+        this.queueTilesForPano(queue, tileDownloader, this.priorityCriteria.pano, s);  //把当前pano的512下载了
+        
+        
+        if (this.priorityCriteria.upcomingPanos) {// 添加即将走到的点(之前用于导览路线)512 tiles
             this.queueTilesForPanos(queue, this.priorityCriteria.upcomingPanos, tileDownloader, s, TilePrioritizer.MAX_UPCOMING_PANOS_TOADD);
         }
         i.length = 0;
-        if (this.canDownloadSize(l)) {//l没超过最大size限制的话
+        
+        //把当前pano角度范围内的tile按照分辨率从低到高加入队列
+        
+        if (this.canDownloadSize(l)) {//1024如果在限制范围内的话
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, l, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
-        }
-
-        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);
+        } 
+        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs); //排序
         TilePrioritizer.appendQueue(queue, i);
+        
+        //添加邻近点t 512的tiles
         this.queueTilesForPanos(queue, t, tileDownloader, s, TilePrioritizer.MAX_SCORED_PANOS_TOADD);
         i.length = 0;
         
         
         //NARROW    :
-        if (this.canDownloadSize(c)) {
+        if (this.canDownloadSize(c)) {//2048
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, c, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
         }
 
-        if (this.canDownloadSize(h)) {
+        if (this.canDownloadSize(h)) {//4096
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, h, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
-        }
-
-        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);
+        } 
+        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);//排序
         TilePrioritizer.appendQueue(queue, i);
         i.length = 0;
 
-        if (this.canDownloadSize(l)) {
+        if (this.canDownloadSize(l)) {//1024
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, l, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
         }
 
-        if (this.canDownloadSize(c)) {
+        if (this.canDownloadSize(c)) {//2048
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, c, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
         }
 
-        if (this.canDownloadSize(h)) {
+        if (this.canDownloadSize(h)) {//4096
             this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, h, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
         }
 
-        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);
+        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);//排序
         TilePrioritizer.appendQueue(queue, i);
-        this.queueTilesForPanos(queue, e, tileDownloader, s);
+        
+        
+        
+        this.queueTilesForPanos(queue, e, tileDownloader, s); // 如果前面有populateNeighborPanos的话,这步就是加neibour
     }
 }()
 TilePrioritizer.prototype.queueTilesInDirectionForPano = function () {
@@ -280,7 +312,7 @@ TilePrioritizer.prototype.queueTilesInDirectionForPano = function () {
         t = new THREE.Vector3;
     return function (i, n, pano, o, a, dirs, c) {
         
-        var dir = dirs.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
+        var dir = dirs.datasetsLocal.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
         //var dir = dirs
         
         t.copy(dir);

+ 3 - 2
src/modules/Images360/tile/TileUtils.js

@@ -1,5 +1,5 @@
-import {GLCubeFaces} from '../../../defines'
-import MathLight from '../../../utils/MathLight'
+import {GLCubeFaces} from '../../../defines.js'
+import MathLight from '../../../utils/MathLight.js'
 import * as THREE from "../../../../libs/three.js/build/three.module.js";
 
 
@@ -111,6 +111,7 @@ TileUtils.getTileLocation = function(size, t, result) {
     result.tileY = Math.floor(l / a);
     result.face = r;
     result.faceTileIndex = l;
+    return result
 }
 ,
 

+ 1 - 2
src/modules/OrientedImages/OrientedImageControls.js

@@ -1,9 +1,8 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import {EventDispatcher} from "../../EventDispatcher.js";
 
  
-export class OrientedImageControls extends EventDispatcher{
+export class OrientedImageControls extends THREE.EventDispatcher{
 	
 	constructor(viewer){
 		super();

+ 2 - 3
src/modules/OrientedImages/OrientedImages.js

@@ -1,7 +1,6 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import {OrientedImageControls} from "./OrientedImageControls.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import {OrientedImageControls} from "./OrientedImageControls.js"; 
 
 // https://support.pix4d.com/hc/en-us/articles/205675256-How-are-yaw-pitch-roll-defined
 // https://support.pix4d.com/hc/en-us/articles/202558969-How-are-omega-phi-kappa-defined
@@ -119,7 +118,7 @@ export class OrientedImage{
 
 };
 
-export class OrientedImages extends EventDispatcher{
+export class OrientedImages extends THREE.EventDispatcher{
 
 	constructor(){
 		super();

+ 253 - 0
src/modules/Particles/ParticleEditor.js

@@ -0,0 +1,253 @@
+
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import FireParticle from '../../objects/fireParticle/fire/FireParticle.js'
+import SmokeParticle from '../../objects/fireParticle/smoke/SmokeParticle.js'
+import ExplodeParticle from '../../objects/fireParticle/explode/ExplodeParticle.js'
+import CurveCtrl from '../../objects/tool/CurveCtrl.js'
+import {LineDraw} from "../../utils/DrawUtil.js";
+import Common from '../../utils/Common.js'
+import {EventDispatcher} from "../../EventDispatcher.js";
+import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js";
+
+
+
+const colors = {
+    'fire+smoke':0xffffff,
+    'smoke': 0xffffff,
+    'explode':0xffffff,
+}
+
+
+let depthMatPrefix = {
+    clipDistance : 100, occlusionDistance:60, /* 变为backColor距离 */ 
+    maxClipFactor:0.5, backColor:"#777"  ,
+    useDepth:true, transparent: !0,
+}
+let lineMats; 
+let getLineMat = function(type){
+    if(!lineMats){
+        lineMats = {
+            'fire+smoke':LineDraw.createFatLineMat($.extend(depthMatPrefix,{  
+                color: colors['fire+smoke'],  
+                lineWidth: 2 
+            })),
+            'smoke' :LineDraw.createFatLineMat($.extend(depthMatPrefix,{  
+                color: colors['smoke'],  
+                lineWidth: 2   
+            })),
+            'explode' :LineDraw.createFatLineMat($.extend(depthMatPrefix,{  
+                color: colors['explode'],  
+                lineWidth: 2 
+            })),           
+        }        
+    }
+    return lineMats[type]
+}
+
+let handleMats
+let getHandleMat = function(type){ 
+    if(!handleMats){
+        let texLoader = new THREE.TextureLoader()
+        
+        handleMats = {   
+            "fire+smoke" :   new DepthBasicMaterial($.extend(depthMatPrefix,{  
+                map: texLoader.load(Potree.resourcePath+'/textures/icon-fire.png' ), 
+                color:  colors['fire+smoke'],                
+            })),
+            "smoke" :   new DepthBasicMaterial($.extend(depthMatPrefix,{     
+                map: texLoader.load(Potree.resourcePath+'/textures/icon-smoke.png' ),  
+                color:  colors['smoke'],              
+            })),
+            "explode" :   new DepthBasicMaterial($.extend(depthMatPrefix,{   
+                map: texLoader.load(Potree.resourcePath+'/textures/icon-explode.png' ), 
+                color:  colors['explode'],      
+            })), 
+        } 
+    }
+    return handleMats[type]
+}
+
+let ParticleEditor = {
+    
+    bus: new EventDispatcher,
+    particleGroup : new THREE.Object3D ,
+    curveGroup:new THREE.Object3D ,
+    init:function(){
+        this.particleGroup.name = 'particles'
+        viewer.scene.scene.add( this.particleGroup );
+        
+        
+        this.curveGroup.name = 'particles-curves'
+        viewer.scene.scene.add( this.curveGroup );
+        
+        
+    },
+    addParticle : function(prop={}){
+        
+         
+        let particle
+        if(prop.type == 'fire'){ 
+            particle = new FireParticle(prop) 
+            
+        }else if(prop.type == 'smoke'){
+            particle = new SmokeParticle(prop) 
+            
+        }else if(prop.type == 'explode'){
+            particle = new ExplodeParticle(prop) 
+        }
+        
+        this.particleGroup.add(particle)
+         
+        
+        
+        return particle
+    }
+    ,
+    removeParticle(particle){
+        //particle.dispatchEvent('delete')
+        particle.dispose();
+        this.particleGroup.remove(particle)
+        particle.curve.dispose()
+    }
+    ,
+    update(delta){
+        this.particleGroup.children.forEach(e=>e.update(delta))
+    }
+    ,
+    
+    startInsertion(type = 'fire', prop={}){  //viewer.modules.ParticleEditor.startInsertion()
+        let deferred = $.Deferred();
+        let particles = [];
+        
+        let finish = (ifDone)=>{ 
+            if(ifDone){
+                deferred.resolve(particles) 
+            }
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"addSth"
+            }); 
+            viewer.removeEventListener('global_click', click) 
+            this.bus.removeEventListener('cancel_insertions',cancel)
+        }
+        
+        let curve = new CurveCtrl([], getLineMat(type), colors[type], type+'_curve', {handleMat:getHandleMat(type)} )
+        this.curveGroup.add(curve)
+        prop.curve = curve
+        prop.type = type 
+        //console.log('创建curve',type,curve.uuid)
+        
+        let cancel = ()=>{
+            console.log('cancel_insertions', curve.uuid )
+            curve.dispose();
+            finish(false)
+        }
+        this.bus.dispatchEvent('cancel_insertions')//删除旧的
+        this.bus.addEventListener('cancel_insertions',cancel)
+        
+        var click = (e)=>{  
+            if(e.button === THREE.MOUSE.RIGHT){  
+                if(curve.points.length>=1){ //if(type.includes('fire') || type.includes('smoke')  ){ 
+                      
+                    particles = this.createFromData(prop) 
+                    finish(true)                        
+                } 
+                return  
+            }
+            
+             
+            var I = e.intersect && (e.intersect.orthoIntersect || e.intersect.location)
+            if(!I)return
+            
+            curve.addPoint(I, null, true) 
+            
+            if(type == 'explode'){ 
+                particles = this.createFromData(prop)   
+                
+                finish(true)
+            }
+            
+            return {stopContinue:true}//防止继续执行别的侦听,如flytopano
+        }
+     
+        
+        viewer.addEventListener('global_click', click, 10)//add importance:10
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "add",  name:"addSth"
+        });
+        
+        return deferred.promise()
+    },
+    
+    
+    
+    createFromData(prop){
+        const type = prop.type;
+        var particles = []
+        let curve = prop.curve;
+        if(!curve){
+            curve = new CurveCtrl(prop.points, getLineMat(type), colors[type], type+'_curve', {handleMat:getHandleMat(type)} )
+            this.curveGroup.add(curve)
+        }
+        
+        if(type.includes('fire') || type.includes('smoke')  ){
+            if(type.includes('fire')){
+                var fire = this.addParticle({
+                    type : 'fire',
+                    positions : curve.points,
+                    curve,
+                    radius : prop.radius,
+                    height: prop.height,
+                    strength : prop.strength,
+                })
+                particles.push(fire)
+            }
+            if(type.includes('smoke')){ 
+                var smoke = this.addParticle({
+                    type : 'smoke', 
+                    positions : curve.points,
+                    curve,
+                    positionStyle : 'sphere' , 
+                    strength : prop.smokeStrength,
+                    radius: prop.smokeRadius,
+                    height: prop.smokeHeight,
+                })
+                particles.push(smoke)
+            }
+             
+            
+        }else if(type == 'explode'){
+            var explode = this.addParticle({
+                type : 'explode', 
+                position : curve.points[0],
+                strength: prop.strength,
+                radius : prop.radius,
+                particleSpaceTime: prop.particleSpaceTime,
+                curve,
+                delayStartTime:prop.delayStartTime,
+            })
+            particles.push(explode)
+        }    
+
+        var geoNeedsUpdate 
+        curve.addEventListener('dragCurvePoint',()=>{ 
+            geoNeedsUpdate = true 
+            Common.intervalTool.isWaiting('particlePointChange', ()=>{ //延时update,防止卡顿  
+                if(geoNeedsUpdate){ 
+                    particles.forEach(e=>e.updateGeometry())  
+                    geoNeedsUpdate = false 
+                    curve.dispatchEvent('sendUpdatePoints')
+                    return true  
+                }
+            }, 400)  
+        })
+        
+        
+        
+        
+        return particles
+    }
+}
+
+
+
+export default ParticleEditor

+ 198 - 24
src/modules/clipModel/Clip.js

@@ -1,16 +1,32 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import {BoxVolume} from '../../utils/Volume'
+import {BoxVolume} from '../../objects/tool/Volume.js'
 import {  ClipTask, ClipMethod} from "../../defines.js"
-import {mapClipBox} from '../../utils/mapClipBox'
-import Common from '../../utils/Common'
-import {Images360} from '../Images360/Images360'
-
+import {mapClipBox} from '../../objects/tool/mapClipBox.js'
+import Common from '../../utils/Common.js'
+import math from '../../utils/math.js' 
+import {Images360} from '../Images360/Images360.js' 
 
 const defaultBoxWidth = 6;  //navvis:  10
-                           //navvis position: si {x: 0, y: 0, z: 0}
-
-var Clip = {
+                            //navvis position: si {x: 0, y: 0, z: 0}
    
+var Clip = {
+    bus : new THREE.EventDispatcher,
+    selectedDatasets : [],    
+    changeCallback(force){ 
+        if(Potree.settings.isOfficial){  
+            Common.intervalTool.isWaiting('clipSelectedDatasets', ()=>{ //延时update,防止卡顿
+                let pointclouds = this.getIntersectPointcloud() 
+                if(force || Common.getDifferenceSet(pointclouds,this.selectedDatasets).length){  
+                    this.selectedDatasets = pointclouds 
+                    //console.error('clipSelectedDatasets',selectedDatasets)
+                    this.bus.dispatchEvent({type:'updateSelectedDatasets', selectedDatasets:pointclouds.map(e=>e.dataset_id) })
+                    force = false
+                    return true 
+                } 
+            },  300)  
+        } 
+    },
+
     enter:function(){
         this.previousView = { 
 			position: viewer.images360.position,
@@ -20,11 +36,16 @@ var Clip = {
             ifShowMarker : Potree.settings.ifShowMarker,
             
 		} 
-        let bound = viewer.scene.pointclouds[0].bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。
+
+        let pointcloud = this.getPointcloud() 
+        let bound = pointcloud.bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。
         let boundSize = bound.getSize(new THREE.Vector3())
         let target = this.getTarget(bound.getCenter(new THREE.Vector3())); //navvis的位置xy是用相机位置 this.ViewService.mainView.getCamera().position  我觉得也可以用第一个漫游点的,或者最接近bound中心的漫游点
         let scale = new THREE.Vector3(defaultBoxWidth,defaultBoxWidth, boundSize.z)//z和navvis一样
-        let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3) 
+        
+        let eyeDir = viewer.scene.view.direction.clone().setZ(0/* -boundSize.z/3 */).multiplyScalar(-defaultBoxWidth)  //为了使所在楼层不变,不修改z
+
+        //let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3) 
         let position = new THREE.Vector3().addVectors(target, eyeDir)
         
         Potree.settings.displayMode = 'showPointCloud'
@@ -53,21 +74,24 @@ var Clip = {
                 this.mapBox.center.setX(this.box.position.x)
                 this.mapBox.center.setY(this.box.position.y)
                 this.mapBox.updatePoints() 
+                this.changeCallback()
             })
             this.box.addEventListener('scale_changed',e=>{
                 var scale = this.box.scale 
-                this.mapBox.updatePoints(scale) 
+                this.mapBox.updatePoints(scale)
+                this.changeCallback()
             })
             this.box.addEventListener('orientation_changed',e=>{
                 this.mapBox.angle = this.box.rotation.z
                 this.mapBox.rotateBar.rotation.z = this.mapBox.angle
                 this.mapBox.updatePoints()
+                this.changeCallback()
             })
             viewer.scene.addVolume(this.box);
             
         }
         
-        {
+        {//map
             let boxRotateBack = ()=>{//不知道是不是这么写。 因为可能z的旋转不一定都在z 
                 this.box.rotation.x = 0;
                 this.box.rotation.y = 0;
@@ -79,6 +103,7 @@ var Clip = {
                 this.box.position.setX(this.mapBox.center.x)
                 this.box.position.setY(this.mapBox.center.y)
                 boxRotateBack()
+                this.changeCallback()
             })
             this.mapBox.addEventListener('dragChange',e=>{
                 var scale = this.mapBox.getScale() 
@@ -87,10 +112,12 @@ var Clip = {
                 this.box.position.setX(this.mapBox.center.x)
                 this.box.position.setY(this.mapBox.center.y)
                 boxRotateBack()
+                this.changeCallback()
             })
             this.mapBox.addEventListener('rotate',e=>{ 
                 this.box.rotation.z = this.mapBox.angle 
                 boxRotateBack()
+                this.changeCallback()
             })
         }
         
@@ -104,11 +131,40 @@ var Clip = {
         
         Potree.settings.unableNavigate = true
         Potree.settings.ifShowMarker = false
-        viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', false)  
+        viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', false)   
+        //viewer.updateVisible(viewer.mapViewer.cursor, 'clipModel', false)//隐藏地图游标
         viewer.inputHandler.toggleSelection(this.box);
         viewer.inputHandler.fixSelection = true
         viewer.transformationTool.frame.material.color.set(Potree.config.clip.color)//navvis 15899953 
-         
+        viewer.setPointStandardMat(true) 
+        
+        {
+            this.events = {
+                flyToPos : (e)=>{ 
+                    let dis = 2
+                    let target = e.position
+                    //position = new THREE.Vector3().subVectors(target, this.scene.view.direction)
+                    
+                    //永远朝向框的中心
+                    /* let dir = new THREE.Vector3().subVectors(this.box.position, e.position).normalize()
+                    position = new THREE.Vector3().subVectors(target, dir) */
+                    
+                  
+                    target = this.box.position
+                    position = e.position
+                    //为了方便缩放操作,直接使用box中心作为target
+                    
+                    
+                    let duration = 1000
+                    viewer.scene.view.setView({position,  duration,  target})
+                } 
+            }
+            
+            this.bus.addEventListener('flyToPos',this.events.flyToPos) 
+        }
+        this.editing = true
+        
+        setTimeout(()=>{this.changeCallback(true)},1)
     },
     
     leave:function(){
@@ -121,17 +177,48 @@ var Clip = {
         Potree.settings.unableNavigate = false
         Potree.settings.ifShowMarker = this.previousView.ifShowMarker
         viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', true)  
-        
+        //viewer.updateVisible(viewer.mapViewer.cursor, 'clipModel', true) 
         viewer.setView(this.previousView)
         viewer.setLimitFar(true)
+        viewer.setPointStandardMat(false) 
+        
+        
+        {
+            this.bus.removeEventListener('flyToPos',this.events.flyToPos) 
+            this.events = null 
+        }
+        this.editing = false
+    },
+    
+
+
+    getPointcloud:function(){ //找一个离当前最近的点云,且最好有漫游点
+        let pointclouds = viewer.scene.pointclouds.filter(e=>e.panos.length>0)
+        if(pointclouds.length == 0)pointclouds = viewer.scene.pointclouds;
+
+
+        let result = Common.sortByScore(pointclouds,[],[e=>{
+            let center = e.bound.getCenter(new THREE.Vector3)
+            let size = e.bound.getSize(new THREE.Vector3).length() / 2 
+            let posToCenter = viewer.images360.position.distanceTo(center)
+            return size / posToCenter 
+        }])
+        
+        return result[0].item
     },
     
+
     getTarget:function(boundCenter){//box位置。要找一个有点云的地方。方案1相机位置, 方案2接近相机的漫游点, 方案3接近中心的漫游点。选择方案2,因最大概率有点云
         var target = new THREE.Vector3()
         var cameraPos = viewer.images360.position;
-        var pano = Common.find(viewer.images360.panos , [], [Images360.sortFunctions.floorDistanceToPoint(cameraPos)]);
-        target.copy(pano.position) 
-        target.setZ(boundCenter.z)
+        var pano = Common.find(viewer.images360.panos , [], [Images360.sortFunctions.floorDisSquaredToPoint(cameraPos)]);
+        if(pano){
+            target.copy(pano.position) 
+            target.setZ(boundCenter.z)
+        }else{
+            target.copy(boundCenter)
+        }
+        
         return target
     },
     /* switchMap:function(state){
@@ -139,28 +226,115 @@ var Clip = {
         
     }, */
     
-    download:function(){
+    download:function( ){
+        
+        if(this.getIntersectPointcloud().length == 0){
+            return null
+        }
+        
+        
         var visiPointclouds = viewer.scene.pointclouds.filter(e=> viewer.getObjVisiByReason(e, 'datasetSelection'))
         let data = {   
             transformation_matrix: visiPointclouds.map((cloud)=>{
-                return {
-                    id: cloud.dataset_id,
-                    matrix: this.getTransformationMatrix(cloud).elements,
+                let data = {
+                    id: cloud.dataset_id, 
+                    matrix : this.getTransformationMatrix(cloud).elements, 
                     modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
-                } 
+                }  
+                return data
             }) ,
             aabb: "b-0.5 -0.5 -0.5 0.5 0.5 0.5" //剪裁空间( 所有点在乘上这个矩阵后, 还能落在 1 * 1 * 1的box内的点就是所裁剪的
            
         }
-        console.log(data)
+        
         return data
         //https://testlaser.4dkankan.com/indoor/t-ia44BhY/api/pointcloud/crop
     },
     
+    
+    
+    downloadNoCrop(){//不剪裁  下载整个点云
+        
+        var visiPointclouds = viewer.scene.pointclouds.filter(e=> viewer.getObjVisiByReason(e, 'datasetSelection'))
+        let data = {   
+            transformation_matrix: visiPointclouds.map((cloud)=>{
+                let data = {
+                    id: cloud.dataset_id, 
+                    matrix : new THREE.Matrix4().elements, //固定值
+                    modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
+                }  
+                return data
+            }) ,
+            aabb: "b-12742000 -12742000 -12742000 12742000 12742000 12742000" //固定剪裁空间 
+           
+        }
+        console.log(data)
+        return data
+         
+        
+        
+    },
+    
+    
     getTransformationMatrix:function(pointcloud) {//剪裁矩阵
         var invMatrix = new THREE.Matrix4().getInverse(this.box.matrixWorld) 
         return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose()
+    },
+
+
+    getIntersectPointcloud(){ 
+        var boxBound = new THREE.Box3(
+            new THREE.Vector3(-0.5,-0.5,-0.5), new THREE.Vector3(0.5,0.5,0.5),
+        ).applyMatrix4(this.box.matrixWorld)    //large boundingbox
+        
+       /*  var boxTightPoints = this.box.children[0].geometry.vertices.map(e=>e.clone().applyMatrix4(this.matrixWorld)) 
+            console.log(boxTightPoints) 
+        */
+        
+        let boxMatrixInverse = new THREE.Matrix4().copy(this.box.matrixWorld).invert();
+
+        let boxPoints = [
+            new THREE.Vector3(boxBound.min.x, boxBound.min.y,0),
+            new THREE.Vector3(boxBound.max.x, boxBound.min.y,0),
+            new THREE.Vector3(boxBound.max.x, boxBound.max.y,0),
+            new THREE.Vector3(boxBound.min.x, boxBound.max.y,0)
+        ]
+
+        var intersect = (pointcloud)=>{
+        
+            if(!pointcloud.bound.intersectsBox(boxBound))return false
+            //判断box和点云的tight bound是否相交(因为box可以任意旋转,且实在找不到三维中的立方体相交的函数,所以直接用boxBound) 
+            var points = pointcloud.getUnrotBoundPoint('all') 
+            let rings = math.getPolygonsMixedRings([points.slice(0,4), boxPoints] , true) 
+            //console.log(pointcloud.dataset_id, pointcloud.name, rings.length) 
+            if(rings.length > 1 )return false
+
+            {//再用frustum和数据集的sphere相交试试,能排除一些错误
+                let a = Potree.Utils.isInsideBox(points,  boxMatrixInverse) 
+                if(!a){
+                    console.log('没能经过isInsideBox测试')
+                }
+                return a
+            }
+            return true 
+            
+        }
+
+        
+
+
+        return viewer.scene.pointclouds.filter(e=>intersect(e)) 
+        
+       
+
     }
+    
+    
+   
+    /* 
+    裁剪点云时,2D界面显示全部平面图,按楼层切换显示。 
+     */
+    
 }
 
 

+ 259 - 48
src/modules/datasetAlignment/Alignment.js

@@ -1,42 +1,201 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import SplitScreen from "../../utils/SplitScreen"
-import math from "../../utils/math"
-
-
+import SplitScreen4Views from "../../utils/SplitScreen4Views.js"
+import math from "../../utils/math.js"
+import History from "../../utils/History.js"
 
 var Alignment = {
-    SplitScreen, 
+    SplitScreen: SplitScreen4Views, 
     handleState:null,  //操作状态 'translate'|'rotate'
+    bus: new THREE.EventDispatcher(), 
+    
+    prepareRecord : true, 
+    
+    writeToHistory(content){ 
+        if(!this.prepareRecord)return;
+        this.prepareRecord = false
+        this.history.writeIn(content)
+    },
+    
+    
+    /* undo(){//撤销一步 
+        let last = this.history.pop();
+        last && last.forEach(item=>{
+            this.applyTemp(item)  
+        })
+        
+    },  */ 
+    
+    applyTemp(item){ 
+        var pointcloud = viewer.scene.pointclouds.find(p=>p.dataset_id+p.name == item.sid)
+            pointcloud.orientationUser = item.orientationUser
+            pointcloud.translateUser = item.translateUser
+            this.setMatrix( pointcloud )
+    },
+    getTemp(pointclouds){//记录最近一次保存后的状态,便于恢复
+        pointclouds = pointclouds || viewer.scene.pointclouds
+        return pointclouds.map(e=>{
+            return {
+                sid : e.dataset_id+e.name,
+                orientationUser : e.orientationUser,
+                translateUser : e.translateUser.clone(),
+            }
+        } )
+    },
+    
+    
+    
     init:function(){ 
-        let rotateInfo  
+        let rotateInfo   
         
         viewer.fpControls.addEventListener("transformPointcloud",(e)=>{ 
+            if(e.pointclouds[0].dataset_id == Potree.settings.originDatasetId){//禁止手动移动初始数据集
+                return this.bus.dispatchEvent('forbitMoveOriginDataset') 
+            }
+            
+            
+            this.writeToHistory(this.getTemp(e.pointclouds) ) 
+             
+            
+            
+            
+        
             if(this.handleState == 'translate'){
-                Alignment.translate(e.pointcloud,e.moveVec)
+                e.pointclouds.forEach(cloud=>Alignment.translate(cloud, e.moveVec))
+                
+                
             }else if(this.handleState == 'rotate'){
-                 
-                let center = e.pointcloud.translateUser //移动到的位置就是中心
-                if(!rotateInfo){
-                    rotateInfo = {
-                        orientationUser : e.pointcloud.orientationUser,
-                        vecStart : new THREE.Vector3().subVectors(e.intersectStart, center).setZ(0),
-                        pointcloud: e.pointcloud
-                    } 
+                if(Potree.settings.editType == 'pano'){
+                    
+                    let center = e.intersectStart //旋转中心是mousedown的位置
+                    if(e.intersect.equals(center))return
+                    if(!rotateInfo){  
+                        rotateInfo = {
+                            orientationUser : e.pointclouds[0].orientationUser,
+                            //vecStart : e.moveVec, // 首次移动向量 只有八个方向,精度太小,所以延迟 
+                            pointclouds: e.pointclouds
+                        } 
+                        this.bus.dispatchEvent({type:'rotateStart', startPoint:center})
+                        return
+                    }else if(!rotateInfo.vecStart){  
+                        let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
+                        if(vec.length() * e.camera.zoom >  30){  //在屏幕上距离初始点有一定距离后开始算
+                            //console.log('moveVec',vec)
+                            rotateInfo.vecStart = vec
+                        }
+                    }else{
+                        
+                        let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
+                        let angle = math.getAngle(rotateInfo.vecStart,vec,'z')   
+                        let diffAngle = rotateInfo.orientationUser + angle - rotateInfo.pointclouds[0].orientationUser
+                        
+                        rotateInfo.pointclouds.forEach(cloud=>{
+                            
+                           /*  let centerNoTranfrom = Potree.Utils.datasetPosTransform({ toDataset: true, pointcloud:cloud, position: center }) //中心点在数据集中的位置
+                            Alignment.rotate(cloud, null, diffAngle)
+                             
+                            let centerNow = Potree.Utils.datasetPosTransform({ fromDataset: true, pointcloud:cloud, position: centerNoTranfrom }) //中心点的现在位置
+                            let shift = new THREE.Vector3().subVectors( center, centerNow); //偏移量
+                            Alignment.translate(cloud,shift)   //使center还保留在原位
+                            //let centerNow1 = Potree.Utils.datasetPosTransform({ fromDataset: true, pointcloud:rotateInfo.pointcloud, position: centerNoTranfrom }) //中心点的现在位置
+                                 */
+                                 
+                            Alignment.rotateAround(center, cloud, null, diffAngle)   
+                                
+                                 
+                        })
+                        
+                        
+                    }
+                    this.bus.dispatchEvent({type:'rotate', endPoint: e.intersect})
+                    
                 }else{ 
-                    let vec = new THREE.Vector3().subVectors(e.intersectPoint, center).setZ(0)
-                    let angle = math.getAngle(rotateInfo.vecStart,vec,'z')   
-                    let diffAngle = rotateInfo.orientationUser + angle - rotateInfo.pointcloud.orientationUser
-                    Alignment.rotate(rotateInfo.pointcloud, null, diffAngle)
-                }
-                
+                    let center = e.pointclouds[0].translateUser //移动到的位置就是中心
+                    if(e.intersect.equals(center))return
+                    if(!rotateInfo){  
+                        rotateInfo = {
+                            orientationUser : e.pointclouds[0].orientationUser,
+                            vecStart : new THREE.Vector3().subVectors(e.intersectStart, center).setZ(0),
+                            //pointclouds: e.pointclouds
+                            pointcloud: e.pointclouds[0]
+                        }  
+                    }else{ 
+                        let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
+                        let angle = math.getAngle(rotateInfo.vecStart,vec,'z')   
+                        let diffAngle = rotateInfo.orientationUser + angle - rotateInfo.pointcloud.orientationUser
+                        Alignment.rotate(rotateInfo.pointcloud, null, diffAngle)
+                    }
+                }    
             } 
         })
         
         
         viewer.fpControls.addEventListener("end",(e)=>{ 
-            rotateInfo = null
+            rotateInfo = null 
+            this.prepareRecord = true
+        })
+        
+        viewer.inputHandler.addEventListener('keydown',e=>{ 
+            if(e.keyCode == 90 && e.event.ctrlKey){//Z
+                this.history.undo()
+            } 
+        })  
+		 
+        
+        // cursor:
+        
+        let updateCursor = (e)=>{ 
+            if(e.drag || !this.editing)return  //仅在鼠标不按下时更新:
+            
+            let handleState = Alignment.handleState
+            
+            if(e.hoverViewport.alignment && handleState && e.hoverViewport.alignment[handleState]){
+                if(handleState == 'translate'){
+                    if( e.intersect && e.intersect.location ){ 
+                        viewer.dispatchEvent({
+                            type : "CursorChange", action : "add",  name:"movePointcloud"
+                        })
+                    }else{
+                        viewer.dispatchEvent({
+                            type : "CursorChange", action : "remove",  name:"movePointcloud"
+                        })
+                    }
+                }else if(handleState == 'rotate'){ 
+                    if( e.intersect && e.intersect.location ){ 
+                        viewer.dispatchEvent({
+                            type : "CursorChange", action : "add",  name:"rotatePointcloud"
+                        })
+                    }else{
+                        viewer.dispatchEvent({
+                            type : "CursorChange", action : "remove",  name:"rotatePointcloud"
+                        })
+                    }
+                }  
+            }else{
+                //清空:
+                viewer.dispatchEvent({
+                    type : "CursorChange", action : "remove",  name:"movePointcloud" 
+                })
+                viewer.dispatchEvent({
+                    type : "CursorChange", action : "remove",  name:"rotatePointcloud" 
+                })
+            }                
+        }
+        
+         
+        viewer.addEventListener('global_mousemove',updateCursor)  
+        viewer.addEventListener('global_drop',updateCursor)//拖拽结束  
+       
+            
+        
+        
+        viewer.addEventListener('updateModelBound', (e)=>{
+            if(this.editing){
+                this.SplitScreen.updateCameraOutOfModel() 
+            } 
         })
+        
+        
     },
     
     
@@ -52,6 +211,9 @@ var Alignment = {
         pointcloud.transformMatrix = matrix.clone();//为该数据集的变化矩阵。 对应navvis的m2w_
         pointcloud.transformInvMatrix.copy(matrix).invert()
         pointcloud.rotateMatrix = rotMatrix
+        pointcloud.rotateInvMatrix.copy(rotMatrix).invert()
+        
+        
         pointcloud.panos.forEach(e=>e.transformByPointcloud())
         
         
@@ -67,8 +229,31 @@ var Alignment = {
             Alignment.changeCallBack && Alignment.changeCallBack();
         } 
         
+        if(pointcloud.spriteNodeRoot){
+            pointcloud.spriteNodeRoot.matrixWorld.copy(pointcloud.matrixWorld)//.multiplyMatrices(pointcloud.matrixWorld, pointcloud.matrixWorld);	
+        } 
+
+        viewer.updateModelBound();
+        //pointcloud.updateBound()
+        pointcloud.getPanosBound()  
+        
+
+    },
+    
+    
+    rotateAround(center, pointcloud, deg, angle){//绕center点转动
+        var angle = angle != void 0 ? angle : THREE.Math.degToRad(deg) 
+        let vec1 = new THREE.Vector3().subVectors(pointcloud.translateUser, center);
+        let rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle)
+        let vec2 = vec1.clone().applyMatrix4(rotMatrix) //将到旋转中心的偏差也转动
+        let vec3 = new THREE.Vector3().subVectors(vec2,vec1); //这个就是多出来的一步translateUser
+        this.rotate(pointcloud, deg, angle) 
+        this.translate(pointcloud, vec3)
+        //绕点转动就是比普通转动多一步移动到相对center的某个位置。 1 初始点云移动到自己的position; 2 移动一个vec1  3绕原点旋转 4再移动一个原本的translateUser。 绘制出来后发现移动量就是第二步vec旋转后的偏移
     },
-    rotate:function(pointcloud, deg, angle){//假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
+    
+    
+    rotate:function(pointcloud, deg, angle){//绕各自中心转动(各自的position)   假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
         var angle = angle != void 0 ? angle : THREE.Math.degToRad(deg)   //正逆负顺
         pointcloud.orientationUser += angle
         Alignment.setMatrix(pointcloud)
@@ -78,44 +263,57 @@ var Alignment = {
         Alignment.setMatrix(pointcloud)
     },
     
-    saveTemp:function(){//记录最近一次保存后的状态,便于恢复
-        this.originData = viewer.scene.pointclouds.map(e=>{
-            return {
-                id : e.dataset_id,
-                orientationUser : e.orientationUser,
-                translateUser : e.translateUser.clone(),
-            }
-        } )
-    },
+    
+    
+    
     enter:function(){
-        this.saveTemp()  
-        
+        //this.saveTemp()  
+        this.originData = this.getTemp() 
         
+        this.SplitScreen.split({alignment:true})
+         
+        viewer.images360.panos.forEach(pano=>{
+            viewer.updateVisible(pano.mapMarker, 'split4Screens', false)
+        }) 
         
-        SplitScreen.splitScreen4Views({alignment:true})
         viewer.viewports.find(e=>e.name == 'mapViewport').alignment = {rotate:true,translate:true};
-        viewer.viewports.find(e=>e.name == 'Right').alignment = {translate:true};
-        viewer.viewports.find(e=>e.name == 'Back').alignment = {translate:true};
+        viewer.viewports.find(e=>e.name == 'right').alignment = {translate:true};
+        viewer.viewports.find(e=>e.name == 'back').alignment = {translate:true};
+        
         
         this.editing = true
+        
+        viewer.updateFpVisiDatasets()
+        
+        
+        
     },
     leave:function(){
         this.switchHandle(null)
         
-        this.originData.forEach(e=>{//恢复
+        /* this.originData.forEach(e=>{//恢复
             var pointcloud = viewer.scene.pointclouds.find(p=>p.dataset_id == e.id)
             this.translate(pointcloud, new THREE.Vector3().subVectors(e.translateUser , pointcloud.translateUser))
             this.rotate(pointcloud, null, e.orientationUser - pointcloud.orientationUser)
+        }) */
+        this.originData.forEach(e=>{//恢复
+            this.applyTemp(e)
         })
         
         
-        
-        SplitScreen.recoverFrom4Views()
-        
+        this.SplitScreen.recover()
+        viewer.images360.panos.forEach(pano=>{
+            viewer.updateVisible(pano.mapMarker, 'split4Screens', true)
+        }) 
         this.editing = false
-        
-         
-        
+        this.history.clear() 
+        viewer.updateFpVisiDatasets()
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"movePointcloud" 
+        })
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"rotatePointcloud" 
+        })
     } 
     
     ,
@@ -128,14 +326,19 @@ var Alignment = {
         viewer.dispatchEvent({
             type : "CursorChange", action : "remove",  name:"rotatePointcloud" 
         })
+        
+        this.bus.dispatchEvent({type:'switchHandle' , state })
+        
+        
+
     },
     
     
     save: function(){//保存所有数据集的位置和旋转
         let callback = ()=>{//保存成功后
-            this.saveTemp();
+            this.originData = this.getTemp()   //this.saveTemp();
             //需要修改 测量线的position。漫游点已经实时修改了
-            viewer.updateModelBound();
+            
             viewer.scene.measurements.forEach(e=>e.transformByPointcloud())
             viewer.images360.updateCube(viewer.bound)
         }
@@ -151,8 +354,7 @@ var Alignment = {
                 //transformMatrix: e.transformMatrix.elements,
             }
         })
-        data = data[0]//暂时只传第一个
-        
+        //data = JSON.stringify(data)
         
         //test: 退出后保留结果
         if(!Potree.settings.isOfficial){
@@ -165,6 +367,15 @@ var Alignment = {
     
 }
 
+
+Alignment.history = new History({ 
+    callback: (data)=>{ 
+        data.forEach(item=>{
+            Alignment.applyTemp(item)  
+        }) 
+    } 
+})
+
 /* 
 
 关于控制点:

+ 616 - 0
src/modules/mergeModel/MergeEditor.js

@@ -0,0 +1,616 @@
+ 
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import cameraLight from '../../utils/cameraLight.js' 
+
+import math from "../../utils/math.js"
+import Common from '../../utils/Common.js' 
+import {LineDraw, MeshDraw} from "../../utils/DrawUtil.js"; 
+import {transitions, easing, lerp} from '../../utils/transitions.js'
+import SplitScreen from "../../utils/SplitScreen.js";
+import InfiniteGridHelper from '../../objects/InfiniteGridHelper.js'
+import Compass from "../../objects/tool/Compass.js";
+import {TransformControls} from "../../objects/tool/TransformControls.js";
+import History from "../../utils/History.js"
+
+ 
+const texLoader = new THREE.TextureLoader() 
+      texLoader.crossOrigin = "anonymous" 
+  
+const edgeStrengths = {
+    pointcloud: 4,
+    glb: 100
+}
+const viewportProps = [{
+    left:0,
+    bottom:0,
+    width: 0.5,height:1,
+    name : 'top',    
+    axis:["x","y"],
+    direction : new THREE.Vector3(0,0,-1), //镜头朝向 
+    active: true,
+    //相机位置在z轴正向
+    limitBound: new THREE.Box3(new THREE.Vector3(-Infinity,-Infinity, 1),new THREE.Vector3(Infinity,Infinity,5000)), //在地面以上
+    margin:{x:50, y:150} ,
+},
+{
+    left:0.5,
+    bottom:0,
+    width: 0.5,height:1,
+    name : 'right', 
+    axis:["y","z"],
+    direction : new THREE.Vector3(1,0,0), 
+    active: true,
+    //相机位置在x轴负向  右下角屏
+    viewContainsPoints:[new THREE.Vector3(0,0,0)],
+    margin:{x:300, y:250} ,
+} ]
+ 
+ 
+ 
+let MergeEditor = {
+    bus:new THREE.EventDispatcher(), 
+     
+    
+    SplitScreen : new SplitScreen(),
+    
+    init(){  
+        { 
+            let ground = this.ground = new InfiniteGridHelper(1, 10000, new THREE.Color('#fff'), 10000, 0.2, 0.3)
+            viewer.scene.scene.add(ground) 
+            //再加两条线否则在正侧边看不到
+            let line1 = LineDraw.createLine([new THREE.Vector3(-10000, 0, 0),new THREE.Vector3(10000, 0, 0) ], {color:'#666', dontAlwaysSeen:true})
+            let line2 = LineDraw.createLine([new THREE.Vector3(0, -10000, 0),new THREE.Vector3(0, 10000, 0) ], {mat:line1.material})
+            ground.renderOrder = Potree.config.renderOrders.model + 1//line1.renderOrder + 1  //要比模型低,否则模型透明时效果不对
+            ground.add(line1)
+            ground.add(line2)
+             
+            ground.material.polygonOffset = true //多边形偏移(视觉上没有移动模型位置),防止闪烁
+            ground.material.polygonOffsetFactor = 100 //多边形偏移因子
+			ground.material.polygonOffsetUnits = 10 //多边形偏移单位  
+            ground.material.depthWrite = false            
+            //ground.material.depthTest = false
+            line1.material.polygonOffset = true
+            line1.material.polygonOffsetFactor = 130  
+			line1.material.polygonOffsetUnits = 10  
+            line1.material.depthWrite = false   
+            //见笔记:透明物体的材质设置
+        }
+        
+        
+        {
+            
+            this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{
+                dontHideWhenFaceCamera: true,
+            });
+            //this.transformControls.space = 'local'//为了在当前方向上平移
+            this.transformControls.setSize(1.5)
+            viewer.scene.scene.add(this.transformControls)
+            
+            //右屏
+            this.transformControls2 = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{ 
+                dontHideWhenFaceCamera: true,
+            }); 
+            this.transformControls.setSize(1.5)
+            viewer.scene.scene.add(this.transformControls2) 
+            viewer.setObjectLayers(this.transformControls2, 'layer2' )  
+            
+            let mouseDown = (e)=>{
+                 
+                viewer.outlinePass.edgeStrength = 0//暂时消失线
+                 
+            }
+            let mouseUp = (e)=>{
+                 
+                this.updateEdgeStrength()
+                 
+            }
+            this.transformControls.addEventListener('mouseDown',mouseDown)
+            this.transformControls2.addEventListener('mouseDown',mouseDown)
+            this.transformControls.addEventListener('mouseUp',mouseUp)
+            this.transformControls2.addEventListener('mouseUp',mouseUp)
+            
+            
+        }
+        
+        
+        {
+            
+            this.secondCompass = new Compass(null)
+            
+        }
+        
+        viewer.setControls(viewer.orbitControls)
+        //viewer.mainViewport.view.fixZWhenPan = true
+        viewer.orbitControls.constantlyForward = true
+        
+        
+        viewer.addEventListener('global_single_click',(e)=>{
+            if(
+                this.noNeedSelection  //如模型查看页
+                || viewer.scene.cameraAnimations.some(c=>c.onUpdate) //正在播放
+                || e.drag && e.drag.notPressMouse   //在加测量线
+                || viewer.mainViewport.view.isFlying() //有其他校准
+                || this.split           //分屏中
+                || e.clickElement //触发别的点击事件,如测量时click marker  /* && e.clickElement != e.intersect.object */
+            ){
+                return
+            }
+            
+            if(e.intersect){
+                let object = e.intersect.object || e.intersect.pointcloud
+                let objects = this.getAllObjects()
+                if(objects.includes(object)){ 
+                    this.selectModel(object) 
+                }else{
+                    //if(!viewer.inputHandler.selection[0]){//正在平移和旋转,不允许取消
+                        this.selectModel(null)
+                    //}
+                }                    
+            }else{
+                //if(!viewer.inputHandler.selection[0]){
+                    this.selectModel(null)
+                //}                
+            }
+        })  
+        
+        viewer.inputHandler.addEventListener('keydown', (e)=>{
+            if((e.event.key).toLowerCase() == "h" ){ 
+                this.fadeOutlineAuto = !this.fadeOutlineAuto
+                this.showModelOutline(this.selected,!!this.selected)
+            } 
+        })
+        viewer.ssaaRenderPass.enabled = false
+        viewer.outlinePass.enabled = true
+        //Potree.settings.intersectWhenHover = false
+        //viewer.updateVisible(viewer.reticule, 'force', false)
+        
+        viewer.mainViewport.camera.near = 0.05; // too small will result in z-fighting
+        
+        viewer.addEventListener('updateModelBound', (e)=>{
+            if(this.split){ 
+                this.SplitScreen.updateCameraOutOfModel(/* this.selected && [this.selected] */) 
+            } 
+        })
+        
+        
+        {//校准页面拖拽
+            //左右屏都可以拖拽模型,旋转只能左屏
+            let dragInfo  
+            let drag = (e)=>{ 
+                if(this.split &&  this.selected && this.transformState && (e.dragViewport.name == 'top' || this.transformState == 'translate')   ){ 
+                    if(e.type == 'global_mousedown' ){ //开始
+                        //if((e.intersect.object || e.intersect.pointcloud) == this.selected){
+                        if(e.intersect.pointclouds.includes(this.selected) || e.intersect.allElements.some(e=>e.object == this.selected)){
+                            
+                            dragInfo = {}   
+                            //if(this.selected.isPointcloud){ 
+                                viewer.outlinePass.edgeStrength = 0//暂时消失线
+                            //} 
+                        }  
+                    }
+                         
+                    if(e.type == 'global_drag' && dragInfo  ){ 
+                        if(this.transformState == 'translate'){ 
+                        
+                            let moveVec = Potree.Utils.getOrthoCameraMoveVec(e.drag.pointerDelta, e.dragViewport.camera )//最近一次移动向量
+                            this.selected.position.add(moveVec)
+                            
+                            this.selected.dispatchEvent("position_changed")
+                        }else if(this.transformState == 'rotate'){
+                            
+                            let vec = new THREE.Vector3().subVectors(e.intersect.orthoIntersect || e.intersect.location, this.selected.boundCenter).setZ(0)
+                            if(dragInfo.lastVec == void 0){//global_mousedown
+                                dragInfo.lastVec = vec 
+                                return
+                            }
+                            let angle = math.getAngle(dragInfo.lastVec, vec, 'z')   
+                            dragInfo.lastVec = vec
+                            
+                            //this.selected.rotation.z += angle //局部
+                            
+                            
+                            /* object.quaternion.copy( .setFromAxisAngle( new THREE.Vector3(0,0,1), angle ) );
+                            object.quaternion.multiply( quaternionStart ).normalize(); */
+                            let diffQua = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3(0,0,1), angle )
+                            this.selected.quaternion.premultiply(diffQua) //世界
+                            
+                            
+                            this.selected.dispatchEvent("rotation_changed")
+                        }
+                        
+                        return {stopContinue:true}
+                    } 
+                }
+                
+            }
+            
+            viewer.addEventListener('global_mousedown',  drag) 
+            viewer.addEventListener('global_drag', drag, 10)
+            viewer.addEventListener('global_mousemove', (e)=>{
+                if(this.split && this.transformState && !e.drag && (e.hoverViewport.name == 'top' ||  this.transformState == 'translate')){
+                    
+                    /* if(this.lastHoverViewport != e.hoverViewport){
+                        this.lastHoverViewport = e.hoverViewport
+                        this.transformControls.view = e.hoverViewport.view
+                        this.transformControls.camera = e.hoverViewport.camera
+                        this.transformControls.hideAxis( this.transformState, e.hoverViewport.name == 'top' ? [z] : [x,y]);
+                    } */
+                    
+                    
+                    
+                    
+                    let mouseover =  e.intersect.pointclouds.includes(this.selected) || e.intersect.allElements.some(e=>e.object == this.selected)
+                    //let mouseover = (e.intersect.object || e.intersect.pointcloud) == this.selected
+                    if(mouseover){
+                        if(this.transformState == 'translate'){
+                            viewer.dispatchEvent({
+                                type : "CursorChange", action : "add",  name:"movePointcloud" 
+                            }) 
+                        }else{
+                            viewer.dispatchEvent({
+                                type : "CursorChange", action : "add",  name:"rotatePointcloud" 
+                            }) 
+                        }
+                    }else{
+                        this.clearTranCursor()
+                    }
+                    
+                } 
+            })
+            
+            viewer.addEventListener('global_drop', (e)=>{
+                dragInfo = null
+                this.clearTranCursor()
+                  
+                this.updateEdgeStrength()
+                  
+                
+            })
+        
+        }
+    },
+    
+    
+    clearTranCursor(){
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"movePointcloud" 
+        })
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"rotatePointcloud" 
+        })
+    },
+    
+    enterSplit(){ 
+        this.split = true
+        if(this.selected) this.SplitScreen.focusCenter = this.selected.boundCenter //旋转中心。注意 boundCenter不能直接赋值,否则改变后focusCenter也要改
+        else this.SplitScreen.focusCenter = null
+        
+        this.SplitScreen.splitStart(viewportProps)
+        
+        this.beforeSplit = {
+            pointDensity: Potree.settings.pointDensity,
+        }
+        Potree.settings.pointDensity = 'fourViewports' //强制降低点云质量
+        /* viewer.scene.pointclouds.forEach(e=>{
+            e.material.activeAttributeName = "color"
+            e.material.useFilterByNormal = true  
+        }) */ 
+        //取消outline,改点云颜色为和outline一样的颜色
+        /* if(this.selected && this.selected.isPointcloud){ 
+            this.showModelOutline(this.selected, false) 
+            this.selected.material.activeAttributeName = "color"
+            this.selected.material.color = viewer.outlinePass.visibleEdgeColor 
+        } */
+        
+        
+        viewer.setControls(viewer.fpControls)  
+        viewer.viewports.find(e=>e.name == 'right').rotateSide = true 
+        viewer.viewports.find(e=>e.name == 'top').alignment = true
+       
+         
+        viewer.viewports[1].layersAdd('layer2') 
+        viewer.viewports[0].layersAdd('layer1') 
+        viewer.setObjectLayers(this.transformControls, 'layer1' ) 
+        this.transformControls.view = viewer.viewports[0].view
+        this.transformControls.camera = viewer.viewports[0].camera
+        this.transformControls._gizmo.hideAxis = {translate:['z'], rotate:['x','y','z'] }
+        this.transformControls2.view = viewer.viewports[1].view
+        this.transformControls2.camera = viewer.viewports[1].camera
+        this.transformControls2._gizmo.hideAxis = {translate:['x','y'], rotate:['x','y','z'] }
+        
+
+        this.secondCompass.changeViewport(viewer.viewports[0])
+        this.secondCompass.setDomPos()
+        this.secondCompass.setDisplay(true)   
+        viewer.compass.changeViewport(viewer.viewports[1])
+        viewer.compass.setDomPos()
+        
+    },
+    
+    leaveSplit(){
+        this.split = false
+        this.SplitScreen.unSplit()    
+        viewer.setControls(viewer.orbitControls)
+        
+        Potree.settings.pointDensity = this.beforeSplit.pointDensity
+        /* if(this.selected && this.selected.isPointcloud){ 
+            this.showModelOutline(this.selected, true) 
+            this.selected.material.activeAttributeName = "rgba"  
+        } */
+        this.transformControls.camera = viewer.viewports[0].camera
+        this.transformControls.view = viewer.viewports[0].view 
+        this.transformControls._gizmo.hideAxis = {}
+        viewer.setObjectLayers(this.transformControls, 'sceneObjects' )  //恢复
+        
+        
+        viewer.compass.changeViewport(viewer.viewports[0]) //恢复 
+        viewer.compass.setDomPos()
+        this.secondCompass.setDisplay(false)
+        
+         
+    },
+    
+    rotateSideCamera(angle){
+        this.SplitScreen.rotateSideCamera(viewer.viewports.find(e=>e.name == 'right'), angle)
+    },
+    
+    setTransformState(state){//校准时
+        this.transformState = state  
+        this.clearTranCursor()       
+    },
+    //---------------------------
+    
+    /* writeToHistory(content){ 
+        if(!this.prepareRecord)return;
+        this.prepareRecord = false
+        this.history.push(content)
+    }, */
+    
+    //---------------------------
+    
+    getAllObjects(){
+        return viewer.objs.children.concat(viewer.scene.pointclouds)
+    },
+    
+    getModel(id){
+        let models = this.getAllObjects()
+        return models.find(e=>e.dataset_id == id)
+    },
+    removeModel(model){
+        if(this.selected == model) this.selectModel(null)
+        let dispose = (e)=>{
+            e.geometry && e.geometry.dispose() 
+            e.material && e.material.dispose()
+        }
+        if(model.isPointcloud){
+            dispose(model)
+            viewer.scene.removePointCloud(model)
+        }else{
+            model.traverse(e=>{
+                dispose(e) 
+            })
+            viewer.objs.remove(model)
+        }   
+        
+        
+        
+    },
+    
+    selectModel(model, state=true, fitBound, by2d){
+        if(!model) {
+            model = this.selected
+            state = false
+        }
+         
+        if(state){
+            if(this.selected){
+                if(this.selected == model) return
+                else{
+                    let transToolAttached = !!this.transformControls.object
+                    this.selectModel(this.selected, false, fitBound, by2d)
+                    transToolAttached && this.transformControls.attach(model)
+                }
+            }
+            this.selected = model
+             
+            MergeEditor.focusOn(model, 500, !!fitBound)     //通过在场景里点击模型的话,不focus
+              
+           
+            this.showModelOutline(model)
+            
+            
+            
+            this.updateEdgeStrength()
+            
+            //console.log('selectModel', model)
+            
+        }else{
+            if(this.selected != model)return //model本来就没选中,不需要处理(防止2d先选中新的再取消旧的)
+            this.showModelOutline(model, false)
+             
+            this.selected = null
+            this.transformControls.detach()        //viewer.transformObject(null);
+            //console.log('selectModel', null)
+        } 
+        
+        
+        
+        if(!by2d && model){
+            model.dispatchEvent({type:'changeSelect', selected : state})
+        }
+         
+    },
+    
+    
+    showModelOutline(model, state){ 
+        if(this.fadeOutlineAuto){ 
+            if(state === false){
+                viewer.outlinePass.selectedObjects = []
+                clearTimeout(this.timer)
+                return
+            }
+            
+            viewer.outlinePass.selectedObjects = [model]
+            if(this.timer){
+                clearTimeout(this.timer)
+            }
+            
+            this.timer = setTimeout(()=>{
+                viewer.outlinePass.selectedObjects = []
+            }, 1000)
+        }else{
+            if(state === false){
+                viewer.outlinePass.selectedObjects = []
+            }else{
+                viewer.outlinePass.selectedObjects = [model]
+            }
+        }
+    },
+    
+    updateEdgeStrength(){
+        if(!this.selected)return
+        if(this.selected.isPointcloud){
+            viewer.outlinePass.edgeStrength = edgeStrengths.pointcloud// / this.selected.material.opacity  
+        }else{
+            viewer.outlinePass.edgeStrength = edgeStrengths.glb
+        }  
+    },
+    focusOn(objects, duration = 400, fitBound=true, dontLookUp){  
+        if(!(objects instanceof Array)){
+            objects = [objects]
+        }
+        let boundingBox = new THREE.Box3
+        objects.forEach(object=>{
+            boundingBox.union(object.boundingBox.clone().applyMatrix4(object.matrixWorld))
+        })
+         
+        if(fitBound){
+            viewer.focusOnObject({boundingBox}, 'boundingBox', duration, {dontLookUp, dontChangeCamDir:true}) 
+        }else{ 
+        
+            /* 
+            let position = viewer.inputHandler.intersect ? viewer.inputHandler.intersect.location : boundingBox.getCenter(new THREE.Vector3)
+            position && viewer.focusOnObject({position},  'point', duration, {dontChangePos: true})
+             */
+            let position = viewer.inputHandler.intersect ? viewer.inputHandler.intersect.location : boundingBox.getCenter(new THREE.Vector3)
+            if(!position)return
+            
+            
+            /* let targetOld = viewer.mainViewport.view.getPivot()
+            
+            let projected1 = targetOld.clone().project(viewer.mainViewport.camera);
+            let projected2 = position.clone().project(viewer.mainViewport.camera); //使用其z
+			let targetNew = projected1.clone().setZ(projected2.z).unproject(viewer.mainViewport.camera); 
+            viewer.mainViewport.view.lookAt(targetNew) */
+            
+            
+            viewer.mainViewport.view.radius = viewer.mainViewport.camera.position.distanceTo(position)
+            //为了不改画面,不调节方向了,只能调调radius,一定程度将target靠近model
+        }  
+    },
+    
+    
+    moveBoundCenterTo(model,pos){ //使boundCenter在所要的位置 
+        let diff = new THREE.Vector3().subVectors(pos, model.boundCenter) 
+        model.position.add(diff); 
+    },
+    
+    getBoundCenter(model){
+        if(!model.boundCenter) model.boundCenter = new THREE.Vector3
+        model.boundingBox.getCenter(model.boundCenter).applyMatrix4(model.matrixWorld) 
+    },
+    
+    setModelBtmHeight(model, z ){  
+        //无论模型怎么缩放、旋转,都使最低点为z
+        if(z == void 0) z = model.btmHeight; //维持离地高度
+        else model.btmHeight = z;
+        
+        model.updateMatrixWorld()
+        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  
+        //model.position.z = z + size.z / 2 - center.z 
+        model.position.z += (hopeZ - center.z)
+    },
+    
+    computeBtmHeight(model){ //位移之后重新计算btmHeight
+        model.updateMatrixWorld()
+        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 
+    },
+    
+    
+    maintainBoundXY(model){ //在旋转和缩放后,立即执行这个函数,使boundCenter保持原位
+         
+        model.updateMatrixWorld()
+        let center1 = model.boundCenter.clone();//还未更新的 
+        this.getBoundCenter(model)//更新
+        let center2 = model.boundCenter.clone();
+        let diff = new THREE.Vector2().subVectors(center1,center2); 
+        model.position.x += diff.x;
+        model.position.y += diff.y;
+        model.boundCenter.copy(center1)
+    }, 
+     
+    
+    
+    maintainBoundCenter(model){
+        model.updateMatrixWorld()
+        let center1 = model.boundCenter.clone();//还未更新的 
+        this.getBoundCenter(model)//更新
+        let center2 = model.boundCenter.clone();
+        let diff = new THREE.Vector3().subVectors(center1,center2); 
+        model.position.add(diff)
+        model.boundCenter.copy(center1)
+    },
+    
+    modelTransformCallback(model){
+        
+        model.updateMatrixWorld() 
+        if(model.matrixWorld.equals(model.lastMatrixWorld))return
+        viewer.scene.measurements.forEach(measure=>{
+            let changed
+            measure.points_datasets.forEach((dataset_id,i)=>{
+                if(dataset_id == model.dataset_id){
+                    changed = true
+                    measure.points[i] = Potree.Utils.datasetPosTransform({fromDataset:true,datasetId:dataset_id, position:measure.dataset_points[i].clone()})
+                    measure.updateMarker(measure.markers[i], measure.points[i])
+                   
+                }
+            })
+            if(changed){//仿transformByPointcloud
+                measure.getPoint2dInfo(measure.points)
+                measure.update() 
+                measure.setSelected(false)//隐藏edgelabel  
+            }
+        })
+        
+          
+     
+        
+        model.lastMatrixWorld = model.matrixWorld.clone()
+    }
+}   
+    
+    
+    
+ 
+    
+    
+export default MergeEditor  
+    
+    
+/*  
+
+note:
+
+要注意getHoveredElements只在getIntersect时才使interactables包含加载的model, 也就是model上不能有使之成为interactables的事件,否则在鼠标hover到模型上开始转动的一瞬间很卡。
+
+
+
+
+
+*/ 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1227 - 0
src/modules/panoEdit/panoEditor.js


+ 229 - 74
src/navigation/RouteGuider.js

@@ -1,10 +1,9 @@
 
-import * as THREE from "../../libs/three.js/build/three.module.js";
-import {Utils} from "../utils.js";
-import { EventDispatcher } from "../EventDispatcher.js";
-import Sprite from '../viewer/Sprite'
-
-
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import {Utils} from "../../utils.js"; 
+import Sprite from '../../objects/Sprite.js'
+import Common from "../../utils/Common.js";
+import browser from '../../utils/browser.js'
 const texLoader = new THREE.TextureLoader()
 const arrowSpacing = 1 //间隔
 const arrowSize = arrowSpacing * 0.5
@@ -13,33 +12,63 @@ const planeGeo = new THREE.PlaneBufferGeometry(1,1);
 const sphereSizeInfo = {
       nearBound : 2, scale:arrowSize, restricMeshScale : true,
 }
-
-
-export class RouteGuider extends EventDispatcher{
+//const arrowsShowingCount = 25; //场景里最多展示多少个箭头
+const arrowShowMinDis = 10
+export class RouteGuider extends THREE.EventDispatcher{
     constructor () {
 		super();
         
         this.route = [];
         this.curve = []
+        this.scenePoints = []
         this.sceneMeshGroup = new THREE.Object3D;
         this.mapMeshGroup = new THREE.Object3D;
         this.generateDeferred;
         viewer.addEventListener('loadPointCloudDone',this.init.bind(this))
         
         this.lastResult;//保存上一个的结果,以便于反向
-    
+        this.datasetIds = [];//起始和终点的datasetId
     }
     init(){
         if(this.inited) return;
+        
         let zoom;
         viewer.mapViewer.addEventListener('camera_changed', e => {
+            if(!this.routeStart || !this.routeEnd) return   
             var camera = e.viewport.camera
-            if(camera.zoom != zoom){ 
-                this.updateMapArrows(true)
-                zoom = camera.zoom
-            } 
+           
+            Common.intervalTool.isWaiting('routeCameraInterval', ()=>{ //延时update,防止卡顿
+                if(camera.zoom != zoom){ 
+                    //console.log('updateMapArrows')
+                    this.updateMapArrows(true)
+                    zoom = camera.zoom         
+                    return true 
+                } 
+            }, browser.isMobile()?500:200)
         })
         
+   
+        
+       
+        //let lastPos = new THREE.Vector3
+        viewer.addEventListener('camera_changed', e => {
+            if(!this.routeStart || !this.routeEnd || !e.changeInfo.positionChanged) return
+            Common.intervalTool.isWaiting('routeCameraInterval', ()=>{ //延时update,防止卡顿
+                //let currPos = viewer.scene.getActiveCamera().position
+             
+                //if(!currPos.equals(lastPos)){
+                   // lastPos.copy(currPos)
+                    this.updateArrowDisplay() 
+                     
+                    return true 
+                //}
+            }, 1000)
+            
+            
+                        
+        })
+        
+        
         
         var polesMats = {
             shadowMat: new THREE.MeshBasicMaterial({ 
@@ -78,17 +107,17 @@ export class RouteGuider extends EventDispatcher{
             map
         }))
         this.arrow.scale.set(arrowSize,arrowSize,arrowSize)
-        viewer.setObjectLayers(this.arrow, 'route' )
+        viewer.setObjectLayers(this.arrow, 'sceneObjects' )
          
         
-        this.testArrow = this.arrow.clone();
+        /* this.testArrow = this.arrow.clone();
         this.testArrow.material = this.arrow.material.clone()
-        this.testArrow.material.color = 'red'
+        this.testArrow.material.color = 'red' */
         
         this.arrows = new THREE.Object3D;
         this.sceneMeshGroup.add(this.arrows)
         
-        viewer.setObjectLayers(this.sceneMeshGroup, 'route' )
+        viewer.setObjectLayers(this.sceneMeshGroup, 'sceneObjects' )
         //this.sceneMeshGroup.traverse(e=>e.renderOrder = 90)
         
         
@@ -106,30 +135,65 @@ export class RouteGuider extends EventDispatcher{
             map: texLoader.load(Potree.resourcePath+'/textures/map_instruction_target_reached.png' )  
         }))
         this.mapMarkStart.renderOrder = this.mapMarkEnd.renderOrder = 2//在箭头之上 */
+         
+        let map2 = texLoader.load(Potree.resourcePath+'/textures/routePoint_map_fsna.png' ) 
+        this.mapArrowMats = {
+            default: new THREE.MeshBasicMaterial({
+                transparent:true, depthTest:false,
+                map:map2, 
+            }),
+            
+            fade: new THREE.MeshBasicMaterial({
+                transparent:true, depthTest:false,
+                map:map2, 
+                opacity:0.4
+            }), 
+        }
         
-        this.mapArrow = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial({
-            transparent:true, depthTest:false,
-            map: texLoader.load(Potree.resourcePath+'/textures/routePoint_map_fsna.png' )  
-        }))
+        
+        
+        this.mapArrow = new THREE.Mesh( planeGeo, this.mapArrowMats.default) 
         this.mapArrow.scale.set(arrowSize,arrowSize,arrowSize)
         this.mapArrows = new THREE.Object3D;
         this.mapArrows.name = 'mapArrows'
-        //viewer.setObjectLayers(this.mapArrow, 'route' )
-        
+         
         
         
-        /* this.mapMeshGroup.add(this.mapMarkStart)
-        this.mapMeshGroup.add(this.mapMarkEnd) */
         this.mapMeshGroup.add(this.mapArrows)
         this.mapMeshGroup.name = 'mapRouteLayer'
         this.mapMeshGroup.visible = false
         
-        viewer.mapViewer.emit('add',{object:this.mapMeshGroup, name:'route'})
+        viewer.mapViewer.dispatchEvent({type:'add', object:this.mapMeshGroup, name:'route'})
         this.mapArrow.layers.mask = this.mapArrows.layers.mask // 修改成和map中的layer一样的
         
+        
+        
+        viewer.modules.SiteModel.bus.addEventListener('FloorChange',()=>{
+            if(this.routeStart && this.routeEnd){
+                this.updateOpacityAtMap()
+            }  
+        }) 
         this.inited = true
     }
     
+    updateOpacityAtMap(){//只有当前楼层的透明度为1
+        var currentFloor = viewer.modules.SiteModel.currentFloor
+        //console.log('updateOpacityAtMap', currentFloor && currentFloor.name)
+        const lift = 0.3 // 因为发送请求时用的是floorPosition的高度,而它可能会到画好的floor之下,所以有误差
+        this.mapArrows.children.forEach((arrow,index)=>{
+            let pos = this.mapPoints[index].clone()
+                pos.z += lift  
+            let inSide = currentFloor && currentFloor.ifContainsPoint(pos)
+            arrow.material = inSide ? this.mapArrowMats.default : this.mapArrowMats.fade
+            //console.log('arrow',index, arrow.material.opacity)
+        }) 
+        
+        viewer.mapViewer.dispatchEvent('content_changed')
+    }//但是如果楼层刚好只框柱相机位置而没框住地面位置就不好了……
+    
+    
+    
+    
     
     createPole(polesMats, name){
         const height = 1.5, sphereCount = 6, shadowSize = sphereSizeInfo.scale,  sphereSize = 0.04
@@ -145,10 +209,12 @@ export class RouteGuider extends EventDispatcher{
             var sphere = new Sprite({mat: polesMats.sphereMat}) 
             sphere.position.set(0,0,sliceDis*(i+1))
             sphere.scale.set(sphereSize,sphereSize,sphereSize);
+            sphere.visible = false
             group.add(sphere)
         }
         
         var hatSphere = new Sprite({mat: polesMats.hatMats[name], sizeInfo:sphereSizeInfo}) 
+        sphere.visible = false
         hatSphere.position.set(0,0,height)
         hatSphere.scale.copy(shadow.scale)
         group.add(hatSphere)
@@ -175,7 +241,7 @@ export class RouteGuider extends EventDispatcher{
     setArrowDir(arrows,index){
         let arrow = arrows[index]
         var nextOne = arrows[index+1];
-        var nextPos = nextOne ? nextOne.position : this.routeEnd
+        var nextPos = nextOne ? nextOne.position : this.endPolePos //routeEnd
         var direction = new THREE.Vector3().subVectors(arrow.position, nextPos).setZ(0);
         //direction.normalize();
         //console.log(direction.toArray())
@@ -186,38 +252,65 @@ export class RouteGuider extends EventDispatcher{
     
     
      
+     
     
-    
-    setRouteStart(pos, dealMapZ, ifReset){
+    setRouteStart(pos, dealZ , datasetId  ){
         if(this.routeStart && pos && this.routeStart.equals(pos)) return //可能重复设置
-        this.routeStart = pos && new THREE.Vector3().copy(pos) 
-        //if(dealMapZ && this.routeStart) this.routeStart.setZ(0)//后端要求设置为0,但设置后反而得不到了。直接用的是接近boundingbox.min.z的值
-            console.log('setRouteStart',ifReset,this.routeStart&&this.routeStart.toArray()) 
-       
-        if(ifReset){
-            this.bus && this.bus.emit('reposStartMarker', pos)
-        }else{
-            this.generateRoute()
+        this.routeStart = pos && new THREE.Vector3().copy(pos)  
+        if(dealZ && this.routeStart){
+            this.routeStart.setZ(this.getZAtMap()) 
+            this.bus && this.bus.emit('reposStartMarker', this.routeStart)
         }
+        console.log('setRouteStart',this.routeStart&&this.routeStart.toArray()) 
+        
+        this.datasetIds[0] = datasetId
+        
+        //this.setStartPole(pos)
+        
+        this.generateRoute()
+        
         
     }
     
+    setStartPole(pos){
+        this.startPolePos = pos
+        this.bus && this.bus.emit('reposStartMarker', pos)
+    }
+     
     
-    
-    setRouteEnd(pos, dealMapZ, ifReset){ 
+    setRouteEnd(pos, dealZ , datasetId  ){ 
         if(this.routeEnd && pos && this.routeEnd.equals(pos)) return 
         this.routeEnd = pos && new THREE.Vector3().copy(pos)
-        //if(dealMapZ && this.routeEnd) this.routeEnd.setZ(0)//后端要求设置为0
-            console.log('setRouteEnd',ifReset,this.routeEnd&&this.routeEnd.toArray())        
-        
-        if(ifReset){
-            this.bus && this.bus.emit('reposEndMarker', pos)
-        }else{
-            this.generateRoute()
+        if(dealZ && this.routeEnd){
+            this.routeEnd.setZ(this.getZAtMap())
+            this.bus && this.bus.emit('reposEndMarker', this.routeEnd)
         }
+        console.log('setRouteEnd',this.routeEnd&&this.routeEnd.toArray())        
+        this.datasetIds[1] = datasetId
+        //this.setEndPole(pos)
+        this.generateRoute()
+        
     }
     
     
+    getZAtMap(){  
+        
+        //找到position.z与当前高度最接近的漫游点 
+        let result = Common.sortByScore(viewer.images360.panos,[],[e=> -(Math.abs(e.position.z - viewer.images360.position.z)) ])
+        let pano = result && result[0] && result[0].item
+        
+        return pano ? pano.floorPosition.z : viewer.bound.boundingBox.min.z + 1 
+        //若在平面图上画实在得不到当前楼层的,大概率是楼层画得不好,那就只能去获取当前楼层的了
+        
+        //navvis的高度取的是主视图所在楼层的中心高度(可能再高些)
+        
+    }
+    
+    setEndPole(pos){
+        this.endPolePos = pos
+        this.bus && this.bus.emit('reposEndMarker', pos)
+    }
+    
     getSourceProjectionIndex(route) {//真正的起始
         var e = route.findIndex(function(t) {
             return t.instruction && t.instruction.type === 'source_projection_to_navgraph'
@@ -233,6 +326,7 @@ export class RouteGuider extends EventDispatcher{
     
     generateRoute(){
         if(!this.routeStart || !this.routeEnd){ 
+            
             return
         }
         
@@ -260,7 +354,7 @@ export class RouteGuider extends EventDispatcher{
             this.updateMapArrows() 
             this.displayRoute()
             
-            {
+            {//map focus on this area
                 
                 const minBound = new THREE.Vector2(1,1)//针对垂直线,在地图上只有一个点
                 let bound = new THREE.Box2;
@@ -268,7 +362,13 @@ export class RouteGuider extends EventDispatcher{
                     bound.expandByPoint(e)
                 })
                 let size = bound.getSize(new THREE.Vector2)
-                let margin = viewer.mapViewer.viewports[0].resolution2.clone().multiplyScalar(0.01)
+                let markerSize = new THREE.Vector2(115,40) //起始和终点的标识呈长方形
+                let areaSize = viewer.mapViewer.viewports[0].resolution2
+                let areaArea = areaSize.x * areaSize.y
+                if(areaArea> 800 * 400){//是放大的 
+                    markerSize.multiplyScalar(areaArea / (800 * 400) /* / (size.x * size.y) */) 
+                }
+                let margin = size.clone().divide(viewer.mapViewer.viewports[0].resolution2).multiply(markerSize) ///边距 重点是起始和终点的标识占据较大
                 size.add(margin)
                 let center = bound.getCenter(new THREE.Vector2)
                 
@@ -287,19 +387,36 @@ export class RouteGuider extends EventDispatcher{
         if(Potree.fileServer){
             let dealData = (data)=>{
                 
-                if(!data){
+                if(!data.data){
                     console.log('没有数据')
-                    this.bus && this.bus.emit('gotResult','没有数据')
+                    let result
+                    if(data && data.code == 4002){
+                        result = data;//正被修改数据集
+                    }else if(this.routeStart.distanceTo(this.routeEnd) < 1){
+                        result = { code: 500, msg: '距离太短,无法规划路线' }
+                    }else{
+                        result = { code: 500, msg: '超出数据集范围,无法规划路线' }
+                    }
+                    this.clearRoute() 
+
+
+                    this.setStartPole(this.routeStart) 
+                    this.setEndPole(this.routeEnd) 
+                    
+                    this.displayRoute() //还是要显示一下起始
+                    this.bus && this.bus.emit('gotResult', result )
                     
                     return //this.generateDeferred && this.generateDeferred.resolve()
                 }
-                 
+                
+                
+                data = data.data 
                   
                 this.clearRoute()
                 let length = data.length
                 
-                if(length == 0){//可能距离太短
-                    console.log('路径点数为0,直接取起点和终点连线')
+                if(length < 2){//可能距离太短 
+                    console.log('路径点数为'+length+',直接取起点和终点连线') 
                     this.route = [this.routeStart, this.routeEnd];
                 }else{ 
                     let startIndex = this.getSourceProjectionIndex(data)
@@ -309,15 +426,17 @@ export class RouteGuider extends EventDispatcher{
                     let effectiveItems = data.slice(startIndex, endIndex + 1 );//只要点云范围内的点
                     effectiveItems.forEach((item,i)=>{ 
                         let pos = viewer.transform.lonlatToLocal.forward(item.location.slice(0))
-                        pos = new THREE.Vector3().fromArray(pos)
+                        pos = new THREE.Vector3().fromArray(pos)//.setZ(item.z)
                         this.route.push(pos)
                     })
                     
                     console.log(this.route)
                     
-                    this.setRouteStart(this.route[0],false, true) 
-                    this.setRouteEnd(this.route[this.route.length-1],false,true) 
+                    
                 }
+                this.setStartPole(this.route[0]) 
+                this.setEndPole(this.route[this.route.length-1]) 
+                
                 create()
                 /*
                     distance: 0.17581000000000116
@@ -334,21 +453,20 @@ export class RouteGuider extends EventDispatcher{
             
             
             
-            if(this.lastResult){
-                let data, use;  //直接用上次的结果
+            if(this.lastResult && (this.lastResult.data || this.lastResult.data.code != 4002)){//正被修改数据集的话要重新计算
+                let data = Common.CloneObject(this.lastResult.data) ,  use;  //直接用上次的结果
                 if(this.lastResult.routeStart.equals(this.routeStart) &&  this.lastResult.routeEnd.equals(this.routeEnd)){//和上次请求相同
-                    use = true
-                    data = this.lastResult.data
-                    
+                    use = true 
                 }else if(this.lastResult.routeStart.equals(this.routeEnd) &&  this.lastResult.routeEnd.equals(this.routeStart)){//..反向
                     use = true
-                    data = this.lastResult.data.slice(0).reverse()
+                    if(data.data){
+                        data.data = this.lastResult.data.data.slice(0).reverse()
+                    }    
                 }
                 if(use){
                     console.log('直接用上次的结果')
                     return setTimeout(()=>{dealData(data)}, 1)//延迟是为了等待获得 RouteGuider.generateDeferred
-                     
-                    
+                       
                 }
                 
             }
@@ -369,7 +487,10 @@ export class RouteGuider extends EventDispatcher{
                 destination_latitude: endLonlat.y,
                 destination_z: end.z
             };
-            let url = `/laser/route/${Potree.settings.number}/getRoute?`
+            
+            
+            //let url = `/laser/route/${Potree.settings.number}/getRoute/${this.datasetIds[0]}/${this.datasetIds[1]}?`
+            let url = `/laser/route/${Potree.settings.number}/getRoute/${Potree.settings.originDatasetId}?`
             for(let i in query){
                 url+= (i + '='+ query[i] +'&')
             }
@@ -381,10 +502,11 @@ export class RouteGuider extends EventDispatcher{
                 this.lastResult = {//保存数据
                     routeStart : this.routeStart.clone(),
                     routeEnd: this.routeEnd.clone(),
-                    data : data.data
+                    data,
+                     
                 }
                 
-                dealData(data.data)
+                dealData(data)
                 
             })
             
@@ -414,6 +536,9 @@ export class RouteGuider extends EventDispatcher{
         if(this.route.length == 0)return  
         var zoom = viewer.mapViewer.camera.zoom
         let count = Math.max(2,Math.round(this.routeLength * zoom  / arrowSpacing / 25))//点数
+        
+        if(count == this.mapPoints.length+1)return//没变
+
         const mapPoints = this.curve.getSpacedPoints( count ); 
         mapPoints.splice(0,1);//去掉首尾
         mapPoints.pop() 
@@ -424,33 +549,62 @@ export class RouteGuider extends EventDispatcher{
         this.mapArrow.scale.set(scale*0.6,scale*0.6,scale*0.6) 
         /* this.mapMarkStart.scale.set(scale,scale,scale) 
         this.mapMarkEnd.scale.set(scale,scale,scale)  */
-         
+        
         
         if(ifReset){//因为缩放而重新排布箭头
             this.clearRoute({resetMap:true})
             this.displayRoute({resetMap:true}) 
         }
+        this.updateOpacityAtMap()
     }
     
     
+    updateArrowDisplay(){//根据当前位置更新显示一定范围内的箭头'
     
+        if(this.scenePoints.length == 0)return
+        
+        /* var a = Common.sortByScore(this.scenePoints , null, [(point)=>{   //是否还要再requires里限制最远距离?
+            var playerPos = viewer.scene.getActiveCamera().position.clone().setZ(0)
+            
+            var pos = point.clone().setZ(0) 
+            
+            return -pos.distanceTo(playerPos);
+            
+        }]);
+        //获得展示的起始点 
+        let start = a[0].item
+        let startIndex = this.scenePoints.indexOf(start)
+        this.arrows.children.forEach((e,i)=>{
+            if(i<startIndex || i>startIndex+arrowsShowingCount)e.visible = false
+            else e.visible = true
+        }) */
+        
+        let cameraPos = viewer.scene.getActiveCamera().position
+        this.arrows.children.forEach((e,i)=>{
+            if(e.position.distanceTo(cameraPos) < arrowShowMinDis) e.visible = true
+            else e.visible = false
+        })
+        
+        
+    }
     
     
     displayRoute(o={}){
         if(!o.resetMap){ 
             
-            this.poleStart.position.copy(this.routeStart)
-            this.poleEnd.position.copy(this.routeEnd)
+            this.poleStart.position.copy(this.startPolePos || this.routeStart)
+            this.poleEnd.position.copy(this.endPolePos || this.routeEnd)
             /* this.mapMarkStart.position.copy(this.routeStart).setZ(0)
             this.mapMarkEnd.position.copy(this.routeEnd).setZ(0) */
             this.scenePoints.forEach(e=>this.addArrow(e))
             this.arrows.children.forEach((e,i)=>this.setArrowDir(this.arrows.children,i));
         }
-        this.sceneMeshGroup.visible = true 
-        this.mapMeshGroup.visible = true   
+        this.sceneMeshGroup.traverse(e=>e.visible = true)  
+        this.mapMeshGroup.visible = true
         this.mapPoints.forEach(e=>this.addMapArrow(e))
         this.mapArrows.children.forEach((e,i)=>this.setArrowDir(this.mapArrows.children,i));
         viewer.mapViewer.dispatchEvent({'type':'content_changed'})
+        this.updateArrowDisplay()
     }
     
     clearRoute(o={}){
@@ -470,8 +624,9 @@ export class RouteGuider extends EventDispatcher{
             this.mapArrows.remove(e)
         })
         
-        this.sceneMeshGroup.visible = false 
+        this.sceneMeshGroup.traverse(e=>e.visible = false)  //包括sprite也要设置,防止update
         this.mapMeshGroup.visible = false
+        viewer.mapViewer.dispatchEvent({'type':'content_changed'})
     }
     
     clear(){//退出

+ 660 - 132
src/modules/siteModel/BuildingBox.js

@@ -1,126 +1,410 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import {ctrlPolygon} from '../../utils/ctrlPolygon'
-import {LineDraw, MeshDraw } from "../../utils/DrawUtil";
-import Sprite from '../../viewer/Sprite'
+import {ctrlPolygon} from '../../objects/tool/ctrlPolygon.js'
+import {LineDraw, MeshDraw } from "../../utils/DrawUtil.js";  
+import math  from "../../utils/math.js";
+import Sprite from '../../objects/Sprite.js'
 /* import {config} from '../settings' */
-
+import searchRings from "../../utils/searchRings.js";
+import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js";
 
 let texLoader = new THREE.TextureLoader() 
 
  
 let markerMats
-let markerSizeInfo = {width2d:30}
+let markerSizeInfo = {width2d:35}
 let color = new THREE.Color('#FFF')
-
-
-let faceMats = {
-    building: new THREE.MeshPhongMaterial({
-        color:"#00bbff",
-        side:THREE.DoubleSide,
-        opacity:0.1,
-        transparent:true,
-        depthTest:false
-    }),
-    floor:  new THREE.MeshPhongMaterial({
-        color:"#eeee00",
-        side:THREE.DoubleSide,//BackSide,
-        opacity:0.06,
-        transparent:true,
-        depthTest:false
-    }),
-    floorSelect: new THREE.MeshPhongMaterial({
-        color:"#eeee00",
-        side:THREE.DoubleSide,
-        opacity:0.15,
-        transparent:true,
-        depthTest:false
-    }),
-    room: new THREE.MeshPhongMaterial({
-        color:"#ff44ee",
-        side:THREE.DoubleSide,//BackSide,
-        opacity:0.06,
-        transparent:true,
-        depthTest:false
-    }),
-    roomSelect: new THREE.MeshPhongMaterial({
-        color:"#ff44ee",
-        side:THREE.DoubleSide,//BackSide,
-        opacity:0.15,
-        transparent:true,
-        depthTest:false
-    }),
-    
+let faceMats
+let getFaceMat = (name)=>{
+    if(!faceMats){ //navvis材质可以搜gridTexture
+        let gridTex = texLoader.load( Potree.resourcePath+'/textures/gridmap.png' ) 
+            gridTex.wrapS = gridTex.wrapT = THREE.RepeatWrapping   
+            //gridTex.repeat.set(0.5,0.5)//放大一些
+        faceMats = { 
+            dataset: new THREE.MeshStandardMaterial({
+                color:812922,   
+                side:THREE.DoubleSide, 
+                opacity:0.2,
+                transparent:true,  
+                depthTest:false,
+                wireframe:true
+            }),
+            building: new THREE.MeshStandardMaterial({
+                color:812922,  metalness: 0.2, roughness:0.8,
+                side:THREE.DoubleSide, 
+                opacity:0.1,
+                transparent:true,  
+                depthTest:true
+            }),
+            buildingSelect: new THREE.MeshStandardMaterial({
+                color:36582,  metalness: 0, roughness:1,
+                side:THREE.DoubleSide,
+                opacity:0.1,
+                transparent:true,
+                depthTest:true
+            }),
+            floor:  new THREE.MeshStandardMaterial({
+                color:11708469,  metalness: 0.1, roughness:1,
+                side:THREE.DoubleSide,//BackSide,
+                opacity:0.05,
+                transparent:true,
+                depthTest:true, 
+            }),
+             
+            /* floorSelect: new THREE.MeshStandardMaterial({
+                color:16707151,  metalness: 0, roughness:1,
+                side:THREE.DoubleSide,
+                opacity:1,
+                transparent:true,
+                depthTest:true,
+                polygonOffset : true,//是否开启多边形偏移 
+				polygonOffsetFactor : -0.75,//多边形偏移因子
+				polygonOffsetUnits : -4.0,//多边形偏移单位 
+                map: gridTex,
+            }),  */ 
+            floorSelect: new DepthBasicMaterial({
+                map: gridTex,
+                color:16707151,  
+                side:THREE.DoubleSide,//BackSide, 
+                opacity:1,
+                transparent:true, 
+                useDepth : true,
+                /* polygonOffset : true,//是否开启多边形偏移 
+				polygonOffsetFactor : -0.75,//多边形偏移因子
+				polygonOffsetUnits : -4.0,//多边形偏移单位  */
+                
+                clipDistance : 1, occlusionDistance:1, /* occlusionDistance:变为backColor距离, clipDistance:opacity到达0或者1-maxClipFactor时的距离 */  
+                maxClipFactor:0.4, backColor:'#efe' //backColor:"#669988"  ,
+                  
+            }),
+            
+            room: new THREE.MeshStandardMaterial({
+                color:"#ff44ee",  metalness: 0, roughness:1,
+                side:THREE.DoubleSide,//BackSide,
+                opacity:0.08,
+                transparent:true, 
+                depthTest:false, 
+            }), 
+            /* roomSelect: new THREE.MeshStandardMaterial({
+                color:"#ff44ee",  metalness: 0.3, roughness:1,
+                side:THREE.DoubleSide,//BackSide,
+                opacity:1,
+                transparent:true,
+                depthTest:true,
+                polygonOffset : true,//是否开启多边形偏移.(开启是因为和floor重叠了会闪烁)
+				polygonOffsetFactor : -0.75,//多边形偏移因子
+				polygonOffsetUnits : -4.0,//多边形偏移单位  
+                map: gridTex,
+            }), */
+            roomSelect: new DepthBasicMaterial({
+                map: gridTex,
+                color:"#ff44ee", 
+                side:THREE.DoubleSide,//BackSide, 
+                opacity:1,
+                transparent:true, 
+                useDepth : true,
+                /* polygonOffset : true,//是否开启多边形偏移 
+				polygonOffsetFactor : -0.75,//多边形偏移因子
+				polygonOffsetUnits : -4.0,//多边形偏移单位  */
+                
+                clipDistance : 1, occlusionDistance:0.5, /* occlusionDistance:变为backColor距离, clipDistance:opacity到达0或者1-maxClipFactor时的距离 */  
+                maxClipFactor:0.6, backColor:'#ff88dd'//"#cc99c2"  ,
+                 
+            })
+        }
+    }
+    return faceMats[name]
 }
 
 
 
+ 
+
 export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, floor, room
     constructor(prop) {
         prop.dimension = '3d'
-        super('siteModel_'+prop.type_,  prop);
+        //prop.name = Potree.config.siteModel.names[prop.buildType] + 
+         
+        super('siteModel_'+prop.buildType,  prop);
+        
+         
+        
         this.midMarkers = []
+        this.buildChildren = []//子实体
+        this.holes = [] //在这创建的hole
+        this.parentHoles = [];//floor从building那得到的当层holes
+        this.mats = {} //材质
+        
+        this.panos = this.panos || [];
+        this.center //中心点
         
-        if(this.type_=='floor'){//一旦添加了楼层,建筑原本的faceMesh lineMesh就隐藏
-            /* this.buildParent.box.visible = false 
-            this.buildParent.lineMesh.visible = false  */
+        if(this.buildType=='floor'){
             
-            this.points = this.buildParent.points;//完全等于建筑的点
+            this.points = prop.points = this.buildParent.points;//完全等于建筑的点
+            this.buildParent.holes.forEach(hole=>{//从building获取holes
+                let floorHole = new BuildingBox({
+                    buildType : 'hole', 
+                    buildParent:this,
+                    originHole : hole, //整栋大楼在当层的hole
+                    ifDraw: this.ifDraw  || Potree.settings.drawEntityData
+                });
+                this.parentHoles.push(floorHole)
+                this.add(floorHole) 
+                
+                floorHole.points = hole.points//完全等于建筑的点
+            })
         } 
+        if(this.buildType == 'room' || this.buildType == 'hole'){
+            this.restrictArea = this.buildParent  //不能超出的区域
+        }
         
-        this.box = this.createBox()
-        this.add(this.box)
+        if(this.ifDraw){ //只存储空间模型信息,不绘制
+            if(this.buildType != 'hole'){
+                this.box = this.createBox()
+                this.add(this.box)
+            }
+            {
+                this.lineMesh = LineDraw.createLine([],{color})
+                this.lineMesh.name = 'buildingLines'
+                this.lineMesh.visible = false            
+                this.add(this.lineMesh) 
+                viewer.setObjectLayers(this.lineMesh, 'bothMapAndScene' ) 
+            }
+            
+            
+            this.addEventListener('dragChange',(e)=>{ //修改中点
+                this.updateTwoMidMarker(e.index)
+            }) 
+            
+        }
         
-        this.lineMesh = LineDraw.createLine([],{color})
-        this.lineMesh.name = 'buildingLines'        
-        this.add(this.lineMesh) 
-        viewer.setObjectLayers(this.lineMesh, 'sceneObjects' )
+        this.initData(prop)
         
-        this.buildChildren = []//子实体
         
-        this.addEventListener('dragChange',(e)=>{ //修改中点
-            this.isNew || this.updateTwoMidMarker(e.index)
-        })
-   
+    } 
+    
+    
+    initData(prop){
+        if(prop.ifDraw){
+            super.initData(prop) 
+        }else{
+            if(prop.points){
+                this.points = prop.points  
+            }
+            
+            
+        }
         
     } 
     
+    intersectPointcloudVolume(pointcloud){//和pointcloud的重叠体积
+        var bound = this.getBound()
+        let bound2 = pointcloud.bound;
+        if(!bound.intersectsBox(bound2)) return 0;
+        
+        
+        let {zMin , zMax} =  this.getRealZ()
+        let min = Math.min(zMin, bound2.min.z);
+        let max = Math.max(zMax, bound2.max.z); 
+        let height1 = zMax - zMin
+        let height2 = bound2.max.z-bound2.min.z
+        let coverHeight = height1 + height2 - (max-min)//重叠高度 <=0是没重叠
+         
+        let boxPoints = pointcloud.getUnrotBoundPoint() //获取tightBound的四个点。 如果是有旋转角度的点云,这个和pointcloud.bound的四个点是不一致的,覆盖面积小于pointcloud.bound
+       
+        let areaWhole = 0  
+        let area1 = this.getArea()
+        let area2 = Math.abs(math.getArea(boxPoints))
+         
+        {//计算points与点云总面积 (但是把hole也加入了面积)(并集,重叠部分只算一次)  
+            let rings = math.getPolygonsMixedRings([this.points, boxPoints] )
+            
+            rings.forEach(e=>{ 
+                areaWhole+=e.area
+            })
+        }
+        
+        let coverHoleArea = 0 //holes与数据集重叠的部分
+        let holes = this.holes.concat(this.parentHoles)
+        let holesArea = 0    //所有holes面积相加
+        let areaHoleWithPointcloud = 0 //hole和点云的面积并集
+        
+        if(holes.length>0){//还要再扣除holes与数据集重叠的部分。其中holes为mix轮廓
+            let outHoles = []//没有重合的holes的外轮廓
+            /* if(holes.length>=2){//合并holes。如果能在绘制时直接合并holes就好啦,这步就转移到那去,但是要删除hole好麻烦
+                  
+                let holes_ = holes.map(e=>e.points)
+                
+                outHoles = math.getPolygonsMixedRings(holes_,  true )
+                outHoles.forEach(e=>{ 
+                    holesArea+=e.area
+                }) 
+                outHoles = outHoles.map(e=>e.points)
+                
+                
+            }else{
+                outHoles = holes.map(e=>e.points)
+                outHoles.forEach(e=> holesArea += Math.abs(math.getArea(e)))                
+            } */
+            holesArea = this.getHolesArea()
+            
+            //holes与数据集重叠的部分
+            
+            {    
+                let polygons = outHoles.concat([boxPoints]) 
+                let rings = math.getPolygonsMixedRings(polygons)
+                rings.forEach(e=>{ 
+                    areaHoleWithPointcloud+=e.area
+                })
+                coverHoleArea = holesArea + area2 - areaHoleWithPointcloud//hole和点云的交集
+            }
+             
+        }
+        
+        
+        let coverArea = area1 + area2 - areaWhole - coverHoleArea; //重叠面积
+         
+        return coverArea * coverHeight
+    }
+    
+    
+    addHole(points=[]){  
+        let prop = {
+            buildType : 'hole',
+            zMin : this.zMin,
+            zMax : this.zMax,
+            points, 
+            buildParent:this,
+            ifDraw: this.ifDraw || Potree.settings.drawEntityData
+        }
+        //hole的zMin zMax跟随buildParent
+        var hole = new BuildingBox(prop);
+        this.holes.push(hole)
+        
+        if(this.buildType == 'building'){//为每一层添加对应的hole
+            this.buildChildren.forEach(floor=>{
+                let floorHole = new BuildingBox({
+                    buildType : 'hole', 
+                    zMin : this.zMin,
+                    zMax : this.zMax,
+                    buildParent:floor,
+                    originHole : hole, //整栋大楼在当层的hole
+                    ifDraw: this.ifDraw  || Potree.settings.drawEntityData
+                });
+                floor.parentHoles.push(floorHole)
+                floor.add(floorHole)
+                floorHole.points = hole.points//完全等于建筑的点
+            })
+            
+        }
+        
+        this.add(hole);//直接加在这,不加meshGroup了
+        this.update() //update box mesh
+        return hole
+        
+        //hole不创建box,只有它的buildParent需要更新box。 但有线条和marker.  hole不在buildChildren里,但有buildParent
+        
+    }
+    
+    
+    removeHole(hole){// 这个hole不会是parentHoles里的。
+        hole.dispose()
+        
+        if(this.buildType == 'building'){ //若是整栋大楼的hole,在每层去除它的对应hole
+            this.buildChildren.forEach(floor=>{ 
+                let holeAtFloor = floor.parentHoles.find(e=>e.originHole == this )
+                let index = floor.parentHoles.indexOf(holeAtFloor)  
+                index > -1 && floor.parentHoles.splice(index, 1)                
+                holeAtFloor.dispose() 
+            })
+        } 
+        
+        let index = this.holes.indexOf(hole)
+        if(index>-1){
+            this.holes.splice(index, 1)
+        }
+        this.remove(hole)
+        this.update()
+    }
+    
+    
     
     
     createBox(){ 
         var geometry = new THREE.Geometry();
-        var mesh = new THREE.Mesh(geometry, faceMats[this.type_])
+        
+        
+        
+        this.mats.boxDefault = getFaceMat(this.buildType)  
+        this.mats.boxSelected = getFaceMat(this.buildType+'Select')     
+        
+        
+        var mesh = new THREE.Mesh(geometry, this.mats.boxDefault)
         mesh.name = 'buildingBox';
-        if(this.type_ == 'floor'){
-            viewer.setObjectLayers(mesh, 'siteModelMapUnvisi' )
+        if(this.buildType == 'floor'){
+            viewer.setObjectLayers(mesh, 'siteModelMapUnvisi' ) //楼层默认在地图不显示,为了不会叠加透明度
         }else{
-            viewer.setObjectLayers(mesh, 'sceneObjects' )
+            viewer.setObjectLayers(mesh, 'bothMapAndScene' )
         }
+         
+        
+        
+        //mesh.frustumCulled = false;
         return mesh
     }
     
     
     
     addMarker(o={} ){
-        if(this.type_=='floor')return; //楼层不需要marker
+        if(this.buildType=='floor')return; //楼层不需要marker
         
-        let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_marker"} )
-        
-        marker.renderOrder = 3 
+        let marker = new Sprite({mat:this.getMarkerMaterial('default'), renderOrder : 3, sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_marker"} )
+       
          
         viewer.setObjectLayers(marker, 'siteModeOnlyMapVisi' ) 
         
         o.marker = marker
         super.addMarker(o)
         
+        if(!this.selected)viewer.updateVisible(marker,'select',false) 
          
+        let addClickEvent = (e)=>{ 
+            let click = (e) => {   
+                this.dispatchEvent({type:'clickMarker', marker } )  //由entity发送给sitemodel统一处理
+            }; 
+            marker.addEventListener('click', click); 
+            marker.addEventListener('clickSelect', (e)=>{
+                 this.setMarkerSelected(marker, e.state ? 'select' : 'unselect' );  
+            }); 
+            marker.removeEventListener('addHoverEvent',addClickEvent) 
+        }
+        marker.addEventListener('addHoverEvent',addClickEvent)//当非isNew时才添加事件
+        if(!this.isNew){
+            marker.dispatchEvent('addHoverEvent')
+        }
+     
         return marker
     }
     
-    
+    removeMarker(index){
+        super.removeMarker(index);
+        if(!this.isNew){
+            //重新添加midMarkers 
+            this.midMarkers.forEach(e=>this.remove(e));
+            this.midMarkers = [] 
+            this.addMidMarkers() 
+        }
+        
+        this.update();  
+        if(this.points.length == 2 && this.box){//清除原先length>=3时候的
+            this.box.geometry = new THREE.Geometry();
+        } 
+        
+     
+    }
     
     addMidMarker(index, point){
-        if(this.type_=='floor')return; //楼层不需要marker
+        if(this.buildType=='floor')return; //楼层不需要marker
         let marker = new Sprite({mat:this.getMarkerMaterial('midPrepare'), sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_midMarker"} )
         this.midMarkers = [...this.midMarkers.slice(0,index), marker, ...this.midMarkers.slice(index,this.midMarkers.length)]
         
@@ -129,23 +413,23 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         viewer.setObjectLayers(marker, 'siteModeOnlyMapVisi' ) 
         { // Event Listeners  
             let mouseover = (e) => {
-                this.setMarkerSelected(e.object, true, 'single');
+                this.setMarkerSelected(e.object, 'hover', 'single'); 
                 viewer.dispatchEvent({
                     type : "CursorChange", action : "add",  name:"markerMove"
                 }) 
             };
             let mouseleave = (e) => {
-                this.setMarkerSelected(e.object, false, 'single');
+                this.setMarkerSelected(e.object, 'unhover', 'single');
                 viewer.dispatchEvent({
                     type : "CursorChange", action : "remove",  name:"markerMove"
                 })
             }
             let drag = (e) => {
                 let index = this.midMarkers.indexOf(marker)
-                let newMarker = this.addMarker({index:(index+1), point:marker.position.clone() }) 
+                let newMarker = this.addMarker({index:(index+1), point:marker.position.clone()  }) 
                 this.addMidMarker(index+1, new THREE.Vector3 )
                 this.updateTwoMidMarker(index+1) 
-                this.setMarkerSelected(marker, false) 
+                this.setMarkerSelected(marker, 'unhover') 
                 viewer.inputHandler.startDragging(newMarker , {/* dragViewport:viewer.mapViewer.viewports[0],  */   } ); //notPressMouse代表不是通过按下鼠标来拖拽.  dragViewport指定了只能在地图上拖拽
             }
             marker.addEventListener('drag', drag );
@@ -155,11 +439,14 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         }
         this.add(marker)
         this.updateMarker(marker, point)
+        if(!this.selected)viewer.updateVisible(marker,'select',false) 
         return marker
     }
     
     
     
+    
+    
     addMidMarkers(){//第一次画好所有marker后,一次性为线段增加中点marker
         let length = this.points.length
         this.points.forEach((point,index)=>{
@@ -170,6 +457,7 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
     }
     
     updateTwoMidMarker(index){//更新第index个marker两边的midMarker
+        if(!this.midMarkers.length)return
         let length = this.points.length
         let last = this.points[(index-1+length)%length] //它之前的marker位置
         let next = this.points[(index+1)%length];//它之后的marker位置
@@ -182,35 +470,84 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         this.updateMarker(nextMidMarker, nextMid) 
     }
     
-    dispose(){
+     
+    
+    
+    
+    dispose(){//销毁geo、remove from parent
         super.dispose()
-        this.box.geometry.dispose();
-        this.lineMesh.geometry.dispose();
-        
-        
-        this.buildChildren.forEach(e=>e.dispose())
+        this.box && this.box.geometry.dispose();
+        this.lineMesh && this.lineMesh.geometry.dispose();
+        this.holes.forEach(e=>e.dispose()) 
+        this.parentHoles.forEach(e=>e.dispose()) 
+        //this.buildChildren.forEach(e=>e.dispose())
+        this.dispatchEvent('dispose')
+    }
+    
+    
+    
+    updateBox(){
+        if(!this.box)return
+        this.box.geometry.dispose()
+        var shrink = this.buildType == 'room' ? 0.11 : this.buildType == 'floor' ?  0.082 :  0.2   ;//防止mesh重叠冲突(给一个不寻常的数字)  但离远了还是会有点闪烁
+        if(this.points.length >= 3){ 
+            let holes = this.holes.concat(this.parentHoles)
+            let holesPoints = holes.filter(e=>e.points.length>2).map(e=>e.points)
+            this.box.geometry = MeshDraw.getExtrudeGeo(this.points, holesPoints, {
+                depth:this.zMax-this.zMin-shrink,
+                UVGenerator: new MetricUVGenerator()
+            }) 
+            if(this.buildType == 'building' ){
+                this.box.position.z = this.zMin - shrink / 2  
+            }else{
+                this.box.position.z = this.zMin + shrink / 2 
+            }
+             
+        }
     }
     
-    update(ifUpdateMarkers, dontUpdateChildren){ 
-        super.update(ifUpdateMarkers)
+     
+    
+    update(options={}){ 
+        super.update(this.buildType != 'floor' && options.ifUpdateMarkers)
+        let length = this.points.length
         
         
-        this.box.geometry.dispose()
-        let length = this.points.length 
-        if(length >= 3){ 
-            this.box.geometry = MeshDraw.getExtrudeGeo(this.points, this.zMax-this.zMin) 
-            this.box.position.z = this.zMin
+       
+        {//确保一下一样
+            if(this.originHole){ 
+                this.points = this.originHole.points //完全等于building的hole
+            }
+            if(this.buildType == 'hole'){
+                this.zMin = this.buildParent.zMin;
+                this.zMax = this.buildParent.zMax;
+            } 
         }
         
-       /*  if(!this.isNew){
-            this.midMarkers
+        
+        
+        if(!options.dontUpdateBox){
+            let boxOwner
+            if(this.buildType == 'hole'){
+                if(this.buildParent.buildType == 'building'){ //若是整栋大楼的hole,在每层都要更新下它的对应hole
+                    this.buildParent.buildChildren.forEach(floor=>{
+                        let holeAtFloor = floor.parentHoles.find(e=>e.originHole == this ) 
+                        holeAtFloor && holeAtFloor.update()  //刚开始创建时还没创建对应的 holeAtFloor会为null
+                    })
+                } 
+                boxOwner = this.buildParent 
+            }else{
+                boxOwner = this
+            }
+            boxOwner.updateBox()
         }
-         */
+        
+        
         
         {//update lines
-            //let zMin = 0, zMax = 1
-            let positions = [];
             
+            let positions = [];
+           
             this.points.forEach((point, index)=>{
                 
                 //竖线:
@@ -218,41 +555,126 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
                 
                 //横线
                 let nextPoint = this.points[(index+1)%length]; 
-                if(point == nextPoint)return;//when length==1
+                if(!nextPoint)return;//when length==1
                 
                 positions.push(point.clone().setZ(this.zMax), nextPoint.clone().setZ(this.zMax))//上横线
                 positions.push(point.clone().setZ(this.zMin), nextPoint.clone().setZ(this.zMin))//下横线
-            })
+            }) 
             
             LineDraw.moveLine(this.lineMesh,positions)
              
         }
         
+        if(!options.dontUpdateChildren){
+            if(this.buildType == 'building'  ){
+                this.buildChildren.forEach(floor=>{
+                    floor.points = this.points 
+                    floor.update()
+                })
+                
+            } 
+            
+            { 
+                let holes = this.holes.concat(this.parentHoles) 
+                holes.forEach(hole=> {  
+                    
+                    hole.update({dontUpdateBox:true})//父级更新了box,hole就不需要更新box了
+                }) 
+                    
+            } 
         
-        if(this.type_ == 'building' && !dontUpdateChildren){
-            this.buildChildren.forEach(e=>{
-                e.points = this.points
-                e.update()
-            })
+        } 
+    }
+    
+    
+     
+    
+    
+    
+    getHolesArea(){
+        let holes = this.holes.concat(this.parentHoles)
+        let outHoles, holesArea = 0
+        if(holes.length>=2){//合并holes。如果能在绘制时直接合并holes就好啦,这步就转移到那去,但是要删除hole好麻烦
+              
+            let holes_ = holes.map(e=>e.points)
+            
+            outHoles = math.getPolygonsMixedRings(holes_,  true )
+            outHoles.forEach(e=>{ 
+                holesArea+=e.area
+            }) 
+            outHoles = outHoles.map(e=>e.points)
+            
             
+        }else{
+            outHoles = holes.map(e=>e.points)
+            outHoles.forEach(e=> holesArea += Math.abs(math.getArea(e)))                
         }
-        
-        
+        return holesArea
         
     }
     
-    removeMarker(index ){  
-        super.removeMarker(index) 
-        this.update(); 
-        if(this.points.length == 2){//清除原先length>=3时候的
-            this.box.geometry = new THREE.Geometry();
+    getArea(ifRidOfHoles){//面积
+        //不排除hole
+        return Math.abs(math.getArea(this.points)) - (ifRidOfHoles ? this.getHolesArea() : 0)
+    }
+    
+    getVolume(ifRidOfHoles){//体积
+        let {zMin , zMax} = this.getRealZ()
+        let height = zMax - zMin;
+        if(isNaN(height))height = 0
+        return this.getArea(ifRidOfHoles) * height
+    
+    }
+    
+    getRealZ(){//求真实高度时用到的
+        let zMin , zMax
+        if (this.buildType == 'building') {
+            //building的zMax和zMin一样的所以要算
+            let top = this.buildChildren[this.buildChildren.length - 1]
+            let btm = this.buildChildren[0] 
+            zMin = btm ? btm.zMin : 0  //建好的建筑不加楼的话是0
+            zMax = top ? top.zMax : 0
+        }else if(this.buildType == 'hole'){
+            return this.buildParent.getRealZ()
+        }else{
+            zMin = this.zMin,  zMax = this.zMax
         }
-        //this.dispatchEvent({type: 'marker_removed', measurement: this});
+        return {zMin,zMax}
     }
     
     
+    /* getDrawZ(){ //画线和box时用到的z
+        let zMin , zMax
+        if(this.buildType == 'hole'){
+             if(this.buildParent.buildType == 'building' && atFloor){ 
+                 zMin = atFloor.zMin,  zMax = atFloor.zMax 
+             }else{
+                 zMin = this.buildParent.zMin,  zMax = this.buildParent.zMax
+             }
+        }else{
+            zMin = this.zMin,  zMax = this.zMax
+        }
+        
+        return {zMin, zMax}
+        
+    } */
+    
+    
     
+    getBound(){
+        let bound = new THREE.Box3
+         
+        let {zMin , zMax} = this.getRealZ()
+        
+        let points = this.buildType == 'floor' ? this.buildParent.points : this.points
+        points.forEach(p=>{
+            bound.expandByPoint(p.clone().setZ(zMin))
+            bound.expandByPoint(p.clone().setZ(zMax))
+        }) 
+        return bound
+    }
     
+     
     getMarkerMaterial(type) {
         if(!markerMats){
             markerMats = {  
@@ -271,13 +693,21 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
                     map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
                     depthTest:false,
                 }),  
-                select:    new THREE.MeshBasicMaterial({   
+                hover:    new THREE.MeshBasicMaterial({   
                     transparent: !0,
                     color,
                     opacity: 1,
                     map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
                     depthTest:false,
                      
+                }),
+                select:    new THREE.MeshBasicMaterial({   
+                    transparent: !0,
+                    color:new THREE.Color('#00C8AF'),
+                    opacity: 1,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
+                    depthTest:false,
+                     
                 }),   
             }
             
@@ -289,63 +719,161 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
     
     setMarkerSelected(marker, state, hoverObject){ 
         //console.warn(marker.id , state, hoverObject)
-        if(state){
+         
+        
+        if(state == 'select'){
+            marker.selected = true
             marker.material = this.getMarkerMaterial('select')
+        }else if(state == 'unselect'){
+            marker.selected = false
+            marker.material = this.getMarkerMaterial('default')
         }else{
-            if(marker.name.includes('mid')){
-                marker.material = this.getMarkerMaterial('midPrepare')
-            }else{
-                marker.material = this.getMarkerMaterial('default')
-            } 
+            if(marker.selected)return //选中时不允许修改为除了'unselect'以外的状态
+            
+            if(state == 'hover'){
+                marker.material = this.getMarkerMaterial('hover')
+            }else if(state == 'unhover'){
+                if(marker.name.includes('mid')){
+                    marker.material = this.getMarkerMaterial('midPrepare')
+                }else{
+                    marker.material = this.getMarkerMaterial('default')
+                } 
+            }
         }
     }
     
     
     select(){
+        //最多再显示一层子级的线,如building不会显示room中的hole的线
+        //box是一直显示的,但会切换材质 
+        
+        /* 
+            选中                  box                                     线
+            
+            building            自己(底盘)选中                 自己, floor不带hole
+        
+            floor               自己选中                         自己, room不带hole
+            
+            room                自己选中                         自己   
+        
+         */  //注:自己的就代表定包括hole,如果有parentHoles的也(building上的hole的对应)
+        
+        
+        //console.log('select '+this.name,   this.selected)
+        
         if(this.selected)return
         
+        if(this.box){
+            this.box.material = this.mats.boxSelected;
+        }
         
-        if(this.type_ == 'building'){
+        if(this.buildType == 'building'|| this.buildType == 'floor'){
             this.buildChildren.forEach(e=>{
                 e.lineMesh.visible = true
             })
-            
-        }else if(this.type_ == 'floor'){
-            
-            this.box.material = faceMats.floorSelect
-            viewer.setObjectLayers(this.box, 'sceneObjects' )
-             
-        }else if(this.type_ == 'room'){
-            this.box.material = faceMats.roomSelect
+              
+            if(this.buildType == 'floor'){
+                viewer.setObjectLayers(this.box, 'bothMapAndScene' ) 
+                viewer.setObjectLayers(this.buildParent.box, 'siteModelMapUnvisi' ) //当选中floor或room时,building在地图不可见
+            }
+        }else if(this.buildType == 'room'){
+            viewer.setObjectLayers(this.buildParent.box, 'bothMapAndScene' )
+            viewer.setObjectLayers(this.buildParent.buildParent.box, 'siteModelMapUnvisi' )
         }
+        
+        
+        
+        
+        
         this.lineMesh.visible = true
         this.markers && this.markers.forEach(e=>viewer.updateVisible(e,'select',true) )
         this.midMarkers && this.midMarkers.forEach(e=>e.visible = true)
         
-        this.selected = true
+        let holes = this.holes.concat(this.parentHoles)
+        holes.forEach(e=>e.select()) 
         
+        this.selected = true
+        this.dispatchEvent({type:'select'})
     }
     
     
     unselect(){
+        if(!this.selected)return
+        //console.log('unselect '+this.name  )
+        if(this.box){
+            this.box.material = this.mats.boxDefault;
+        }
         
-        if(this.type_ == 'building'){ 
-            this.buildChildren.forEach(e=>{
+        if(this.buildType == 'building' || this.buildType == 'floor'){ 
+            this.buildChildren.forEach(e=>{  //(这里要保证选中前要先取消选中,否则如选中房间后取消了楼层,房间线就隐藏了)
                 e.lineMesh.visible = false
             })  
-        }else if(this.type_ == 'floor'){ 
-            viewer.setObjectLayers(this.box, 'siteModelMapUnvisi' )
-            this.box.material = faceMats.floor
-        }else if(this.type_ == 'room'){
-            this.box.material = faceMats.room
+            
+            if(this.buildType == 'floor'){
+                viewer.setObjectLayers(this.box, 'siteModelMapUnvisi' ) 
+                viewer.setObjectLayers(this.buildParent.box, 'bothMapAndScene' ) 
+            }
+            
+        }else if(this.buildType == 'room'){
+            viewer.setObjectLayers(this.buildParent.box, 'siteModelMapUnvisi' )
+            viewer.setObjectLayers(this.buildParent.buildParent.box, 'bothMapAndScene' )
         }
+        
         this.lineMesh.visible = false
         this.markers && this.markers.forEach(e=>viewer.updateVisible(e,'select',false) )
         this.midMarkers && this.midMarkers.forEach(e=>e.visible = false)
         
+        let holes = this.holes.concat(this.parentHoles)
+        holes.forEach(e=>e.unselect()) 
+        
         this.selected = false
+        this.dispatchEvent({type:'unselect'})
     }
     
+     
+    
     
+    ifContainsPoint(position){//看它所定义的空间是否包含某个坐标(要排除hole)
+    
+        let {zMin , zMax} = this.getRealZ()
+        if(position.z < zMin ||  position.z > zMax   ) return
+    
+        let holes = this.holes.concat(this.parentHoles)
+        let holesPoints = holes.filter(e=>e!=this && e.points.length>2).map(e=>e.points) 
+        let inShape = math.isPointInArea(this.points, holesPoints, position) 
+        
+         
+        return !!inShape 
+    }
      
-}
+}
+
+
+
+
+
+class MetricUVGenerator{
+    constructor(){
+        this.a = new THREE.Vector3,
+        this.b = new THREE.Vector3,
+        this.c = new THREE.Vector3,
+        this.d = new THREE.Vector3
+    }
+    generateTopUV(t, e, n, r, o) {
+        return [new THREE.Vector2(e[3 * n],e[3 * n + 1]), new THREE.Vector2(e[3 * r],e[3 * r + 1]), new THREE.Vector2(e[3 * o],e[3 * o + 1])]
+    }
+    
+    generateSideWallUV(t, e, n, r, o, a) {
+        var s = e;
+        this.a.set(s[3 * n], s[3 * n + 1], s[3 * n + 2]),
+        this.b.set(s[3 * r], s[3 * r + 1], s[3 * r + 2]),
+        this.c.set(s[3 * o], s[3 * o + 1], s[3 * o + 2]),
+        this.d.set(s[3 * a], s[3 * a + 1], s[3 * a + 2]);
+        var c = this.a.x !== this.b.x
+          , l = c ? this.b : this.d
+          , u = this.a.distanceTo(l)
+          , d = l.distanceTo(this.c);
+        return [new THREE.Vector2(this.a.x,0), c ? new THREE.Vector2(this.a.x + u,0) : new THREE.Vector2(this.a.x,d), new THREE.Vector2(this.a.x + u,d), c ? new THREE.Vector2(this.a.x,d) : new THREE.Vector2(this.a.x + u,0)]
+    }
+}
+ 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1001 - 294
src/modules/siteModel/SiteModel.js


+ 2 - 3
src/navigation/DeviceOrientationControls.js

@@ -14,10 +14,9 @@
  *
  */
 
-import * as THREE from "../../libs/three.js/build/three.module.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import * as THREE from "../../libs/three.js/build/three.module.js"; 
 
-export class DeviceOrientationControls extends EventDispatcher{
+export class DeviceOrientationControls extends THREE.EventDispatcher{
 	constructor(viewer){
 		super();
 

+ 23 - 24
src/navigation/EarthControls.js

@@ -1,10 +1,9 @@
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
-import {MOUSE} from "../defines.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Buttons} from "../defines.js";
+import {Utils} from "../utils.js"; 
 
-export class EarthControls extends EventDispatcher {
+export class EarthControls extends THREE.EventDispatcher {
 	constructor (viewer) {
 		super(viewer);
 
@@ -51,12 +50,12 @@ export class EarthControls extends EventDispatcher {
 			let view = this.viewer.scene.view;
 
 			// let camera = this.viewer.scene.camera;
-			let mouse = e.drag.end;
+			let Buttons = e.drag.end;
 			let domElement = this.viewer.renderer.domElement;
 
-			if (e.drag.mouse === MOUSE.LEFT) {
+			if (e.drag.Buttons === Buttons.LEFT) {
 
-				let ray = Utils.mouseToRay(this.viewer.inputHandler.pointer/* mouse */, camera, domElement.clientWidth, domElement.clientHeight);
+				let ray = Utils.ButtonsToRay(this.viewer.inputHandler.pointer/* Buttons */, camera, domElement.clientWidth, domElement.clientHeight);
 				let plane = new THREE.Plane().setFromNormalAndCoplanarPoint(
 					new THREE.Vector3(0, 0, 1),
 					this.pivot);
@@ -82,10 +81,10 @@ export class EarthControls extends EventDispatcher {
 						this.viewer.setMoveSpeed(speed);
 					}
 				}
-			} else if (e.drag.mouse === MOUSE.RIGHT) {
+			} else if (e.drag.Buttons === Buttons.RIGHT) {
 				let ndrag = {
-					x: e.drag.mouseDelta.x / this.renderer.domElement.clientWidth,
-					y: e.drag.mouseDelta.y / this.renderer.domElement.clientHeight
+					x: e.drag.ButtonsDelta.x / this.renderer.domElement.clientWidth,
+					y: e.drag.ButtonsDelta.y / this.renderer.domElement.clientHeight
 				};
 
 				let yawDelta = -ndrag.x * this.rotationSpeed * 0.5;
@@ -115,9 +114,9 @@ export class EarthControls extends EventDispatcher {
 			}
 		};
 
-		let onMouseDown = e => {
-			let I = Utils.getMousePointCloudIntersection(
-				e.mouse, 
+		let onButtonsDown = e => {
+			let I = Utils.getButtonsPointCloudIntersection(
+				e.Buttons, 
 				this.scene.getActiveCamera(), 
 				this.viewer, 
 				this.scene.pointclouds, 
@@ -135,7 +134,7 @@ export class EarthControls extends EventDispatcher {
 			this.dispatchEvent({type: 'end'});
 		};
 
-		let onMouseUp = e => {
+		let onButtonsUp = e => {
 			this.camStart = null;
 			this.pivot = null;
 			this.pivotIndicator.visible = false;
@@ -146,14 +145,14 @@ export class EarthControls extends EventDispatcher {
 		};
 
 		let dblclick = (e) => {
-			this.zoomToLocation(e.mouse);
+			this.zoomToLocation(e.Buttons);
 		};
 
 		this.addEventListener('drag', drag);
 		this.addEventListener('drop', drop);
-		this.addEventListener('mousewheel', scroll);
-		this.addEventListener('mousedown', onMouseDown);
-		this.addEventListener('mouseup', onMouseUp);
+		this.addEventListener('Buttonswheel', scroll);
+		this.addEventListener('Buttonsdown', onButtonsDown);
+		this.addEventListener('Buttonsup', onButtonsUp);
 		this.addEventListener('dblclick', dblclick);
 	}
 
@@ -166,11 +165,11 @@ export class EarthControls extends EventDispatcher {
 		this.zoomDelta.set(0, 0, 0);
 	}
 	
-	zoomToLocation(mouse){
+	zoomToLocation(Buttons){
 		let camera = this.scene.getActiveCamera();
 		
-		let I = Utils.getMousePointCloudIntersection(
-			mouse,
+		let I = Utils.getButtonsPointCloudIntersection(
+			Buttons,
 			camera,
 			this.viewer,
 			this.scene.pointclouds);
@@ -184,7 +183,7 @@ export class EarthControls extends EventDispatcher {
 			let minimumJumpDistance = 0.2;
 
 			let domElement = this.renderer.domElement;
-			let ray = Utils.mouseToRay(this.viewer.inputHandler.pointer, camera, domElement.clientWidth, domElement.clientHeight);
+			let ray = Utils.ButtonsToRay(this.viewer.inputHandler.pointer, camera, domElement.clientWidth, domElement.clientHeight);
 
 			let nodes = I.pointcloud.nodesOnRay(I.pointcloud.visibleNodes, ray);
 			let lastNode = nodes[nodes.length - 1];
@@ -237,8 +236,8 @@ export class EarthControls extends EventDispatcher {
 		
 		// compute zoom
 		if (this.wheelDelta !== 0) {
-			let I = Utils.getMousePointCloudIntersection(
-				this.viewer.inputHandler.mouse, 
+			let I = Utils.getButtonsPointCloudIntersection(
+				this.viewer.inputHandler.Buttons, 
 				this.scene.getActiveCamera(), 
 				this.viewer, 
 				this.scene.pointclouds);

+ 344 - 109
src/navigation/FirstPersonControls.js

@@ -14,15 +14,12 @@
  */
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
-import {MOUSE} from "../defines.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
-import cameraLight from "../utils/cameraLight.js";
-
-
+import {Buttons} from "../defines.js";
+import {Utils} from "../utils.js"; 
+import cameraLight from "../utils/cameraLight.js"; 
+import Common from "../utils/Common.js"; 
  
-
-export class FirstPersonControls extends EventDispatcher {
+export class FirstPersonControls extends THREE.EventDispatcher {
 	constructor (viewer, viewport) {
 		super();
         
@@ -59,14 +56,15 @@ export class FirstPersonControls extends EventDispatcher {
 
         };
 
-		this.fadeFactor = 50;
+		this.fadeFactor = 20;
 		this.yawDelta = 0;
 		this.pitchDelta = 0;
 		this.translationDelta = new THREE.Vector3(0, 0, 0);
 		this.translationWorldDelta = new THREE.Vector3(0, 0, 0);
 
 		this.tweens = [];
-
+        this.dollyStart = new THREE.Vector2
+        this.dollyEnd = new THREE.Vector2
         //this.enableChangePos = true
         
         this.viewer.addEventListener('camera_changed',(e)=>{
@@ -76,10 +74,23 @@ export class FirstPersonControls extends EventDispatcher {
 
 		let drag = (e) => {
             if(!this.enabled)return 
-            let viewport = e.drag.dragViewport;
+            let viewport = e.dragViewport;
             if(!viewport)return
             let camera = viewport.camera 
-            let mode = e.drag.mouse === MOUSE.LEFT && (!e.drag.dragViewport || e.drag.dragViewport.name == 'MainView') ? 'rotate' : 'pan'
+            let mode 
+            if(e.isTouch){
+                if(e.touches.length == 1){
+                    mode = (!e.dragViewport || e.dragViewport.name == 'MainView') ? 'rotate' : 'pan' 
+                }else if(e.touches.length == 2){
+                    mode = 'scale'
+                }else{
+                    mode = (!e.dragViewport || e.dragViewport.name == 'MainView') ? 'pan' : 'scale' 
+                } 
+            }else{
+                //mode = e.buttons === Buttons.LEFT && (!e.dragViewport || e.dragViewport.name == 'MainView') ? 'rotate' : 'pan'
+                mode = e.buttons === Buttons.LEFT && camera.type != 'OrthographicCamera' ? 'rotate' : 'pan'
+            }
+            //console.log('mode  ', mode )
             let moveSpeed = this.currentViewport.getMoveSpeed();
             if (e.drag.startHandled === undefined) {///???????
 				e.drag.startHandled = true;
@@ -87,58 +98,139 @@ export class FirstPersonControls extends EventDispatcher {
 				this.dispatchEvent({type: 'start'});
 			}
             
-			if (mode == 'rotate') {//旋转   (为什么开启调试时旋转很慢?)
+                
+            if (mode.includes('rotate')) {//旋转 
 				
                 //来自panoramaControl updateRotation
+                if(!this.pointerDragStart){
+                   return this.pointerDragStart = e.pointer.clone()
+                }
+                
                 
-                let _matrixWorld = camera.matrixWorld
-                camera.matrixWorld = new THREE.Matrix4;//unproject 前先把相机置于原点 
                 
-                var e1 = new THREE.Vector3(e.drag.pointerDragStart.x,e.drag.pointerDragStart.y,-1).unproject(camera)
-                  , t = new THREE.Vector3(e.drag.pointer.x,e.drag.pointer.y,-1).unproject(camera)
-                  , i = Math.sqrt(e1.x * e1.x + e1.z * e1.z)
-                  , n = Math.sqrt(t.x * t.x + t.z * t.z)
-                  , o = Math.atan2(e1.y, i)
-                  , a = Math.atan2(t.y, n);
+                let view = this.scene.view;
+                if(Potree.settings.rotAroundPoint && this.intersectStart && this.canMovePos(viewport) && !viewer.images360.isAtPano() && !this.viewer.inputHandler.pressedKeys[17]){//定点旋转:   以当前intersect的点为target旋转,不改点在屏幕中的位置
+                    let distance = camera.position.distanceTo(this.intersectStart.location)                                               //不按下ctrl的话                 
+                      
+                    //按照orbitControl的方式旋转:
+                    let rotationSpeed = 2.5;  
+                    
+                    this.yawDelta -= e.drag.pointerDelta.x * rotationSpeed;
+                    this.pitchDelta += e.drag.pointerDelta.y * rotationSpeed;
+
                    
-                this.pitchDelta +=  o - a  //上下旋转
-                e1.y = 0,
-                t.y = 0; 
+                    //先更新一下相机:    
+                    this.update() 
+                    view.applyToCamera(camera)
+                    
+                    //然后得到新的相机角度下,原先点在屏幕中的位置所对应的3d点现在的坐标。只需要平移一下新旧坐标差值即可。
+                    let newPointerDir = viewer.inputHandler.getMouseDirection(this.intersectStart.pointer).direction.clone().multiplyScalar(distance)
+                    let pivot = new THREE.Vector3().addVectors(camera.position, newPointerDir)  //新的3d点
+                     
+                    let moveVec = new THREE.Vector3().subVectors(pivot, this.intersectStart.location)
+                     
+                    this.translationWorldDelta.copy(moveVec.negate()) 
+                    //立即更新下,防止因update和此drag频率不同而打滑。
+                    this.update()
+                    view.applyToCamera(camera)
+                    
+               
+                }else{
+                  
+                    
+                    let _matrixWorld = camera.matrixWorld
+                    camera.matrixWorld = new THREE.Matrix4;//unproject 前先把相机置于原点 
+                    
+                    var e1 = new THREE.Vector3(this.pointerDragStart.x,this.pointerDragStart.y,-1).unproject(camera)
+                      , t = new THREE.Vector3(e.pointer.x,e.pointer.y,-1).unproject(camera)
+                      , i = Math.sqrt(e1.x * e1.x + e1.z * e1.z)
+                      , n = Math.sqrt(t.x * t.x + t.z * t.z)
+                      , o = Math.atan2(e1.y, i)
+                      , a = Math.atan2(t.y, n);
+                       
+                    this.pitchDelta +=  o - a  //上下旋转
+                    e1.y = 0,
+                    t.y = 0; 
+                    
+                    var s = Math.acos(e1.dot(t) / e1.length() / t.length());
+                    
+                    if(!isNaN(s)){
+                        var yawDelta = s    //左右旋转 
+                        this.pointerDragStart.x > e.pointer.x && (yawDelta *= -1) 
+                        this.yawDelta += yawDelta
+                    } 
+                    
+                    
+                    //console.log('rotate:', this.pitchDelta, e.pointer.toArray(), this.pointerDragStart.toArray())
+                    
+                    
+                    this.pointerDragStart.copy(e.pointer)
+                     
+                    camera.matrixWorld = _matrixWorld ;
+                    
                 
-                var s = Math.acos(e1.dot(t) / e1.length() / t.length());
                 
-                if(!isNaN(s)){
-                    var yawDelta = s    //左右旋转 
-                    e.drag.pointerDragStart.x > e.drag.pointer.x && (yawDelta *= -1) 
-                    this.yawDelta += yawDelta
                 } 
-                e.drag.pointerDragStart.copy(e.drag.pointer)
-                 
-                camera.matrixWorld = _matrixWorld ;
-              
-			} else if (mode == 'pan') {//平移 
-                if(viewport.unableChangePos)return
+			} 
+            
+            if (mode.includes('pan')) {//平移 
+                if(!this.canMovePos(viewport)){
+                    return
+                } 
+                
                 if(camera.type == "OrthographicCamera"){
                    
-                    
-                    /* let ViewWidthPX = viewport.width * viewer.renderer.domElement.clientWidth
-                    let ViewHeightPX = viewport.height * viewer.renderer.domElement.clientHeight
-                    let cameraViewWidth = camera.right * 2
-                    let cameraViewHeight = camera.top * 2; 
-                     
-                    moveVec.set(-1 * e.drag.mouseDelta.x * cameraViewWidth / ViewWidthPX, e.drag.mouseDelta.y * cameraViewHeight / ViewHeightPX , 0).applyQuaternion(camera.quaternion)  
-                     */
-                    
+                    //console.log(e.drag.pointerDelta, e.pointer, e.drag.end)
                     let moveVec = Utils.getOrthoCameraMoveVec(e.drag.pointerDelta, camera )//最近一次移动向量
+                  
+                    let pointclouds;
+                    let Alignment = window.viewer.modules.Alignment
+                    let MergeEditor = window.viewer.modules.MergeEditor
+                    let handleState = Alignment.handleState
+                    
+                    let a = e.buttons === Buttons.LEFT && viewport.alignment && handleState && viewport.alignment[handleState] 
+                    if(Potree.settings.editType == 'pano'){//右键平移视图、左键操作点云 
+                        let PanoEditor = window.viewer.modules.PanoEditor
+                             
+                        if(a && PanoEditor.selectedPano){
+                            if(!PanoEditor.selectedGroup || !PanoEditor.checkIfAllLinked({group:PanoEditor.selectedGroup}) ){
+                                if(handleState == 'translate' && ( e.drag.intersectStart.pointclouds && Common.getMixedSet(PanoEditor.selectedClouds, e.drag.intersectStart.pointclouds).length  || PanoEditor.selectedPano.hovered)//拖拽到点云上 或 circle
+                                    || handleState == 'rotate' ) 
+                                {  
+                                    pointclouds = PanoEditor.selectedClouds
+                                }  
+                            }else{
+                                PanoEditor.dispatchEvent('needToDisConnect')
+                                console.warn('选中的漫游点连通了整个数据集,不允许移动')
+                            } 
+                        }
+                        
+                        if(!pointclouds && e.buttons === Buttons.LEFT && viewport.rotateSide){
+                            return PanoEditor.rotateSideCamera(-e.drag.pointerDelta.x)
+                        }
+                    }else if(Potree.settings.editType == 'merge'){ 
+                        if(e.buttons === Buttons.LEFT && viewport.rotateSide){ 
+                            return MergeEditor.rotateSideCamera(-e.drag.pointerDelta.x)
+                        }  
                     
-                    let handleState = window.viewer.modules.Alignment.handleState
-                    if(viewport.alignment && handleState && viewport.alignment[handleState] && e.drag.intersectStart.pointcloud){
+                    }else{ 
+                        /* if(Alignment.selectedClouds && Alignment.selectedClouds.length){
+                            pointclouds = a && e.drag.intersectStart.pointclouds && Common.getMixedSet(Alignment.selectedClouds, e.drag.intersectStart.pointclouds).length && Alignment.selectedClouds
+                            
+                        }else{ */
+                            pointclouds = a && e.drag.intersectStart.pointcloud && [e.drag.intersectStart.pointcloud]
+                        //}
+                        
+                    }
+                      
+                    if(pointclouds){
                         this.dispatchEvent({
                             type : "transformPointcloud", 
-                            intersectPoint: e.intersectPoint.orthoIntersect,   
+                            intersect: e.intersect.orthoIntersect,   
                             intersectStart: e.drag.intersectStart.orthoIntersect,
                             moveVec,        
-                            pointcloud: e.drag.intersectStart.pointcloud,
+                            pointclouds, 
+                            camera
                         }) 
                     }else{ 
                         
@@ -150,32 +242,41 @@ export class FirstPersonControls extends EventDispatcher {
                     
                 }else{  
                     if(e.drag.intersectStart){//如果拖拽着点云 
-                         
+                        
                         if(e.drag.z == void 0){//拖拽开始
                             let pointerStartPos2d = e.drag.intersectStart.location.clone().project(camera);//识别到的点云点的位置
                             e.drag.z = pointerStartPos2d.z //记录z,保持拖拽物体到屏幕距离不变,所以z深度不变
                             e.drag.projectionMatrixInverse = camera.projectionMatrixInverse.clone()
                             //防止吸附到最近点上(因为鼠标所在位置并非识别到的点云点的位置,需要得到鼠标所在位置的3d坐标。)
-                            let pointerStartPos2dReal = new THREE.Vector3(e.drag.pointerDragStart.x,e.drag.pointerDragStart.y, e.drag.z);
+                            let pointerStartPos2dReal = new THREE.Vector3(this.pointerDragStart.x,this.pointerDragStart.y, e.drag.z);
                             e.drag.translateStartPos = pointerStartPos2dReal.clone().unproject(camera);
                             /* this.viewer.dispatchEvent({ 
                                 type: 'dragPanBegin', 
                                 projectionMatrixInverse : e.drag.projectionMatrixInverse
                             }); */
+                            //console.log('开始拖拽', e.pointer.clone())
                         }  
                         //拖拽的过程中将projectionMatrixInverse替换成开始拖拽时的,因为near、far一直在变,会导致unproject计算出的3d坐标改变很大而闪烁。
                         var _projectionMatrixInverse = camera.projectionMatrixInverse;
                         camera.projectionMatrixInverse = e.drag.projectionMatrixInverse;
                         
                         
-                        let newPos2d = new THREE.Vector3(e.drag.pointer.x,e.drag.pointer.y,   e.drag.z );
+                        let newPos2d = new THREE.Vector3(e.pointer.x,e.pointer.y,   e.drag.z );
                         let newPos3d = newPos2d.clone().unproject(camera);
                         let moveVec = newPos3d.clone().sub( e.drag.translateStartPos  /*  e.drag.intersectStart.location  */ );//移动相机,保持鼠标下的位置永远不变,所以用鼠标下的新位置减去鼠标下的原始位置
                         
                       
                         camera.projectionMatrixInverse = _projectionMatrixInverse
                         this.translationWorldDelta.copy(moveVec.negate())  //这里没法用add,原因未知,会跳动
-                          
+                        //console.log('pan 1', this.translationWorldDelta.clone())   
+                        
+                         
+                        
+                        //四指松开剩三指时会偏移一下,暂不知道哪里的问题,或许跟开头防止点云吸附有关?
+                        
+                        
+                        
+                        
                     }else{ //如果鼠标没有找到和点云的交点,就假设移动整个模型(也可以去扩大范围寻找最近点云)
                          
                         /* let center = viewer.scene.pointclouds[0].position;
@@ -190,63 +291,123 @@ export class FirstPersonControls extends EventDispatcher {
                         }  */
                         let lastIntersect = viewport.lastIntersect ? (viewport.lastIntersect.location || viewport.lastIntersect) : viewer.bound.center  //该viewport的最近一次鼠标和点云的交点
                         let speed = camera.position.distanceTo(lastIntersect)   
-                        let fov = cameraLight.getHFOVForCamera(camera, camera.aspect, 1)
-                        let ratio = speed  * Math.tan(THREE.Math.degToRad(fov)/2) 
+                        let fov = cameraLight.getHFOVForCamera(camera, true)
+                        let ratio = speed  * Math.tan(fov/2) 
                         this.translationDelta.x -= e.drag.pointerDelta.x  * ratio
                         this.translationDelta.z -= e.drag.pointerDelta.y  * ratio 
-                         
-                         
-                    
+                         //console.log('pan2', e.drag.pointerDelta)
                     }
                 } 
-                
+                this.useAttenuation = false
 			}
+            
+            
+            if(mode.includes('scale')){
+ 
+                this.dollyEnd.subVectors(e.touches[0].pointer, e.touches[1].pointer);
+                //if(!this.dollyStart)return
+                var scale = this.dollyEnd.length() / this.dollyStart.length()
+
+                //console.log('scale ',scale)
+                 
+                let pointer = new THREE.Vector2().addVectors(e.touches[0].pointer, e.touches[1].pointer).multiplyScalar(0.5);//两个指头的中心点
+                
+                dolly({
+                    pointer,
+                    scale, camera
+                })
+                this.dollyStart.copy(this.dollyEnd);
+                
+            }
             //最好按ctrl可以变为dollhouse的那种旋转
 		};
 
 		let drop = e => {
             if(!this.enabled)return    
 			this.dispatchEvent({type: 'end'});
+            
 		};
 
-		let scroll = (e) => {
-            if(!this.enabled)return   
-            this.setCurrentViewport(e)            
-            if(this.currentViewport.unableChangePos)return 
-            let camera = e.hoverViewport.camera
-            let speed = this.currentViewport.getMoveSpeed() || 1 
+
+        let dolly = (e={})=>{
+                       
+            if(Potree.settings.displayMode == 'showPanos' && this.currentViewport == viewer.mainViewport/* this.currentViewport.unableChangePos */){//全景时 
+                this.dispatchEvent({type:'dollyStopCauseUnable',delta:e.delta, scale:e.scale})
+                return 
+            }
+            
+            let camera = e.camera
+            
+            
             if(camera.type == "OrthographicCamera"){
                 let ratio
-                if(e.delta == 0){//mac
-                   return 
-                }else if (e.delta < 0) {
-                    ratio = 0.9 
-                } else if (e.delta > 0) {
-                    ratio = 1.1
-                } 
+                if(e.delta != void 0){//滚轮缩放
+                    if(e.delta == 0){//mac
+                       return 
+                    }else if (e.delta < 0) {
+                        ratio = 0.9 
+                    } else if (e.delta > 0) {
+                        ratio = 1.1
+                    } 
+                }else{
+                    ratio = e.scale //触屏缩放
+                }
+                
                 let zoom = camera.zoom * ratio
-                let limit = Potree.config.OrthoCameraLimit.zoom
-                zoom = THREE.Math.clamp(zoom, limit.min,limit.max )
+                let limit = camera.zoomLimit
+                if(limit) zoom = THREE.Math.clamp(zoom, limit.min,limit.max )
+                
+                
+                let pointerPos = new THREE.Vector3(e.pointer.x, e.pointer.y,0.5); 
+                let oldPos = pointerPos.clone().unproject(camera);
                 
                 if(camera.zoom != zoom){ 
                     camera.zoom = zoom
                     camera.updateProjectionMatrix()
                 }
+                let newPos = pointerPos.clone().unproject(camera);
                 
-               
-                
+                //定点缩放, 恢复一下鼠标所在位置的位置改变量
+                let moveVec = new THREE.Vector3().subVectors(newPos,oldPos)
+                this.translationWorldDelta.add(moveVec.negate()) 
+                this.useAttenuation = false
             }else{
-                var direction = this.currentViewport.view.direction.clone();
-            
-                var vec = direction.multiplyScalar(speed * 7)
-                if (e.delta < 0) {
-                    this.translationWorldDelta.copy(vec.negate())
-                } else if (e.delta > 0) {
-                    this.translationWorldDelta.copy(vec)
+                let speed , direction
+                 
+                
+                if(e.delta != void 0){//滚轮缩放 
+                    speed =  this.currentViewport.getMoveSpeed() * 15
+                    
+                    //var direction = this.currentViewport.view.direction.clone();
+                    direction = this.viewer.inputHandler.getMouseDirection().direction  //定点缩放
+                 
+                    
+                    if(e.delta == 0){//mac
+                        return 
+                    }else if (e.delta < 0) {
+                        speed *= -1
+                    } 
+                }else{
+                    const constantDis =  this.currentViewport.getMoveSpeed() * 200 //constantDis = 10;//常量系数,当放大一倍时前进的距离。可以调整
+                    speed = (e.scale-1)*constantDis //触屏缩放
+                    //pointer = new THREE.Vector2().addVectors().multiplyScalar(0.5);//两个指头的中心点
+                    direction = this.viewer.inputHandler.getMouseDirection(e.pointer).direction  //定点缩放
                 }
+                 
+                this.useAttenuation = true
+                var vec = direction.multiplyScalar(speed )
+                this.translationWorldDelta.copy(vec)
+                
             }
+        }
+
+		let scroll = (e) => {
+            if(!this.enabled || !e.hoverViewport)return 
+            this.setCurrentViewport(e)
             
-            
+                
+            e.camera = e.hoverViewport.camera                
+            dolly(e) 
 		};
 
 		let dblclick = (e) => { 
@@ -258,11 +419,69 @@ export class FirstPersonControls extends EventDispatcher {
 		};
 
 		this.viewer.addEventListener('global_drag', drag);
+        /* this.viewer.addEventListener('global_touchmove', (e)=>{ 
+            if(!this.enabled)return
+            if(e.touches.length>1){//单指的就触发上一句 
+                //console.log('global_touchmove' )
+                drag(e)
+            }
+        }); */
 		this.viewer.addEventListener('global_drop', drop);
 		this.viewer.addEventListener('global_mousewheel', scroll);
 		this.viewer.addEventListener('global_dblclick', dblclick);
         
-        this.viewer.addEventListener('startDragging', this.setCurrentViewport.bind(this))
+        
+        
+        let prepareScale = (e)=>{//触屏的scale 
+            this.dollyStart.subVectors(e.touches[0].pointer, e.touches[1].pointer);
+        }
+        let prepareRotate = (e)=>{ 
+            this.pointerDragStart = e.pointer.clone()  
+            this.intersectStart = e.intersect && e.intersect.location && {
+                location : e.intersect.location,
+                pointer :  e.intersect.location.clone().project(e.dragViewport.camera) //intersect点在屏幕中的位置
+            }
+            //console.log('prepareRotate' )
+        }
+        let preparePan = (e)=>{//触屏的pan点云    还是会偏移
+            this.pointerDragStart = e.pointer.clone() 
+              
+            e.drag.z = void 0  //清空    
+            drag(e) //触屏点击时更新的pointer直接用一次drag
+            //console.log('preparePan '   )
+        }
+        
+        this.viewer.addEventListener('global_mousedown'/* 'startDragging' */, (e)=>{
+            if(!this.enabled)return
+            this.setCurrentViewport(e)
+            prepareRotate(e) 
+        })
+        
+        
+        //注意,每次增减指头都会修改pointer,需要更新下状态
+        this.viewer.addEventListener('global_touchstart', (e)=>{
+            if(!this.enabled)return
+
+            if(e.touches.length==2){//只监听开头两个指头
+                prepareScale(e)
+            }else if(e.touches.length>=3){
+                preparePan(e)
+            }
+        })
+        this.viewer.addEventListener('global_touchend', (e)=>{
+            if(!this.enabled)return
+            if(e.touches.length==2){//停止平移,开始scale
+                prepareScale(e)
+            }else if(e.touches.length==1){//停止scale,开始rotate
+                prepareRotate(e)
+            }else if(e.touches.length>=3){//重新准备下平移(因为抬起的指头可能包含平移使用的数据),否则抬起时漂移
+                preparePan(e)
+            }
+        })
+       
+        
+        
+        
         /* this.viewer.addEventListener('enableChangePos', (e)=>{
             if(!this.enabled)return
             this.enableChangePos = e.canLeavePano 
@@ -271,7 +490,11 @@ export class FirstPersonControls extends EventDispatcher {
 
        
 	}
-    
+    canMovePos(viewport){
+        if(viewport == viewer.mainViewport && (Potree.settings.displayMode == 'showPanos' 
+        || viewer.images360.bumping || viewer.images360.latestToPano))return false
+        else return true
+    }
     setEnable(enabled){
         this.enabled = enabled;
          
@@ -299,23 +522,19 @@ export class FirstPersonControls extends EventDispatcher {
 
 
     setCurrentViewport(o={}){//add
-        if(!this.enabled && !o.force)return
+        if(!this.enabled && !o.force )return
         if(o.hoverViewport && this.currentViewport != o.hoverViewport ){
-            this.currentViewport = o.hoverViewport 
-            
-            if(this.currentViewport.camera.type == 'OrthographicCamera'){
-                this.lockElevationOri = true
-                this.lockRotation = true
-            }else{
-                this.lockElevationOri = false
-                this.lockRotation = false
-            }
-     
-     
-     
+            this.currentViewport = o.hoverViewport  
 			//this.viewer.setMoveSpeed(this.currentViewport.radius/100);
             this.setFPCMoveSpeed(this.currentViewport)
         }
+        if(this.currentViewport.camera.type == 'OrthographicCamera'){
+            this.lockElevationOri = true
+            this.lockRotation = true
+        }else{
+            this.lockElevationOri = false
+            this.lockRotation = false
+        }
     }
 
      
@@ -343,7 +562,7 @@ export class FirstPersonControls extends EventDispatcher {
 			camera,
 			this.viewer,
 			this.scene.pointclouds); */
-        var I = this.viewer.inputHandler.intersectPoint
+        var I = this.viewer.inputHandler.intersect
 		if (!I) {
 			return;
 		}
@@ -406,10 +625,10 @@ export class FirstPersonControls extends EventDispatcher {
 		}
 	}
 
-	update (delta) {
+	update (delta=1) {
         if(!this.enabled)return
  
-        
+        //console.log('update')
 		let view = this.currentViewport.view  
 
 		{ // cancel move animations on user input
@@ -457,7 +676,7 @@ export class FirstPersonControls extends EventDispatcher {
                 }
             }
         
-            if(!this.currentViewport.unableChangePos){
+            if(this.canMovePos(this.currentViewport) && !this.lockKey){
                 if(this.lockElevation){
                     let dir = view.direction;
                     dir.z = 0;
@@ -495,19 +714,27 @@ export class FirstPersonControls extends EventDispatcher {
                 } else if (moveDown) {
                     this.translationWorldDelta.z = -this.currentViewport.getMoveSpeed();
                 }
+                
+                
+                if(moveUp || moveDown || moveForward || moveBackward){
+                    this.useAttenuation = false
+                }
+                
             } 
 		}
 
 		{ // apply rotation
 			let yaw = view.yaw;
 			let pitch = view.pitch;
-
+             
+            
 			yaw += this.yawDelta /* * delta; */
 			pitch += this.pitchDelta/*  * delta; */
 
 			view.yaw = yaw;
 			view.pitch = pitch;
             
+
             
             this.yawDelta = 0
             this.pitchDelta = 0 
@@ -532,7 +759,10 @@ export class FirstPersonControls extends EventDispatcher {
 				this.translationWorldDelta.y /* * delta */,
 				this.translationWorldDelta.z /* * delta */
 			);
-            this.translationWorldDelta.set(0,0,0)
+             
+            
+            
+            //this.translationWorldDelta.set(0,0,0)
 		}
 
 		{ // set view target according to speed
@@ -547,12 +777,17 @@ export class FirstPersonControls extends EventDispatcher {
 		}
  
 
-        { // decelerate over time
+        if(this.useAttenuation){ //只有滚轮缩放时开启
 			let attenuation = Math.max(0, 1 - this.fadeFactor * delta);
-			/* this.yawDelta *= attenuation;
+          
+			 /*this.yawDelta *= attenuation;
 			this.pitchDelta *= attenuation; 
-			this.translationDelta.multiplyScalar(attenuation);
-			this.translationWorldDelta.multiplyScalar(attenuation);*/
-		}   
+			this.translationDelta.multiplyScalar(attenuation);*/
+			this.translationWorldDelta.multiplyScalar(attenuation);
+		}else{
+            
+            this.translationWorldDelta.set(0,0,0)
+            
+        }  
 	}
 };

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 846 - 447
src/navigation/InputHandler.js


+ 220 - 36
src/navigation/OrbitControls.js

@@ -14,12 +14,13 @@
  */
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
-import {MOUSE} from "../defines.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Buttons} from "../defines.js";
+import {Utils} from "../utils.js"; 
+
+let minRadius = 2
 
  
-export class OrbitControls extends EventDispatcher{
+export class OrbitControls extends THREE.EventDispatcher{
 	
 	constructor(viewer){
 		super();
@@ -30,9 +31,11 @@ export class OrbitControls extends EventDispatcher{
 		this.scene = null;
 		this.sceneControls = new THREE.Scene();
 
-		this.rotationSpeed = 5;
+		this.rotationSpeed = 3;  //旋转速度
+        
+         
 
-		this.fadeFactor = 20;
+		this.fadeFactor = 100;
 		this.yawDelta = 0;
 		this.pitchDelta = 0;
 		this.panDelta = new THREE.Vector2(0, 0);
@@ -41,39 +44,103 @@ export class OrbitControls extends EventDispatcher{
 		this.doubleClockZoomEnabled = true;
 
 		this.tweens = [];
-
+        this.dollyStart = new THREE.Vector2
+        this.dollyEnd = new THREE.Vector2
+        
+        
+        this.keys = {
+            FORWARD: ['W'.charCodeAt(0), 38],
+            BACKWARD: ['S'.charCodeAt(0), 40],
+            LEFT: ['A'.charCodeAt(0), 37],
+            RIGHT: ['D'.charCodeAt(0), 39],
+            UP: ['Q'.charCodeAt(0)],
+            DOWN: ['E'.charCodeAt(0)], 
+        };
+        
+        
 		let drag = (e) => {
             if(!this.enabled)return
+ 
+            let viewport = e.dragViewport;
+            if(!viewport || viewport.camera.type == "OrthographicCamera" )return
+            //let camera = viewport.camera 
+          
+
+
 			if (e.drag.object !== null) {
 				return;
 			}
+            let mode
+            
+            if(e.isTouch){
+               
+                if(e.touches.length == 1){
+                    mode = 'rotate'  
+                }else{  
+                    mode = 'scale-pan'
+                }  
+            }else{
+                mode = e.buttons === Buttons.LEFT ? 'rotate' : 'pan'
+            }
+
 
-			if (e.drag.startHandled === undefined) {
-				e.drag.startHandled = true;
 
+
+            
+			if (e.drag.startHandled === undefined) {
+				e.drag.startHandled = true; 
 				this.dispatchEvent({type: 'start'});
 			}
 
-			/* let ndrag = {
-				x: e.drag.mouseDelta.x / this.renderer.domElement.clientWidth,
-				y: e.drag.mouseDelta.y / this.renderer.domElement.clientHeight
-			}; */
+			 
             let ndrag = e.drag.pointerDelta.clone()//.add(new THREE.Vector2(1,1)).multiplyScalar(0.5)
             ndrag.y *= -1
 
-			if (e.drag.mouse === MOUSE.LEFT) {
+			if (mode == 'rotate') { 
+                 
 				this.yawDelta += ndrag.x * this.rotationSpeed;
 				this.pitchDelta += ndrag.y * this.rotationSpeed;
 
-				this.stopTweens();
-			} else if (e.drag.mouse === MOUSE.RIGHT) {
+				
+			} else if(mode == 'pan'){
+               
 				this.panDelta.x += ndrag.x;
 				this.panDelta.y += ndrag.y;
 
-				this.stopTweens();
-			}
+				 
+			}else if(mode == 'scale-pan'){ //add
+                this.dollyEnd.subVectors(e.touches[0].pointer, e.touches[1].pointer); 
+                var scale = this.dollyEnd.length() / this.dollyStart.length() 
+                  
+                this.dollyStart.copy(this.dollyEnd); 
+                this.radiusDelta = (1-scale) * this.scene.view.radius 
+			  
+                //------------------------
+                //平移
+                let pointer = new THREE.Vector2().addVectors(e.touches[0].pointer, e.touches[1].pointer).multiplyScalar(0.5);//两个指头的中心点
+                 
+                let delta = new THREE.Vector2().subVectors(pointer, this.lastScalePointer)
+                delta.y *= -1
+                this.panDelta.add(delta)
+                
+                this.lastScalePointer = pointer.clone()
+                
+                
+                
+                //console.log('scale ',scale, this.radiusDelta)
+                
+            }
+            
+            this.stopTweens();
+            
+            
 		};
-
+        
+        
+        
+         
+        
+        
 		let drop = e => {
             if(!this.enabled)return
 			this.dispatchEvent({type: 'end'});
@@ -82,9 +149,9 @@ export class OrbitControls extends EventDispatcher{
 		let scroll = (e) => {
             if(!this.enabled)return
 			let resolvedRadius = this.scene.view.radius + this.radiusDelta;
-
+            if(resolvedRadius < 0.1 && e.delta>0)return; //防止缩放太小,导致很慢
 			this.radiusDelta += -e.delta * resolvedRadius * 0.1;
-
+            
 			this.stopTweens();
 		};
 
@@ -155,6 +222,40 @@ export class OrbitControls extends EventDispatcher{
 		this.viewer.addEventListener('global_drop', drop);
 		this.viewer.addEventListener('global_mousewheel', scroll);
 		this.viewer.addEventListener('global_dblclick', dblclick);
+        this.viewer.addEventListener('global_touchmove', (e)=>{ 
+            if(e.touches.length>1){//单指的就触发上一句 
+                //console.log('global_touchmove' )
+                drag(e)
+            }
+        });
+        let prepareScale = (e)=>{//触屏的scale
+            this.dollyStart.subVectors(e.touches[0].pointer, e.touches[1].pointer);
+            this.lastScalePointer = new THREE.Vector2().addVectors(e.touches[0].pointer, e.touches[1].pointer).multiplyScalar(0.5);//两个指头的中心点
+              
+        }
+         
+        this.viewer.addEventListener('global_touchstart', (e)=>{
+            if(this.enabled && e.touches.length==2){//只监听开头两个指头
+                prepareScale(e)
+            }
+        })
+        /* this.viewer.addEventListener('global_touchend', (e)=>{
+            if(!this.enabled)return
+            if(e.touches.length==1){//停止scale,开始rotate
+                prepareRotate(e)
+                //this.pointerDragStart = null
+                //console.log('只剩一个', e.pointer.toArray())
+            }
+        }) */
+        
+        
+        this.viewer.addEventListener('focusOnObject',(o)=>{
+            if(o.position && o.CamTarget){
+                let distance = o.position.distanceTo(o.CamTarget)
+                if(distance < minRadius) minRadius = distance * 0.5 //融合页面当focus一个很小的物体时,需要将minRadius也调小
+            }
+        })
+        
 	}
 
 	setScene (scene) {
@@ -170,7 +271,7 @@ export class OrbitControls extends EventDispatcher{
 		this.panDelta.set(0, 0);
 	}
 	
-	zoomToLocation(mouse){
+	/* zoomToLocation(mouse){
         if(!this.enabled)return
 		let camera = this.scene.getActiveCamera();
 		
@@ -180,17 +281,17 @@ export class OrbitControls extends EventDispatcher{
 			this.viewer,
 			this.scene.pointclouds,
 			{pickClipped: true});
-
+         
 		if (I === null) {
 			return;
-		}
+		} 
 
 		let targetRadius = 0;
 		{
 			let minimumJumpDistance = 0.2;
 
 			let domElement = this.renderer.domElement;
-			let ray = Utils.mouseToRay(this.viewer.inputHandler.pointer/* mouse */, camera, domElement.clientWidth, domElement.clientHeight);
+			let ray = Utils.mouseToRay(this.viewer.inputHandler.pointer , camera, domElement.clientWidth, domElement.clientHeight);
 
 			let nodes = I.pointcloud.nodesOnRay(I.pointcloud.visibleNodes, ray);
 			let lastNode = nodes[nodes.length - 1];
@@ -233,7 +334,37 @@ export class OrbitControls extends EventDispatcher{
 
 			tween.start();
 		}
-	}
+	} */
+
+
+
+
+    
+    zoomToLocation(mouse){
+        let I = viewer.inputHandler.intersect;
+        
+        if(!I)return
+        
+        let object = I.object || I.pointcloud;
+        I = I.location
+        
+        
+        if(!I || !object)return;
+        
+        let dis = this.scene.view.position.distanceTo(I); 
+        
+        
+        let bound = object.boundingBox.clone().applyMatrix4(object.matrixWorld)
+        let size = bound.getSize(new THREE.Vector3); 
+        let len = size.length()
+         
+        let distance = THREE.Math.clamp(dis, 0.1, Math.max(len * 0.1, 3) );
+        
+        minRadius = distance
+        viewer.focusOnObject({ position:I }, 'point', null, {distance})
+        
+    }
+
 
 	stopTweens () {
 		this.tweens.forEach(e => e.stop());
@@ -244,6 +375,46 @@ export class OrbitControls extends EventDispatcher{
         if(!this.enabled)return
 		let view = this.scene.view;
 
+
+
+
+        { // accelerate while input is given
+			let ih = this.viewer.inputHandler;
+
+			let moveForward = this.keys.FORWARD.some(e => ih.pressedKeys[e]);
+			let moveBackward = this.keys.BACKWARD.some(e => ih.pressedKeys[e]);
+			let moveLeft = this.keys.LEFT.some(e => ih.pressedKeys[e]);
+			let moveRight = this.keys.RIGHT.some(e => ih.pressedKeys[e]);
+			let moveUp = this.keys.UP.some(e => ih.pressedKeys[e]);
+			let moveDown = this.keys.DOWN.some(e => ih.pressedKeys[e]);
+            
+             
+              
+            let px = 0 , py = 0, pz = 0
+            if(moveForward){
+                py = 1
+            }else if(moveBackward){
+                py = -1
+            }
+            
+            if(moveLeft){
+                px = -1
+            }else if(moveRight){
+                px = 1
+            }
+            if(moveUp){
+                pz = 1
+            }else if(moveDown){
+                pz = -1
+            }
+            
+            (px!=0 || py!=0 || pz!=0) && view.translate(px, py, pz, true); 
+             
+        }
+                
+ 
+
+
 		{ // apply rotation
 			let progression = Math.min(1, this.fadeFactor * delta);
 
@@ -264,9 +435,12 @@ export class OrbitControls extends EventDispatcher{
 		}
 
 		{ // apply pan
-			let progression = Math.min(1, this.fadeFactor * delta);
-			let panDistance = progression * view.radius * 3;
-
+			/* let progression = Math.min(1, this.fadeFactor * delta);
+			let panDistance = progression * view.radius * 3; */
+            let camera = this.scene.getActiveCamera()
+            let panDistance = 2 * view.radius * Math.tan(THREE.Math.degToRad(camera.fov / 2));//参照4dkk。 平移target(也就是平移镜头位置),但还是难以保证跟手(navvis也不一定跟手,但是很奇怪在居中时中心点居然是跟手的,可能计算方式不同)
+            //计算了下确实是这么算的。 平移pivot。 
+            
 			let px = -this.panDelta.x * panDistance;
 			let py = this.panDelta.y * panDistance;
 
@@ -274,15 +448,22 @@ export class OrbitControls extends EventDispatcher{
 		}
 
 		{ // apply zoom
-			let progression = Math.min(1, this.fadeFactor * delta);
-
+			let progression = 1//Math.min(1, this.fadeFactor * delta);
+             
+            
+            
 			// let radius = view.radius + progression * this.radiusDelta * view.radius * 0.1;
 			let radius = view.radius + progression * this.radiusDelta;
-
+                      
 			let V = view.direction.multiplyScalar(-radius);
 			let position = new THREE.Vector3().addVectors(view.getPivot(), V);
-			view.radius = radius;
-
+			
+            if(this.constantlyForward) {// 到达中心点后还能继续向前移动,也就是能推进中心点
+                if(radius < minRadius){  
+                    radius = minRadius
+                } 
+            }
+            view.radius = radius;            
 			view.position.copy(position);
 		}
 
@@ -292,14 +473,17 @@ export class OrbitControls extends EventDispatcher{
 		}
 
 		{ // decelerate over time
-			let progression = Math.min(1, this.fadeFactor * delta);
+			/* let progression = Math.min(1, this.fadeFactor * delta);
 			let attenuation = Math.max(0, 1 - this.fadeFactor * delta);
 
 			this.yawDelta *= attenuation;
 			this.pitchDelta *= attenuation;
 			this.panDelta.multiplyScalar(attenuation);
 			// this.radiusDelta *= attenuation;
-			this.radiusDelta -= progression * this.radiusDelta;
+			this.radiusDelta -= progression * this.radiusDelta; */
+            
+            //取消衰减,直接stop
+            this.stop()  
 		}
 	}
 };

+ 0 - 628
src/navigation/PanoramaControls.js

@@ -1,628 +0,0 @@
-import math from '../util/math'
-import MouseButton from '../enum/MouseButton'
-import ControlEvents from '../enum/eventList/ControlEvents'
-import common from '../util/common'
-import Keys from '../enum/Keys'
-import config from '../../config';
-import transitions from '../util/transitions'
-import lerp from '../util/lerp'
-import easing from '../util/easing'
-import logger from '../../utils/logger'
-import settings from '../settings'
-import {objects} from '../base'
-
-export default class PanoramaControls extends EventEmitter {
-    //漫游模式相机控制器
-    /**
-     * 3D图形变换的坐标系 https://blog.csdn.net/CALL_LKC/article/details/81411034
-     * 功能:主要是在漫游模式下控制相机的方向(相机的移动只是做了事件触发,逻辑不在此)
-     *
-     * 原理: 
-     * @ 以camera本地坐标系作为参考建立球坐标系,有关球坐标系 https://en.wikipedia.org/wiki/Spherical_coordinate_system
-     * @ target作为camera的视点通过鼠标交互在球坐标系运动,然后调用THREE的API:camera.LookAt(target)校正相机方向
-     * 
-     * @param {*} camera 被控制的相机
-     */
-    constructor(camera, dom,player) {
-        super();
-        this.camera = camera; //被控制的相机
-        this.camera.controls = this;
-        this.player = player;
-
-
-        this.target = new THREE.Vector3(0, 0, 0); //相机视点,鼠标交互主要影响的对象
-        this.lookVector = new THREE.Vector3; //相机方向,以单位向量表示
-        this.lookSpeed = .05; //没发现下文用到???
-        this.rotationAcc = new THREE.Vector2; //旋转加速度
-        this.rotationSpeed = new THREE.Vector2; //旋转速度
-
-        /**
-         * 球坐标系的相关参数lat,lon 与 phi,theta 两种表示形式
-         * 注:少了半径参数,因为是用于约束相机的方向,半径长短在此没有意义,单位1即可,体现在方向向量lookVector上
-         */
-        this.lat = 0; //纬度,角度表示,直观
-        this.lon = 0; //经度,角度表示,直观
-        this.phi = 0; //phi,标准球坐标系的参数,弧度表示,用于进行直接计算
-        this.theta = 0; //theta,标准球坐标系的参数,弧度表示,用于进行直接计算
-
-
-        this.enabled = !1; //是否启用
-        this.locked = !1; //是否锁定,可能视角锁死但是其余功能仍在工作
-
-
-        /**
-         * 交互行为相关,有鼠标点击与触摸,点击或触摸的地方在此约定统称为交互点
-         */
-        this.pointer = new THREE.Vector2(0, 0); //交互点的屏幕坐标,有别于DOM坐标,在此存放NDC坐标。(NDC,三维常用坐标系,二维坐标,整个屏幕映射范围(-1,1),屏幕中心为原点,+Y朝上,+X朝右)
-        this.pointersLimit = 2; //触摸事件的触摸点的限制个数
-        this.pointers = []; //存储交互点的坐标
-
-
-
-        this.rotationHistory = [];
-        this.rotationDifference = new THREE.Vector2;
-        this.pointerDragOn = !1;
-        this.pointerDragStart = new THREE.Vector2(0, 0);
-        this.pinchDistance = 0;
-        this.moveStart = new THREE.Vector2;
-        this.moveTolerance = .01; //拖拽与点击两种交互行为通过鼠标移动距离的临界值来区分,即为此
-
-        //鼠标所交互的界面元素
-        this.dom = dom;
-		//固定垂直视角,许钟文
-		this.limitDownAngel = null;
-		this.insideLookLimitDown = null;
-    }
-
-    usable() {
-        return this.enabled && !this.locked
-    }
-
-    /**
-     * 根据新的方向向量计算所指向的球面坐标(lat,lon)
-     * 用到了标准的笛卡尔坐标系转球面坐标系的数学方法
-     * 注:THREE 的 Vector3 与 Spherical 两个数学类有互转的方法
-     * @param {THREE.Vector3} direction 
-     */
-    lookAt(aim, dir) {
-        var t = dir || this.camera.position.clone().sub(aim); //aim所指点的笛卡尔坐标系
-
-        /**
-         * 以下全为笛卡尔坐标->球座标,不多赘述
-         */
-        var i = Math.atan(t.z / t.x);
-        i += t.x < 0 ? Math.PI : 0;
-        i += t.x > 0 && t.z < 0 ? 2 * Math.PI : 0;
-        this.lon = THREE.Math.radToDeg(i) + 180;
-        var n = Math.sqrt(t.x * t.x + t.z * t.z),
-            o = Math.atan(t.y / n);
-        this.lat = -THREE.Math.radToDeg(o);
-    }
-
-    /**
-     * 许钟文  加  看向某个位置
-     * 逐渐看向某个位置 通过改变lon和lat
-     * @param {THREE.Vector3} aim 
-     * @param {THREE.Vector3} cameraPos 
-     * @param {JSON} option 
-     */
-    startLookAt(aim, cameraPos, option) {
-        var useLonLat = option && (option.lon != void 0 || option.lat != void 0);
-        if (!useLonLat) {
-            var e = cameraPos ? cameraPos.clone().sub(aim) : this.camera.position.clone().sub(aim),
-                o = Math.atan(e.z / e.x);
-            o += e.x < 0 ? Math.PI : 0,
-                o += e.x > 0 && e.z < 0 ? 2 * Math.PI : 0;
-            var lon = THREE.Math.radToDeg(o) + 180;
-            var n = Math.sqrt(e.x * e.x + e.z * e.z),
-                i = Math.atan(e.y / n);
-            var lat = -THREE.Math.radToDeg(i);
-
-            var add = (lon - this.lon) % 360;
-            Math.abs(add) > 180 && (add > 0 ? add -= 360 : add += 360);
-            lon = this.lon + add;
-            var add = (lat - this.lat) % 360;
-            Math.abs(add) > 180 && (add > 0 ? add -= 360 : add += 360);
-            lat = this.lat + add;
-        }
-        var time = 1200,
-            speedFuc = easing["easeInOutQuad"];
-        if (option != void 0) {
-            if (option.soon) {
-                this.lon = lon;
-                this.lat = lat;
-                return;
-            }
-            if (option.speed) {
-                /* var a = Math.abs(lon - this.lon) * Math.PI /180;
-                var b = Math.abs(lat - this.lat) * Math.PI /180;
-                var c0 = Math.sqrt(Math.pow(Math.sin(a/2),2) +  Math.pow(Math.sin(b/2),2));
-                var c = Math.asin(c0) * 2;	//得到旋转角度   cos(c/2)的方 = cos(a/2)的方 + cos(b/2)的方
-                time = c / option.speed; */
-                if (useLonLat) {
-                    var c1 = option.lon ? Math.abs(option.lon - this.lon) : 0;
-                    var c2 = option.lat ? Math.abs(option.lat - this.lat) : 0;
-                    var c = c1 + c2;
-                } else
-                    var c = Math.abs(lon - this.lon) + Math.abs(lat - this.lat)
-
-                time = c / option.speed; //总角度除以速度  
-                if (option.time)
-                    time = Math.min(option.time, time);
-            } else if (option.time)
-                time = option.time;
-
-            option.fuc && setTimeout(option.fuc, time)
-            //匀速:
-            option.constantSpeed && (speedFuc = null);
-
-
-        }
-        if (useLonLat) {
-            if (option.lon)
-                transitions.start(lerp.property(this, "lon", option.lon), time, null, 0, speedFuc);
-            if (option.lat)
-                transitions.start(lerp.property(this, "lat", option.lat), time, null, 0, speedFuc);
-        } else {
-            transitions.start(lerp.property(this, "lon", lon), time, null, 0, speedFuc);
-            transitions.start(lerp.property(this, "lat", lat), time, null, 0, speedFuc);
-        }
-    }
-
-    /**
-     * 记录拖拽旋转开始时的一些状态
-     * @param {number} clientX 屏幕坐标
-     * @param {number} clientY 屏幕坐标
-     */
-    startRotationFrom(clientX, clientY) {
-        //以屏幕中心为原点,得到pointer在屏幕的百分比
-        var mouse = math.handelPadding(clientX, clientY, this.dom);
-        math.convertScreenPositionToNDC(mouse.x, mouse.y, this.pointer, this.dom);
-        this.pointerDragOn = !0;
-        this.pointerDragStart.copy(this.pointer);
-        this.moveStart.copy(this.pointer);
-        this.rotationHistory = [];
-        this.rotationSpeed.set(0, 0);
-    }
-
-    onMouseOver(mouseEvent) {
-        !this.pointerDragOn || 0 !== mouseEvent.which && 0 !== mouseEvent.buttons || this.onMouseUp(mouseEvent)
-    }
-
-    onTouchStart(pointerEvent) {
-        if (this.usable()) {
-            pointerEvent.preventDefault();
-            pointerEvent.stopPropagation();
-            switch (pointerEvent.touches.length) {
-                case 1:
-                    this.startRotationFrom(pointerEvent.touches[0].clientX, pointerEvent.touches[0].clientY);
-                    break;
-                case 2:
-                    var t = (pointerEvent.touches[0].clientX - pointerEvent.touches[1].clientX) / app.player.domElement.clientWidth,
-                        i = (pointerEvent.touches[0].clientY - pointerEvent.touches[1].clientY) / app.player.domElement.clientHeight;
-                    this.pinchDistance = Math.sqrt(t * t + i * i);
-            }
-            this.emit(ControlEvents.InputStart, "touch");
-        }
-    }
-
-    onPointerDown(pointerEvent) {
-        if (this.usable() && "touch" === pointerEvent.pointerType)
-        {
-            if (this.pointers.length < this.pointersLimit)
-            {
-                this.pointers.push({
-                    id: pointerEvent.pointerId,
-                    clientX: pointerEvent.clientX,
-                    clientY: pointerEvent.clientY
-                });
-            }
-            pointerEvent.touches = this.pointers;
-            this.onTouchStart(pointerEvent);
-            this.emit(ControlEvents.InputStart, "pointer");
-        }
-    }
-
-    onMouseDown(mouseEvent) {
-        if (this.usable()) {
-            mouseEvent.preventDefault();
-            mouseEvent.stopPropagation();
-            switch (mouseEvent.button) {
-                case MouseButton.LEFT:
-                    this.startRotationFrom(mouseEvent.clientX, mouseEvent.clientY)
-            }
-            this.emit(ControlEvents.InputStart, "mouse")
-        }
-    }
-
-    /**
-     * 根据两帧交互点坐标之间的差值,计算两帧角度差值(rotationDifference)用于旋转
-     * 1.将两次交互点坐标分别映射到3D空间
-     * 2.通过两坐标在XY平面上投影,分别计算与X轴夹角,再求差值作为竖直方向角度差值(rotationDifference.y)
-     * 3.通过两坐标在XZ平面上投影,分别计算与X轴夹角,再求差值作为水平方向角度差值(rotationDifference.x)
-     */
-    updateRotation() {//算拖拽过的角度
-        if (this.usable() && this.pointerDragOn) {
-            this.camera.matrixWorld = new THREE.Matrix4(); //许钟文加  player的cameras里的panorama是不更新matrixworld的,只有player的camera才更新。不明白为什么更新了转动会很慢。为了miniView的相机旋转加这句。
-            //两交互点在3D空间的坐标
-            var pointerDragStart3D = new THREE.Vector3(this.pointerDragStart.x, this.pointerDragStart.y, -1).unproject(this.camera),
-                pointer3D = new THREE.Vector3(this.pointer.x, this.pointer.y, -1).unproject(this.camera)
-
-                //两交互点分别到原点的长度
-                ,
-                pointerDragStart3DLength = Math.sqrt(pointerDragStart3D.x * pointerDragStart3D.x + pointerDragStart3D.z * pointerDragStart3D.z),
-                pointer3DLength = Math.sqrt(pointer3D.x * pointer3D.x + pointer3D.z * pointer3D.z)
-
-                //通过Math.atan2计算分别与X轴的夹角弧度。
-                //注:因为 z = -1,所以两者到原点的长度近似为x分量,此处只是注重方上,数值的大小没有绝对对应关系
-                ,
-                anglePointerDragStart3DToX = Math.atan2(pointerDragStart3D.y, pointerDragStart3DLength) //近似为 anglePointerDragStart3DToX = Math.atan2( pointerDragStart3D.y, pointerDragStart3D.x ) 
-                ,
-                anglePointer3DToX = Math.atan2(pointer3D.y, pointer3DLength); //近似为 anglePointer3DToX = Math.atan2( pointer3D.y, pointer3D.x )
-
-            this.camera.updateMatrix();
-            this.camera.updateMatrixWorld();
-
-            this.rotationDifference.y = THREE.Math.radToDeg(anglePointerDragStart3DToX - anglePointer3DToX);
-            pointerDragStart3D.y = 0;
-            pointer3D.y = 0;
-            var anglePointerDragStart3DToPointer3D = Math.acos(pointerDragStart3D.dot(pointer3D) / pointerDragStart3D.length() / pointer3D.length());
-            // isNaN(s) || (this.rotationDifference.x = THREE.Math.radToDeg(s),
-            // this.pointerDragStart.x < this.pointer.x && (this.rotationDifference.x *= -1)),
-            if (!isNaN(anglePointerDragStart3DToPointer3D))
-            {
-                this.rotationDifference.x = THREE.Math.radToDeg(anglePointerDragStart3DToPointer3D);
-                if (this.pointerDragStart.x < this.pointer.x)
-                {
-                    this.rotationDifference.x *= -1;
-                }
-
-            }
-            this.pointerDragStart.copy(this.pointer);
-        }
-    }
-
-    /**
-     * 处理鼠标移动事件
-     * 1.计算鼠标的NDC坐标
-     * 2.判断是否是拖拽来决定拖拽行为的执行
-     * 3.通过预定义的防误触偏差(moveTolerance),来防止一定的误触
-     */
-    onMouseMove(mouseEvent) {
-        if (this.usable())
-        {
-            //许钟文加handelPadding这一行。
-            var mouse = math.handelPadding(mouseEvent.clientX, mouseEvent.clientY, this.dom)
-            math.convertScreenPositionToNDC(mouse.x, mouse.y, this.pointer, this.dom);
-            if (this.pointerDragOn)
-            {
-                if (Math.abs(this.pointer.x - this.moveStart.x) > this.moveTolerance || Math.abs(this.pointer.y - this.moveStart.y) > this.moveTolerance)
-                {
-                    this.emit(ControlEvents.Move, "mouse");
-                }
-            }
-        }
-    }
-
-
-    /**
-     * 处理触摸移动事件
-     * 1.单点触控记录NDC坐标
-     * 2.双点触控记录两触摸点距离(映射到[0-1]范围)
-     */
-    onTouchMove(pointerEvent) {
-        if (this.usable())
-        {
-            this.emit(ControlEvents.Move, "touch");
-            switch (pointerEvent.touches.length) {
-                case 1:
-                    var mouse = math.handelPadding(pointerEvent.touches[0].clientX, pointerEvent.touches[0].clientY, this.dom);
-                    math.convertScreenPositionToNDC(mouse.x, mouse.y, this.pointer, this.dom);
-                    break;
-                case 2:
-                    var t = (pointerEvent.touches[0].clientX - pointerEvent.touches[1].clientX) / app.player.domElement.clientWidth,
-                        i = (pointerEvent.touches[0].clientY - pointerEvent.touches[1].clientY) / app.player.domElement.clientHeight,
-                        n = this.pinchDistance - Math.sqrt(t * t + i * i);
-                    if (Math.abs(n) > .01)
-                    {
-                        this.emit(ControlEvents.InteractionDirect);
-                        this.emit(ControlEvents.Pinch, n);
-                        this.pinchDistance -= n;
-                    }
-            }
-        }
-    }
-
-    onPointerMove(pointerEvent) {
-        if (this.usable() && "touch" === pointerEvent.pointerType)
-        {
-            this.pointers.forEach(function(t) {
-                if (pointerEvent.pointerId === t.id)
-                {
-                    t.clientX = pointerEvent.clientX;
-                    t.clientY = pointerEvent.clientY;
-                }
-            });
-            pointerEvent.touches = this.pointers;
-            this.onTouchMove(pointerEvent);
-        }
-    }
-
-    /**
-     * 旋转终止后的行为
-     * 1.通过已记录的一组帧旋转量(rotationDifference)求平均值作为停止后惯性速度参考值。
-     * 2.通过设置的rotationAfterMoveMultiplier(惯性速度决定因子,用于手动指定影响惯性速度大小),来计算最后的的惯性速度
-     */
-    endRotation() {
-        this.pointerDragOn = !1;
-        var e = common.averageVectors(this.rotationHistory); 
-        this.rotationSpeed.set(e.x * settings.rotationAfterMoveMultiplierX, e.y * settings.rotationAfterMoveMultiplierY)
-    }
-
-    /**
-     * 触摸结束触发endRotation行为
-     */
-    onTouchEnd(pointerEvent) {
-        if (this.usable())
-        {
-            pointerEvent.preventDefault();
-            pointerEvent.stopPropagation();
-            this.endRotation();
-        }
-    }
-
-    /**
-     * 鼠标抬起触发endRotation行为
-     */
-    onMouseUp(mouseEvent) {
-        if (this.usable())
-        {
-            mouseEvent.preventDefault();
-            mouseEvent.stopPropagation();
-            this.endRotation(); 
-        }
-    }
-
-    onPointerUp(pointerEvent) {
-        if (this.usable() && "touch" === pointerEvent.pointerType)
-        {
-            this.pointers.forEach(function(t, i) {
-                pointerEvent.pointerId === t.id && this.pointers.splice(i, 1)
-            }.bind(this));
-            pointerEvent.touches = this.pointers;
-            this.onTouchEnd(pointerEvent);
-        }
-    }
-
-    /**
-     * 主循环更新,主要通过物理上的刚体旋转行为(角位移,角速度,角加速度,摩擦等)计算得到新的相机视点target,主要是每帧瞬时的状态
-     * 
-     *  updateRotation()计算每帧对应的旋转量 rotationDifference
-     * 
-     * 角位移:rotationDifference与原本lon,lat (等价于phi,theta)累加,得到新的角位移 
-     * 角速度:(rotationDifference数组的平均值 * 速度因子rotationAccelerationInside + 角加速度) - 摩擦rotationFriction。
-     * 
-     * target坐标:新的角位移计算出新的球坐标,转换计算后的球坐标到笛卡尔坐标系
-     * 
-     * @param { number } deltaTime 帧间隔时间。 注:关于帧间隔时间,是个有关物理计算的很重要的值,用于保持物理量与绝对时间的对应而不受帧率的的干扰,下文计算角速度用到。更多请见 https://blog.csdn.net/ChinarCSDN/article/details/82914420
-     */
-    update(deltaTime) { 
-		//移动端是鱼眼模式,过渡的时候需要采用鱼眼的算法来计算
-    	
-        // if(!objects.play.ifPlay()){
-        //     //许钟文-add------某些时刻不旋转
-        //     if(settings.vrEnabled || !this.player.flying &&!this.player.flyRotate && this.player.mode == "panorama"){
-        //         //原地mousemove旋转时,只需要直接改camera的quarternion
-        //         return;
-        //     }
-        // }
-        //许钟文-add------某些时刻不旋转
-        //if(settings.vrEnabled && !window.ifTest || !this.player.flying &&!this.player.flyRotate && this.player.mode == "panorama"&&!objects.play.control.onUpdate){
-            if((settings.vrEnabled && !window.ifTest) || !this.player.flying &&!this.player.flyRotate && this.player.mode == "panorama"&&!objects.play.control.onUpdate){
-            //原地mousemove旋转时,只需要直接改camera的quarternion
-            //logger.info('不执行PanoramaControls的update');
-            return;
-        }
-
-        
-        // 求出新的rotationDifference 
-        this.updateRotation();
-
-        //记录一组rotationDifference 用于求角速度 rotationSpeed。注:见 endRotation()
-        for (this.rotationHistory.push(this.rotationDifference.clone()); this.rotationHistory.length > settings.rotationAfterMoveHistoryCount;)
-        {
-            this.rotationHistory.shift();
-        }
-
-        //计算角位移(交互影响下的)
-        this.lon += this.rotationDifference.x;
-        this.lat += this.rotationDifference.y;
-        this.rotationDifference.set(0, 0);
-        //计算角速度
-		var friction = Math.min(1, settings.rotationFriction * deltaTime * 60) //如果deltaTime > 1/ 60 (比较卡),就增加rotationFriction, 以防止转动过久  
-        this.rotationSpeed.x = this.rotationSpeed.x * (1 - friction) + this.rotationAcc.x * settings.rotationAccelerationInside;
-        this.rotationSpeed.y = this.rotationSpeed.y * (1 - friction) + this.rotationAcc.y * settings.rotationAccelerationInside;
-        //计算角位移(交互后,物理定律影响下的)
-        this.lon += this.rotationSpeed.x * deltaTime;
-        this.lat += this.rotationSpeed.y * deltaTime;
-		
-		//许钟文 
-		if(this.limitDownAngel == null){//许钟文    在手机编辑墙壁时俯视角度可以增大
-			this.lat = Math.max(this.insideLookLimitDown!=null ? this.insideLookLimitDown : settings.insideLookLimitDown, Math.min(settings.insideLookLimitUp, this.lat)); //通过预定义的俯仰角最大最小范围(insideLookLimitUp、insideLookLimitDown)来限制俯仰角。 注:这种数学计算很常见,因此API也很常见(clamp),等价于 this.lat = THREE.Math.clamp( this.lat, settings.insideLookLimitDown, settings.insideLookLimitUp );   
-		}
-		else{
-			this.lat = this.limitDownAngel;//固定垂直视角
-		}
-		//转换为标准球坐标参数形式,并最终转换为笛卡尔坐标系下
-		this.phi = THREE.Math.degToRad(90 - this.lat);
-		this.theta = THREE.Math.degToRad(this.lon);
-		this.lookVector.x = Math.sin(this.phi) * Math.cos(this.theta);
-		this.lookVector.y = Math.cos(this.phi);
-		this.lookVector.z = Math.sin(this.phi) * Math.sin(this.theta);
-
-		//求taget坐标: 当前相机位置 + 方向向量(对于此处旋转来说距离并无意义,方向向量的1即可) 
-		this.target.copy(this.lookVector).add(this.camera.position);
-
-		//THREE的API来更新相机旋转。注:lookAt是四阶矩阵比较常见的API,因此此PanoramaControls计算流程,不算与THREE耦合
-        this.camera.lookAt(this.target);
-    }
-
-    //许钟文 加 用于鱼眼过渡
-    updateByLookVectorFish(onlyGetVector) {
-        var lookVector;
-        //前面几个条件的意思就是:只要不是简单不旋转的那种flying,就需要计算这些:
-        if (!this.player.flying || this.player.flyingToTag ||
-            this.player.flyRotate || this.player.flyingWithRot ||
-            this.player.isWarping() || onlyGetVector) { //减少计算
-
-            //if(!(this.player.flying && !this.player.flyingToTag && !player.isWarping() ) || onlyGetVector){//减少计算
-            this.lat = Math.max(settings.insideLookLimitDown, Math.min(settings.insideLookLimitUp, this.lat)),
-                this.phi = THREE.Math.degToRad(90 - this.lat),
-                this.theta = THREE.Math.degToRad(this.lon)
-            //FishCam_BackDist: 4/11
-            lookVector = new THREE.Vector3;
-            var backDist = settings.FishCam_BackDist;
-            lookVector.x = settings.skyRadius * backDist * Math.sin(this.phi) * Math.cos(this.theta); //改
-            lookVector.y = settings.skyRadius * backDist * Math.cos(this.phi);
-            lookVector.z = settings.skyRadius * backDist * Math.sin(this.phi) * Math.sin(this.theta); //改
-            this.fishState && this.camera.position.copy(lookVector).negate().add(this.target);
-        }
-        if (!onlyGetVector && !this.fishState) {
-            lookVector && this.lookVector.copy(lookVector)
-            this.target = this.player.currentTarget.clone();
-            this.camera.position.copy(this.lookVector).negate().add(this.target);
-            this.camera.lookAt(this.target);
-        } else {
-            return lookVector;
-        }
-    }
-
-    /**
-     * 滚轮行为: 触发自定义事件
-     */
-    onMouseWheel(wheelEvent) {
-        if (this.usable()) {
-            var t = wheelEvent.wheelDelta || -wheelEvent.detail;
-            this.emit(ControlEvents.InteractionDirect);
-            this.emit(ControlEvents.Scroll, t);
-        }
-    }
-
-    /**
-     * 键盘按下:触发自定义事件
-     */
-    onKeyDown(keyboardEvent) {
-        if (this.usable())
-        {
-            if (keyboardEvent.metaKey || keyboardEvent.ctrlKey) {}
-            else {
-
-                if (config.isTyping || config.isDisableControl) {
-                    return
-                }
-
-                keyboardEvent.preventDefault();
-                this.handleKeyDown(keyboardEvent.which);
-            }
-        }
-    }
-
-    handleKeyDown(keyValue) {
-        var t = function(e, t) {
-                this.rotationAcc[e] = t
-            }
-            .bind(this);
-        this.emit(ControlEvents.InteractionKey);
-        var i = !0;
-        switch (keyValue) {
-            case Keys.LEFTARROW:
-            case Keys.J:
-                t("x", -1);
-                break;
-            case Keys.RIGHTARROW:
-            case Keys.L:
-                t("x", 1);
-                break;
-            case Keys.I:
-                t("y", 1);
-                break;
-            case Keys.K:
-                t("y", -1);
-                break;
-            default:
-                i = !1
-        }
-        i && this.emit(ControlEvents.Move, "key")
-    }
-
-    onKeyUp(keyboardEvent) {
-        if (this.usable())
-        {
-            keyboardEvent.preventDefault();
-            keyboardEvent.stopPropagation();
-            this.handleKeyUp(keyboardEvent.which);
-        }
-    }
-
-    handleKeyUp(keyValue) {
-        switch (keyValue) {
-            case Keys.LEFTARROW:
-            case Keys.J:
-            case Keys.RIGHTARROW:
-            case Keys.L:
-                this.rotationAcc.x = 0;
-                break;
-            case Keys.I:
-            case Keys.K:
-                this.rotationAcc.y = 0
-        }
-    }
-
-    startRotating(e, t) {
-        e && (this.rotationAcc.x = e);
-        t && (this.rotationAcc.y = t);
-    }
-
-    /**
-     * 通过物理定律来终止旋转
-     */
-    stopRotating(e) {
-        e && (this.rotationSpeed.x = this.rotationSpeed.y = 0);
-        this.rotationAcc.set(0, 0);
-    }
-
-    reset() {
-        this.pointerDragOn = !1;
-        this.rotationAcc.set(0, 0);
-        this.rotationSpeed.set(0, 0);
-        this.pointers = [];
-    }
-
-    /**
-     * 序列化,用于保存状态。
-     */
-    toJSON() {
-        var cameraSpatialInfo = {
-            camera_position: {
-                x: math.toPrecision(this.camera.position.x, 4),
-                y: math.toPrecision(this.camera.position.y, 4),
-                z: math.toPrecision(this.camera.position.z, 4)
-            },
-            camera_quaternion: {
-                x: math.toPrecision(this.camera.quaternion.x, 4),
-                y: math.toPrecision(this.camera.quaternion.y, 4),
-                z: math.toPrecision(this.camera.quaternion.z, 4),
-                w: math.toPrecision(this.camera.quaternion.w, 4)
-            }
-        };
-        return cameraSpatialInfo;
-    }
-
-    /**
-     * 反序列化,用于读取状态
-     */
-    setStateFromJSON(cameraSpatialInfo) {
-        this.camera.position.copy(cameraSpatialInfo.camera_position);
-        this.camera.quaternion.copy(cameraSpatialInfo.camera_quaternion);
-    }
-}

+ 0 - 107
src/navigation/Reticule.js

@@ -1,107 +0,0 @@
-import * as THREE from "../../libs/three.js/build/three.module.js";
-import {MOUSE} from '../defines.js'
-import {transitions, easing, lerp} from '../utils/transitions.js'
-import math from '../utils/math.js'
-
-
-let texLoader = new THREE.TextureLoader()
-let defaultOpacity =  0.7
-//鼠标指示小圆片 
-export default class Reticule extends THREE.Mesh{
-    constructor(viewer){
-        var defaultTex = texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png'/* reticule-256x256.png'  */)  
-        var crosshairTex = texLoader.load(Potree.resourcePath+'/textures/reticule_cross_hair.png') 
-        
-        super(new THREE.PlaneBufferGeometry(0.15,0.15,1,1),new THREE.MeshBasicMaterial({
-            side: THREE.DoubleSide , 
-            map: defaultTex,
-            transparent:true,
-            depthTest: !1,
-            opacity: defaultOpacity,
-            //depthWrite: !1,
-        })) 
-        
-        //this.layers.set(0/* RenderLayers.RETICULE */);
-        this.renderOrder = 0
-        this.layers.set(Potree.config.renderLayers.marker);
-        
-        
-        this.direction = new THREE.Vector3;
-        this.hidden = !0;
-        this.mouseLastMoveTime = Date.now();
-
-
-        //viewer.inputHandler.addInputListener(this);
-        viewer.addEventListener('global_mousemove',this.move.bind(this))
-        
-        viewer.addEventListener('measureMovePoint',()=>{
-            this.material.map = crosshairTex
-            this.state = 'crosshair' 
-        }) 
-        viewer.addEventListener('endMeasureMove',()=>{
-            this.material.map = defaultTex 
-            this.state = 'default' 
-        }) 
-        this.state = 'default'
-        
-        viewer.setObjectLayers(this, 'reticule' )
-    }
-
-    move(e){
-        if(e.buttons == MOUSE.NONE || this.state == 'crosshair' ){//按下时不更新,除非拖拽测量
-            this.hidden = false,
-            this.mouseLastMoveTime = Date.now()
-            
-            this.updatePosition(e.intersectPoint, e.hoverViewport)
-        }
-    }
-
-    hide(){
-        //console.log("hide Reticule")
-        this.hidden || (this.hidden = !0,
-        transitions.start(lerp.property(this.material , "opacity", 0), 500))
-    }
-
-    show(){
-        if(!this.visible)return
-        //console.log("show Reticule")
-        this.hidden = !1,
-        this.material.opacity <= 0 && transitions.start(lerp.property(this.material, "opacity", defaultOpacity), 300)
-    }
-
-    //鼠标静止一段时间它就会消失
-    updateVisible(){
-        Date.now() - this.mouseLastMoveTime > 1500 && !this.hidden && this.hide()
-    }
-
-    updatePosition(intersectPoint, viewport ){ //在地图(当地图融合到viewer时)和场景里都显示且完全相同(大小可能不同)
-        if (!this.hidden && this.visible) {
-            if (!intersectPoint /* || !intersectPoint.point.normal */)
-                return //this.hide();
-            var atMap = !intersectPoint.location
-            let normal = intersectPoint.point ? new THREE.Vector3().fromArray(intersectPoint.point.normal ) : new THREE.Vector3(0,0,1)//地图无normal
-            let s, camera
-            let location = intersectPoint.location || intersectPoint.orthoIntersect.clone()
-                
-            if(!atMap){
-                camera = viewport.camera
-                let n = camera.position.distanceTo(location)
-                s = 1 + .01 * n;
-                n < 1 && (s -= 1 - n)
-            }else{
-                camera = viewer.mapViewer.camera
-                s = math.getScaleForConstantSize({width2d:300, position:location, camera, resolution:viewport.resolution2} )
-                location.setZ(0);//低于相机高度即可
-            }
-            
-            
-            
-            this.show();
-            this.scale.set(s, s, s);
-            this.direction = this.direction.multiplyScalar(.8); 
-            this.direction.add(normal.clone().multiplyScalar(.2));
-            this.position.copy(location).add(normal.clone().multiplyScalar(.01));
-            this.lookAt(this.position.clone().add(this.direction));
-        }
-    }
-}

+ 4 - 5
src/navigation/VRControls.js

@@ -1,6 +1,5 @@
 
-import * as THREE from "../../libs/three.js/build/three.module.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import * as THREE from "../../libs/three.js/build/three.module.js"; 
 import { XRControllerModelFactory } from '../../libs/three.js/webxr/XRControllerModelFactory.js';
 import {Line2} from "../../libs/three.js/lines/Line2.js";
 import {LineGeometry} from "../../libs/three.js/lines/LineGeometry.js";
@@ -277,7 +276,7 @@ class RotScaleMode{
 };
 
 
-export class VRControls extends EventDispatcher{
+export class VRControls extends THREE.EventDispatcher{
 
 	constructor(viewer){
 		super(viewer);
@@ -336,7 +335,7 @@ export class VRControls extends EventDispatcher{
 
 				let lineMaterial = new LineMaterial({ 
 					color: 0xff0000, 
-					linewidth: 2, 
+					lineWidth: 2, 
 					resolution:  new THREE.Vector2(1000, 1000),
 				});
 
@@ -387,7 +386,7 @@ export class VRControls extends EventDispatcher{
 
 				let lineMaterial = new LineMaterial({ 
 					color: 0xff0000, 
-					linewidth: 2, 
+					lineWidth: 2, 
 					resolution:  new THREE.Vector2(1000, 1000),
 				});
 

+ 3 - 3
src/viewer/Axis.js

@@ -1,7 +1,7 @@
 
 
 import * as THREE from "../../libs/three.js/build/three.module.js"; 
-import {MeshDraw,LineDraw} from '../utils/DrawUtil'
+import {MeshDraw,LineDraw} from '../utils/DrawUtil.js'
  
 /*  
 
@@ -71,9 +71,9 @@ export default class Axis extends THREE.Object3D {// 坐标轴
         
 
             if(axisText == 'y'){
-                group.rotation.x = Math.PI / 2
+                group.rotation.x = -Math.PI / 2
             }else if(axisText == 'x'){
-                group.rotation.y = -Math.PI / 2
+                group.rotation.y = Math.PI / 2
             } 
             
             this.add(group)

+ 141 - 0
src/objects/InfiniteGridHelper.js

@@ -0,0 +1,141 @@
+// Author: Fyrestar https://mevedia.com (https://github.com/Fyrestar/THREE.InfiniteGridHelper)
+import * as THREE from "../../libs/three.js/build/three.module.js";
+
+
+
+class InfiniteGridHelper extends THREE.Mesh{
+     
+    constructor(size1, size2, color, distance, opacity1=0.2, opacity2=1){
+        
+        color = color || new THREE.Color('white');
+        size1 = size1 || 10;
+        size2 = size2 || 100;
+
+        distance = distance || 8000; //可视距离?越远越模糊
+
+        const geometry = new THREE.PlaneBufferGeometry(2, 2, 1, 1);
+
+        const material = new THREE.ShaderMaterial({
+
+            side: THREE.DoubleSide,
+
+            uniforms: {
+                uSize1: {
+                    value: size1
+                },
+                uSize2: {
+                    value: size2
+                },
+                
+                opacity1:{//线条1的
+                    value: opacity1
+                },
+                opacity2:{//线条2的
+                    value: opacity2
+                },
+                
+                uColor: {
+                    value: color
+                },
+                uDistance: {
+                    value: distance
+                }
+            },
+            transparent: true,
+            vertexShader: `
+               
+               varying vec3 worldPosition;
+               
+               uniform float uDistance;
+               
+               void main() {
+               
+                    vec3 pos = position.xyz * uDistance;
+                    pos.xy += cameraPosition.xy;
+                    
+                    worldPosition = pos;
+                    
+                    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
+               
+               }
+               `,
+
+
+            fragmentShader: `
+               
+               varying vec3 worldPosition;
+               
+               uniform float uSize1;
+               uniform float uSize2;
+               uniform float opacity1;
+               uniform float opacity2;
+               uniform vec3 uColor;
+               uniform float uDistance;
+                
+                
+                
+                float getGrid(float size) {
+                
+                    vec2 r = worldPosition.xy / size;
+                    
+                    
+                    vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r);
+                    float line = min(grid.x, grid.y);
+                    
+                
+                    return 1.0 - min(line, 1.0);
+                }
+                //为何侧面看不到线,因为mesh的正侧面都看不到?
+
+               void main() {
+               
+                    
+                      float d = 1.0 - min(distance(cameraPosition.xy, worldPosition.xy) / uDistance, 1.0);
+                    
+                      float g1 = getGrid(uSize1);
+                      float g2 = getGrid(uSize2);
+                      
+                      
+                      gl_FragColor = vec4(uColor.rgb, mix(g2, g1, g1) * pow(d, 3.0));
+                      //gl_FragColor.a = mix(0.5 * gl_FragColor.a, gl_FragColor.a, g2);
+                        gl_FragColor.a = mix(opacity1 * gl_FragColor.a, opacity2 * gl_FragColor.a, g2);
+                        
+                        
+                      if ( gl_FragColor.a <= 0.0 ) discard;
+                    
+               
+               }
+               
+               `,
+
+            extensions: {
+                derivatives: true
+            }
+            
+            
+            
+        })
+        
+        
+        super(geometry, material)
+        this.frustumCulled = false;
+
+    }
+
+
+    
+
+    
+
+};
+
+
+
+export default InfiniteGridHelper
+/* 
+THREE.InfiniteGridHelper.prototype = {
+    ...THREE.Mesh.prototype,
+    ...THREE.Object3D.prototype,
+    ...THREE.EventDispatcher.prototype
+};
+ */

+ 21 - 7
src/utils/Label.js

@@ -1,8 +1,12 @@
 import * as THREE from "../../libs/three.js/build/three.module.js";
-import {Utils} from "../utils.js";
+import {Utils} from "../utils.js";  
 
-class Label{
+
+
+class Label  extends THREE.EventDispatcher{
     constructor(o={}){
+        super()
+        
         this.position = o.pos;
         this.text = o.text || '';
         this.elem = $('<div class="hide"><a></a></div>');
@@ -13,11 +17,18 @@ class Label{
         this.dom = o.dom || viewer.renderArea
         this.camera = o.camera || viewer.scene.getActiveCamera() 
         
-        viewer.addEventListener('camera_changed', ()=>{
-            this.update()
-        })
-       
         
+        let update = (e)=>{
+            this.update(e) 
+        }
+        viewer.addEventListener('camera_changed',  update)
+           
+         
+        this.addEventListener('dispose', ()=>{
+            viewer.removeEventListener('camera_changed',  update)
+            this.dispose()
+            
+        })
         
     }
      
@@ -60,9 +71,12 @@ class Label{
         this.position = pos;
     }
     
-    remove(){
+    dispose(){
         this.elem.remove();
+        this.removeAllListeners()
     }
+     
+     
 }
 
 export default Label;

+ 350 - 0
src/objects/Magnifier.js

@@ -0,0 +1,350 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import math from '../utils/math.js'
+import browser from '../utils/browser.js'
+import Viewport from '../viewer/Viewport.js'
+ 
+const texLoader = new THREE.TextureLoader() 
+const circleGeo = new THREE.CircleGeometry(1.45,100);
+const sphereGeo = new THREE.SphereBufferGeometry(0.018,10,10);
+ 
+ 
+const magDistance_ = 1;//相机离目标位置的距离的分界线,当离得远时要缩小fov以使看到的视野固定(望远镜效果)
+/* const radius_ = 0.2; //当相机离目标位置的距离>magDistance_时,希望看到的视野的半径
+const maxFov = THREE.Math.radToDeg(Math.atan(radius_ / magDistance_ )) * 2//提前计算出当相机离目标位置的距离<magDistance_时的fov,均使用=magDistance_时的fov。只要保证该fov大于主相机的fov就会有放大效果 
+ */
+let w = 200/1.43;
+let maxPX = 1366*1024 //ipad pro.  大于这个分辨率的就直接用devicePixelRatio, 如macbook也是
+const width2dPX = Math.round(window.devicePixelRatio >= 2 ? ( window.screen.width * window.screen.height >= maxPX ? window.devicePixelRatio/1.2 : window.devicePixelRatio/1.5)*w : w)  //触屏或高分辨率的可能要放大些。但在手机上不能太大
+//console.log('width2dPX', width2dPX)
+
+
+
+export default class Magnifier extends THREE.Object3D {//放大镜or望远镜
+	constructor (viewer) {
+		super()
+        this.width = this.height = width2dPX/*  * window.devicePixelRatio */;
+        this.camera = new THREE.PerspectiveCamera(50, 1, 0.01, 10000);  //fov aspect near far
+        this.camera.up = new THREE.Vector3(0,0,1) 
+        
+        
+        this.viewport = new Viewport( null, this.camera, {
+            left:0, bottom:0, width:1, height: 1, name:'magnifier' , cameraLayers:['magnifierContent'],
+            pixelRatio:1
+        })
+        this.viewport.setResolution(this.width, this.height,0,0)
+        
+        { 
+            let density
+            let sizeType
+            let colorType
+            let opacityBefore = new Map()
+            this.viewport.beforeRender = ()=>{
+                viewer.scene.pointclouds.forEach(e=>{//因为更改pointDensity时会自动变opacity,所以这项最先获取
+                    opacityBefore.set(e,e.temp.pointOpacity)  
+                }) 
+                
+                
+                //使放大镜里的pointDensity是'magnifier'  最高质量。
+                density = Potree.settings.pointDensity 
+                Potree.settings.pointDensity = 'magnifier' 
+                 
+                viewer.scene.pointclouds.forEach(e=>{//因为全景模式的pointSizeType是fixed所以要还原下
+                    sizeType = e.material.pointSizeType  
+                    e.material.pointSizeType = Potree.config.material.pointSizeType  
+                     
+                    //材质
+                    colorType = e.material.activeAttributeName
+                    e.material.activeAttributeName = 'rgba'
+                    e.changePointOpacity(1) 
+                   
+                }) 
+            };
+            
+            
+            this.viewport.afterRender = ()=>{
+                Potree.settings.pointDensity = density
+                
+                viewer.scene.pointclouds.forEach(e=>{
+                    e.material.pointSizeType = sizeType
+                    e.material.activeAttributeName = colorType  
+                    e.changePointOpacity(opacityBefore.get(e))  
+                }) 
+            } 
+        }
+        
+        
+        
+        
+        this.renderTarget = new THREE.WebGLRenderTarget(this.width,this.height, { 
+            minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter,
+            format: THREE.RGBAFormat ,
+            /* type: THREE.FloatType,
+            minFilter: THREE.NearestFilter,
+			magFilter: THREE.NearestFilter, 
+			  */ 
+        } )
+        
+		this.rtEDL = new THREE.WebGLRenderTarget(this.width, this.height, {  //好像没用到? 因为这里不绘制测量线
+			minFilter: THREE.NearestFilter,
+			magFilter: THREE.NearestFilter,
+			format: THREE.RGBAFormat,
+			type: THREE.FloatType,
+			depthTexture: new THREE.DepthTexture(undefined, undefined, THREE.UnsignedIntType)
+		});
+        
+        
+        
+        this.mesh = new THREE.Mesh(circleGeo, new THREE.MeshBasicMaterial({
+            side: THREE.DoubleSide , 
+            map: this.renderTarget.texture ,
+            transparent:true,
+            depthTest: !1,
+            //depthWrite: !1,
+        }))
+        this.overlayMesh = new THREE.Mesh(circleGeo, new THREE.MeshBasicMaterial({
+            side: THREE.DoubleSide , 
+            map:texLoader.load(Potree.resourcePath+'/textures/crosshair.png') ,
+            transparent:true,
+            depthTest: !1,
+            //depthWrite: !1,
+        }))
+        this.targetPoint = new THREE.Object3D;
+        
+        this.targetPoint.add(new THREE.Mesh(sphereGeo, new THREE.MeshBasicMaterial({ 
+            color:"#ff0000",
+            transparent:true,
+            opacity:0.5,  
+        })))
+        this.targetPoint.add(new THREE.Mesh(sphereGeo, new THREE.MeshBasicMaterial({ 
+            color:"#ff0000",
+            transparent:true,
+            opacity:0.2, 
+            depthTest:false  //被遮挡层
+        })))
+        
+        this.targetPoint.name = 'magnifierPointTarget'
+        viewer.scene.scene.add(this.targetPoint)
+        viewer.setObjectLayers(this.targetPoint, 'magnifierContent' )
+        
+        this.add(this.mesh)
+        this.add(this.overlayMesh)
+        
+        this.position.set(-1000,-1000,-100000)//令它看不见
+        this.mesh.renderOrder = 10;
+        this.overlayMesh.renderOrder = 11;
+        this.aimPos
+        viewer.setObjectLayers(this, 'magnifier' )
+        //viewer.inputHandler.addInputListener(this)
+        
+        
+        
+        
+        viewer.addEventListener('camera_changed',(e)=>{ // 平移、滚轮时更新
+            if(e.viewport == viewer.mainViewport) this.update() //不过intersectPoint没更新 
+        }) 
+            
+         
+        
+        
+        this.mesh.layers.set(Potree.config.renderLayers.magnifier);
+        this.overlayMesh.layers.set(Potree.config.renderLayers.magnifier);
+        //this.layers.set(Potree.config.renderLayers.magnifier);//这句在外层写没用
+        
+        this.dontRender = false 
+        viewer.addEventListener('global_drag', (e)=>{//拖拽时不渲染。主要是右键平移时渲染延迟了,会闪烁。 
+            this.dontRender = true 
+        })
+        viewer.addEventListener('global_drop', (e)=>{
+            this.dontRender = false
+        })
+        viewer.addEventListener('global_mouseup', (e)=>{//测量时拖拽场景再mouseup
+            this.dontRender = false
+        })
+        
+        var updateVisi = (e)=>{
+            if(e.hoverViewport == viewer.mainViewport){
+                viewer.updateVisible(this,"atViewport", true)
+                this.update(e.intersect && e.intersect.location)
+            }else{
+                viewer.updateVisible(this,"atViewport", false) //小地图不显示
+            } 
+            
+        }
+        
+        viewer.addEventListener('global_mousemove', updateVisi)
+        viewer.addEventListener('global_touchstart', updateVisi)
+        
+        
+        /* viewer.addEventListener("beginSplitView",()=>{
+            this.updateVisible("splitView", false) 
+        })
+        viewer.addEventListener("finishSplitView",()=>{
+            this.updateVisible("splitView", true) 
+        })  */
+         
+         
+        this.addEventListener("setEnable",(e)=>{
+            viewer.updateVisible(this, "enable", e.value) //界面开关
+            /* if(Potree.settings.displayMode == 'showPanos') && e.value){
+                Potree.settings.pointDensity = 'magnifier'
+            }else if() */
+            
+        })
+         
+         
+        if(Potree.settings.isOfficial){
+            viewer.updateVisible(this, "enable", false) 
+        }else{
+            viewer.updateVisible(this, "measure", false) 
+            viewer.addEventListener("measureMovePoint",()=>{//测量开始
+                viewer.updateVisible(this, "measure", true) 
+            })
+            viewer.addEventListener("endMeasureMove",()=>{
+                viewer.updateVisible(this, "measure", false) 
+            })
+        }
+        
+        
+        viewer.scene.view.addEventListener('flyingDone',()=>{
+            if(!this.visible)return
+            let pickWindowSize = 100
+            let intersect = viewer.inputHandler.getIntersect(viewer.mainViewport, viewer.mainViewport.camera, true, pickWindowSize )
+            this.update(intersect && intersect.location)
+        })
+    }
+    
+    
+   
+    
+    //注意:在鼠标没有移动的时候,无法获取到最新的intersect, 放大镜内的内容可能是错误的。全景模式下更奇怪,原因未知
+    update(aimPos){//相机靠近 navvis的做法 
+        var dontRender = this.dontRender || !(aimPos instanceof THREE.Vector3) || Potree.settings.displayMode == 'showPanos' && viewer.images360.flying
+        aimPos = aimPos instanceof THREE.Vector3 ?  aimPos : this.aimPos
+        if(!aimPos  || !this.visible)return
+        
+        
+        
+        var playerCamera = viewer.scene.getActiveCamera()
+        var playerPos = playerCamera.position;//viewer.scene.view.getPivot()
+        var dis = playerPos.distanceTo(aimPos);
+        var dirToCamera = new THREE.Vector3().subVectors(playerPos, aimPos ).normalize()
+        
+        
+        //相机位置
+        var finalDisToAim = dis>magDistance_ ? magDistance_ : dis / 2;
+        this.camera.position.copy(aimPos).add(dirToCamera.multiplyScalar(finalDisToAim))
+        this.camera.lookAt(aimPos)
+        this.camera.fov = playerCamera.fov / 2
+        this.camera.updateProjectionMatrix()
+        
+        
+
+         
+        //自身位置 
+        //let pos2d = viewer.inputHandler.pointer.clone();   //跟随鼠标 
+        let pos2d = Potree.Utils.getPos2d(aimPos, playerCamera, viewer.renderArea, viewer.mainViewport).vector   //更新目标点的实时二维位置
+        let margin = 0.4, maxY = 0.4
+        let screenPos = pos2d.clone().setY(pos2d.y + (pos2d.y>maxY ? -margin : margin ))
+        
+        let newPos = new THREE.Vector3(screenPos.x,screenPos.y,0.8).unproject(playerCamera); //z:-1朝外       
+        let dir = newPos.clone().sub(playerPos).normalize().multiplyScalar(10);//这个数值要大于playerCamera.near
+        let s = dis>magDistance_ ? 1 : dis / magDistance_  ;
+        
+        this.position.copy(playerPos.clone().add(dir))
+        this.quaternion.copy(playerCamera.quaternion); 
+        this.targetPoint.position.copy(aimPos); 
+        this.targetPoint.scale.set(s,s,s)
+        this.aimPos = aimPos
+       
+        
+        var scale = math.getScaleForConstantSize({// 
+            width2d : width2dPX,
+            camera:viewer.scene.getActiveCamera(),  position: this.getWorldPosition(new THREE.Vector3()),
+            resolution: viewer.mainViewport.resolution2 
+        })
+        this.scale.set(scale, scale, scale); 
+ 
+        if(!dontRender){
+            this.waitRender = true
+        } 
+    
+    }
+    
+    
+    /* update(aimPos){  //仅改fov的版本
+        
+        aimPos = aimPos instanceof THREE.Vector3 ?  aimPos : this.aimPos
+        if(!aimPos  || !this.visible)return
+        
+    
+        //相机位置
+        var playerCamera = viewer.scene.getActiveCamera()
+        var playerPos = playerCamera.position;//viewer.scene.view.getPivot()
+        var dis = playerPos.distanceTo(aimPos);
+         
+        
+        if(dis<magDistance_){
+            this.camera.fov = maxFov
+        }else{
+            this.camera.fov = THREE.Math.radToDeg(Math.atan(radius_ / dis )) * 2 //radius_是能看到的范围半径。当dis大于magDistance_时就放大,否则维持fov为maxFov
+        }
+       
+        this.camera.updateProjectionMatrix()
+        this.camera.position.copy(playerPos)
+        this.camera.lookAt(aimPos)
+        
+        this.quaternion.copy(playerCamera.quaternion);
+  
+        let pointer = viewer.inputHandler.pointer.clone();
+        let margin = 0.4, maxY = 0.4
+        let screenPos = pointer.clone().setY(pointer.y + (pointer.y>maxY ? -margin : margin ))
+       
+                
+        let newPos = new THREE.Vector3(screenPos.x,screenPos.y,0.8).unproject(playerCamera); //z:-1朝外       
+        let dir = newPos.clone().sub(playerPos).normalize().multiplyScalar(10);//这个数值要大于playerCamera.near
+        
+        this.position.copy(playerPos.clone().add(dir))
+          
+        this.aimPos = aimPos
+        this.targetPoint.position.copy(aimPos);
+        
+        var scale = math.getScaleForConstantSize({// 
+            width2d : width2dPX,
+            camera:viewer.scene.getActiveCamera(),  position: this.getWorldPosition(new THREE.Vector3()),
+            resolution: viewer.mainViewport.resolution2 
+        })
+        this.scale.set(scale, scale, scale); 
+ 
+        if(!this.dontRender){
+            this.waitRender = true
+        }   
+    }//位置需要计算,不仅仅是点云,所以需要深度图
+     */
+    
+    
+    
+    
+    
+    render(){ 
+ 
+        if(!this.waitRender)return
+        //this.visible = false;//防止放大镜里有自己
+        viewer.render({
+            target : this.renderTarget,
+            viewports : [this.viewport],
+            camera : this.camera,
+            magnifier : true,
+            rtEDL: this.rtEDL 
+            /* width :this.renderTarget.width,
+            height: this.renderTarget.height, */
+        })
+        //this.visible = true;
+        this.waitRender = false
+        
+        
+        
+        
+    }
+   
+}

+ 271 - 0
src/objects/Reticule.js

@@ -0,0 +1,271 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import {Buttons} from '../defines.js'
+import {transitions, easing, lerp} from '../utils/transitions.js'
+import math from '../utils/math.js'
+
+
+let texLoader = new THREE.TextureLoader()
+let defaultOpacity =  0.7
+ 
+
+
+//鼠标指示小圆片 
+export default class Reticule extends THREE.Mesh{
+    constructor(viewer){
+        var defaultTex = texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png'/* reticule-256x256.png'  */)  
+        super(new THREE.PlaneBufferGeometry(0.11,0.11,1,1),new THREE.MeshBasicMaterial({
+            side: THREE.DoubleSide , 
+            map: defaultTex,
+            transparent:true,
+            depthTest: !1,
+            opacity: defaultOpacity,
+            //depthWrite: !1,
+        })) 
+        this.name = 'reticule'
+        this.defaultTex = defaultTex
+        this.crosshairTex = texLoader.load(Potree.resourcePath+'/textures/reticule_cross_hair.png') 
+        this.forbitTex = texLoader.load(Potree.resourcePath+'/textures/pic-forbid.png') 
+        
+        //this.layers.set(0/* RenderLayers.RETICULE */);
+        this.renderOrder = 100
+        this.layers.set(Potree.config.renderLayers.marker);
+        
+        
+        this.direction = new THREE.Vector3;
+       
+        this.mouseLastMoveTime = Date.now();
+        this.hoverViewport;
+        this.matrixMap = new Map 
+        this.matrixAutoUpdate = false 
+        
+
+        this.hide(0)
+
+        //viewer.inputHandler.addInputListener(this);
+        Potree.settings.intersectWhenHover && viewer.addEventListener('global_mousemove',this.move.bind(this))
+        //viewer.addEventListener('global_click',this.move.bind(this))
+        viewer.addEventListener('global_mousedown',this.move.bind(this))//主要针对触屏
+        
+        
+        
+        
+        this.state = {}
+        
+        let startCrossStyle = ()=>{
+            this.state.cross = true
+            this.judgeTex()
+        }
+        let endCrossStyle = ()=>{
+            this.state.cross = false
+            this.judgeTex()
+        }
+        
+        viewer.addEventListener('measureMovePoint',startCrossStyle) 
+        viewer.addEventListener('endMeasureMove',endCrossStyle)
+        viewer.addEventListener('start_inserting_tag',startCrossStyle) 
+        viewer.addEventListener('endTagMove',endCrossStyle)
+        
+        viewer.addEventListener('reticule_forbit',(e)=>{
+            if(this.state.forbit != e.v){
+                console.log('change forbit ',e.v)
+            }
+            this.state.forbit = e.v
+            this.judgeTex() 
+        })
+        
+         
+        
+        viewer.setObjectLayers(this, 'sceneObjects' )
+    }
+
+    judgeTex(){ 
+        if(this.state.forbit){
+            this.material.map = this.forbitTex 
+        }else if(this.state.cross){
+            this.material.map = this.crosshairTex 
+        }else{
+            this.material.map = this.defaultTex 
+        }
+        
+         
+        viewer.mapViewer && viewer.mapViewer.dispatchEvent({type:'content_changed'})
+    }
+
+
+
+
+    move(e){ 
+        if(e.type == "global_mousemove" && (e.isTouch || e.buttons != Buttons.NONE) && this.state != 'crosshair'){
+            return//按下时不更新,除非拖拽测量
+        }
+           
+        this.mouseLastMoveTime = Date.now()
+        
+        this.updatePosition(e.intersect, e.hoverViewport)
+         
+    }
+
+    hide(duration = 500){ 
+        if(this.hidden)return
+        
+ 
+        
+        this.hidden = !0 
+        transitions.start(lerp.property(this.material , "opacity", 0), duration)
+             
+        this.dispatchEvent({type:'update', visible:false})
+        
+        setTimeout(()=>{
+            this.dispatchEvent({type:'update', visible:false})
+        },duration)
+        
+    }
+
+    show(duration = 300){
+         
+        if(!viewer.getObjVisiByReason(this, 'force'))return
+        //console.log("show Reticule")
+        this.hidden = !1
+        
+        if(this.material.opacity <= 0){
+            transitions.start(lerp.property(this.material, "opacity", defaultOpacity), duration)
+        
+            this.dispatchEvent({type:'update', visible:true})
+            
+            setTimeout(()=>{
+                this.dispatchEvent({type:'update', visible:false})
+            },duration)
+                
+        }
+        
+        
+        
+    }
+
+    //鼠标静止一段时间它就会消失
+    updateVisible(){
+        Date.now() - this.mouseLastMoveTime > 1500 && !this.hidden && this.hide()
+    }
+    
+    
+    updateScale(viewport){
+        let s, camera = viewport.camera
+        if(camera.type == "OrthographicCamera"){
+            
+            var sizeInfo = this.state.cross ? {width2d:500} : {minSize : 100,  maxSize : 400,   nearBound : 100 , farBound :  700}
+            s = math.getScaleForConstantSize($.extend(   sizeInfo ,  
+            {position:this.position, camera, resolution:viewport.resolution/* 2 */} ))
+            
+        }else{
+            
+            let n = camera.position.distanceTo(this.position)
+            s = 1 + .1 * n;
+            n < 1 && (s -= 1 - n)
+        }
+        this.scale.set(s, s, s);
+        
+    }
+
+    updateAtViewports(viewport){//当多个viewports时更新。更新大小等
+    
+        if(viewport.name == 'magnifier' )return
+    
+        if(this.hoverViewport && this.hoverViewport.name == 'mapViewport' && viewport != this.hoverViewport){
+            //若是在地图上更新,在其他viewport要隐藏。因为在地图上无法得知高度。     
+            viewer.updateVisible(this, 'hoverMap', false)
+            return; 
+        }
+        viewer.updateVisible(this, 'hoverMap', true)   
+    
+        var matrix = this.matrixMap.get(viewport)
+        if(!matrix){ 
+            this.updateScale(viewport)
+            this.updateMatrix(); 
+            //this.updateMatrixWorld()
+            this.matrixMap.set(viewport, this.matrix.clone())
+        }else{
+            this.matrix.copy(matrix) 
+            //this.updateMatrixWorld()
+        }
+        
+    }
+   
+    
+    
+
+    
+
+    updatePosition(intersect, viewport ){ //在地图(当地图融合到viewer时)和场景里都显示且完全相同(大小可能不同)
+         
+        if (viewer.getObjVisiByReason(this, 'force')) {//没有被强制隐藏,如进入某个页面后强制不显示
+            if (!intersect /* || !intersect.point.normal */){ 
+                 return //this.hide();   
+            }
+                
+            var atMap = !intersect.location
+            let location = intersect.location || intersect.orthoIntersect.clone()
+            let normal  
+            
+            
+            //地图上要瞬间变化 , 因为要使needRender为true很麻烦
+            this.show(atMap ? 0 : 300);
+            
+            
+            if(atMap){ 
+                normal =  new THREE.Vector3(0,0,1)//地图无normal
+                location.setZ(0);//低于相机高度即可
+                this.direction = normal.clone()
+            }else{
+                /* if(intersect.point){ 
+                    if(intersect.pointcloud){
+                        normal = new THREE.Vector3().fromArray(intersect.point.normal ).applyMatrix4( intersect.pointcloud.rotateMatrix  );
+                    }else{//mesh 
+                        normal = new THREE.Vector3().copy(intersect.point.normal).applyQuaternion(intersect.object.quaternion) 
+                    } 
+                }else{
+                    normal = intersect.normal  //when showPanos
+                } */
+                normal = intersect.normal 
+                if(normal){
+                    let ratio = /* Potree.settings.useDepthTex ? 1 : */ 0.2;   
+                    this.direction = this.direction.multiplyScalar(1-ratio); 
+                    this.direction.add(normal.clone().multiplyScalar(ratio)); 
+                }
+                //this.direction = normal.clone() //改为瞬间变化,否则刚hover上某个点时看起来不太对
+            }
+             
+             
+            
+            
+            this.position.copy(location)/* .add(normal.clone().multiplyScalar(.01));  */
+            this.updateMatrix();  //lookAt之前要保证得到matrix
+            this.lookAt(this.position.clone().add(this.direction));
+            
+            
+            this.hoverViewport = viewport //记录下最近一次hover过的viewport  
+            this.updateScale(viewport)
+            
+            
+            {//存储matrix,节省计算
+                this.updateMatrix(); 
+                //this.updateMatrixWorld()
+                this.matrixMap.clear();//重新计算
+                this.matrixMap.set(viewport, this.matrix.clone())
+                //别处会updateMatrixWorld
+            }
+            
+            this.dispatchEvent({type:'update'})
+            
+            //为什么navvis在校准数据集时每个viewport里reticule的朝向都刚好垂直于屏幕,似乎限定在了一定范围内,还是在pick时就只pick范围内的点?
+            
+            
+        }
+    }
+    
+    //navvis在地图等地方看reticule是有厚度的
+    
+    /* updateMatrixWorld(force){
+        console.log('updateMatrixWorld', force)
+        super.updateMatrixWorld(force) 
+    } */
+}

+ 67 - 24
src/viewer/Sprite.js

@@ -10,6 +10,7 @@ export default class Sprite extends THREE.Mesh{
          
         this.root = options.root || this;
         this.renderOrder = options.renderOrder != void 0 ? options.renderOrder : 4;
+        this.pickOrder = options.pickOrder || 0
         this.sizeInfo = options.sizeInfo
         this.dontFixOrient = options.dontFixOrient
         
@@ -18,31 +19,69 @@ export default class Sprite extends THREE.Mesh{
         this.name = options.name || 'sprite'
         this.useViewport = null
         this.viewports = options.viewports//指定更新的viewports
+        this.visible_ = true
         
-        viewer.addEventListener("camera_changed", (e)=>{
-            /* if(viewer.viewports.length == 1){//直接更新。如果有多个不在这更新,在"render.begin"
-                this.update(e)
-            } */
-            this.update(e)            
-        });
         
+        let update = (e)=>{
+            this.update(e) 
+        }
+        viewer.mapViewer && viewer.mapViewer.addEventListener("camera_changed",  update) 
+        viewer.addEventListener("camera_changed",  update) 
+        /* if(viewer.viewports.length == 1){//直接更新。如果有多个不在这更新,在"render.begin"
+            this.update(e)
+        } */
+         
         
-        viewer.mapViewer.addEventListener("camera_changed", (e)=>{
-            this.update(e)     
-        });
-        
-        viewer.addEventListener("render.begin", (e)=>{//before render
-            //if(viewer.viewports.length > 1) this.update(e)  //magnifier时要禁止吗
+        let applyMatrix = (e)=>{
+            this.applyMatrix(e)
+        }
+        viewer.addEventListener("raycaster", applyMatrix)        //before render
+        viewer.addEventListener("render.begin", applyMatrix) //before render  //magnifier时要禁止吗
             
-            this.applyMatrix(e) 
-        });
-        
-        viewer.on("raycaster", (e)=>{//before render
-            this.applyMatrix(e) 
+        this.addEventListener('dispose', ()=>{
+            viewer.mapViewer && viewer.mapViewer.removeEventListener("camera_changed",  update) 
+            viewer.removeEventListener("camera_changed",  update) 
+            viewer.removeEventListener("raycaster", applyMatrix)        //before render
+            viewer.removeEventListener("render.begin", applyMatrix)
+             
+            this.dispose()
         })
+         
     }
     
+    set visible(v){
+        this.visible_ = v  
+        if(v){
+            this.update()
+        }
+    }
+    get visible(){
+        return this.visible_ 
+    }
     
+    realVisible(){
+        let parent = this
+        
+        let v
+        while(parent){
+            if(parent.visible === false){
+                v = false
+                break; 
+            }
+            parent = parent.parent
+        }
+        v = true;
+        if(!this.latestRealVisi && v){//变为可见后先update 
+            this.latestRealVisi = true
+            setTimeout(()=>{
+                this.update()
+            },1)//延迟 防止无限调用
+            return false
+        }
+        
+        this.latestRealVisi = v
+        return v;
+    }
     
     update(e){
         if(!e){
@@ -51,6 +90,7 @@ export default class Sprite extends THREE.Mesh{
             })
             return;
         }
+        if(!this.root || ! this.realVisible() /* this.visible */ )return
         if(this.viewports && !this.viewports.includes(e.viewport) )return
         if(e.viewport.name == 'magnifier')return
         
@@ -67,20 +107,19 @@ export default class Sprite extends THREE.Mesh{
             
             var scale
             if(info.restricMeshScale){//仅限制最大或最小的话,不判断像素大小,直接限制mesh的scale
-                var dis = camera.position.distanceTo(this.getWorldPosition(new THREE.Vector3()))
+                var dis = camera.position.distanceTo(this.root.getWorldPosition(new THREE.Vector3()))
                 if(dis < info.nearBound){
                     scale = info.scale * dis / info.nearBound
                 }else{
                     scale = info.scale
                 }
-            }else{
-                
+            }else{ 
                 scale = math.getScaleForConstantSize($.extend(info,{//规定下最小最大像素 
-                    camera , position:this.getWorldPosition(new THREE.Vector3()) ,
-                    resolution: e.viewport.resolution2
+                    camera , position:this.root.getWorldPosition(new THREE.Vector3()) ,
+                    resolution: e.viewport.resolution//2
                 }))
                 
-            }
+            } 
             
             if(!isNaN(scale)){
                 this.root.scale.set(scale, scale, scale); 
@@ -97,7 +136,7 @@ export default class Sprite extends THREE.Mesh{
         if(!e)e = {viewport:viewer.mainViewport}//随便写一个viewport
         if(e.viewport.name == 'magnifier')return
         if(this.viewports && !this.viewports.includes(e.viewport) )return
-        
+        if( !this.root || !this.realVisible()  )return
         
         var matrix = this.matrixMap.get(e.viewport);
           
@@ -120,4 +159,8 @@ export default class Sprite extends THREE.Mesh{
     }
     
      
+    dispose(){
+        this.removeAllListeners()
+        this.parent && this.parent.remove(this)
+    }
 }

+ 161 - 0
src/objects/Tag.js

@@ -0,0 +1,161 @@
+
+
+
+import * as THREE from "../../libs/three.js/build/three.module.js"; 
+
+import {LineDraw, MeshDraw} from "../utils/DrawUtil.js";  
+import {TextSprite} from './TextSprite.js' 
+import Sprite from './Sprite.js' 
+
+const renderOrders = {
+    line: 0 ,
+    spot: 15, //高过模型
+}
+const planeGeo = new THREE.PlaneGeometry(1,1)
+let texLoader = new THREE.TextureLoader() 
+
+let lineMat = new THREE.LineBasicMaterial({
+    color: '#ffffff', 
+})
+let spotMat 
+const defaultLineLength = 0.6
+const defaultSpotScale = 0.4
+
+class Tag extends THREE.Object3D{
+    constructor(o){
+        
+        super()
+        
+         
+        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.matrixAutoUpdate = false
+        
+        this.build()
+        
+        /* this.spot.addEventListener('mouseover',()=>{
+            
+             
+        }) */
+        
+    }
+    
+    
+    
+    
+    build(){
+        
+        if(!spotMat){
+            spotMat = new THREE.MeshBasicMaterial({
+                transparent:true,
+                map: texLoader.load(Potree.resourcePath+'/textures/spot_default.png' ),  
+            })
+        }
+        let endPos = this.normal.clone().multiplyScalar(this.lineLength) 
+        
+  
+        this.line = LineDraw.createLine([
+            new THREE.Vector3(0,0,0), 
+            endPos
+        ],  {mat:lineMat})
+        
+        
+        let group = new THREE.Object3D()
+        this.spot = new THREE.Mesh(planeGeo, spotMat)  
+        this.spot.scale.set(defaultSpotScale,defaultSpotScale,defaultSpotScale) 
+        this.titleLabel = new TextSprite({root: group, text:'1', sizeInfo:{width2d:200}, 
+            textColor:{r:255,g:255,b:255,a:1.0},
+            backgroundColor:{r:0,g:0,b:0,a:0.8},
+            borderRadius: 6,  
+            fontsize:13,  fontWeight:'',//thick
+            renderOrder : renderOrders.spot, pickOrder:renderOrders.spot,
+        }) //更新sprite时,实际更新的是root: spot的矩阵
+        this.spot.renderOrder = renderOrders.spot;
+        /* const mainLabelProp = { 
+            backgroundColor: {r: defaultColor.r*255, g: defaultColor.g*255, b: defaultColor.b*255, a:config.measure.default.opacity},
+            textColor: {r: textColor.r*255, g: textColor.g*255, b: textColor.b*255, a: 1.0},
+            fontsize:16, 
+            useDepth : true ,
+            renderOrder : 5, pickOrder:5, 
+        } */
+            
+        this.titleLabel.position.set(0,0.4,0)
+        this.titleLabel.sprite.material.depthTest = this.titleLabel.sprite.material.depthWrite = true
+        
+        
+        group.position.copy(endPos)
+        group.add(this.spot)
+        group.add(this.titleLabel)
+        this.add(group);
+        this.add(this.line)
+        
+        
+        
+        
+        viewer.scene.tags.add(this)
+        
+        
+    }
+    
+    
+    changeTitle(title){
+        this.titleLabel.changeText(title)
+    }
+    
+    
+    updateMatrixWorld(force){ //重写,只为了将root当做parent
+         
+        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);
+        this.titleLabel.dispatchEvent({type:'dispose'})
+        
+    } 
+    
+    
+    
+}
+
+
+
+
+export default Tag
+
+
+
+
+
+
+

+ 24 - 19
src/TextSprite.js

@@ -4,31 +4,31 @@
 //  * adapted from http://stemkoski.github.io/Three.js/Sprite-Text-Labels.html
 //  */
 
-import * as THREE from "../libs/three.js/build/three.module.js";
-import Sprite from './viewer/Sprite' 
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import Sprite from './Sprite.js' 
 
 
 //可能还是要用html写,因为要加按钮和图片
 
-export class TextSprite extends THREE.Object3D{
-	
+export class TextSprite extends THREE.Object3D{ 
+    //注:为了分两层控制scale,不直接extend Sprite
 	constructor( options={}){ 
         super()
 		let map = new THREE.Texture();
 		map.minFilter = THREE.LinearFilter;
 		map.magFilter = THREE.LinearFilter;
         
-        this.sprite = new Sprite({
-            sizeInfo:options.sizeInfo, 
-            renderOrder:options.renderOrder,
-            useDepth: options.useDepth,
-            map,
-            root: this     
-          
-        })
+        this.sprite = new Sprite( Object.assign({
+                root:this
+            }
+            ,options,
+            { 
+                map,
+            })
+        )
         this.add(this.sprite)
         
-        
+        this.fontWeight = options.fontWeight == void 0 ? 'Bold' : options.fontWeight
 		this.rectBorderThick = options.rectBorderThick || 0
 		this.textBorderThick = options.textBorderThick || 0
 		this.fontface = 'Arial';
@@ -39,9 +39,12 @@ export class TextSprite extends THREE.Object3D{
         this.borderColor = options.borderColor || { r: 0, g: 0, b: 0, a: 0.0 };
 		this.borderRadius = options.borderRadius || 6;
         if(options.text != void 0)this.setText(options.text)
-        this.name = options.name
-        
+        this.name = options.name 
+         
 		//this.setText(text);
+        
+        
+        this.addEventListener('dispose', this.dispose.bind(this)) 
 	}
 
 	setText(text){
@@ -85,21 +88,21 @@ export class TextSprite extends THREE.Object3D{
 	updateTexture(){
 		let canvas = document.createElement('canvas');
 		let context = canvas.getContext('2d');
-		context.font = 'Bold ' + this.fontsize + 'px ' + this.fontface; 
+		context.font = this.fontWeight + ' ' + this.fontsize + 'px ' + this.fontface; 
        
-        context["font-weight"] = 100; //语法与 CSS font 属性相同。
+        //context["font-weight"] = 100; //语法与 CSS font 属性相同。
 		// get size data (height depends only on font size)
         
         //this.text = '啊啊啊啊啊啊fag'
         
 		let metrics = context.measureText(this.text );
 		let textWidth = metrics.width;
-		let margin = new THREE.Vector2(this.fontsize, this.fontsize*0.4);
+		let margin = new THREE.Vector2(this.fontsize, Math.max(  this.fontsize*0.4, 10)  );
 		let spriteWidth = 2 * margin.x + textWidth + 2 * this.rectBorderThick;
 		let spriteHeight = 2 * margin.y + this.fontsize + 2 * this.rectBorderThick; 
 		context.canvas.width = spriteWidth;
 		context.canvas.height = spriteHeight;
-		context.font = 'Bold ' + this.fontsize + 'px ' + this.fontface;
+		context.font = this.fontWeight + ' ' + this.fontsize + 'px ' + this.fontface; 
 
          
         let diff = 2//针对英文大部分在baseLine之上所以降低一点(metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2
@@ -165,6 +168,8 @@ export class TextSprite extends THREE.Object3D{
     dispose(){
         this.sprite.material.uniforms.map.value.dispose()
         this.parent && this.parent.remove(this)
+        this.sprite.dispatchEvent({type:'dispose'})
+        this.removeAllListeners()
     }
 
 }

+ 475 - 0
src/objects/fireParticle/explode/ExplodeParticle.js

@@ -0,0 +1,475 @@
+import*as THREE from "../../../../libs/three.js/build/three.module.js";
+import {vertexShader, fragmentShader} from './shader.js'
+import Tween from '../Tween.js'
+import Particle from './Particle.js'
+import {util} from './Util.js'
+import {Shape} from './const.js'
+
+let particleTexture
+
+const getTexture = ()=>{
+    if (!particleTexture) {
+        particleTexture = new THREE.TextureLoader().load(Potree.resourcePath + '/textures/explode.png')
+    }
+    return particleTexture
+}
+const sphereGeo = new THREE.SphereBufferGeometry(1, 10,4);
+const sphereMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"})
+
+const defaults = {
+    position: new THREE.Vector3(0,0,1),
+
+    positionShape: Shape.SPHERE,
+
+    positionRange: new THREE.Vector3(1,1,1),
+    //cube
+
+    radius: 1.3,
+    //sphere
+
+    velocityShape: Shape.SPHERE,
+
+    velocity: new THREE.Vector3(0,0,2),
+    //cube
+    velocityRange: new THREE.Vector3(0,0,3),
+    
+    //sphere
+    speed: 0.4, 
+    speedRange: 1,
+
+    size: 0.4,
+    sizeRange: 2,
+    //sizeTween: new Tween( [0, 0.05, 0.3, 0.45], [0, 1, 3, 0.1] ),
+    sizeTween: [[0, 0.04, 0.2, 1],[0.1, 1, 6, 8]] ,
+
+    color: new THREE.Vector3(1.0,1.0,1.0),
+    colorRange: new THREE.Vector3(0,0,0),
+    colorTween: new Tween(),
+
+    opacity: 1.0,
+    opacityRange: 0.0,
+    opacityTween: new Tween([0, 0.06, 0.3, 0.8, 1],[0, 1, 0.3, 0.05, 0]),
+    blendMode: THREE.AdditiveBlending,
+
+    acceleration: 0.5,
+    accelerationRange: 0,
+
+    angle: 0,
+    angleRange: 0,
+    angleVelocity: 0,
+    angleVelocityRange: 0,
+    angleAcceleration: 0,
+    angleAccelerationRange: 0,
+    
+    strength:1,
+    
+    //particlesPerSecond: 8,
+    particleDeathAge: 0.7 , 
+    recycleTimes : 3 , //每个粒子在一次爆炸后循环次数,循环完毕进入particleSpaceTime,等待下一次爆炸.
+    //爆炸时长: particleDeathAge * (recycleTimes+1)
+    particleSpaceTime:   3, //间隔
+    
+    
+}
+
+class ExplodeParticle extends THREE.Points {
+
+    constructor(params) {
+        super()
+         
+
+        this.age = 0
+        this.alive = true
+        //this.deathAge = 60
+        this.loop = true
+
+        this.blendMode = THREE.NormalBlending
+
+        this.setParameters(params)
+        this.createParticles()
+        this.frustumCulled = false//似乎是禁止相机裁剪,否则会在某些角度消失。但是会不会更耗性能呢?
+        //------------------------------------
+
+        this.setSize({viewport:viewer.mainViewport})
+        this.setFov(viewer.fov)
+        
+        let setSize = (e)=>{
+            if(e.viewport.name != "MainView")return
+            this.setSize(e)
+        }
+        let setFov = (e)=>{
+            this.setFov(e.fov) 
+        }
+            
+        /* let reStart = (e)=>{
+            if(e.v){//重新一个个放出粒子,否则会一股脑儿全部出来,因为同时大于粒子周期了一起重新生成出现。
+                setTimeout(()=>{//会先update一次delta为pageUnvisile的时间才触发 
+                    //console.log('归零') 
+                    //this.reStart()
+                },1) 
+            } 
+        } */
+        viewer.addEventListener('resize',setSize) 
+        viewer.addEventListener('fov_changed',setFov)
+        //viewer.addEventListener('pageVisible', reStart)
+        
+        this.addEventListener('dispose',()=>{
+            viewer.removeEventListener('resize',setSize) 
+            viewer.removeEventListener('fov_changed',setFov)
+            //viewer.removeEventListener('pageVisible', reStart)
+        })  
+ 
+        
+    }
+    
+    
+    computeParams(){
+        if(this.curve){
+            this.position.copy(this.curve.points[0])
+        }
+         
+         
+         
+        const minSize = 0.8, maxSize = 10, minRadiusBound = 0.2, maxRadiusBound = 20;
+        let size = minSize + (maxSize - minSize) * THREE.Math.smoothstep(this.radius*this.strength , minRadiusBound, maxRadiusBound);
+        
+        this.sizeTween.values = this.defaultSizeTween.values.map(e=> e*size)
+        
+        this.particleCount = Math.ceil( this.strength  * this.radius * 5 /*  * this.radius  * this.radius */  )
+     
+        this.speed = defaults.speed * this.radius;
+        this.speedRange = defaults.speedRange * this.radius;
+     
+        console.log(this.particleCount)
+        
+        {
+            this.boundPoints = []
+            this.boundPoints.push(this.position.clone())    
+            let maxSize = this.sizeTween.values.slice().sort((a,b)=>b-a)[0]  
+            let margin = maxSize * 0.35 + 0.5;  
+            let scale = this.radius+margin
+            let sphere = new THREE.Sphere(this.position, scale)//加上防止剪裁
+            this.boundingSphere = sphere //虽然还是会有一些后续移动的会超出 
+            this.boundingBox = new THREE.Box3().setFromCenterAndSize(this.position, new THREE.Vector3(scale*2,scale*2,scale*2)) 
+            /* if(!this.debugSphere){
+                this.debugSphere = new THREE.Mesh(sphereGeo, sphereMat)
+                this.add(this.debugSphere)
+            } 
+            this.debugSphere.scale.set(scale,scale,scale)  */
+        }
+    }
+    getPointsForBound(){
+        return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距
+    }   
+
+    reStart(){
+        this.age = 0;
+        
+        this.createParticles()  
+    }
+
+
+    setParameters(params) {
+        
+        params = $.extend({}, defaults, params)
+         
+         
+        for (var key in params) { 
+            let value = params[key] 
+            if (key == 'position')
+                this.position.copy(value)
+            else if (value instanceof Array && value[0]instanceof Array){
+                this[key] = new Tween(...value)
+            }else if(value instanceof THREE.Vector3 || value instanceof THREE.Color){
+                this[ key ] = value.clone()
+            }else{
+                this[key] = value;
+            }
+        }
+        
+        
+        this.defaultSizeTween = this.sizeTween.clone()
+        //Object.assign(this, params) 
+
+        this.particles = []
+        this.age = 0.0
+        this.alive = true
+        
+        this.geometry = new THREE.BufferGeometry() 
+        this.computeParams() 
+        this.material = new THREE.ShaderMaterial({
+            uniforms: {
+                u_sampler: {
+                    value: this.texture || getTexture()
+                },
+                heightOfNearPlane: {
+                    type: "f",
+                    value: 0
+                }//相对far ,以确保画面缩放时点的大小也会缩放
+            },
+            vertexShader,
+            fragmentShader,
+            transparent: true,
+            alphaTest: 0.5,
+            depthTest: this.blendMode == THREE.NormalBlending,
+            blending: this.blendMode
+        })
+         
+         
+    }
+
+
+    createParticles() {
+        this.particles = []
+        const count = this.particleCount
+        const positionArray = new Float32Array(count * 3)
+        const colorArray = new Float32Array(count * 3)
+
+        const sizeArray = new Float32Array(count)
+        const angleArray = new Float32Array(count)
+        const opacityArray = new Float32Array(count)
+        const visibleArray = new Float32Array(count)
+
+        for (let i = 0; i < count; i++) {
+            const particle = this.createParticle()
+            /* positionArray[i * 3] = particle.position.x
+            positionArray[i * 3 + 1] = particle.position.y
+            positionArray[i * 3 + 2] = particle.position.z
+            colorArray[i * 3] = particle.color.r
+            colorArray[i * 3 + 1] = particle.color.g
+            colorArray[i * 3 + 2] = particle.color.b
+            sizeArray[i] = particle.size
+            angleArray[i] = particle.angel
+            opacityArray[i] = particle.opacity
+            visibleArray[i] = particle.alive */
+            this.particles[i] = particle
+        }
+        this.geometry.setAttribute('position', new THREE.BufferAttribute(positionArray,3))
+        this.geometry.setAttribute('color', new THREE.BufferAttribute(colorArray,3))
+        this.geometry.setAttribute('angle', new THREE.BufferAttribute(angleArray,1))
+        this.geometry.setAttribute('size', new THREE.BufferAttribute(sizeArray,1))
+        this.geometry.setAttribute('visible', new THREE.BufferAttribute(visibleArray,1))
+        this.geometry.setAttribute('opacity', new THREE.BufferAttribute(opacityArray,1))
+
+        
+
+    }
+    
+    
+    
+     
+
+    createParticle() {
+
+        const particle = new Particle()
+        particle.sizeTween = this.sizeTween
+        particle.colorTween = this.colorTween
+        particle.opacityTween = this.opacityTween
+        particle.deathAge = this.particleDeathAge
+
+        if (this.positionShape == Shape.CUBE) {
+            particle.position = util.randomVector3(new THREE.Vector3, this.positionRange)
+        }
+
+        if (this.positionShape == Shape.SPHERE) {
+            /* const z = 2 * Math.random() - 1
+              const t = Math.PI * 2 * Math.random()
+              const r = Math.sqrt(1 - z*z)
+              const vec3 = new THREE.Vector3(r * Math.cos(t), r * Math.sin(t), z)
+              particle.position = vec3.multiplyScalar(this.radius)  */
+
+            const y = 2 * Math.random() - 1
+            const t = Math.PI * 2 * Math.random();
+            const r = Math.sqrt(1 - y * y);
+            const vec3 = new THREE.Vector3(r * Math.cos(t),y,r * Math.sin(t));
+            particle.position = vec3.multiplyScalar(this.radius)
+
+        }
+
+        if (this.velocityShape == Shape.CUBE) {
+            particle.velocity = util.randomVector3(this.velocity, this.velocityRange)
+
+        }
+
+        if (this.velocityShape == Shape.SPHERE) {
+            const direction = new THREE.Vector3().addVectors(particle.position, new THREE.Vector3(0,0,this.radius*2))//向上升?
+            const speed = util.randomValue(this.speed, this.speedRange)
+            particle.velocity = direction.normalize().multiplyScalar(speed)
+        }
+
+        particle.acceleration = util.randomValue(this.acceleration, this.accelerationRange)
+
+        particle.angle = util.randomValue(this.angle, this.angleRange)
+        particle.angleVelocity = util.randomValue(this.angleVelocity, this.angleVelocityRange)
+        particle.angleAcceleration = util.randomValue(this.angleAcceleration, this.angleAccelerationRange)
+
+        particle.size = util.randomValue(this.size, this.sizeRange)
+
+        const color = util.randomVector3(this.color, this.colorRange)
+        particle.color = new THREE.Color().setHSL(color.x, color.y, color.z)
+
+        particle.opacity = util.randomValue(this.opacity, this.opacityRange)
+     
+
+        return particle
+    }
+
+    update(dt) {
+        if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了
+            return
+        }
+        if(this.delayStartTime>0){ // 爆炸延迟
+            return this.delayStartTime -= dt 
+        }
+        
+        
+        
+        if(!Potree.Utils.isInsideFrustum(this.boundingSphere, viewer.scene.getActiveCamera())){
+            viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围
+            return
+        }else{
+            viewer.updateVisible(this,'isInsideFrustum', true )
+        }
+        //const timeRatio = 0.5
+        if(dt > 1){
+            console.log('update dt>1', dt)
+        }
+        //dt *= timeRatio
+        let particleDeathAge = this.particleDeathAge/*  * timeRatio */
+        let particleSpaceTime = this.particleSpaceTime /* * timeRatio */
+
+        const recycleIndices = []
+        const recycleAges = []
+        const recycleRebornCount = []
+        
+        const positionArray = this.geometry.attributes.position.array
+        const opacityArray = this.geometry.attributes.opacity.array
+        const visibleArray = this.geometry.attributes.visible.array
+        const colorArray = this.geometry.attributes.color.array
+        const angleArray = this.geometry.attributes.angle.array
+        const sizeArray = this.geometry.attributes.size.array
+
+        for (let i = 0; i < this.particleCount; i++) {
+            const particle = this.particles[i]
+            if (particle.alive) {
+                particle.update(dt)
+                if (particle.age > particleDeathAge) { 
+                    particle.alive = 0.0
+                    if(particle.rebornCount >= this.recycleTimes){
+                        particle.deadAge = particle.age - particleDeathAge //已死亡时间
+                    }else{//直接循环
+                        recycleIndices.push(i)
+                        recycleAges.push(/* ( */particle.age - particleDeathAge/* )%(this.particleDeathAge ) */)
+                        recycleRebornCount.push(particle.rebornCount+1)
+                    }
+                    
+                }
+                positionArray[i * 3] = particle.position.x
+                positionArray[i * 3 + 1] = particle.position.y
+                positionArray[i * 3 + 2] = particle.position.z
+                colorArray[i * 3] = particle.color.r
+                colorArray[i * 3 + 1] = particle.color.g
+                colorArray[i * 3 + 2] = particle.color.b
+                visibleArray[i] = particle.alive
+                opacityArray[i] = particle.opacity
+                angleArray[i] = particle.angle
+                sizeArray[i] = particle.size
+            }else{
+                if(particle.rebornCount >= this.recycleTimes){
+                    if(particle.age > particleDeathAge) {//其他已经死亡的粒子的时间继续增加
+                        particle.deadAge += dt
+                    }
+                }
+            }                
+                
+             
+            
+            if (particle.rebornCount >= this.recycleTimes && particle.age > particleDeathAge) {//已经死亡 
+                if(particle.deadAge >=  particleSpaceTime){//死亡时间超过设定的间隔时间后重启 
+                    recycleIndices.push(i)
+                    let wholeTime = particleDeathAge * (this.recycleTimes+1) + particleSpaceTime 
+                    recycleAges.push((particle.deadAge - particleSpaceTime)% wholeTime ) //剩余时间就是重生后的age
+                    recycleRebornCount.push(0)
+                } 
+            }
+            
+        }
+        
+        
+        
+
+        this.geometry.attributes.size.needsUpdate = true
+        this.geometry.attributes.color.needsUpdate = true
+        this.geometry.attributes.angle.needsUpdate = true
+        this.geometry.attributes.visible.needsUpdate = true
+        this.geometry.attributes.opacity.needsUpdate = true
+        this.geometry.attributes.position.needsUpdate = true
+
+        if (!this.alive)
+            return
+
+        if (this.age < particleDeathAge) {
+            let startIndex = Math.round(this.particleCount * (this.age + 0)/ particleDeathAge)
+            let endIndex = Math.round(this.particleCount * (this.age + dt)/ particleDeathAge)
+            if (endIndex > this.particleCount) {
+                endIndex = this.particleCount
+            }
+            for (let i = startIndex; i < endIndex; i++) {
+                this.particles[i].alive = 1.0
+            }
+        }
+
+
+
+        for (let j = 0; j < recycleIndices.length; j++) {
+            let i = recycleIndices[j]
+            
+            this.particles[i] = this.createParticle()
+            this.particles[i].alive = 1.0  //出生
+            this.particles[i].age = recycleAges[j]  
+            this.particles[i].rebornCount= recycleRebornCount[j]
+            /* if(this.particles[i].age < particleDeathAge){
+                positionArray[i * 3] = this.particles[i].position.x
+                positionArray[i * 3 + 1] = this.particles[i].position.y
+                positionArray[i * 3 + 2] = this.particles[i].position.z
+                visibleArray[i] = particle.alive?
+            } */
+        }
+        this.geometry.attributes.position.needsUpdate = true
+
+        this.age += dt
+
+        if (this.age > this.deathAge && !this.loop) {
+            this.alive = false
+        }
+
+    }
+
+    setSize(e) {
+        let viewport = e.viewport
+        this.screenHeight = viewport.resolution.y
+        this.setPerspective(this.fov, this.screenHeight)
+    }
+
+    setFov(fov) {
+        this.fov = fov
+        this.setPerspective(this.fov, this.screenHeight)
+    }
+
+    setPerspective(fov, height) {
+        //this.uniforms.heightOfNearPlane.value = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
+        let far = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
+        this.material.uniforms.heightOfNearPlane.value = far
+    }
+    updateGeometry(){ 
+        this.computeParams()
+        this.reStart()  
+    }
+    dispose(){
+        this.geometry.dispose();
+        this.material.dispose();
+        this.dispatchEvent('dispose') 
+    }
+}
+
+export default ExplodeParticle

+ 57 - 0
src/objects/fireParticle/explode/Particle.js

@@ -0,0 +1,57 @@
+// import { vertexShader, fragmentShader } from './shader'
+// import Tween from './tween'
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+
+const DEG2RAD = Math.PI / 180
+
+class Particle {
+  
+  constructor() {
+    
+    this.position = new THREE.Vector3()
+    this.velocity = new THREE.Vector3()
+     
+    
+    this.angle = 0
+    this.angleVelocity = 0
+    this.angleAcceleration = 0
+    this.size = 16
+    this.color = new THREE.Color()
+    this.opacity = 1
+
+    this.rebornCount = 0//重生次数
+    this.age = 0
+    this.alive = 0 //注意,一开始时是未出生的
+    this.deadAge = 0//已死亡时间
+    this.sizeTween = null
+    this.colorTween = null
+    this.opacityTween = null
+  }
+
+  update(dt) { 
+    //s = s0 + (v0 + at) * t 或 lastS + delta(vt)
+    
+    this.position.add(this.velocity.clone().multiplyScalar(dt))
+    this.velocity.multiplyScalar( 1+this.acceleration*dt )
+    
+    this.angle += this.angleVelocity * DEG2RAD * dt
+    this.angleVelocity += this.angleAcceleration * DEG2RAD * dt
+    this.age += dt
+
+    if(this.sizeTween.times.length > 0) {
+      this.size = this.sizeTween.lerp(this.age/this.deathAge)
+    }
+
+    if(this.colorTween.times.length > 0) {
+      const colorHSL = this.colorTween.lerp(this.age/this.deathAge)
+      this.color = new THREE.Color().setHSL(colorHSL.x, colorHSL.y, colorHSL.z)
+    }
+
+    if(this.opacityTween.times.length > 0) {
+      this.opacity = this.opacityTween.lerp(this.age/this.deathAge)
+    }
+  }
+
+}
+
+export default Particle

+ 18 - 0
src/objects/fireParticle/explode/Util.js

@@ -0,0 +1,18 @@
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+export class Util {
+    constructor() {}
+
+    randomValue(min, max) {
+        //return min + max * (Math.random() - 0.5)
+        let p = Math.random()
+        return min * p + max * (1-p)
+    }
+
+    randomVector3(min, max) {
+        const rand3 = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5)
+        return new THREE.Vector3().addVectors(min, new THREE.Vector3().multiplyVectors(max, rand3))
+    }
+}
+
+const util = new Util();
+export { util };

+ 4 - 0
src/objects/fireParticle/explode/const.js

@@ -0,0 +1,4 @@
+export const Shape = {
+    CUBE: 1,
+    SPHERE: 2
+  }

+ 40 - 0
src/objects/fireParticle/explode/shader.js

@@ -0,0 +1,40 @@
+export const vertexShader = `
+  attribute vec3 color;
+  attribute float size;
+  attribute float angle;
+  attribute float opacity;
+  attribute float visible;
+  varying vec4 vColor;
+  varying float vAngle;
+  uniform float heightOfNearPlane;
+  
+  void main() {
+    if(visible > 0.5) {
+      vColor = vec4(color, opacity);
+    } else {
+      vColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+    vAngle = angle;
+    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
+    gl_Position = projectionMatrix * mvPosition;
+    
+    gl_PointSize = ( heightOfNearPlane * size ) / gl_Position.w;
+  }
+` 
+
+export const fragmentShader = `
+  uniform sampler2D u_sampler;
+  varying vec4 vColor;
+  varying float vAngle;
+  void main() {
+    gl_FragColor = vColor;
+    float u = cos(vAngle);
+    float v = sin(vAngle);
+    vec2 uv = vec2(
+      u * (gl_PointCoord.x - 0.5) + v * (gl_PointCoord.y - 0.5) + 0.5, 
+      u * (gl_PointCoord.y - 0.5) - v * (gl_PointCoord.x - 0.5) + 0.5
+    );
+    vec4 texture = texture2D(u_sampler, uv);
+    gl_FragColor = gl_FragColor * texture;
+  }
+`

+ 274 - 0
src/objects/fireParticle/fire/FireParticle.js

@@ -0,0 +1,274 @@
+// import { vertexShader, fragmentShader } from './shaders'
+// import Tween from './tween'
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+import { vertexShader, fragmentShader } from './shader.js'
+
+let ONE_SPRITE_ROW_LENGTH = 0.25    //对应着色器的0.25
+let texture 
+ 
+
+const getTexture = ()=>{
+    if(!texture){
+        texture = new THREE.TextureLoader().load( Potree.resourcePath+'/textures/fire.png')
+    }
+    return texture
+}
+
+const boxGeo = new THREE.BoxBufferGeometry(1,1,1,1);
+const boxMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"})
+
+
+
+class FireParticle extends THREE.Points{
+  
+    constructor (prop) {
+        super()
+        
+        for ( var key in prop ){
+            this[key] = prop[key]
+        }
+        
+        
+         
+        this.strength = this.strength || 1
+        
+        
+         
+        this.radius = prop.radius || 1;
+        this.height = prop.height || 5;  
+        
+        this.computeParams()
+        this.geometry = this.createGeometry( this.radius, this.height, this.particleCount );
+         
+        
+        if(this.color == void 0)   this.color = 0xff3200
+        this.createMaterial( );  //小蓝火:0x00338f
+        
+      
+         
+        //---?:
+        this.velocity = new THREE.Vector3()
+        this.acceleration = new THREE.Vector3()
+        
+        this.angle = 0
+        this.angleVelocity = 0
+        this.angleAcceleration = 0
+        this.size = 16
+        
+        this.opacity = 1
+
+        this.age = 0
+        this.alive = 0
+
+        this.sizeTween = null
+        this.colorTween = null
+        this.opacityTween = null
+
+        
+         
+        this.setSize({viewport:viewer.mainViewport})
+        this.setFov(viewer.fov)
+        
+        let setSize = (e)=>{
+            if(e.viewport.name != "MainView")return
+            this.setSize(e)
+        }
+        let setFov = (e)=>{
+            this.setFov(e.fov) 
+        }
+            
+        viewer.addEventListener('resize',setSize) 
+        viewer.addEventListener('fov_changed',setFov)
+          
+        this.addEventListener('dispose',()=>{
+            viewer.removeEventListener('resize',setSize) 
+            viewer.removeEventListener('fov_changed',setFov)
+        })  
+          
+    }
+
+ 
+    computeParams(){  
+        let length = (this.curve ? this.curve.wholeLength : 0) + this.radius * 2 //加上首尾的半径
+        
+        
+        const minSize = 0.3, maxSize = 3, minRadiusBound = 0.3, maxRadiusBound = 10;
+        this.size = minSize + (maxSize - minSize) * THREE.Math.smoothstep(this.radius, minRadiusBound, maxRadiusBound);
+        //console.log('fire material  particle size:', size )
+        
+        this.particleCount =  Math.ceil(   length * Math.sqrt(this.strength  * this.height )   * this.radius / (this.size * this.size) *  25  )        
+        //console.log('fire particleCount',this.particleCount)
+    }
+    getPointsForBound(){
+        return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距
+    }
+
+    getBound(points){ // points为生成点(圆心)
+        this.boundPoints = []  
+        let boundingBox = new THREE.Box3()
+        
+         
+        let margin = this.size * 0.13 + 0.3; 
+        
+        points.forEach(bottom=>{ 
+            let top = bottom.clone()
+            top.z +=  this.height 
+            boundingBox.expandByPoint(bottom);
+            boundingBox.expandByPoint(top);
+            this.boundPoints.push(bottom,top)            
+        })
+        let xyExpand = this.radius+margin 
+        boundingBox.expandByVector(new THREE.Vector3(xyExpand,xyExpand,margin)) 
+        this.boundingBox = boundingBox
+        
+        /* if(!this.debugBox){
+            this.debugBox = new THREE.Mesh(boxGeo, boxMat)
+            this.add(this.debugBox)
+        }
+        
+        this.debugBox.scale.copy(boundingBox.getSize(new THREE.Vector3))
+        this.debugBox.position.copy(boundingBox.getCenter(new THREE.Vector3))  */
+         
+    }
+
+
+    createGeometry( radius, height, particleCount){
+        let geometry = new THREE.BufferGeometry()
+         
+        
+        let count , points
+        if(this.positions.length>1){
+             
+            const spaceDis = 0.2;//间隔距离
+            
+            count = Math.ceil(this.curve.wholeLength / spaceDis) + 1 
+            //console.log('count', count)
+            points = this.curve.getSpacedPoints( count );  //得到的数量会比count多一个
+            count = points.length  
+            //得到的点不太均匀,两端容易点少。
+            this.getBound(points) 
+            
+        }else{
+            this.getBound(this.positions) 
+        }
+         
+        
+        var position = new Float32Array(particleCount * 3);
+        var randam = new Float32Array(particleCount);
+        var sprite = new Float32Array(particleCount);
+        var centerHeight = new Float32Array(particleCount);
+        
+        for (var i = 0; i < particleCount; i++) {
+            
+            var center = new THREE.Vector3().copy(this.positions.length>1 ? points[Math.floor(i/particleCount * count)] : this.positions[0])
+            centerHeight[i] = center.z
+            
+            if (i === 0) { 
+                // to avoid going out of Frustum
+                position[i * 3 + 0] = center.x;
+                position[i * 3 + 1] = center.y;
+                position[i * 3 + 2] = center.z;
+            }else{  
+                var r = Math.sqrt(Math.random()) * radius;
+                var angle = Math.random() * 2 * Math.PI;
+                position[i * 3 + 0] = center.x + Math.cos(angle) * r; 
+                position[i * 3 + 1] = center.y + Math.sin(angle) * r;
+                position[i * 3 + 2] = center.z + (radius - r) / radius * height/2 + height/2; //不太明白这句为什么能达到height高度
+               
+                sprite[i] = 0.25 * (Math.random() * 4 | 0);
+                randam[i] = Math.random();
+                //center在底部
+            }
+            
+            
+        }
+        
+        geometry.setAttribute('centerHeight', new THREE.BufferAttribute(centerHeight, 1));
+        geometry.setAttribute('position', new THREE.BufferAttribute(position, 3));
+        geometry.setAttribute('randam', new THREE.BufferAttribute(randam, 1));
+        geometry.setAttribute('sprite', new THREE.BufferAttribute(sprite, 1));
+        return geometry;
+    }
+    
+    
+    
+    
+    updateGeometry(){ 
+        this.computeParams()
+        this.geometry.dispose() 
+        this.geometry = this.createGeometry( this.radius, this.height, this.particleCount )
+        this.material.uniforms.size.value = this.size
+    }
+
+
+
+
+    createMaterial(){
+         
+        
+        const material = new THREE.ShaderMaterial( {
+            uniforms:{
+                color: { type: "c", value: new THREE.Color(this.color) },
+                size: { type: "f", value: this.size},
+                u_sampler: { type: "t", value: getTexture() },
+                time: { type: "f", value: 0.0 },
+                heightOfNearPlane: { type: "f", value:0},  //相对far ,以确保画面缩放时点的大小也会缩放
+                height :{ type: "f", value:this.height}  ,
+            },
+            vertexShader,
+            fragmentShader,
+            blending: THREE.AdditiveBlending, //加法融合模式 glBlendFunc(GL_ONE, GL_ONE)
+            depthTest: true,
+            depthWrite: false,
+            transparent: true
+
+        } ); 
+        this.material = material
+        this.setPerspective(this.fov, this.screenHeight)
+    }
+
+
+    setSize(e){
+        let viewport = e.viewport
+        this.screenHeight = viewport.resolution.y
+        this.setPerspective(this.fov, this.screenHeight)  
+    }
+    
+    setFov(fov){
+        this.fov = fov
+        this.setPerspective(this.fov, this.screenHeight) 
+    }
+    
+    
+    setPerspective(fov, height){
+        //this.uniforms.heightOfNearPlane.value = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
+        let far = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
+        this.material.uniforms.heightOfNearPlane.value = far 
+    }
+
+    
+    
+    update(delta){
+        if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了
+            return
+        }
+        if(!Potree.Utils.isInsideFrustum(this.boundingBox, viewer.scene.getActiveCamera())){
+            viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围
+            //console.log('unvi')
+            return
+        }else{
+            viewer.updateVisible(this,'isInsideFrustum', true )
+        } 
+        delta *= 1//更改速度
+        
+        this.material.uniforms.time.value = (this.material.uniforms.time.value + delta) % 1; 
+    }
+    
+    dispose(){
+        this.geometry.dispose();
+        this.material.dispose();
+        this.dispatchEvent('dispose') 
+    }
+}
+
+export default FireParticle

+ 63 - 0
src/objects/fireParticle/fire/shader.js

@@ -0,0 +1,63 @@
+export const vertexShader = `
+    attribute float randam;
+    attribute float sprite;
+    attribute float centerHeight;  //add
+    
+    //uniform float fireHeight;  //add 
+    uniform float time;
+    uniform float size;
+    uniform float heightOfNearPlane;
+    
+    
+    
+    
+    //varying float heightRatio;
+    varying float vSprite;
+    varying float vOpacity; 
+    float PI = 3.14;
+
+    float quadraticIn( float t ) 
+    { 
+        float tt = t * t;
+        return tt * tt; 
+        //变化曲线  越来越快
+    } 
+    
+    void main() {
+        float progress = fract( time + ( 2.0 * randam - 1.0 ) );
+        float progressNeg = 1.0 - progress;
+        float ease = quadraticIn( progress );
+        float influence = sin( PI * ease );
+        //vec3 newPosition = position * vec3( 1.0,  1.0 , ease);
+        vec3 newPosition = position;
+        newPosition.z = (newPosition.z - centerHeight) * ease + centerHeight;
+         
+        gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
+        gl_PointSize = ( heightOfNearPlane * size ) / gl_Position.w;
+        vOpacity = min( influence * 4.0, 1.0 ) * progressNeg;
+        vSprite = sprite;
+        
+        //heightRatio = (newPosition.z - centerHeight) / fireHeight ;
+        
+    }
+` 
+
+export const fragmentShader = `
+    uniform vec3 color;
+    uniform sampler2D u_sampler;
+
+    varying float vSprite;
+    varying float vOpacity;
+    //varying float heightRatio;
+
+    void main() 
+    {
+        
+       
+        vec2 texCoord = vec2(gl_PointCoord.x * 0.25 + vSprite, gl_PointCoord.y);
+         
+        gl_FragColor = vec4( texture2D( u_sampler, texCoord ).xyz * color * vOpacity, 1.0 );
+          
+         
+    }
+`

+ 57 - 0
src/objects/fireParticle/smoke/Particle.js

@@ -0,0 +1,57 @@
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+import Tween from '../Tween.js'
+export default class Particle{
+    constructor(prop={}){
+        this.position     = new THREE.Vector3();
+        this.velocity     = new THREE.Vector3(); // units per second
+         
+        this.angle             = 0;
+        this.angleVelocity     = 0; // degrees per second
+        this.angleAcceleration = 0; // degrees per second, per second
+        this.size = 16.0;
+    
+        this.color   = new THREE.Color();
+        this.opacity = 1.0;
+                
+        this.age   = 0;
+        this.alive = 0; // use float instead of boolean for shader purposes	
+        this.lastChangeVage = 0 //add
+
+
+        this.sizeTween    = prop.sizeTween || new Tween( [0, 1], [32, 128] );
+		this.opacityTween = prop.opacityTween || new Tween( [0.8, 2], [0.5, 0] );
+		this.colorTween   = prop.colorTween || new Tween( [0.4, 1], [ new THREE.Vector3(0,0,0.2), new THREE.Vector3(0, 0, 0.5) ] );
+   
+   
+   
+   }
+
+    update(dt)
+    {
+        this.position.add(this.velocity.clone().multiplyScalar(dt))
+        this.velocity.multiplyScalar( 1+this.acceleration*dt )
+        
+        // convert from degrees to radians: 0.01745329251 = Math.PI/180
+        this.angle         += this.angleVelocity     * 0.01745329251 * dt;
+        this.angleVelocity += this.angleAcceleration * 0.01745329251 * dt;
+
+        this.age += dt;
+        
+        // if the tween for a given attribute is nonempty,
+        //  then use it to update the attribute's value
+
+        if ( this.sizeTween.times.length > 0 )
+            this.size = this.sizeTween.lerp( this.age/this.deathAge );
+                    
+        if ( this.colorTween.times.length > 0 )
+        {
+            var colorHSL = this.colorTween.lerp( this.age/this.deathAge );
+            this.color = new THREE.Color().setHSL( colorHSL.x, colorHSL.y, colorHSL.z );
+        }
+        
+        if ( this.opacityTween.times.length > 0 )
+        {
+            this.opacity = this.opacityTween.lerp( this.age/this.deathAge);
+        }
+    }
+}

+ 594 - 0
src/objects/fireParticle/smoke/SmokeParticle.js

@@ -0,0 +1,594 @@
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+import Particle from './Particle.js'
+import Tween from '../Tween.js'
+import { vertexShader, fragmentShader } from './shader.js'
+
+const Type = Object.freeze({ "CUBE":1, "SPHERE":2 });
+let particleTexture  
+
+const getTexture = ()=>{
+    if(!particleTexture){
+        particleTexture = new THREE.TextureLoader().load( Potree.resourcePath+'/textures/smokeparticle.png')
+    }
+    return particleTexture
+}
+const boxGeo = new THREE.BoxBufferGeometry(1,1,1,1);
+const boxMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"})
+
+
+
+
+const defaults = 
+{
+    positions: [],
+    positionStyle    : "sphere",
+    positionBase     : new THREE.Vector3( 0, 0, 0 ),
+ 
+    positionSpread   : new THREE.Vector3( 1, 1, 0), //cube
+     
+    radius   :   1,       // sphere
+        
+    velocityStyle    : 'cube',
+     
+    velocityBase     : new THREE.Vector3( 0,  0,  0.5),     // cube  基础速度
+    velocitySpread   : new THREE.Vector3( 1, 1, 0.3), 
+    
+    accelerationBase : 0.3,             //基础加速度
+    accelerationSpread : 0.6,	
+    
+    //没使用
+    speedBase  : 0.1,       //sphere
+    speedSpread : 0.5,
+          
+    
+
+    angleBase               : 0,
+    angleSpread             : 360,
+    angleVelocityBase       : 1,
+    angleVelocitySpread     : 30,
+    angleAccelerationBase   : 1,
+    angleAccelerationSpread : 5,
+        
+    sizeBase    :   0,  
+    sizeSpread  :   0,
+    sizeTween    : [[0, 0.3,   1], [0.3, 1.4,  6 ]], 
+    
+    
+    colorBase   :   new THREE.Vector3(0.0, 1.0, 0.5), 
+    colorSpread :   new THREE.Vector3(0.0, 0.0, 0.0),
+    colorTween   : new Tween( [0.2, 1], [ new THREE.Vector3(0,0,0.4), new THREE.Vector3(0, 0, 0.1) ] ),
+
+    opacityBase     :   0.1,//1.0,
+    opacitySpread   :   0.2,
+    opacityTween :[ [0, 0.1, 0.9, 1], [0.1, 0.4 , 0.03, 0 ] ], 
+     
+    //particlesPerSecond : 20,
+    strength : 1,
+    particleDeathAge   : 3,  //从底下升起后能持续的时间		
+    //emitterDeathAge    : 60 // time (seconds) at which to stop creating particles.
+    height : 3,
+};
+
+
+const debugSphere = new THREE.Mesh(new THREE.SphereBufferGeometry(0.03, 5,5), new THREE.MeshBasicMaterial({color:'white',depthTest:false}))
+
+export default class SmokeParticle extends THREE.Points{
+    constructor(prop={}) {
+        super()
+        
+         
+        this.blendStyle = THREE.NormalBlending; // false; 
+        this.emitterAge = 0.0;
+        //this.emitterAlive = true;
+       
+        prop = $.extend({}, defaults, prop)
+        for ( var key in prop ){
+            let value = prop[key] 
+            if(value instanceof Array && value[0] instanceof Array ) this[ key ] = new Tween(...value)
+            else if(value instanceof THREE.Vector3 || value instanceof THREE.Color){
+                this[ key ] = value.clone()
+            }else{
+                this[ key ] = value
+            }
+        }
+        
+        this.defaultSizeTween = this.sizeTween.clone()
+        this.defaultOpacityTween = this.opacityTween.clone()
+        
+        
+        this.geometry = new THREE.BufferGeometry()
+        this.computeParams()
+        this.createMaterial()
+        this.createGeometry()
+        
+        this.dynamic = true;
+        this.sortParticles = true; 
+        this.frustumCulled = false//似乎是禁止相机裁剪,否则会在某些角度消失。但是会不会更耗性能呢?
+       
+        prop.position && this.position.copy(prop.position)
+        
+        
+        
+        
+        //---------------------------------------
+        this.setSize({viewport:viewer.mainViewport})
+        this.setFov(viewer.fov)
+        
+        let setSize = (e)=>{
+            if(e.viewport.name != "MainView")return
+            this.setSize(e)
+        }
+        let setFov = (e)=>{
+            this.setFov(e.fov) 
+        }
+        /* let reStart = (e)=>{
+            if(e.v){//重新一个个放出粒子,否则会一股脑儿全部出来,因为同时大于粒子周期了一起重新生成出现。
+                setTimeout(()=>{//会先update一次delta为pageUnvisile的时间才触发 
+                    //console.log('归零') 
+                    //this.reStart()
+                },1) 
+            } 
+        } */
+        viewer.addEventListener('resize',setSize) 
+        viewer.addEventListener('fov_changed',setFov)
+        //viewer.addEventListener('pageVisible', reStart)
+        
+        this.addEventListener('dispose',()=>{
+            viewer.removeEventListener('resize',setSize) 
+            viewer.removeEventListener('fov_changed',setFov)
+            //viewer.removeEventListener('pageVisible', reStart)
+        })   
+        
+    }
+    
+    
+    
+    computeParams(){   
+         
+        let length = (this.curve ? this.curve.wholeLength : 0) + this.radius * 2 //加上首尾的半径
+        //注意:烟最低高度一米, 0<strength<1
+        if(this.positionStyle == 'cube'){
+            this.positionSpread.set(this.radius,this.radius,0)
+        } 
+        this.velocityBase.set(0,0, (this.height - 0.5 * this.accelerationBase * this.particleDeathAge * this.particleDeathAge) / this.particleDeathAge )
+        //let height = this.velocityBase.z * this.particleDeathAge + 0.5 * this.accelerationBase * this.particleDeathAge * this.particleDeathAge;//s = V0 * t + 0.5 * a * t*t ;     
+        this.velocityBase.z = Math.max(0,this.velocityBase.z);
+        this.particleCount =  Math.ceil(  length * Math.sqrt(this.strength * this.height * this.radius )   )  
+        this.particleCount = Math.max(5,this.particleCount)
+        { 
+            const minSize = 1, maxSize = 2, minBound = 0.01, maxBound = 1;
+            let size = minSize + (maxSize - minSize) * THREE.Math.smoothstep( this.strength, minBound, maxBound);
+                
+            this.sizeTween.values = this.defaultSizeTween.values.map(e=> e*size)
+        }
+        { 
+            const minSize = 1 , maxSize = 1.5, minBound = 0.01, maxBound = 1;
+            let opac = minSize + (maxSize - minSize) * THREE.Math.smoothstep( this.strength, minBound, maxBound);
+                
+            this.opacityTween.values = this.defaultOpacityTween.values.map(e=> e*opac )
+        }
+         
+        //console.log('smoke  particleCount',this.particleCount)
+        
+        
+        
+        
+    }
+
+    reStart(){
+        this.emitterAge = 0; 
+        this.createGeometry()  
+    }
+
+
+    updateGeometry(){ 
+        this.computeParams()
+        this.reStart() 
+        
+    }
+    
+    
+    createParticle(center)
+    {
+        var particle = new Particle({
+            sizeTween : this.sizeTween,
+            opacityTween : this.opacityTween,
+            colorTween : this.colorTween,
+        });
+        particle.deathAge = this.particleDeathAge
+        particle.center = center
+         
+        
+        if (this.positionStyle == 'cube')
+            particle.position = this.randomVector3( this.positionBase, this.positionSpread ); 
+        if (this.positionStyle == 'sphere')
+        {
+            /* var z = 2 * Math.random() - 1    
+            var t = Math.PI * 2 * Math.random();
+            var r = Math.sqrt( 1 - z*z ) ;
+            var vec3 = new THREE.Vector3( r * Math.cos(t), r * Math.sin(t), z );
+            particle.position = new THREE.Vector3().addVectors( this.positionBase, vec3.multiplyScalar( this.radius ) );
+           */
+            //怎么改半径
+            let y = 2 * Math.random() - 1    
+            let t = Math.PI * 2 * Math.random();
+            let r = Math.sqrt( 1 - y*y ) ; //因为 r*r = 1-y*y = x*x + z*z = r*r(cos^2 + sin^2 );
+            let lowDownRatio = 0.2 //压低近平面
+            let vec3 = new THREE.Vector3( r * Math.cos(t), y, Math.abs(r * Math.sin(t) ) * lowDownRatio);
+            particle.position = new THREE.Vector3().addVectors( this.positionBase, vec3.multiplyScalar( this.radius ) );
+           
+          
+        } 
+         
+        particle.position.add(center)//add
+         
+         
+         
+         
+         
+        if ( this.velocityStyle == 'cube' )
+        {
+            particle.velocity  = this.randomVector3( this.velocityBase,  this.velocitySpread ); 
+        }
+        if ( this.velocityStyle == 'sphere' )  
+        {
+            //var direction = particle.position.clone()
+            var direction = new THREE.Vector3(0,0,1) //烟应该都是向上的
+            var speed     = this.randomValue( this.speedBase, this.speedSpread );
+            particle.velocity  = direction.normalize().multiplyScalar( speed );
+        }
+        
+        particle.acceleration = this.randomValue( this.accelerationBase, this.accelerationSpread ); 
+
+        particle.angle             = this.randomValue( this.angleBase,             this.angleSpread );
+        particle.angleVelocity     = this.randomValue( this.angleVelocityBase,     this.angleVelocitySpread );
+        particle.angleAcceleration = this.randomValue( this.angleAccelerationBase, this.angleAccelerationSpread );
+
+        particle.size = this.randomValue( this.sizeBase, this.sizeSpread );
+
+        var color = this.randomVector3( this.colorBase, this.colorSpread );
+        particle.color = new THREE.Color().setHSL( color.x, color.y, color.z );
+        
+        particle.opacity = this.randomValue( this.opacityBase, this.opacitySpread );
+
+        particle.age   = 0;
+        particle.alive = 0; // particles initialize as inactive
+        return particle;
+    }			
+
+
+    getPointsForBound(){
+        return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距
+    }
+
+    getBound(points){ // points为生成点(圆心)
+        this.boundPoints = [] 
+        let boundingBox = new THREE.Box3()
+        
+        
+        let maxSize = this.sizeTween.values.slice().sort((a,b)=>b-a)[0]            
+        let margin0 = maxSize * 0.11
+        let margin1 = margin0 + 0.5   ;//保守估计还会飘出这么多距离吧: size + 飘动  
+        
+         
+        points.forEach(bottom=>{ 
+            let top = bottom.clone()
+            top.z +=  this.height 
+            boundingBox.expandByPoint(bottom);
+            boundingBox.expandByPoint(top); 
+            this.boundPoints.push(bottom,top)
+        })
+        let xyExpand = this.radius+margin1 
+        boundingBox.expandByVector(new THREE.Vector3(xyExpand,xyExpand,0))
+        boundingBox.min.z -= margin0 
+        boundingBox.max.z += margin1 
+       
+       
+       
+        this.boundingBox = boundingBox
+        
+        /* if(!this.debugBox){
+            this.debugBox = new THREE.Mesh(boxGeo, boxMat)
+            this.add(this.debugBox)
+        }
+        
+        this.debugBox.scale.copy(boundingBox.getSize(new THREE.Vector3))
+        this.debugBox.position.copy(boundingBox.getCenter(new THREE.Vector3))  */  
+         
+    }
+
+    createGeometry(){
+        this.particleArray = []
+        const positions = [];
+        const colors = [];
+        const alives = [];
+        const opacitys = [];
+        const sizes = [];
+        const angles = [];
+               
+        let count, points;
+        if(this.positions.length>1){
+             
+            const spaceDis = 0.6;//间隔距离
+            
+            count = Math.ceil(this.curve.wholeLength / spaceDis) + 1 
+             
+            points = this.curve.getSpacedPoints( count );  
+            
+            count = points.length
+            
+            /* points.forEach(e=>  { 
+                var sphere = debugSphere.clone();
+                sphere.position.copy(e)
+                viewer.scene.scene.add(sphere)
+            }) */
+            let haventGetPoints = points.slice() 
+            var getRanPoints = function(i){
+                var a = Math.random()
+                let choseIndex = Math.floor(haventGetPoints.length * a)
+                var point = haventGetPoints[choseIndex]
+                if(haventGetPoints.length == 1){
+                    haventGetPoints = points.slice()  
+                }else{
+                    haventGetPoints.splice(choseIndex, 1)
+                }
+                return point
+            }
+            
+            
+            this.getBound(points)
+        }else{
+            this.getBound(this.positions)
+        }
+        
+        
+        
+        
+        
+        for (var i = 0; i < this.particleCount; i++)
+        {
+            var center = new THREE.Vector3().copy(this.positions.length>1 ? getRanPoints(i)  : this.positions[0])
+             
+            //var center = new THREE.Vector3().copy(this.positions.length>1 ? points[Math.floor(i/this.particleCount * count)] : this.positions[0])
+              
+             
+            // remove duplicate code somehow, here and in update function below.
+            this.particleArray[i] = this.createParticle(center);
+            positions[3*i] = this.particleArray[i].position.x
+            positions[3*i+1] = this.particleArray[i].position.y
+            positions[3*i+2] = this.particleArray[i].position.z
+
+            colors[3*i] = this.particleArray[i].color.r 
+            colors[3*i+1] = this.particleArray[i].color.g
+            colors[3*i+2] = this.particleArray[i].color.b
+
+            alives[i] = this.particleArray[i].alive
+            opacitys[i] = this.particleArray[i].opacity
+            sizes[i] = this.particleArray[i].size
+            angles[i] = this.particleArray[i].angle
+        }
+
+        this.geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array(positions), 3  ));
+        this.geometry.setAttribute( 'customColor', new THREE.BufferAttribute( new Float32Array(colors), 3 ) );
+        this.geometry.setAttribute( 'customVisible', new THREE.BufferAttribute( new Float32Array(alives), 1 ) );
+        this.geometry.setAttribute( 'customOpacity', new THREE.BufferAttribute( new Float32Array(opacitys), 1 ) );
+        this.geometry.setAttribute( 'customSize', new THREE.BufferAttribute( new Float32Array(sizes), 1 ) );
+        this.geometry.setAttribute( 'customAngle', new THREE.BufferAttribute( new Float32Array(angles), 1 ) );
+    }
+    
+    createMaterial(){
+        this.material = new THREE.ShaderMaterial( 
+        {
+            uniforms: 
+            {
+                u_sampler:   { type: "t", value: getTexture() },
+                heightOfNearPlane: { type: "f", value:0}  //相对far ,以确保画面缩放时点的大小也会缩放
+            },
+            vertexShader:   vertexShader,vertexShader,
+            fragmentShader: fragmentShader,
+            transparent: true,
+            alphaTest: 0.5, // if having transparency issues, try including: alphaTest: 0.5, 
+            blending: this.blendStyle,
+            depthTest: this.blendStyle != THREE.NormalBlending
+        });
+        
+        
+        this.setPerspective(this.fov, this.screenHeight)
+        
+        
+    }
+    
+
+    update(dt){
+        if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了
+            return
+        }
+        if(!Potree.Utils.isInsideFrustum(this.boundingBox, viewer.scene.getActiveCamera())){
+            viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围
+            //console.log('unvi')
+            return
+        }else{
+            viewer.updateVisible(this,'isInsideFrustum', true )
+        } 
+        
+        
+        
+        if(dt > 1){
+            console.log('update dt>1', dt)
+        }
+         
+        //dt *= 0.5;
+        
+        const recycleIndices = [];
+        const recycleAges = []
+        
+        
+        const positions = [];
+        const colors = [];
+        const alives = [];
+        const opacitys = [];
+        const sizes = [];
+        const angles = [];
+    
+       
+        
+        
+        
+        
+        for (var i = 0; i < this.particleCount; i++)
+        {
+            if ( this.particleArray[i].alive )
+            {
+                  
+                if ( this.velocityStyle == 'cube' )
+                {        //一定几率改变下方向
+                    let ratio = Math.random()
+                    if(this.particleArray[i].age - this.particleArray[i].lastChangeVage > this.particleDeathAge*ratio  ){
+                        
+                        this.particleArray[i].velocity = this.randomVector3( this.velocityBase, this.velocitySpread ); 
+                        
+                        this.particleArray[i].lastChangeVage = this.particleArray[i].age
+                    }
+                }else{
+                    /* if(this.particleArray[i].age - this.particleArray[i].lastChangeVage > this.particleDeathAge*0.3  ){
+                        if( Math.random()>0.1){//一定几率改变下方向
+                            var speed  = this.randomValue( this.speedBase, this.speedSpread ); 
+                            this.particleArray[i].velocity = this.randomVector3( new THREE.Vector3,   new THREE.Vector3(1,1,1) ); 
+                            this.particleArray[i].velocity.normalize().multiplyScalar( speed );
+                        }
+                        this.particleArray[i].lastChangeVage = this.particleArray[i].age
+                    } */
+                    
+                    
+                }
+                 
+                
+                this.particleArray[i].update(dt);
+
+                // check if particle should expire
+                // could also use: death by size<0 or alpha<0.
+                if ( this.particleArray[i].age > this.particleDeathAge ) 
+                {
+                    this.particleArray[i].alive = 0.0;
+                    recycleIndices.push(i);
+                    recycleAges.push((this.particleArray[i].age - this.particleDeathAge)%(this.particleDeathAge ))
+                } 
+                
+                
+                // update particle properties in shader
+                positions[3*i] = this.particleArray[i].position.x
+                positions[3*i+1] = this.particleArray[i].position.y
+                positions[3*i+2] = this.particleArray[i].position.z
+
+                colors[3*i] = this.particleArray[i].color.r 
+                colors[3*i+1] = this.particleArray[i].color.g
+                colors[3*i+2] = this.particleArray[i].color.b
+
+                alives[i] = this.particleArray[i].alive
+                opacitys[i] = this.particleArray[i].opacity
+                sizes[i] = this.particleArray[i].size
+                angles[i] = this.particleArray[i].angle
+            }		
+        }
+
+        // check if particle emitter is still running
+        //if ( !this.emitterAlive ) return;
+
+        this.geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array(positions), 3 ) );
+        this.geometry.setAttribute( 'customColor', new THREE.BufferAttribute( new Float32Array(colors), 3 ) );
+        this.geometry.setAttribute( 'customVisible', new THREE.BufferAttribute( new Float32Array(alives), 1 ) );
+        this.geometry.setAttribute( 'customOpacity', new THREE.BufferAttribute( new Float32Array(opacitys), 1 ) );
+        this.geometry.setAttribute( 'customSize', new THREE.BufferAttribute( new Float32Array(sizes), 1 ) );
+        this.geometry.setAttribute( 'customAngle', new THREE.BufferAttribute( new Float32Array(angles), 1 ) );
+
+        this.geometry.attributes.customColor.needsUpdate = true;
+        this.geometry.attributes.customVisible.needsUpdate = true;
+        this.geometry.attributes.customOpacity.needsUpdate = true;
+        this.geometry.attributes.customSize.needsUpdate = true;
+        this.geometry.attributes.customAngle.needsUpdate = true;
+
+        // if no particles have died yet, then there are still particles to activate
+        if ( this.emitterAge < this.particleDeathAge ) //开始时一个个放出来
+        {
+            
+            let particlesPerSecond = this.particleCount / this.particleDeathAge
+            // determine indices of particles to activate
+            var startIndex = Math.round( particlesPerSecond * (this.emitterAge +  0) );
+            var endIndex = Math.round( particlesPerSecond * (this.emitterAge + dt) );
+            if  ( endIndex > this.particleCount ) 
+                endIndex = this.particleCount; 
+                
+            for (var i = startIndex; i < endIndex; i++)
+                this.particleArray[i].alive = 1.0;		
+        }
+
+        // if any particles have died while the emitter is still running, we imediately recycle them
+        for (var j = 0; j < recycleIndices.length; j++)
+        {
+            var i = recycleIndices[j]; 
+            this.particleArray[i] = this.createParticle(this.particleArray[i].center);
+            this.particleArray[i].alive = 1.0; // activate right away
+            this.particleArray[i].age = recycleAges[j]
+            positions[3*i] = this.particleArray[i].position.x
+            positions[3*i+1] = this.particleArray[i].position.y
+            positions[3*i+2] = this.particleArray[i].position.z
+        }
+        this.geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array(positions), 3 ) );
+        this.geometry.attributes.position.needsUpdate = true;
+
+        // stop emitter?
+        this.emitterAge += dt;
+        //if ( this.emitterAge > this.emitterDeathAge )  this.emitterAlive = false;
+    }
+
+    randomValue(base, spread)
+    {
+        //return base + spread * (Math.random() - 0.5);
+        let p = Math.random()
+        return base * p + spread * (1-p)
+        
+    }
+
+    randomVector3(base, spread)
+    {
+        var rand3 = new THREE.Vector3( Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5 );
+        return new THREE.Vector3().addVectors( base, new THREE.Vector3().multiplyVectors( spread, rand3 ) );
+    }
+    
+    
+    
+    
+    setSize(e){
+        let viewport = e.viewport
+        this.screenHeight = viewport.resolution.y
+        this.setPerspective(this.fov, this.screenHeight)  
+    }
+    
+    setFov(fov){
+        this.fov = fov
+        this.setPerspective(this.fov, this.screenHeight) 
+    }
+    
+    
+    setPerspective(fov, height){
+        //this.uniforms.heightOfNearPlane.value = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
+        let far = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
+        this.material.uniforms.heightOfNearPlane.value = far 
+    }
+    
+    dispose(){
+        this.geometry.dispose();
+        this.material.dispose();
+        this.dispatchEvent('dispose') 
+    }
+}
+
+
+/* 
+    改进:如果有必要
+    
+    根据curve中分成的点,分成多个簇,每个簇掌管该部分的可见性和particle的数量。
+    在camera_changed时根据远近修改每个簇的particle的数量,当然不会大于初始创建的个数。多出的随机隐藏。
+
+
+ */

+ 0 - 0
src/objects/fireParticle/smoke/shader.js


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä