xzw 2 years ago
parent
commit
ac62ff0041
65 changed files with 7874 additions and 2200 deletions
  1. 12 9
      libs/three.js/build/three.module.js
  2. 1 1
      libs/three.js/loaders/DDSLoader.js
  3. 117 170
      libs/three.js/loaders/DRACOLoader.js
  4. 92 35
      libs/three.js/loaders/GLTFLoader.js
  5. 481 499
      libs/three.js/loaders/KTX2Loader.js
  6. 2 2
      libs/three.js/loaders/OBJLoader.js
  7. 52 0
      libs/three.js/loaders/draco/draco_decoder.js
  8. BIN
      libs/three.js/loaders/draco/draco_decoder.wasm
  9. 104 0
      libs/three.js/loaders/draco/draco_wasm_wrapper.js
  10. 21 0
      libs/three.js/loaders/ktx/basis_transcoder.js
  11. BIN
      libs/three.js/loaders/ktx/basis_transcoder.wasm
  12. 10 5
      src/PointCloudOctree.js
  13. 40 19
      src/Potree.js
  14. 46 6
      src/PotreeRenderer.js
  15. 43 0
      src/materials/BasicMaterial.js
  16. 25 21
      src/materials/DepthBasicMaterial.js
  17. 1 1
      src/materials/EyeDomeLightingMaterial.js
  18. 0 106
      src/materials/InfiniteGridMaterial.js
  19. 1 1
      src/materials/ModelTextureMaterial.js
  20. 17 9
      src/materials/shaders/edl.fs
  21. 62 32
      src/modules/CameraAnimation/CameraAnimation.js
  22. 98 58
      src/modules/Images360/Images360.js
  23. 18 9
      src/modules/Images360/Panorama.js
  24. 14 4
      src/modules/Images360/tile/TileDownloader.js
  25. 2 0
      src/modules/Images360/tile/TilePrioritizer.js
  26. 253 0
      src/modules/Particles/ParticleEditor.js
  27. 52 29
      src/modules/datasetAlignment/Alignment.js
  28. 616 0
      src/modules/mergeModel/MergeEditor.js
  29. 285 108
      src/modules/panoEdit/panoEditor.js
  30. 651 0
      src/modules/route/RouteGuider.js
  31. 33 18
      src/modules/siteModel/SiteModel.js
  32. 28 14
      src/navigation/FirstPersonControls.js
  33. 264 112
      src/navigation/InputHandler.js
  34. 109 14
      src/navigation/OrbitControls.js
  35. 141 0
      src/objects/InfiniteGridHelper.js
  36. 2 2
      src/objects/Magnifier.js
  37. 32 22
      src/objects/Reticule.js
  38. 30 6
      src/objects/Sprite.js
  39. 161 0
      src/objects/Tag.js
  40. 17 17
      src/objects/TextSprite.js
  41. 273 40
      src/objects/tool/Compass.js
  42. 39 20
      src/objects/tool/Measure.js
  43. 36 17
      src/objects/tool/MeasuringTool.js
  44. 95 0
      src/objects/tool/TagTool.js
  45. 1981 0
      src/objects/tool/TransformControls.js
  46. 1 1
      src/objects/tool/VolumeTool.js
  47. 50 23
      src/objects/tool/ctrlPolygon.js
  48. 7 7
      src/objects/tool/mapClipBox.js
  49. 49 32
      src/settings.js
  50. 391 16
      src/start.js
  51. 23 8
      src/utils.js
  52. 7 0
      src/utils/Common.js
  53. 2 2
      src/utils/CursorDeal.js
  54. 48 0
      src/utils/History.js
  55. 122 376
      src/utils/SplitScreen.js
  56. 9 2
      src/utils/math.js
  57. 7 2
      src/utils/transitions.js
  58. 87 94
      src/viewer/EDLRenderer.js
  59. 6 2
      src/viewer/Scene.js
  60. 30 23
      src/viewer/View.js
  61. 6 6
      src/viewer/map/Map.js
  62. 1 1
      src/viewer/map/MapViewer.js
  63. 30 4
      src/viewer/sidebar.js
  64. 580 194
      src/viewer/viewer.js
  65. 61 1
      改bug的历史.txt

+ 12 - 9
libs/three.js/build/three.module.js

@@ -6845,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 ++ ) {
@@ -8540,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;
 
 			}
@@ -36774,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 );
@@ -42117,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 ) {
@@ -42128,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 );

+ 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
 
 	};
 

File diff suppressed because it is too large
+ 481 - 499
libs/three.js/loaders/KTX2Loader.js


+ 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 ) {
 

File diff suppressed because it is too large
+ 52 - 0
libs/three.js/loaders/draco/draco_decoder.js


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


File diff suppressed because it is too large
+ 104 - 0
libs/three.js/loaders/draco/draco_wasm_wrapper.js


File diff suppressed because it is too large
+ 21 - 0
libs/three.js/loaders/ktx/basis_transcoder.js


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


+ 10 - 5
src/PointCloudOctree.js

@@ -237,7 +237,7 @@ export class PointCloudOctree extends PointCloudTree {
         
         
         this.pcoGeometry.addEventListener('updateNodeMaxLevel', this.updateNodeMaxLevel.bind(this))
-        
+        this.isPointcloud = true    //add
 	}
 
 
@@ -274,7 +274,7 @@ export class PointCloudOctree extends PointCloudTree {
             this.nodeMaxLevel = level 
             //viewer.dispatchEvent({type:'updateNodeMaxLevel', pointcloud: this, nodeMaxLevel:level}) 
              
-            console.log('updateNodeMaxLevel ' + this.dataset_id + " : "+ this.nodeMaxLevel)                
+            //console.log('updateNodeMaxLevel ' + this.dataset_id + " : "+ this.nodeMaxLevel)                
               
             this.setPointLevel()//重新计算
              
@@ -1996,10 +1996,15 @@ export class PointCloudOctree extends PointCloudTree {
         } else {
             this.temp.pointOpacity = num
         }
-         
+        
+        if(Potree.settings.editType == 'merge'){ //not AdditiveBlending
+            return this.material.opacity = num
+        }
+      
         if (num == 1) {
             this.material.opacity = 1
-        } else {
+        } else { 
+
             let str = (Potree.settings.sizeFitToLevel?'sizeFit:':'')+ (canMoreThanOne ? 'canMoreThanOne:':'') +this.temp.pointOpacity+':'+this.maxLevel+':'+this.nodeMaxLevel
             let value = this.temp.opacity[str]  //储存。防止每次渲染(反复切换density)都要算。
             if(value){
@@ -2073,7 +2078,7 @@ export class PointCloudOctree extends PointCloudTree {
     
     
     ifContainsPoint(pos){
-        if(!this.bound.containsPoint(pos))return
+        if(!this.bound || !this.bound.containsPoint(pos))return
         var points = this.getUnrotBoundPoint()
         return math.isPointInArea(points, null, pos)  
     } 

+ 40 - 19
src/Potree.js

@@ -1,5 +1,5 @@
 export {config, settings} from "./settings.js";
-export {start, panoEditStart} from "./start.js";
+export * from "./start.js";
  
 
 export * from "./Actions.js";
@@ -143,35 +143,48 @@ 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=>{ 
+            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 = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/datasets`
         //现在只能加载得了本地的了
-        path = `${Potree.scriptPath}/data/${Potree.settings.number}/getDataSet.json`
+        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)
     
 }
 
@@ -180,6 +193,7 @@ export async function loadDatasets(callback){//之后直接把path写进来
 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]
@@ -203,15 +217,20 @@ export async function loadMapEntity(datasetId, force){
     } 
     
     needLoads.forEach(dataset_id=>{
-        let floorplanType = Potree.settings.floorplanType[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){ 
+        /* 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`
             
-        }
+        } */
+        path = `${prefix}/laser/tiledMap/${Potree.settings.number}/tiledMap/${floorplanType}/${dataset_id}` 
+        
         Potree.settings.floorplanRequests[dataset_id] = true //开始加载了
         return loadFile(path, callback.bind(this,  dataset_id, floorplanType)  )
     })
@@ -227,8 +246,9 @@ export async function loadPanos(datasetId, callback){
         path = `/laser/filter/${Potree.settings.number}/query` + 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.scriptPath}/data/${Potree.settings.number}/panos-${datasetId}.json`
+        path = `${Potree.settings.urls.prefix}/laser/filter/${Potree.settings.number}/query` + query
+       
          
     }
     return loadFile(path, callback) 
@@ -305,7 +325,7 @@ export function Log(value, color, fontSize){
 
  
 
-export function loadPointCloud(path, name, sceneCode, timeStamp, callback){
+export function loadPointCloud(path, name, sceneCode, timeStamp, callback, onError){
 	let loaded = function(e){
 		e.pointcloud.name = name;
         e.pointcloud.sceneCode = sceneCode //对应4dkk的场景码
@@ -334,6 +354,7 @@ export function loadPointCloud(path, name, sceneCode, timeStamp, callback){
 				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);

+ 46 - 6
src/PotreeRenderer.js

@@ -691,7 +691,7 @@ export class Renderer {
 
 			let node = stack.pop();
 
-			if (node instanceof PointCloudTree) {
+			if (node instanceof PointCloudTree) { 
 				octrees.push(node);
 				continue;
 			}
@@ -1227,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);
@@ -1572,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);
 		}
 

+ 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

+ 25 - 21
src/materials/DepthBasicMaterial.js

@@ -26,7 +26,9 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
 		}  
         
         let defines = {};
-        if(o.useDepth && Features.EXT_DEPTH.isSupported())defines.useDepth = ''
+        
+        let useDepth = o.useDepth && Features.EXT_DEPTH.isSupported()/*  && Potree.settings.matUseDepth */
+        if(useDepth )defines.useDepth = ''
         if(o.map)defines.use_map = '' 
         super({ 
             uniforms,
@@ -42,31 +44,33 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
             this.opacity = o.opacity
         }
        
-        if(o.useDepth && Features.EXT_DEPTH.isSupported()) 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(useDepth) this.useDepth_ = true
         
-        let viewport = viewer.mainViewport;
-             
-        setSize( {viewport} )
         
-        viewer.addEventListener('resize',(e)=>{ 
-            if(!e.viewport || e.viewport.camera.isPerspectiveCamera){//地图不需要
-                setSize(e)
-                //console.log(this.name +  viewportOffset.toArray())     
-            } 
-        })  
       
         
         if(this.useDepth){  
+        
+            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('camera_changed', (e)=>{
                 if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) 

+ 1 - 1
src/materials/EyeDomeLightingMaterial.js

@@ -73,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++) {

+ 0 - 106
src/materials/InfiniteGridMaterial.js

@@ -1,106 +0,0 @@
-
-import * as THREE from "../../libs/three.js/build/three.module.js";
- 
-
- 
-let vertexShader = `
-           
-   varying vec3 worldPosition;
-   
-   uniform float uDistance;
-   
-   void main() {
-   
-        vec3 pos = position.xzy * uDistance;
-        pos.xz += cameraPosition.xz;
-        
-        worldPosition = pos;
-        
-        gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
-   
-   }
-   ` 
-
-
-let fragmentShader = `
-   
-   varying vec3 worldPosition;
-   
-   uniform float uSize1;
-   uniform float uSize2;
-   uniform vec3 uColor;
-   uniform float uDistance;
-    
-    
-    
-    float getGrid(float size) {
-    
-        vec2 r = worldPosition.xz / 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);
-    }
-    
-   void main() {
-   
-        
-          float d = 1.0 - min(distance(cameraPosition.xz, worldPosition.xz) / 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);
-        
-          if ( gl_FragColor.a <= 0.0 ) discard;
-        
-   
-   }
-`
-
-
-
-
-
-
-
-
-export default class InfiniteGridMaterial extends THREE.ShaderMaterial{
-    constructor(o={}){ 
-        let color = o.color instanceof THREE.Color ? o.color : new THREE.Color(o.color || 'white');
-        let size1 = o.size1 || 10;
-        let size2 = o.size2 || 100; 
-        let distance = o.distance || 8000;
- 
-        let uniforms = { 
-            uSize1: {
-                value: size1
-            },
-            uSize2: {
-                value: size2
-            },
-            uColor: {
-                value: color
-            },
-            uDistance: {
-                value: distance
-            }
-        } 
-        
-        super({
-            uniforms,
-            vertexShader,
-            fragmentShader,
-            transparent: true,
-            side :  THREE.DoubleSide,
-            extensions: {
-                derivatives: true
-            }
-        }) 
-    }
-}

+ 1 - 1
src/materials/ModelTextureMaterial.js

@@ -370,7 +370,7 @@ export default class ModelTextureMaterial extends THREE.RawShaderMaterial {
     
     
     updateDepthTex(pano){
-        if( !Potree.settings.hasDepthTex || !pano || !pano.depthTex || pano!=this.pano0 && pano!=this.pano1)return
+        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 

+ 17 - 9
src/materials/shaders/edl.fs

@@ -38,16 +38,16 @@ float response(float depth){
 	
 	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);  //获取差值
+			//}
 		}
 	}
 	
@@ -60,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);
     } 
     

+ 62 - 32
src/modules/CameraAnimation/CameraAnimation.js

@@ -68,9 +68,10 @@ export class CameraAnimation extends THREE.EventDispatcher{
 
         this.targets = [];  
 		this.createPath();
-        this.duration = 5;
+        this.duration = 5; 
 		this.percent = 0;
         this.currentIndex = 0
+        this.durations = []
         this.quaternions = [];
         
         if(!Potree.settings.isTest){
@@ -178,6 +179,7 @@ export class CameraAnimation extends THREE.EventDispatcher{
         }else{
             position.copy(posInfo.position)
             target.copy(posInfo.target)
+             
         }
             
         this.posCurve.addPoint(position, index/* , true */)
@@ -185,6 +187,9 @@ export class CameraAnimation extends THREE.EventDispatcher{
         let targetSvg = new HandleSvg(target, colors.target)
         targetSvg.visible = this.visible 
         this.targets = [...this.targets.slice(0,index), targetSvg, ...this.targets.slice(index,length)]
+        if(this.useDurSlice){//不使用全局的duration,而是分段的
+            this.durations = [...this.durations.slice(0,index), posInfo.duration, ...this.durations.slice(index,length)]
+        }
         
         
 		this.dispatchEvent({
@@ -241,7 +246,9 @@ export class CameraAnimation extends THREE.EventDispatcher{
 			index
 		});
         this.targetLines.remove(this.targetLines.children[index])
-        
+        if(this.useDurSlice){
+            this.durations.splice(index, 1)
+        }
         
     }
 
@@ -312,41 +319,64 @@ export class CameraAnimation extends THREE.EventDispatcher{
         if(length<2){
             return this.newPointsPercents = []
         }
-        const maxSpaceDur = this.duration / length  //每两点之间修改间隔时间后,最大时间
-        const durPerRad = 0.8     //每弧度应该占用的时间
-        const minSpaceDur =  Math.min(0.8, maxSpaceDur)//每两点之间修改间隔时间后,最小时间
-        const maxAngleSpaceDur = THREE.Math.clamp(durPerRad * Math.PI, minSpaceDur, maxSpaceDur )// 最大可能差距是180度
-        
-        
-        
-        var percents = this.posCurve.pointsPercent; 
         var newPercents = [0];
         
         
-        for(let i=1;i<length;i++){
-            let diff = (percents[i] - percents[i-1]) * this.duration  //间隔时间
-            let percent 
-            let curMin = minSpaceDur
-            if(diff < maxAngleSpaceDur){ //若小于最大旋转时间
-                let rad = this.quaternions[i].angleTo(this.quaternions[i-1]) 
-                curMin = THREE.Math.clamp(rad * durPerRad, minSpaceDur, maxSpaceDur)
- 
-            }  
-
-            diff = Math.max(diff, curMin) 
-            percent = newPercents[i-1] + (diff / this.duration) //得到新的percent
+        
+        if(this.useDurSlice){ //已经设定好了每一段的duration的话
+            let sums = [0]
+            let sum = 0, last
+            for(let i=0;i<length-1;i++){ //去掉最后一个duration,因为已到终点
+                let duration = this.durations[i];
+                sum += duration;   
+                last = duration;
+                sums.push(sum) 
+            }
+            for(let i=1;i<length;i++){ 
+                newPercents.push(sum == 0 ? i/length : sums[i] / sum)
+            } 
+             
+        }else{
             
+            const maxSpaceDur = this.duration / length  //每两点之间修改间隔时间后,最大时间
+            const durPerRad = 0.8     //每弧度应该占用的时间
+            const minSpaceDur =  Math.min(0.8, maxSpaceDur)//每两点之间修改间隔时间后,最小时间
+            const maxAngleSpaceDur = THREE.Math.clamp(durPerRad * Math.PI, minSpaceDur, maxSpaceDur )// 最大可能差距是180度
             
-            newPercents.push(percent) 
-        }
-        
-        let maxPercent = newPercents[length-1]  //最后一个,若扩充过时间,就会>1
+            
+            
+            var percents = this.posCurve.pointsPercent; 
+            
+            
+            
+            for(let i=1;i<length;i++){
+                let diff = (percents[i] - percents[i-1]) * this.duration  //间隔时间
+                let percent 
+                let curMin = minSpaceDur
+                if(diff < maxAngleSpaceDur){ //若小于最大旋转时间
+                    let rad = this.quaternions[i].angleTo(this.quaternions[i-1]) 
+                    curMin = THREE.Math.clamp(rad * durPerRad, minSpaceDur, maxSpaceDur)
+     
+                }  
+
+                diff = Math.max(diff, curMin) 
+                percent = newPercents[i-1] + (diff / this.duration) //得到新的percent
+                
+                
+                newPercents.push(percent) 
+            }
+            
+            let maxPercent = newPercents[length-1]  //最后一个,若扩充过时间,就会>1
 
 
-        if( !math.closeTo(maxPercent, 1)){
-            let scale = 1 / maxPercent //需要压缩的比例 <1  这一步会让实际得到的间隔更小 
-            newPercents = newPercents.map(e=> e*=scale ) 
+            if( !math.closeTo(maxPercent, 1)){
+                let scale = 1 / maxPercent //需要压缩的比例 <1  这一步会让实际得到的间隔更小 
+                newPercents = newPercents.map(e=> e*=scale ) 
+            }
+                
+            
         }
+        
         this.newPointsPercents = newPercents;
         //console.log(newPercents)
     }
@@ -524,7 +554,7 @@ export class CameraAnimation extends THREE.EventDispatcher{
                 percent = 0
                 hasPlayedTime += e.delta
                 transitionRatio = startTransitionRatio;
-                console.log('延迟开始') 
+                //console.log('延迟开始') 
                 if(hasPlayedTime > startDelay){
                     tStart = performance.now(); 
                 }
@@ -559,7 +589,7 @@ export class CameraAnimation extends THREE.EventDispatcher{
                     this.pause()
                 }else{ 
                     hasStoppedTime += e.delta
-                    console.log('延迟结束') 
+                    //console.log('延迟结束') 
                 }
                 
 			}
@@ -608,7 +638,7 @@ export class CameraAnimation extends THREE.EventDispatcher{
         this.posCurve.dispose()
         //this.targetCurve.dispatchEvent({type:'dispose'}) 
         this.targets.forEach(e=>e.dispose())
-        
+        this.durations = []
         this.node.parent.remove(this.node);
     }
 }

+ 98 - 58
src/modules/Images360/Images360.js

@@ -63,8 +63,8 @@ export class Images360 extends THREE.EventDispatcher{
         
          
         this.cube = new THREE.Mesh(new THREE.BoxBufferGeometry(1,1,1,1),new ModelTextureMaterial());
- 
-        this.cube.visible = false; 
+        viewer.updateVisible(this.cube,'showSkybox', false )
+        
          
         this.cube.layers.set(Potree.config.renderLayers.skybox)
         this.cube.name = 'skyboxCube'
@@ -108,7 +108,7 @@ export class Images360 extends THREE.EventDispatcher{
         
         this.depthSampler = new DepthImageSampler(); 
         this.addEventListener('loadedDepthImg',(e)=>{
-            this.updateDepthTex(e.pano)
+            e.loaded && this.updateDepthTex(e.pano)
         })
         
         
@@ -141,7 +141,8 @@ export class Images360 extends THREE.EventDispatcher{
         
 
         let click = (e) => {//不用"mouseup" 是因为 mouseup有drag object时也会触发 
-            if(Potree.settings.unableNavigate || this.flying  || !e.isTouch && e.button != THREE.MOUSE.LEFT || e.drag &&  e.drag.object //拖拽结束时不算
+            if(e.clickElement ||
+                Potree.settings.unableNavigate || this.flying  || !e.isTouch && e.button != THREE.MOUSE.LEFT || e.drag &&  e.drag.object //拖拽结束时不算
                || Potree.settings.editType == 'pano' && viewer.modules.PanoEditor.activeViewName != 'mainView'
                 ||   Potree.settings.editType == 'merge' && !e.intersectPoint || viewer.inputHandler.hoveredElements[0] && viewer.inputHandler.hoveredElements[0].isModel && e.intersectPoint.distance > viewer.inputHandler.hoveredElements[0].distance
             )  return 
@@ -150,6 +151,8 @@ export class Images360 extends THREE.EventDispatcher{
             if(Potree.settings.editType != 'pano' && Potree.settings.editType != 'merge'){
                 if( e.hoverViewport == viewer.mapViewer.viewports[0]){
                     return viewer.mapViewer.dispatchEvent(e/* {type:'global_click',e } */) 
+                }else if(e.hoverViewport != viewer.mainViewport){ //如数据集校准其他viewport
+                    return
                 }
             }
              
@@ -167,7 +170,7 @@ export class Images360 extends THREE.EventDispatcher{
 
         viewer.addEventListener("global_mousemove", (e) => { 
             if(!Potree.settings.unableNavigate && Potree.settings.ifShowMarker && e.hoverViewport == viewer.mainViewport){//如果不显示marker,就在点击时再更新
-                this.updateClosestPano(e.intersectPoint)
+                this.updateClosestPano(e.intersect)
             }
 		});
         
@@ -248,7 +251,7 @@ export class Images360 extends THREE.EventDispatcher{
                 },
                 set:  (mode)=> {
                     latestRequestMode = mode
-                    console.warn('Request setMode: ' + mode)  
+                    //console.warn('Request setMode: ' + mode)  
                                    
                     if(mode != displayMode){ 
                         let config = Potree.config.displayMode[mode]
@@ -273,17 +276,24 @@ export class Images360 extends THREE.EventDispatcher{
                                 //要改成飞进最近的。。。 
                                 if(this.panos.length == 0)return
                                 //this.modeChanging = true //主要是因为到全景图不会立刻成功
+                                let wait = ()=>{
+                                    this.removeEventListener('flyToPanoDone',wait)  
+                                    if(latestRequestMode == mode ){
+                                        Potree.settings.displayMode = mode 
+                                    } 
+                                }
                                 this.flyToPano({
                                     pano: this.findNearestPano(),   
-                                    dealDoneWhenCancel:true,
-                                    callback: ()=>{
+                                    //dealDoneWhenCancel:true,
+                                    /* callback: ()=>{ 
                                         setTimeout(()=>{ //防止循环,所以延迟
                                            if(latestRequestMode == mode ){
                                                 Potree.settings.displayMode = mode 
                                             } 
                                         },1)  
-                                    }
+                                    } */
                                 }) 
+                                this.addEventListener('flyToPanoDone',wait)   //等待飞行完毕。flyToPano的callback可能不执行所以换这个。但也可能被cancel
                                 
                                 return;
                             }else{
@@ -324,7 +334,7 @@ export class Images360 extends THREE.EventDispatcher{
                         viewer.scene.pointclouds.forEach(e=>{
                             viewer.updateVisible(e, 'displayMode', config2.showPoint, 2 ) 
                         })
-                        this.cube.visible = config2.showSkybox
+                        
                         if(config2.pointUsePanoTex){  
                             viewer.scene.pointclouds.forEach(e=>{
                                 e.material.setProjectedPanos(this.currentPano,this.currentPano,  1) 
@@ -335,7 +345,10 @@ export class Images360 extends THREE.EventDispatcher{
                             })
                         }
                         
-                        this.cube.visible = config.atPano.showSkybox 
+
+                        viewer.updateVisible(this.cube,'showSkybox',config2.showSkybox )// this.cube.visible = config2.showSkybox
+                        
+                        //this.cube.visible = config.atPano.showSkybox 
                         if(this.cube.visible){
                             //this.cube.material.setProjectedPanos(this.currentPano, this.currentPano, 1)
                         
@@ -362,7 +375,7 @@ export class Images360 extends THREE.EventDispatcher{
                             viewport: 
                         }) */
                         
-                        viewer.mainViewport.unableChangePos = !config.canLeavePano
+                        //viewer.mainViewport.unableChangePos = !config.canLeavePano
                          
                         
                         displayMode = mode
@@ -406,7 +419,7 @@ export class Images360 extends THREE.EventDispatcher{
                          
                          
                         this.dispatchEvent({type:'endChangeMode',mode})  
-                        console.log('setModeSuccess: ' + mode)       
+                        //console.log('setModeSuccess: ' + mode)       
                     }else{
                         
                         //this.dispatchEvent({type:'endChangeMode',mode})    
@@ -518,7 +531,7 @@ export class Images360 extends THREE.EventDispatcher{
     }
     
 
-    set flying(v){//正在飞向pano
+    /* set flying(v){//正在飞向pano
         this.flying_ = !!v
         //console.log('this.flying_ ', !!v )
         //this.emit('flying', this.flying_)
@@ -528,7 +541,7 @@ export class Images360 extends THREE.EventDispatcher{
     }
     get flying(){
         return this.flying_
-    }
+    } */
     
     
     
@@ -608,7 +621,12 @@ export class Images360 extends THREE.EventDispatcher{
     }
 
 
-	
+	cancelFlyToPano(){//取消当前已有的飞行准备,前提是相机还未移动
+        if(viewer.mainViewport.view.isFlying())return
+        this.nextPano = null 
+        this.latestToPano = null
+        //this.flying = false
+    }
 
 
     flyToPano(toPano) {  //飞向漫游点
@@ -618,26 +636,31 @@ export class Images360 extends THREE.EventDispatcher{
         }
         
         if(!toPano.pano.enabled)return
-
+  
         /* if(!this.currentPano){
             return this.focusPano(toPano) 
-        } */
+        } */ 
         
-        let done = (makeIt)=>{
+        let done = (makeIt, disturb)=>{
             //console.log('flyToPano done ', toPano.pano.id, makeIt ) 
-            if(makeIt || toPano.dealDoneWhenCancel) {
-                toPano.callback && toPano.callback() 
-                this.flying = false
+            if(makeIt || disturb) { // disturb已经开始飞行但中途取消
+                toPano.callback && toPano.callback(makeIt) 
+                //this.flying = false
+                this.cancelFlyToPano()
                 this.updateClosestPano(this.closestPano,false) //飞行结束后取消点击漫游点时得到的closestPano
+            }else{
+                console.log('makeit fail')
             }
+           
+            this.dispatchEvent({type:'flyToPanoDone', makeIt})
             //this.dispatchEvent('cameraMoveDone')
             toPano.deferred && toPano.deferred.resolve(makeIt)  //测量线截图时发现,resolve需要写在flying=false 后才行。
         }
-        if(this.currentPano == toPano.pano && this.isAtPano() && !toPano.target ){
+        if(this.currentPano == toPano.pano && this.isAtPano() && !toPano.target && !toPano.quaternion  ){
             this.dispatchEvent({type:'flyToPano', toPano})
             return done(true);
         }
-        if(this.flying){
+        if(this.latestToPano && this.latestToPano != toPano){
             return done(false)
         }
         
@@ -655,12 +678,18 @@ export class Images360 extends THREE.EventDispatcher{
         toPano.duration = duration
         //console.warn("flyto "+pano.id + ' duration: ' + duration )     
         
-        this.nextPano = pano
-        //不飞的话是否不要执行这段?
+        this.nextPano = pano 
+        this.latestToPano = toPano
+        //this.flying = true  //防止新的请求
         
-        {
+        
+        
+        {//不飞的话是否不要执行这段?
+            
             let wait = ()=> {
-                setTimeout( ()=>{ 
+                if(this.latestToPano != toPano)return //如果取消了
+                setTimeout(()=>{ 
+                    if(this.latestToPano != toPano)return
                     this.flyToPano(toPano) 
                 },1)
                 this.removeEventListener('loadedDepthImg', wait)
@@ -679,8 +708,7 @@ export class Images360 extends THREE.EventDispatcher{
                 }  
             } 
         }
-		 
-        this.cube.visible = config.atPano.showSkybox 
+        viewer.updateVisible(this.cube,'showSkybox', config.atPano.showSkybox ) // this.cube.visible = config.atPano.showSkybox 
         /* this.cube.visible && this.cube.material.setProjectedPanos(this.currentPano, pano, 0)
          
         if(config.transition.showPoint){
@@ -724,7 +752,7 @@ export class Images360 extends THREE.EventDispatcher{
         }
         
         
-        this.flying = true
+        
         
         let fly = ()=>{
             
@@ -760,11 +788,11 @@ export class Images360 extends THREE.EventDispatcher{
                         e.material.uniforms.progress.value = progress 
                     })
                 },
-                cancelFun:()=>{ done(false) },
+                cancelFun:()=>{ done(false, true) },
                 Easing:easeName  
             })
             
-            duration > 0 && (this.flying = true) //再写一遍 防止cancel其他项目导致flying为false
+            //duration > 0 && (this.flying = true) //再写一遍 防止cancel其他项目导致flying为false
             
 
             
@@ -1890,9 +1918,7 @@ export class Images360 extends THREE.EventDispatcher{
     } */
 
     bump(direction) {//撞墙弹回效果
-        if (!this.flying) {  
-            
-            console.log('bump')
+        if (!this.bumping && !this.latestToPano) {  
             let distance = Potree.settings.displayMode == 'showPanos' ? 0.3 : 0.2;//感觉点云模式比全景模式更明显,所以降低
             let currentPos = this.position.clone()
             let endPosition = new THREE.Vector3().addVectors(this.position, direction.clone().multiplyScalar(distance)) 
@@ -1902,19 +1928,19 @@ export class Images360 extends THREE.EventDispatcher{
                 callback:()=>{ 
                     viewer.scene.view.setView({position:currentPos, duration: duration*5, 
                         callback: ()=>{ 
-                            this.flying = false
+                            this.bumping = false
                             //this.dispatchEvent('cameraMoveDone')
                         }, 
                         Easing:'easeInOutSine',
-                        cancelFun:()=>{this.flying = false}
+                        cancelFun:()=>{this.bumping = false}
                     })
-                    this.flying = true  
+                    this.bumping = true  
                 }, 
                 
-                cancelFun:()=>{this.flying = false},  
+                cancelFun:()=>{this.bumping = false},  
                 Easing:'easeInOutSine'
             })  
-            this.flying = true        
+            this.bumping = true        
         }
         //备注:将4dkk中的‘前后方向变化fov、左右方向移动镜头’ 都改为移动镜头。 因为这里无法判断左右离壁距离。
  
@@ -1948,7 +1974,7 @@ export class Images360 extends THREE.EventDispatcher{
         } */
          
         if(!Potree.settings.ifShowMarker){//不显示marker的时候mousemove没更新鼠标最近点所以更新
-            this.updateClosestPano(viewer.inputHandler.intersectPoint)
+            this.updateClosestPano(viewer.inputHandler.intersect)
         }
         //console.log('flyToPanoClosestToMouse',this.closestPano)
         
@@ -2049,7 +2075,7 @@ export class Images360 extends THREE.EventDispatcher{
                 } 
             } */
         ]; 
-        if(viewer.inputHandler.intersectPoint){//方便上下楼, 考虑panos之间的角度差
+        if(viewer.inputHandler.intersectPoint && this.currentPano ){//方便上下楼, 考虑panos之间的角度差
             let pos1 = this.currentPano.floorPosition
             let vec1 = new THREE.Vector3().subVectors(viewer.inputHandler.intersectPoint.location, pos1 ).normalize()//应该只有atPano时才会执行到这吧? 
             list.push(  function(pano) { 
@@ -2082,7 +2108,7 @@ export class Images360 extends THREE.EventDispatcher{
     
 
     isNeighbour(pano0, pano1){//是否之间没有遮挡(在加载visibles之前,自己算) 最好pano0是currentPano
-    
+        if(!pano0 || !pano1)return
         let map0 = this.neighbourMap[pano0.id];
         if(!map0){
             map0 = {}
@@ -2094,7 +2120,7 @@ export class Images360 extends THREE.EventDispatcher{
             this.neighbourMap[pano1.id] = map1
         }
         
-        
+         
         let ifNeighbour = map0[pano1.id]//map0.get(pano1)
         
         if(ifNeighbour == void 0){
@@ -2138,20 +2164,10 @@ export class Images360 extends THREE.EventDispatcher{
 
 
     updateClosestPano(intersect, state) {//hover到的pano   大多数时候是null 
-        /* if(this.isAtPano() ){ 
-             filterFuncs.push(Images360.filters.not(this.currentPano));
-            
-            //当静止在漫游点时closestPano只限制在每个漫游点附近,而在观看整个模型时,范围夸大,识别为离鼠标最近的漫游点。 (故而要排除flying时)
- 			filterFuncs.push(Images360.filters.inFloorDirection(this.position, viewer.scene.view.direction, .25))//许钟文改
-            filterFuncs.push(Images360.filters.isCloseEnoughTo(intersect, 0.35));
-            filterFuncs.push(Images360.filters.isEnabled())  
-        }else{
-			 
-        } */
-        
         
+        /* 
         var pano
-        if(this.isAtPano() ){
+        if(this.isAtPano() ){  //为什么之前Panorama要加这个限制?
             if(intersect instanceof Panorama){
                 pano = state ? intersect : null
             }else{
@@ -2165,9 +2181,25 @@ export class Images360 extends THREE.EventDispatcher{
             let sortFuncs = Potree.settings.editType != 'pano'? [Images360.sortFunctions.floorDisSquaredToPoint(intersect)] : [Images360.sortFunctions.disSquaredToPoint(intersect)] 
             pano = Common.find(this.panos,  filterFuncs, sortFuncs);
         }
+         */
         
         
-        
+        var pano
+         
+        if(intersect instanceof Panorama){
+            pano = state ? intersect : null
+        }else{
+            if(this.isAtPano()){
+                return
+            }else{
+                if(this.flying)return; 
+                var filterFuncs = [];
+                intersect = intersect && intersect.location
+                if(!intersect)return
+                let sortFuncs = Potree.settings.editType != 'pano'? [Images360.sortFunctions.floorDisSquaredToPoint(intersect)] : [Images360.sortFunctions.disSquaredToPoint(intersect)] 
+                pano = Common.find(this.panos,  filterFuncs, sortFuncs);
+            }
+        }
         
         if (pano != this.closestPano) {
             pano && (this.isPanoHover = !0);
@@ -2388,6 +2420,14 @@ export class Images360 extends THREE.EventDispatcher{
                 //console.log(pano.id, dis1,dis2,  cos,  result)
                 return result
             } 
+        },(pano)=>{ 
+            if(pano.depthTex && o.checkIntersect){      //没加载好的话,不管了
+                let intersect = viewer.inputHandler.ifBlockedByIntersect(target, 0.1 , null, null, null, pano)
+                if(intersect){
+                    //console.log('intersected', pano.id )
+                    return -10000
+                }else return 0
+            }else return 0 
         }) 
      
 		/* var temp = {position:point}

+ 18 - 9
src/modules/Images360/Panorama.js

@@ -32,8 +32,8 @@ let getMarerMat = function(){
     }
     return  new DepthBasicMaterial({opacity:0.7, side: THREE.DoubleSide , map:markerTex.default ,transparent:true, 
         clipDistance: 2,  occlusionDistance:1,  //不能设置太短,因为过渡时深度不准确
-        //depthTest: !!Potree.settings.hasDepthTex,
-        useDepth:  !!Potree.settings.hasDepthTex
+        //depthTest: !!Potree.settings.useDepthTex,
+        useDepth:  !!Potree.settings.useDepthTex
         //改为DepthBasicMaterial是因为原Basic的材质过渡时会先隐藏后出现
     })    
 }
@@ -101,6 +101,11 @@ class Panorama extends THREE.EventDispatcher{
         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里的标记
+            
+            
             this.panosData = o
             
             //数据中原本的位置朝向
@@ -114,8 +119,7 @@ class Panorama extends THREE.EventDispatcher{
             this.quaternion = new THREE.Quaternion()  //{w: 0, x: 0, y: 0, z: 1}
             //this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion 
             this.visibles = o.visibles 
-            this.pointcloud = viewer.scene.pointclouds.find(e=>e.panoUuid == o.uuid) 
-            this.pointcloud.panos.push(this)
+            
             
             const height = 1.4; //相机高度
             this.originFloorPosition = this.originPosition.clone()
@@ -144,8 +148,8 @@ class Panorama extends THREE.EventDispatcher{
             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.sceneCode + '|' + this.originID  //不会更改的标记
+            this.sid = this.pointcloud.dataset_id + '|' + this.originID  //不会更改的标记
             //全景图和Cube的水平采样起始坐标相差90度 
             
 
@@ -233,18 +237,22 @@ class Panorama extends THREE.EventDispatcher{
         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/testdata/${Potree.settings.number}/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})
+            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(){
           
@@ -288,6 +296,7 @@ class Panorama extends THREE.EventDispatcher{
         
          
         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) 

+ 14 - 4
src/modules/Images360/tile/TileDownloader.js

@@ -58,14 +58,20 @@ class TileDownloader extends THREE.EventDispatcher{
   
     start() { 
         this.downloadCubeTex = true 
-        //viewer.updateVisible(this,'pano', true ) 
-        this.judgeStart()
+        if(!Potree.settings.useDepthTex){
+            viewer.updateVisible(this,'pano', true )
+            this.judgeStart()            
+        }else{
+            this.refreshInterval || this.judgeStart()
+        }
     }
 
     stop() {
         this.downloadCubeTex = false
-        //viewer.updateVisible(this,'pano', false )
-        //this.judgeStart()
+        if(!Potree.settings.useDepthTex){
+            viewer.updateVisible(this,'pano', false )
+            this.judgeStart()
+        } 
     }
 
     judgeStart(){//add
@@ -345,6 +351,10 @@ class TileDownloader extends THREE.EventDispatcher{
     
 
     getTiles(d, sceneNum){
+        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}`    
     }
 

+ 2 - 0
src/modules/Images360/tile/TilePrioritizer.js

@@ -204,6 +204,8 @@ TilePrioritizer.insertSortedPanoTile = function (e, t, pano, dir) {
 
 
 TilePrioritizer.prototype.filterDepthTex = function (panos ) {//仅下载depthTex
+    if(!Potree.settings.useDepthTex || !this.priorityCriteria.pano)return
+    
     let cameraDirLocals = this.priorityCriteria.cameraDirs.vectorForward
     let t = [] 
     //获得视野范围内的邻近点位序列t

+ 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

+ 52 - 29
src/modules/datasetAlignment/Alignment.js

@@ -1,30 +1,30 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import SplitScreen from "../../utils/SplitScreen.js"
+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(), 
-    history:[], 
+    
     prepareRecord : true, 
     
     writeToHistory(content){ 
         if(!this.prepareRecord)return;
         this.prepareRecord = false
-        this.history.push(content)
+        this.history.writeIn(content)
     },
     
     
-    undo(){//撤销一步 
+    /* 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)
@@ -55,6 +55,9 @@ var Alignment = {
             
             
             this.writeToHistory(this.getTemp(e.pointclouds) ) 
+             
+            
+            
             
         
             if(this.handleState == 'translate'){
@@ -65,7 +68,7 @@ var Alignment = {
                 if(Potree.settings.editType == 'pano'){
                     
                     let center = e.intersectStart //旋转中心是mousedown的位置
-                    if(e.intersectPoint.equals(center))return
+                    if(e.intersect.equals(center))return
                     if(!rotateInfo){  
                         rotateInfo = {
                             orientationUser : e.pointclouds[0].orientationUser,
@@ -75,14 +78,14 @@ var Alignment = {
                         this.bus.dispatchEvent({type:'rotateStart', startPoint:center})
                         return
                     }else if(!rotateInfo.vecStart){  
-                        let vec = new THREE.Vector3().subVectors(e.intersectPoint, center).setZ(0)
+                        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.intersectPoint, center).setZ(0)
+                        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
                         
@@ -104,11 +107,11 @@ var Alignment = {
                         
                         
                     }
-                    this.bus.dispatchEvent({type:'rotate', endPoint: e.intersectPoint})
+                    this.bus.dispatchEvent({type:'rotate', endPoint: e.intersect})
                     
                 }else{ 
                     let center = e.pointclouds[0].translateUser //移动到的位置就是中心
-                    if(e.intersectPoint.equals(center))return
+                    if(e.intersect.equals(center))return
                     if(!rotateInfo){  
                         rotateInfo = {
                             orientationUser : e.pointclouds[0].orientationUser,
@@ -117,7 +120,7 @@ var Alignment = {
                             pointcloud: e.pointclouds[0]
                         }  
                     }else{ 
-                        let vec = new THREE.Vector3().subVectors(e.intersectPoint, center).setZ(0)
+                        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)
@@ -134,7 +137,7 @@ var Alignment = {
         
         viewer.inputHandler.addEventListener('keydown',e=>{ 
             if(e.keyCode == 90 && e.event.ctrlKey){//Z
-                this.undo()
+                this.history.undo()
             } 
         })  
 		 
@@ -142,13 +145,13 @@ var Alignment = {
         // cursor:
         
         let updateCursor = (e)=>{ 
-            if(e.drag)return  //仅在鼠标不按下时更新:
+            if(e.drag || !this.editing)return  //仅在鼠标不按下时更新:
             
             let handleState = Alignment.handleState
             
             if(e.hoverViewport.alignment && handleState && e.hoverViewport.alignment[handleState]){
                 if(handleState == 'translate'){
-                    if( e.intersectPoint && e.intersectPoint.location ){ 
+                    if( e.intersect && e.intersect.location ){ 
                         viewer.dispatchEvent({
                             type : "CursorChange", action : "add",  name:"movePointcloud"
                         })
@@ -158,7 +161,7 @@ var Alignment = {
                         })
                     }
                 }else if(handleState == 'rotate'){ 
-                    if( e.intersectPoint && e.intersectPoint.location ){ 
+                    if( e.intersect && e.intersect.location ){ 
                         viewer.dispatchEvent({
                             type : "CursorChange", action : "add",  name:"rotatePointcloud"
                         })
@@ -179,14 +182,18 @@ var Alignment = {
             }                
         }
         
-        if(Potree.settings.editType != 'pano'){
-            viewer.addEventListener('global_mousemove',updateCursor)  
-            viewer.addEventListener('global_drop',updateCursor)//拖拽结束  
-        }
+         
+        viewer.addEventListener('global_mousemove',updateCursor)  
+        viewer.addEventListener('global_drop',updateCursor)//拖拽结束  
+       
             
         
         
-        
+        viewer.addEventListener('updateModelBound', (e)=>{
+            if(this.editing){
+                this.SplitScreen.updateCameraOutOfModel() 
+            } 
+        })
         
         
     },
@@ -226,10 +233,10 @@ var Alignment = {
             pointcloud.spriteNodeRoot.matrixWorld.copy(pointcloud.matrixWorld)//.multiplyMatrices(pointcloud.matrixWorld, pointcloud.matrixWorld);	
         } 
 
-        //viewer.updateModelBound();
-        pointcloud.updateBound()
+        viewer.updateModelBound();
+        //pointcloud.updateBound()
         pointcloud.getPanosBound()  
-
+        
 
     },
     
@@ -263,7 +270,7 @@ var Alignment = {
         //this.saveTemp()  
         this.originData = this.getTemp() 
         
-        SplitScreen.splitScreen4Views({alignment:true})
+        this.SplitScreen.split({alignment:true})
          
         viewer.images360.panos.forEach(pano=>{
             viewer.updateVisible(pano.mapMarker, 'split4Screens', false)
@@ -278,6 +285,8 @@ var Alignment = {
         
         viewer.updateFpVisiDatasets()
         
+        
+        
     },
     leave:function(){
         this.switchHandle(null)
@@ -292,14 +301,19 @@ var Alignment = {
         })
         
         
-        SplitScreen.recoverFrom4Views()
+        this.SplitScreen.recover()
         viewer.images360.panos.forEach(pano=>{
             viewer.updateVisible(pano.mapMarker, 'split4Screens', true)
         }) 
         this.editing = false
-        this.history.length = 0
+        this.history.clear() 
         viewer.updateFpVisiDatasets()
-        
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"movePointcloud" 
+        })
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"rotatePointcloud" 
+        })
     } 
     
     ,
@@ -353,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到模型上开始转动的一瞬间很卡。
+
+
+
+
+
+*/ 

+ 285 - 108
src/modules/panoEdit/panoEditor.js

@@ -8,8 +8,8 @@ import {View} from "../../viewer/View.js";
 import Viewport from "../../viewer/Viewport.js";
 import Sprite from "../../objects/Sprite.js";
 import {transitions, easing, lerp} from '../../utils/transitions.js'
-
-
+import {TransformControls} from "../../objects/tool/TransformControls.js";
+import SplitScreen from "../../utils/SplitScreen.js"
 
 
 
@@ -18,7 +18,7 @@ let images360, Alignment,  SiteModel
 
 const texLoader = new THREE.TextureLoader() 
     texLoader.crossOrigin = "anonymous" 
-    
+const rotQua = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI)    
 const lineMats = {}
 const circleMats = {}
 
@@ -40,22 +40,18 @@ const opacitys = {
 const cameraProps = [ 
     { 
         name : 'top',    
-        axis:["x","y"], 
-        //相机位置在z轴正向 
-        currentDir : new THREE.Vector3(0,0,-1), //镜头朝向
+        axis:["x","y"],  
+        direction : new THREE.Vector3(0,0,-1), //镜头朝向
         openCount:0,
     },
     { 
         name : 'right', 
         axis:["y","z"], 
-        currentDir : new THREE.Vector3(1,0,0),
-        //相机位置在x轴正向
+        direction : new THREE.Vector3(1,0,0), 
         openCount:0,
     }  
 ]   
 
-const targetPlane = new THREE.Plane()
-
 
 
 
@@ -73,8 +69,7 @@ class PanoEditor extends THREE.EventDispatcher{
         this.orthoCamera = new THREE.OrthographicCamera(-100, 100, 100, 100, 0.01, 10000)
         this.selectedPano;
         this.selectedGroup;
-        this.operation;
-        this.shiftTarget = new THREE.Vector3 //project在targetPlane上的位置
+        this.operation; 
         this.visiblePanos = []
     }
      
@@ -99,7 +94,55 @@ class PanoEditor extends THREE.EventDispatcher{
              
         }
     
-    
+        {
+            this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{
+                dontHideWhenFaceCamera: true,
+            }); 
+            this.transformControls.setSize(1.5)
+            viewer.scene.scene.add(this.transformControls)
+            this.transformControls._gizmo.hideAxis = {translate:[], rotate:['x','y','e'] }
+            
+            
+            this.fakeMarkerForTran = new THREE.Mesh(new THREE.BoxBufferGeometry(0.3,0.3,0.3) , new THREE.MeshBasicMaterial({
+                color:"#FFFFFF",  opacity:0.4,  transparent:true, visible:false
+            }));//一个看不见的mesh,只是为了让transformControls移动点云
+            viewer.scene.scene.add(this.fakeMarkerForTran)
+            
+            
+       
+            let afterMoveCircle = (type)=>{
+                  
+                if(type == 'position'){
+                    let moveVec = new THREE.Vector3().subVectors(this.fakeMarkerForTran.position, this.fakeMarkerForTran.oldState.position)
+                    this.selectedClouds.forEach(cloud=>Alignment.translate(cloud, moveVec))
+                }else{
+                    let center = this.selectedPano.position;
+                    let forward = new THREE.Vector3(0,1,0);
+                    let vec1 = forward.clone().applyQuaternion(this.fakeMarkerForTran.oldState.quaternion)
+                    let vec2 = forward.clone().applyQuaternion(this.fakeMarkerForTran.quaternion)
+                     
+                    let diffAngle = math.getAngle(vec1,vec2,'z')    
+                        
+                    this.selectedClouds.forEach(cloud=>{ 
+                        Alignment.rotateAround(center, cloud, null, diffAngle)    
+                    })
+                }
+                
+                
+                this.fakeMarkerForTran.oldState = {
+                    position: this.fakeMarkerForTran.position.clone(),
+                    quaternion: this.fakeMarkerForTran.quaternion.clone(),
+                }
+            }
+             
+            this.fakeMarkerForTran.addEventListener('position_changed', afterMoveCircle.bind(this,'position')) 
+            this.fakeMarkerForTran.addEventListener("rotation_changed", afterMoveCircle.bind(this,'rotation') )
+          
+            
+        }
+        
+        
+        
         this.initViews()
         
         
@@ -131,9 +174,14 @@ class PanoEditor extends THREE.EventDispatcher{
             this.switchView('top')
             
             SiteModel.bus.addEventListener('initDataDone',()=>{ 
-                this.gotoFloor(SiteModel.entities.find(e=>e.buildType == 'floor'))  //任意一层
+                let floor = SiteModel.entities.find(e=>e.buildType == 'floor' && e.panos.length) //选择有漫游点的一层
+                if(!floor){
+                    floor = 'all'  //SiteModel.entities.find(e=>e.buildType == 'floor')
+                    console.log('没有一层有漫游点?!')
+                }
+                this.gotoFloor(floor)  
             })
-            
+             
             
             Alignment.bus.addEventListener('switchHandle', this.updateCursor.bind(this))
             
@@ -144,8 +192,12 @@ class PanoEditor extends THREE.EventDispatcher{
                      this.setLinkOperateState('addLink',false)
                      this.setLinkOperateState('removeLink',false)
                 }else if(this.clickToZoomInEnabled){
-                        
-                    this.zoomIn(e.intersectPoint.orthoIntersect, e.pointer)
+                    if(this.activeViewName == 'mainView'){
+                        viewer.controls.zoomToLocation(e.mouse)
+                    }else{
+                        this.zoomIn(e.intersect.orthoIntersect, e.pointer)
+                    }
+                     
                      
                     this.setZoomInState(false)
                 }
@@ -184,7 +236,7 @@ class PanoEditor extends THREE.EventDispatcher{
                     if(this.operation != 'addLink' || this.activeViewName != 'top' || !this.selectedPano){
                         return this.linkGuideLine.visible = false
                     }
-                    LineDraw.updateLine(this.linkGuideLine, [this.selectedPano.position, e.intersectPoint.orthoIntersect.clone().setZ(this.selectedPano.position.z)] ) 
+                    LineDraw.updateLine(this.linkGuideLine, [this.selectedPano.position, e.intersect.orthoIntersect.clone().setZ(this.selectedPano.position.z)] ) 
                     this.linkGuideLine.visible = true
                 }
                 
@@ -200,19 +252,49 @@ class PanoEditor extends THREE.EventDispatcher{
     }
     
     
-                
+    setTranMode(mode){//rotate or translate 
+        this.tranMode = mode
+        if(this.activeViewName == 'mainView'){
+            mode && this.transformControls.setMode(mode)
+            this.updateTranCtl() 
+        }else{
+            Alignment.switchHandle(mode) 
+        } 
+    }
+    
+    updateTranCtl(){// 设置3D页面的transformControls相关
+        if(!this.tranMode || !this.selectedPano || this.activeViewName != 'mainView' ) {
+            return this.transformControls.detach() 
+        }else if(this.checkIfAllLinked({group:this.selectedGroup})){
+            this.dispatchEvent('needToDisConnect')
+            return this.transformControls.detach()
+        }
+        this.transformControls.attach(this.fakeMarkerForTran)
+        let {position, quaternion} = this.getPanoPose(this.selectedPano);
+        this.fakeMarkerForTran.position.copy(position)
+        this.fakeMarkerForTran.quaternion.copy(quaternion)
+        this.fakeMarkerForTran.oldState = {
+            position: position.clone(),
+            quaternion: quaternion.clone(),
+        }
+    }
     
     
      
     //////////////////////////////////
-    initViews(){ 
+    initViews(){
+        this.splitScreenTool = new SplitScreen
+        this.targetPlane = viewer.mainViewport.targetPlane = new THREE.Plane()
+        this.shiftTarget = viewer.mainViewport.shiftTarget = new THREE.Vector3 //project在targetPlane上的位置
+         
+        
         for(let i=0;i<2;i++){
             let prop = cameraProps[i];
             let view = new View()  
             this.views[prop.name] = view 
             this.cameras[prop.name] = this.orthoCamera
-            
-            view.direction = prop.currentDir
+             
+            view.direction = prop.direction
         }
         this.views.mainView = viewer.mainViewport.view
         this.cameras.mainView = viewer.mainViewport.camera
@@ -230,14 +312,15 @@ class PanoEditor extends THREE.EventDispatcher{
         this.activeViewName = name 
         let lastView = this.views[this.lastViewName]
         let lastCamera = this.cameras[this.lastViewName]
-        
-        //let aspect = viewer.mainViewport.camera.aspect
         viewer.mainViewport.view = view
         viewer.mainViewport.camera = camera 
-        //targetPlane.setFromNormalAndCoplanarPoint( prop.currentDir.clone(), center ) 
-        targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )  
-        targetPlane.projectPoint(view.position, this.shiftTarget )  //target转换到过模型中心的平面,以保证镜头一定在模型外
-        view.position.copy(this.getPosOutOfModel())
+        if(lastCamera)lastView.zoom = lastCamera.zoom
+        
+        this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )  
+        this.targetPlane.projectPoint(view.position, this.shiftTarget )  //target转换到过模型中心的平面,以保证镜头一定在模型外
+        view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport))
+        if(view.zoom)camera.zoom = view.zoom//恢复上次的zoom
+         
         
         viewer.updateScreenSize({forceUpdateSize:true})//更新camera aspect  left等
         this.updateCursor()
@@ -245,7 +328,7 @@ class PanoEditor extends THREE.EventDispatcher{
         
         
         
-        if(name == 'mainView'){
+        if(name == 'mainView'){ 
             viewer.mainViewport.alignment =  null
             viewer.scene.pointclouds.forEach(e=>{
                 e.material.activeAttributeName = 'rgba' 
@@ -254,7 +337,7 @@ class PanoEditor extends THREE.EventDispatcher{
                  
             })
             viewer.updateVisible(viewer.reticule, 'force', true)
-            
+             
             if(lastView){
                 
                 view.copy(lastView)
@@ -268,18 +351,20 @@ class PanoEditor extends THREE.EventDispatcher{
                 
                 console.log('最近',nearestPano )
                 
-                if(nearestPano && nearestPano[0] ){
-                  
-                    
+                if(nearestPano && nearestPano[0] ){ //尽量不变画面范围,使pano点保持原位,转换到mainView
                     let halfHeight = lastCamera.top/lastCamera.zoom 
                     let dis = halfHeight / Math.tan( THREE.Math.degToRad(camera.fov/2)) 
                     view.position.add(direction.clone().multiplyScalar(-nearestPano[0].score - dis))
-                    console.log('getCloser', -nearestPano[0].score - dis)
+                    //console.log('getCloser', -nearestPano[0].score - dis)
+                    this.lastDisToPano = dis //记录一下
                 }
                 
             }
+            
+            viewer.fpControls.lockKey = false
+            
         }else{
-            if(prop.openCount == 0){
+            if(prop.openCount == 0){//只需执行一次
                 this.viewportFitBound(name,  boundSize, center)
             }
             prop.openCount ++;
@@ -301,17 +386,27 @@ class PanoEditor extends THREE.EventDispatcher{
             viewer.updateVisible(viewer.reticule, 'force', false)      
             
             if(name == 'top') viewer.mainViewport.alignment = {rotate:true,translate:true};
-            else if(name == 'right') viewer.mainViewport.alignment = {translate:true, rotateSide:true};
-            
+            if(name == 'right'){
+                viewer.mainViewport.alignment = {translate:true, rotateSide:true}; 
+                viewer.mainViewport.rotateSide = true
+            }else{
+                viewer.mainViewport.rotateSide = false
+            }
+            viewer.fpControls.lockKey = true
+             
         } 
         
+        
+        this.updateTranCtl()
+        this.setTranMode() // update
         this.setZoomInState(false) //取消放大模式
+        
     }
     
     
     //new THREE.Plane().setFromNormalAndCoplanarPoint( normal, this.points[0]  )
     
-    viewportFitBound(name, boundSize_, target){  //使一个viewport聚焦在某个范围
+    viewportFitBound(/* name, boundSize_, target */){  //使一个viewport聚焦在某个范围
         /* let viewport = viewer.mainViewport 
         let {boundSize, center} = viewer.bound
     
@@ -319,10 +414,10 @@ class PanoEditor extends THREE.EventDispatcher{
         
         let expand = 10;
         
-        targetPlane.setFromNormalAndCoplanarPoint( prop.currentDir.clone(), center ) 
+        targetPlane.setFromNormalAndCoplanarPoint( prop.direction.clone(), center ) 
         let shiftTarget = targetPlane.projectPoint(target, new THREE.Vector3() )  //target转换到过模型中心的平面,以保证镜头一定在模型外
        
-        this.setCameraPose(shiftTarget, prop.currentDir) 
+        this.setCameraPose(shiftTarget, prop.direction) 
          
          
         if(name == 'top'){
@@ -332,8 +427,8 @@ class PanoEditor extends THREE.EventDispatcher{
             
             let vec1 = new THREE.Vector3(boundSize_.x, 0,0);
             let vec2 = new THREE.Vector3(0,boundSize_.y,0);
-            let v1 = vec1.projectOnPlane( prop.currentDir.clone() )  
-            let v2 = vec2.projectOnPlane( prop.currentDir.clone() )  
+            let v1 = vec1.projectOnPlane( prop.direction.clone() )  
+            let v2 = vec2.projectOnPlane( prop.direction.clone() )  
             
             
             var width = Math.max(v1.length()+v2.length(),    boundSize_.z * viewport.camera.aspect)//视口宽度(米)
@@ -345,31 +440,34 @@ class PanoEditor extends THREE.EventDispatcher{
         
         if(viewer.mainViewport.resolution.x == 0 || viewer.mainViewport.resolution.y == 0){
             return setTimeout(()=>{
-                this.viewportFitBound(name, boundSize_, target)
+                this.viewportFitBound(/* name, boundSize_, target */)
             },10)
         }
-        this.gotoFloor(this.currentFloor, true, 0)
+         
+        this.gotoFloor(this.currentFloor, true, 0, null, true)
         
         
     } 
     
     
-    
-    
     rotateSideCamera(angle){//侧视图绕模型中心水平旋转
+        this.splitScreenTool.rotateSideCamera(viewer.mainViewport, angle) 
+    }
+    
+    /* rotateSideCamera(angle){//侧视图绕模型中心水平旋转
         var prop = cameraProps.find(v => v.name == 'right' )
         
         let {boundSize, center} = viewer.bound
          
          
         //找到平移向量
-        targetPlane.setFromNormalAndCoplanarPoint(viewer.mainViewport.view.direction /* prop.currentDir.clone() */, center ) 
+        targetPlane.setFromNormalAndCoplanarPoint(viewer.mainViewport.view.direction  , center ) 
         targetPlane.projectPoint(viewer.mainViewport.view.position,  this.shiftTarget )  //target转换到过模型中心的平面,以保证镜头一定在模型外
         let vec = new THREE.Vector3().subVectors(center, this.shiftTarget)//相对于中心的偏移值,旋转后偏移值也旋转
         
         //旋转
         var rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle) 
-        //prop.currentDir.applyMatrix4(rotMatrix)
+        //prop.direction.applyMatrix4(rotMatrix)
         viewer.mainViewport.view.direction = viewer.mainViewport.view.direction.applyMatrix4(rotMatrix)
          
          
@@ -377,13 +475,13 @@ class PanoEditor extends THREE.EventDispatcher{
         this.shiftTarget.subVectors(center,vec) //新的
         
         
-        viewer.mainViewport.view.position = this.getPosOutOfModel()
-        //this.setCameraPose(/* this.shiftTarget, prop.currentDir */)
+        viewer.mainViewport.view.position = this.splitScreenTool.getPosOutOfModel(viewer.mainViewport)  // this.getPosOutOfModel()
+  
         
-    }
+    } */
      
     
-    getPosOutOfModel(){//已知shiftTarget和currentDir后
+    /* getPosOutOfModel(){//已知shiftTarget和currentDir后
         let {boundSize, center} = viewer.bound
         let expand = 10;
         let view = viewer.mainViewport.view
@@ -393,12 +491,12 @@ class PanoEditor extends THREE.EventDispatcher{
         return position 
          
         
-    }
+    } */
     
-    zoomIn(intersectPoint, pointer){ 
+    zoomIn(intersect, pointer){ 
         let camera = viewer.mainViewport.camera
         let endZoom = 700
-        //this.moveFit(intersectPoint, {endZoom:viewer.mainViewport.camera.zoom < aimZoom ? aimZoom : null}  , 300) 
+        //this.moveFit(intersect, {endZoom:viewer.mainViewport.camera.zoom < aimZoom ? aimZoom : null}  , 300) 
         let startZoom = camera.zoom
         if(startZoom >= endZoom){return}
         
@@ -411,17 +509,19 @@ class PanoEditor extends THREE.EventDispatcher{
         let radius = boundSize.length() / 2  
         return radius + expand 
     } */
-    moveFit(pos, info, duration){ 
-        targetPlane.projectPoint(pos, this.shiftTarget)  //target转换到过模型中心的平面,以保证镜头一定在模型外
+    moveFit(pos, info, duration){  
+        var margin = {x:200, y:230}      
+        this.splitScreenTool.viewportFitBound(viewer.mainViewport,  info.bound,  pos, duration, margin )
+        /* targetPlane.projectPoint(pos, this.shiftTarget)  //target转换到过模型中心的平面,以保证镜头一定在模型外 this.shiftTarget是得到的
         info.endPosition = this.getPosOutOfModel() 
-        info.margin = {x:200, y:230}      
+        info
         let view = viewer.mainViewport.view 
-        view.moveOrthoCamera(viewer.mainViewport, info ,  duration   )
+        view.moveOrthoCamera(viewer.mainViewport, info ,  duration   ) */
  
     }
     
     setZoomInState(state, informinformBy2d){//是否点击后可放大
-        if(state && this.activeViewName == 'mainView')return console.log('3D不可放大')
+        //if(state && this.activeViewName == 'mainView')return console.log('3D不可放大')
         this.clickToZoomInEnabled = !!state
     
     
@@ -439,7 +539,7 @@ class PanoEditor extends THREE.EventDispatcher{
     }
      
      
-    gotoFloor(floor, force, duration = 600, informBy2d){// 选择不同楼层, 切换点位显示。   'all'为全部显示
+    gotoFloor(floor, force, duration = 600, informBy2d, fitBound){// 选择不同楼层, 切换点位显示。   'all'为全部显示
         
         floor = floor || 'all' 
    
@@ -479,7 +579,7 @@ class PanoEditor extends THREE.EventDispatcher{
         } 
         
         if(this.activeViewName != 'mainView'   ){ 
-            this.moveFit(center, {bound},  duration)
+            fitBound && this.moveFit(center, {bound},  duration)
         }else if(this.activeViewName == 'mainView'){
             if(floor != 'all'){ //切换一下位置,因为原处点云会消失
                  viewer.scene.view.setView({position:center,  duration })
@@ -590,9 +690,16 @@ class PanoEditor extends THREE.EventDispatcher{
         
         let old = this.operation
         this.operation = state ? name : null
-        if(this.selectedLine){
-            this.selectedLine.dispatchEvent('click')//删除 
-        } 
+        
+        if(this.operation == 'removeLink'){
+            if(this.selectedLine){
+                this.selectedLine.dispatchEvent('click')//删除 
+            } 
+            if(this.selectedPano){
+                this.selectedPano.circle.dispatchEvent('click')//删除 
+            }
+        }
+        
         if(this.operation != 'addLink'){
             this.linkGuideLine.visible = false
         }
@@ -635,25 +742,30 @@ class PanoEditor extends THREE.EventDispatcher{
      
     
      
-    groupChange(pano0, pano1, type){//修改group
+    groupChange(pano0, pano1, type){//修改group (type == 'remove'时,pano1可以为空)
         if(type == 'add'){
             Common.pushToGroupAuto([pano0, pano1], this.panoGroup  )
         }else{ 
-            let atGroup = this.panoGroup.find(e=>e.includes(pano0) && e.includes(pano1));//所在组
+            let atGroup = this.panoGroup.find(e=>e.includes(pano0) && (e.includes(pano1) || !pano1));//所在组
             
             if(!atGroup){
-                return console.log('这两个pano原本就不在一个组', pano0.id, pano1.id)
+                if(pano1){
+                    console.log('这两个pano原本就不在一个组', pano0.id, pano1.id)
+                }else{
+                    console.log('pano0不在任何组', pano0)
+                }
+                return 
             }
             
             //断开连接时,因为组内没有其他成员的连接信息,所以需要清除整组,并将剩余的一个个重新连接
             this.panoGroup.splice(this.panoGroup.indexOf(atGroup),1) //删除
             
             
-            atGroup.forEach(pano=>{//然后再重新生成这两个和组的关系,各分组
+            atGroup.forEach(pano=>{//然后再重新生成这两个和组的关系,各分组
                 if(pano == pano0 || pano == pano1)return
-                for(let i in this.panoLink[pano.id]){
-                    if(this.panoLink[pano.id][i]){
-                        let pano_ = images360.getPano(i)
+                for(let id in this.panoLink[pano.id]){
+                    if(this.panoLink[pano.id][id]){
+                        let pano_ = images360.getPano(id)
                         Common.pushToGroupAuto([pano, pano_], this.panoGroup  )
                     } 
                 } 
@@ -664,16 +776,38 @@ class PanoEditor extends THREE.EventDispatcher{
         }
     } 
     
-    linkChange(pano0, pano1, type){//修改link
+    linkChange(pano0, pano1, type){//修改link  (type == 'remove'时,pano1可以为空)
+        
     
+        let temp = []
+        
         if(type == 'add'){
+            if(!pano1)return console.error('不支持add时pano1为空')
             this.panoLink[pano0.id][pano1.id] = this.panoLink[pano0.id][pano1.id] || {}
             this.panoLink[pano1.id][pano0.id] = this.panoLink[pano1.id][pano0.id] || {}
+        }else{  
+            if(!pano1){
+                for(let id in this.panoLink[pano0.id]){
+                    if(this.panoLink[pano0.id][id]){
+                        this.panoLink[id][pano0.id] = false
+                        temp.push(id)
+                    }
+                }
+                this.panoLink[pano0.id] = {} //全部断连
+            }else{
+                this.panoLink[pano0.id][pano1.id] = false
+                this.panoLink[pano1.id][pano0.id] = false 
+            }
+        }
+        
+        if(!pano1){ //全部断连
+            temp.forEach(id=>{
+                this.lineChange(pano0, images360.getPano(id) , type)
+            }) 
         }else{
-            this.panoLink[pano0.id][pano1.id] = false
-            this.panoLink[pano1.id][pano0.id] = false 
+            this.lineChange(pano0, pano1, type)  
         }
-        this.lineChange(pano0, pano1, type) 
+        
         this.groupChange(pano0, pano1, type)
         
         //this.updateSelectGroup()
@@ -687,12 +821,13 @@ class PanoEditor extends THREE.EventDispatcher{
             if(this.panoLink[pano0.id][pano1.id].line) return 
             let line = LineDraw.createFatLine([pano0.position, pano1.position], {material:lineMats.default})
             line.name = `${pano0.id}-${pano1.id}`
-            line.renderOrder = renderOrders.line
+            line.renderOrder = line.pickOrder = renderOrders.line
             this.lineMeshes.add(line) 
             this.panoLink[pano0.id][pano1.id].line = this.panoLink[pano1.id][pano0.id].line = line
             
             
             line.addEventListener('mouseover', ()=>{
+                if(this.clickToZoomInEnabled)return
                 if(this.activeViewName == 'mainView')return                
                 if(this.selectedLine != line)line.material = lineMats.hovered 
                 viewer.dispatchEvent({
@@ -700,6 +835,7 @@ class PanoEditor extends THREE.EventDispatcher{
                 });
             });  
             line.addEventListener('mouseleave', ()=>{
+                if(this.clickToZoomInEnabled)return
                 //if(this.activeViewName == 'mainView')return
                 if(this.selectedLine != line)line.material = lineMats.default
                 viewer.dispatchEvent({
@@ -707,6 +843,7 @@ class PanoEditor extends THREE.EventDispatcher{
                 });
             }); 
             line.addEventListener('click', (e)=>{
+                if(this.clickToZoomInEnabled)return
                 if(this.activeViewName == 'mainView')return
                 if(this.operation == 'removeLink'){
                     if(this.selectedLine == line) this.selectLine(null)
@@ -741,6 +878,9 @@ class PanoEditor extends THREE.EventDispatcher{
     
     
     
+                
+             
+    
     addPanoMesh(){ 
         let map = texLoader.load(Potree.resourcePath+'/textures/correct_n.png' )  
         circleMats.default = new THREE.MeshBasicMaterial({
@@ -785,7 +925,8 @@ class PanoEditor extends THREE.EventDispatcher{
             var circle = new Sprite({mat: circleMats.default, sizeInfo:{
                     minSize : 50 ,  maxSize : 120,   nearBound : 2, farBound : 10, 
                 },
-                renderOrder : renderOrders.circle
+                renderOrder : renderOrders.circle,
+                pickOrder: renderOrders.circle
             })   //new THREE.Sprite(circleMats.default) 
             
             circle.name = 'panoCircle'
@@ -796,7 +937,6 @@ class PanoEditor extends THREE.EventDispatcher{
             this.panoMeshs.add(circle)
             
             
-            
             setPos(circle) 
             pano.addEventListener('rePos', setPos.bind(this,circle))
                
@@ -819,7 +959,12 @@ class PanoEditor extends THREE.EventDispatcher{
                 this.hoverPano(pano,false) 
             })
             circle.addEventListener('click', ()=>{ 
-                if(this.activeViewName == 'mainView')return
+                //if(this.activeViewName == 'mainView')return
+                if(this.clickToZoomInEnabled)return
+                if(this.operation == 'removeLink'){ 
+                    this.linkChange(pano, null, 'remove')  //删除所有连接
+                } 
+                
                 if(this.selectedPano == circle.pano) return this.selectPano(null)
                 if(this.operation == 'addLink' && this.selectedPano){
                     this.linkChange(this.selectedPano, circle.pano, 'add')
@@ -830,7 +975,9 @@ class PanoEditor extends THREE.EventDispatcher{
                 //    this.linkChange(this.selectedPano, circle.pano, 'remove')
                 //    //this.setLinkOperateState('removeLink',false)
                 //    return
-                // }  
+                // } 
+                
+                
                 this.selectPano(circle.pano)
             })
             
@@ -840,6 +987,7 @@ class PanoEditor extends THREE.EventDispatcher{
     
     
     hoverPano(pano, state){
+        if(this.clickToZoomInEnabled)return
         if(pano && state){ //在hover一个pano之前,一定会先取消已经hover的pano, 最多存在一个hovered的pano
             if(this.hoveredPano == pano)return
              
@@ -851,7 +999,7 @@ class PanoEditor extends THREE.EventDispatcher{
             pano.hovered = true  
             
              
-            if(this.activeViewName == 'mainView' || Alignment.handleState && this.selectedPano && this.selectedPano == pano)return
+            if(/* this.activeViewName == 'mainView' ||  */Alignment.handleState && this.selectedPano && this.selectedPano == pano)return
               
             if(this.operation != 'addLink' || !this.selectedPano || this.selectedPano == pano){ // this.selectedPano == pano?
                 viewer.dispatchEvent({
@@ -878,20 +1026,20 @@ class PanoEditor extends THREE.EventDispatcher{
     selectPano(pano, informinformBy2d, force){
         if(this.selectedPano == pano && !force)return
         
-         
+        let lastSeletedPano = this.selectedPano
         if(this.selectedPano){ 
+            
             this.selectedPano.circle.material = circleMats.default 
             this.selectedPano.circle.renderOrder = renderOrders.circle 
 
-            if(this.activeViewName != 'mainView'){
+            if(this.activeViewName == 'mainView'){
+                 
+            }else{
                 this.selectedClouds.forEach(e=>{
                     e.changePointOpacity(opacitys.default,true)
                     e.material.color = pointColor.default;  
                 })
-                
-                //this.selectedPano.pointcloud.changePointOpacity(opacitys.default,true)
-                //this.selectedPano.pointcloud.material.color = Potree.config.material.pointColor
-            } 
+            }
              
         }
      
@@ -901,14 +1049,16 @@ class PanoEditor extends THREE.EventDispatcher{
         
         if(pano){
             this.selectedPano.circle.material = circleMats.selected  
-            this.selectedPano.circle.renderOrder = renderOrders.circleSelected //侧视图能显示在最前
-            //this.selectedPano.pointcloud.material.color = '#ff0000'
-            //this.selectedPano.pointcloud.changePointOpacity(opacitys.selected,true) 
-            this.selectedClouds.forEach(e=>{
-                e.changePointOpacity(opacitys.selected,true)
-                e.material.color = pointColor.selected;  
-            })
-          
+            this.selectedPano.circle.renderOrder = this.selectedPano.circle.pickOrder = renderOrders.circleSelected //侧视图能显示在最前
+             
+            if(this.activeViewName == 'mainView'){
+                
+            }else{
+                this.selectedClouds.forEach(e=>{
+                    e.changePointOpacity(opacitys.selected,true)
+                    e.material.color = pointColor.selected;  
+                })
+            }
             
             
             
@@ -929,14 +1079,25 @@ class PanoEditor extends THREE.EventDispatcher{
         }
         
         
-        this.updateCursor()
+        this.updateCursor() 
+        this.updateTranCtl()
 
 
-
-
-
-        informinformBy2d || this.dispatchEvent({type:'panoSelect', pano })
-        
+        if(informinformBy2d){
+            if(this.selectedPano){
+                if(this.activeViewName == 'mainView'){ //平移,focus选中的pano
+                    let distance = this.lastDisToPano || 5; 
+                    if(lastSeletedPano){
+                        distance = viewer.mainViewport.camera.position.distanceTo(lastSeletedPano.position)
+                    }
+                    viewer.focusOnObject({ position:this.selectedPano.position}, 'point', null, {distance })
+                }else{
+                    this.moveFit(this.selectedPano.position, {}, 500)
+                }
+            } 
+        }else{
+            this.dispatchEvent({type:'panoSelect', pano })
+        } 
     }
     
     
@@ -975,14 +1136,25 @@ class PanoEditor extends THREE.EventDispatcher{
             group = this.panoGroup.find(panos=>panos[0].pointcloud.dataset_id == datasetId )
             if(!group)return //要找的数据集的pano全部都孤立了
         }
-        
+        if(!datasetId)return
         let panos = Potree.settings.datasetsPanos[datasetId].panos
         return panos.length == group.length
     }
     
+   
+    
+    getPanoPose(pano){
+        let pose = {
+            position:  pano.position.clone(),
+            quaternion:  new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua)  ,
+        }
+        return pose
+        
+    }
+    
     exportSavingData(){//输出漫游点新的坐标和朝向、以及连接信息
         let sweepLocations = {}
-
+        
         for(let datasetId in Potree.settings.datasetsPanos ) {
             let {panos} = Potree.settings.datasetsPanos[datasetId]
             let data = panos.map(pano=>{
@@ -992,13 +1164,18 @@ class PanoEditor extends THREE.EventDispatcher{
                         visibles.push(viewer.images360.getPano(id).index)
                     }
                 }
-                
+                let {position, quaternion} = this.getPanoPose(pano);
                 return Object.assign({},  pano.panosData,  {
                     uuid: pano.uuid, 
-                    pose:{
-                        translation: dealData(pano.position.clone().negate()),
-                        rotation: dealData(new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix)  ),
+                    /* pose:{
+                        translation: dealData(pano.position.clone() ),
+                        rotation: dealData(new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua)  ),
+                    }, */
+                    pose : { 
+                        translation : dealData(position), 
+                        rotation : dealData(quaternion) 
                     },
+                    
                     visibles,
                     
                     //subgroup: 0,group: 1, "id_view":..

+ 651 - 0
src/modules/route/RouteGuider.js

@@ -0,0 +1,651 @@
+
+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
+const planeGeo = new THREE.PlaneBufferGeometry(1,1);
+
+const sphereSizeInfo = {
+      nearBound : 2, scale:arrowSize, restricMeshScale : true,
+}
+//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
+           
+            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({ 
+                transparent:true, depthTest:false,
+                map: texLoader.load(Potree.resourcePath+'/textures/pano_instruction_bottomMarker.png' )  
+            }),
+            sphereMat : new THREE.MeshBasicMaterial({
+                transparent:true, depthTest:false,
+                map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' )  
+            }), 
+            hatMats:{
+                start:  new THREE.MeshBasicMaterial({
+                    transparent:true, depthTest:false,
+                    map: texLoader.load(Potree.resourcePath+'/textures/pano_instruction_start_route.png' )  
+                }),
+                end:  new THREE.MeshBasicMaterial({
+                    transparent:true, depthTest:false,
+                    map: texLoader.load(Potree.resourcePath+'/textures/pano_instruction_target_reached.png' )  
+                }) 
+            }
+        }
+        polesMats.shadowMat.map.anisotropy = 4 
+        
+        this.poleStart = this.createPole(polesMats, 'start') 
+        this.poleEnd = this.createPole(polesMats, 'end') 
+        
+        this.sceneMeshGroup.add(this.poleStart)
+        this.sceneMeshGroup.add(this.poleEnd)
+        
+        
+        let map = texLoader.load(Potree.resourcePath+'/textures/routePoint_panorama.png' )  
+        map.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊 
+        this.arrow = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
+            transparent:true,
+            depthTest:false, 
+            map
+        }))
+        this.arrow.scale.set(arrowSize,arrowSize,arrowSize)
+        viewer.setObjectLayers(this.arrow, 'sceneObjects' )
+         
+        
+        /* this.testArrow = this.arrow.clone();
+        this.testArrow.material = this.arrow.material.clone()
+        this.testArrow.material.color = 'red' */
+        
+        this.arrows = new THREE.Object3D;
+        this.sceneMeshGroup.add(this.arrows)
+        
+        viewer.setObjectLayers(this.sceneMeshGroup, 'sceneObjects' )
+        //this.sceneMeshGroup.traverse(e=>e.renderOrder = 90)
+        
+        
+        viewer.scene.scene.add(this.sceneMeshGroup);
+        this.sceneMeshGroup.visible = /* this.poleStart.visibile = this.poleEnd.visibile = */ false
+       
+        //-------------map---------------------
+        
+        /* this.mapMarkStart = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial({
+            transparent:true, depthTest:false,
+            map: texLoader.load(Potree.resourcePath+'/textures/map_instruction_start_route.png' )  
+        }))
+        this.mapMarkEnd = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial({
+            transparent:true, depthTest:false,
+            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, this.mapArrowMats.default) 
+        this.mapArrow.scale.set(arrowSize,arrowSize,arrowSize)
+        this.mapArrows = new THREE.Object3D;
+        this.mapArrows.name = 'mapArrows'
+         
+        
+        
+        this.mapMeshGroup.add(this.mapArrows)
+        this.mapMeshGroup.name = 'mapRouteLayer'
+        this.mapMeshGroup.visible = false
+        
+        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
+        
+        var group = new THREE.Object3D;
+            group.name = 'pole_'+name
+        var shadow = new THREE.Mesh(planeGeo,polesMats.shadowMat)
+        shadow.scale.set(shadowSize,shadowSize,shadowSize)
+        var sliceDis = height / (sphereCount+1);
+        group.add(shadow) 
+         
+        for(let i=0;i<sphereCount;i++){
+            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)
+        return group
+    }
+    
+    
+    addTestArrow(){
+        
+    }
+    
+    addArrow(position){ 
+        var arrow = this.arrow.clone()
+        arrow.position.copy(position); 
+        this.arrows.add(arrow); 
+    }
+    addMapArrow(position){ 
+        var mapArrow = this.mapArrow.clone()
+        mapArrow.position.copy(position).setZ(0) 
+        this.mapArrows.add(mapArrow);
+    }
+    
+    
+    setArrowDir(arrows,index){
+        let arrow = arrows[index]
+        var nextOne = arrows[index+1];
+        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())
+        var angle = Math.atan2(direction.y, direction.x ) + Math.PI/2 //Math.PI/2是因为贴图本身箭头方向不朝x
+        arrow.rotation.z = angle
+        //console.log(angle)
+    }
+    
+    
+     
+     
+    
+    setRouteStart(pos, dealZ , datasetId  ){
+        if(this.routeStart && pos && this.routeStart.equals(pos)) return //可能重复设置
+        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, dealZ , datasetId  ){ 
+        if(this.routeEnd && pos && this.routeEnd.equals(pos)) return 
+        this.routeEnd = pos && new THREE.Vector3().copy(pos)
+        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'
+        });
+        return e < 0 ? 0 : e
+    }
+    getDestinationProjectionIndex(route) {//真正的终点
+        var e = route.findIndex(function(t) {
+            return t.instruction && t.instruction.type === "destination_projection_to_navgraph"
+        });
+        return e < 0 ? route.length - 1 : e
+    }
+    
+    generateRoute(){
+        if(!this.routeStart || !this.routeEnd){ 
+            
+            return
+        }
+        
+        
+        //array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
+        
+        
+        let create = ()=>{ 
+            this.routeLength = this.route.reduce((total, currentValue, currentIndex, arr)=>{
+                if(currentIndex == 0)return 0
+                return total + currentValue.distanceTo(arr[currentIndex-1]);
+            },0)
+            let count = Math.max(2,Math.round(this.routeLength / arrowSpacing))//点数
+            
+            const curve = new THREE.CatmullRomCurve3( this.route ); 
+            curve.curveType = 'chordal'//'centripetal'  'catmullrom'这个可能会超出路径外
+            this.curve = curve
+            
+            const scenePoints = curve.getSpacedPoints( count );//更平均
+            //const scenePoints = curve.getPoints( count );
+            scenePoints.splice(0,1);//去掉首尾
+            scenePoints.pop()
+            this.scenePoints = scenePoints
+            
+            this.updateMapArrows() 
+            this.displayRoute()
+            
+            {//map focus on this area
+                
+                const minBound = new THREE.Vector2(1,1)//针对垂直线,在地图上只有一个点
+                let bound = new THREE.Box2;
+                this.route.forEach(e=>{
+                    bound.expandByPoint(e)
+                })
+                let size = bound.getSize(new THREE.Vector2)
+                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)
+                
+                size.x = Math.max(size.x, minBound.x )
+                size.y = Math.max(size.y, minBound.y )
+                let duration = 1000
+                viewer.mapViewer.moveTo(center, size, duration)
+            }
+            
+            this.bus.emit('gotResult', {dis:this.routeLength})
+            /* this.generateDeferred && this.generateDeferred.resolve({dis:this.routeLength})
+            this.generateDeferred = null */
+        }
+        
+        
+        if(Potree.fileServer){
+            let dealData = (data)=>{
+                
+                if(!data.data){
+                    console.log('没有数据')
+                    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 < 2){//可能距离太短 
+                    console.log('路径点数为'+length+',直接取起点和终点连线') 
+                    this.route = [this.routeStart, this.routeEnd];
+                }else{ 
+                    let startIndex = this.getSourceProjectionIndex(data)
+                    let endIndex = this.getDestinationProjectionIndex(data)
+                    
+                    
+                    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)//.setZ(item.z)
+                        this.route.push(pos)
+                    })
+                    
+                    console.log(this.route)
+                    
+                    
+                }
+                this.setStartPole(this.route[0]) 
+                this.setEndPole(this.route[this.route.length-1]) 
+                
+                create()
+                /*
+                    distance: 0.17581000000000116
+                    distance_to_previous: 0.17581000000000116
+                    id: 567
+                    instruction: {type: 'source_projection_to_navgraph'}
+                    latitude: 22.366605927999238
+                    location: (3) [113.5957510575092, 22.366605927999238, -1.12419]
+                    longitude: 113.5957510575092
+                    z: -1.12419
+                */
+            }
+            
+            
+            
+            
+            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 
+                }else if(this.lastResult.routeStart.equals(this.routeEnd) &&  this.lastResult.routeEnd.equals(this.routeStart)){//..反向
+                    use = true
+                    if(data.data){
+                        data.data = this.lastResult.data.data.slice(0).reverse()
+                    }    
+                }
+                if(use){
+                    console.log('直接用上次的结果')
+                    return setTimeout(()=>{dealData(data)}, 1)//延迟是为了等待获得 RouteGuider.generateDeferred
+                       
+                }
+                
+            }
+            
+            
+            
+            
+            let start = this.routeStart.clone();
+            let end = this.routeEnd.clone();
+            let startLonlat = viewer.transform.lonlatToLocal.inverse(start)
+            let endLonlat = viewer.transform.lonlatToLocal.inverse(end)
+            
+            var query = {
+                source_longitude: startLonlat.x,
+                source_latitude: startLonlat.y,
+                source_z: start.z,
+                destination_longitude: endLonlat.x,
+                destination_latitude: endLonlat.y,
+                destination_z: end.z
+            };
+            
+            
+            //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] +'&')
+            }
+            
+            Potree.fileServer.get(url).then((data)=>{
+                console.log(data.data)
+                if(!this.routeStart || !this.routeEnd)return 
+                
+                this.lastResult = {//保存数据
+                    routeStart : this.routeStart.clone(),
+                    routeEnd: this.routeEnd.clone(),
+                    data,
+                     
+                }
+                
+                dealData(data)
+                
+            })
+            
+            
+        }else{
+            //创个直线
+            /* const sliceDis = 1
+            let dis = this.routeStart.distanceTo(this.routeEnd);
+            let count = Math.max(2,Math.round(dis / sliceDis))//点数
+            let realSlideDis = dis / (count-1);
+            let dir = new THREE.Vector3().subVectors(this.routeEnd, this.routeStart).normalize().multiplyScalar(realSlideDis);
+            this.route = [this.routeStart];
+            for(let i=0;i<count-1;i++){
+                let lastOne = this.route[i];
+                this.route.push(new THREE.Vector3().addVectors(lastOne,dir))
+            }
+            this.route.splice(0,1) //route不用包含收尾 */
+            this.clearRoute()
+            this.route = [this.routeStart, this.routeEnd]
+            create()
+            
+        }
+          
+    }
+    
+    updateMapArrows(ifReset){
+        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() 
+        this.mapPoints = mapPoints
+        
+        
+        var scale = 25/zoom
+        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.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.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={}){
+        if(!o.resetMap){
+            this.routeLength = 0
+            this.route = []
+            this.scenePoints = []
+            this.mapPoints = []
+            let arrows = this.arrows.children.slice(0)
+            arrows.forEach(e=>{
+                this.arrows.remove(e)
+            })
+        } 
+        
+        let mapArrows = this.mapArrows.children.slice(0) 
+        mapArrows.forEach(e=>{
+            this.mapArrows.remove(e)
+        })
+        
+        this.sceneMeshGroup.traverse(e=>e.visible = false)  //包括sprite也要设置,防止update
+        this.mapMeshGroup.visible = false
+        viewer.mapViewer.dispatchEvent({'type':'content_changed'})
+    }
+    
+    clear(){//退出
+        console.log('导航clear') 
+        this.routeStart = null
+        this.routeEnd = null
+        this.clearRoute()
+        
+    }
+}
+
+//大概每十米要花一秒
+
+
+
+/* 
+
+    存在的问题:
+    路径不准确。起始点和终点偏移。
+
+
+ */

+ 33 - 18
src/modules/siteModel/SiteModel.js

@@ -1,5 +1,5 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import SplitScreen from "../../utils/SplitScreen.js"
+import SplitScreen4Views from "../../utils/SplitScreen4Views.js"
 import {BuildingBox} from "./BuildingBox.js"
 import Common from "../../utils/Common.js";
 import {Images360} from '../Images360/Images360.js'
@@ -27,7 +27,7 @@ var SiteModel = {
         
         viewer.scene.scene.add(this.meshGroup) 
         this.meshGroup.name = 'siteModel' 
-        this.SplitScreen = SplitScreen
+        this.SplitScreen = SplitScreen4Views
         
         if(Potree.settings.editType == 'pano'){
             return
@@ -151,7 +151,7 @@ var SiteModel = {
         
         
         let mapViewport = viewer.mapViewer.viewports[0]
-        SplitScreen.splitScreen4Views({siteModel:true/* , viewports:[{name:'Top',viewport : mapViewport  }] */})
+        this.SplitScreen.split({siteModel:true/* , viewports:[{name:'Top',viewport : mapViewport  }] */})
         
         
         viewer.viewports.forEach(e=>{
@@ -180,7 +180,7 @@ var SiteModel = {
         Potree.Log('sitemodel leave')
         
         let mapViewport = viewer.mapViewer.viewports[0]
-        SplitScreen.recoverFrom4Views()
+        this.SplitScreen.recover()
 
         viewer.viewports.forEach(e=>{
             if(e.name != 'mapViewport'){
@@ -518,22 +518,25 @@ var SiteModel = {
    
     ,
    
-    createFromData:function( buildType, parent ,sid, name, points=[], holes=[], zMin, zMax, initial,panos,flagPano){ 
+    createFromData:function( buildType, parent ,sid, name, points=[], holes=[], zMin, zMax, initial,panos_,flagPano){ 
         if(buildType != 'building' && buildType != 'floor' && buildType != 'room' ) return
           
         
         var {points, zMax, zMin} = this.getPreDealData(points,  zMin, zMax , initial, buildType, parent )
         
         
-        
+        let panos = []
         {
             
-            let getPano = (id)=>{
-                return viewer.images360.panos.find(pano=>pano.id == id)
-            }
-            
-            panos = panos ? panos.map(e=>getPano(e)) : [];
-            flagPano = flagPano != void 0 ? getPano(flagPano) : null ; //最中心的pano 或者 最靠近该实体的pano(当panos为空时)
+             
+            if(panos_){
+                panos_.forEach(sid=>{
+                    let pano = viewer.images360.getPano(sid,'sid')
+                    if(pano)panos.push(pano)
+                })
+            } 
+             
+            flagPano = flagPano != void 0 ? viewer.images360.getPano(flagPano,'sid') : null ; //最中心的pano 或者 最靠近该实体的pano(当panos为空时)
                 
             if(!this.editing && buildType == 'floor' && !flagPano){//没有的话可能是自动添加的floor,直接用parent的吧
                 panos = parent.panos;
@@ -868,11 +871,11 @@ var SiteModel = {
         
         let firstZ, firstIntersect;
         let drag = (e)=>{
-            var intersectPoint = e.intersectPoint.orthoIntersect //不要点云的intersect,只要orthocamera算出的平面intersect
+            var intersect = e.intersect.orthoIntersect //不要点云的intersect,只要orthocamera算出的平面intersect
              
             if(firstIntersect != void 0){
                 
-                let moveZ = intersectPoint.z - firstIntersect                 
+                let moveZ = intersect.z - firstIntersect                 
                 if(this.selected.buildType == 'floor'){//楼层   
                     //限制高度不能超过上下
                     if(e.target == height_pull_box_up){  
@@ -892,7 +895,7 @@ var SiteModel = {
                     }
                 }  
             }else{
-                firstIntersect = intersectPoint.z  
+                firstIntersect = intersect.z  
             }               
         }
         
@@ -1073,7 +1076,15 @@ var SiteModel = {
                 let panos = entity.panos.filter(e=>pointcloud.panos.includes(e));
                 let panoCount = panos.length
                 
-                let score = volume / cloudVolume + panoCount / pointcloud.panos.length
+                let score = volume / cloudVolume 
+                
+                if(pointcloud.panos.length > 0){
+                    score += panoCount / pointcloud.panos.length
+                }else{
+                    //score += 1
+                }
+                
+                
                  
                 scores.push({entity, volume, panoCount, score}) 
                 
@@ -1091,6 +1102,10 @@ var SiteModel = {
             while(1){
                 let scores = getScores(pointcloud, entities, cloudVolume)
                 if(scores.length == 0 || scores[0].volume/cloudVolume < 0.0001 && scores[0].volume < 3 ){//如果约等于0 
+                    if(scores[0] && scores[0].entity.buildType == 'room'){ 
+                        pointcloud.belongToEntity = scores[0].entity; //当该数据集在楼层中,但不在楼层中的任意一个房间时,任意挂载在其中一个房间上。
+                        break
+                    } 
                     pointcloud.belongToEntity = null
                     break;
                 }else{
@@ -1198,9 +1213,9 @@ var SiteModel = {
     focusEntity(id){
         var entity = this.entities.find(e => e.sid == id)
         let boundingBox = entity.getBound()
-        let boundSize = boundingBox.getSize(new THREE.Vector3())
+        //let boundSize = boundingBox.getSize(new THREE.Vector3())
         let center = boundingBox.getCenter(new THREE.Vector3())
-        this.SplitScreen.focusOnObject(boundSize, center) 
+        this.SplitScreen.focusOnObject(boundingBox, center) 
         
         this.gotoEntity(id, false, 0)
         

+ 28 - 14
src/navigation/FirstPersonControls.js

@@ -109,7 +109,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                 
                 
                 let view = this.scene.view;
-                if(Potree.settings.rotAroundPoint && this.intersectStart && !viewport.unableChangePos && !viewer.images360.isAtPano() && !this.viewer.inputHandler.pressedKeys[17]){//定点旋转:   以当前intersect的点为target旋转,不改点在屏幕中的位置
+                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的方式旋转:
@@ -174,7 +174,10 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 			} 
             
             if (mode.includes('pan')) {//平移 
-                if(viewport.unableChangePos)return
+                if(!this.canMovePos(viewport)){
+                    return
+                } 
+                
                 if(camera.type == "OrthographicCamera"){
                    
                     //console.log(e.drag.pointerDelta, e.pointer, e.drag.end)
@@ -182,6 +185,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                   
                     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] 
@@ -196,13 +200,19 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                                     pointclouds = PanoEditor.selectedClouds
                                 }  
                             }else{
+                                PanoEditor.dispatchEvent('needToDisConnect')
                                 console.warn('选中的漫游点连通了整个数据集,不允许移动')
                             } 
                         }
                         
-                        if(!pointclouds && e.buttons === Buttons.LEFT && viewport.alignment.rotateSide){
-                            return PanoEditor.rotateSideCamera(e.drag.pointerDelta.x)
+                        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)
+                        }  
+                    
                     }else{ 
                         /* if(Alignment.selectedClouds && Alignment.selectedClouds.length){
                             pointclouds = a && e.drag.intersectStart.pointclouds && Common.getMixedSet(Alignment.selectedClouds, e.drag.intersectStart.pointclouds).length && Alignment.selectedClouds
@@ -216,7 +226,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                     if(pointclouds){
                         this.dispatchEvent({
                             type : "transformPointcloud", 
-                            intersectPoint: e.intersectPoint.orthoIntersect,   
+                            intersect: e.intersect.orthoIntersect,   
                             intersectStart: e.drag.intersectStart.orthoIntersect,
                             moveVec,        
                             pointclouds, 
@@ -321,7 +331,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 
         let dolly = (e={})=>{
                        
-            if(this.currentViewport.unableChangePos){//全景时 
+            if(Potree.settings.displayMode == 'showPanos' && this.currentViewport == viewer.mainViewport/* this.currentViewport.unableChangePos */){//全景时 
                 this.dispatchEvent({type:'dollyStopCauseUnable',delta:e.delta, scale:e.scale})
                 return 
             }
@@ -392,9 +402,9 @@ export class FirstPersonControls extends THREE.EventDispatcher {
         }
 
 		let scroll = (e) => {
-            if(!this.enabled)return 
+            if(!this.enabled || !e.hoverViewport)return 
             this.setCurrentViewport(e)
-          
+            
                 
             e.camera = e.hoverViewport.camera                
             dolly(e) 
@@ -427,9 +437,9 @@ export class FirstPersonControls extends THREE.EventDispatcher {
         }
         let prepareRotate = (e)=>{ 
             this.pointerDragStart = e.pointer.clone()  
-            this.intersectStart = e.intersectPoint && e.intersectPoint.location && {
-                location : e.intersectPoint.location,
-                pointer :  e.intersectPoint.location.clone().project(e.dragViewport.camera) //intersect点在屏幕中的位置
+            this.intersectStart = e.intersect && e.intersect.location && {
+                location : e.intersect.location,
+                pointer :  e.intersect.location.clone().project(e.dragViewport.camera) //intersect点在屏幕中的位置
             }
             //console.log('prepareRotate' )
         }
@@ -480,7 +490,11 @@ export class FirstPersonControls extends THREE.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;
          
@@ -548,7 +562,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 			camera,
 			this.viewer,
 			this.scene.pointclouds); */
-        var I = this.viewer.inputHandler.intersectPoint
+        var I = this.viewer.inputHandler.intersect
 		if (!I) {
 			return;
 		}
@@ -662,7 +676,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                 }
             }
         
-            if(!this.currentViewport.unableChangePos){
+            if(this.canMovePos(this.currentViewport) && !this.lockKey){
                 if(this.lockElevation){
                     let dir = view.direction;
                     dir.z = 0;

+ 264 - 112
src/navigation/InputHandler.js

@@ -9,6 +9,9 @@ import {KeyCodes} from "../KeyCodes.js";
 import {Utils} from "../utils.js";
 import {Buttons} from "../defines.js";  
 import Common from "../utils/Common.js";
+import DepthBasicMaterial from '../materials/DepthBasicMaterial.js'
+
+
 
 export class InputHandler extends THREE.EventDispatcher {
 	constructor (viewer,scene) {
@@ -46,6 +49,7 @@ export class InputHandler extends THREE.EventDispatcher {
 			this.domElement.tabIndex = 2222;
 		}
         
+        this.lastPointerUpTime = 0
         
         this.touches = []
 
@@ -68,7 +72,7 @@ export class InputHandler extends THREE.EventDispatcher {
 		this.domElement.addEventListener('mousewheel', this.onMouseWheel.bind(this), false);
 		this.domElement.addEventListener('DOMMouseScroll', this.onMouseWheel.bind(this), false); // Firefox
         
-		this.domElement.addEventListener('dblclick', this.onDoubleClick.bind(this)); 
+		//this.domElement.addEventListener('dblclick', this.onDoubleClick.bind(this));  //因为双击时间间隔是跟随系统的所以不好判断
 		
         this.domElement.addEventListener('keydown', this.onKeyDown.bind(this));
 		window.addEventListener('keyup', this.onKeyUp.bind(this));
@@ -76,12 +80,12 @@ export class InputHandler extends THREE.EventDispatcher {
         //window.addEventListener('focus',()=>{
         window.addEventListener('blur',this.onKeyUp.bind(this)); //add
             
-          
-        
+             
+         
 		this.domElement.addEventListener('touchstart', this.onTouchStart.bind(this));
 		this.domElement.addEventListener('touchend', this.onTouchEnd.bind(this));
 		this.domElement.addEventListener('touchmove', this.onTouchMove.bind(this));
-        
+          
         
         {
             this.addEventListener('isMeasuring',(e)=>{ 
@@ -359,7 +363,7 @@ export class InputHandler extends THREE.EventDispatcher {
 				});
 			//}
 		}
-
+        this.needSingleClick = false//add
 		e.preventDefault();
 	}
 
@@ -394,11 +398,10 @@ export class InputHandler extends THREE.EventDispatcher {
 		this.dragViewport = this.hoverViewport = viewport
       
         
-        if(isTouch){ 
-            this.hoveredElements = this.getHoveredElements(); 
-              
-            let intersectPoint = this.getIntersect(viewport  )
-            
+        if(isTouch || !Potree.settings.intersectWhenHover ){ 
+            this.hoveredElements = this.getHoveredElements();
+            this.intersect = this.getIntersect(viewport)
+            //this.intersect = this.getWholeIntersect()
             
         }
         
@@ -446,7 +449,7 @@ export class InputHandler extends THREE.EventDispatcher {
         }
          
        
-        this.drag.intersectStart = this.intersectPoint;
+        this.drag.intersectStart = this.intersect;
         this.mouseDownMouse = this.mouse.clone()
         
         this.pointerDownTime = Date.now()
@@ -461,7 +464,19 @@ export class InputHandler extends THREE.EventDispatcher {
 		 
 	}
 
-
+    /* getWholeIntersect(hoveredElements, intersectPoint){//add
+        hoveredElements = hoveredElements || this.hoveredElements
+        intersectPoint = intersectPoint || this.intersectPoint  
+        if(Potree.settings.intersectOnObjs && hoveredElements[0] && hoveredElements[0].object.isModel){
+            return {//模拟点云的intersectPoint的结构写法
+                hoveredElement : hoveredElements[0] ,
+                location: hoveredElements[0].point,
+                point: {normal: hoveredElements[0].face.normal },
+                distance: hoveredElements[0].distance,
+                object: hoveredElements[0].object
+            } 
+        }else return intersectPoint  
+    } */
 
 
     getEventDesc(e,isTouch){//搜集dispatchEvent要给的一般数据 
@@ -470,12 +485,17 @@ export class InputHandler extends THREE.EventDispatcher {
             mouse: this.mouse, 
             pointer:this.pointer,
             drag :this.drag,  
+            isTouch,
             dragViewport : this.dragViewport,
             hoverViewport: this.hoverViewport,
            // button: isTouch ? 0 : e.button,
-            intersectPoint:this.intersectPoint,
-            isTouch,
+            //intersectPoint:this.intersectPoint,
+            hoveredElement: this.hoveredElements[0],
+            intersect: this.intersect//this.getWholeIntersect() , //可能包含mesh上的,针对融合页面
         }
+         
+        
+        
         if(e){
             o.isAtDomElement = e.target == this.domElement 
         } 
@@ -501,6 +521,7 @@ export class InputHandler extends THREE.EventDispatcher {
         if(isTouch && e.touches.length >= 1){ 
             return
         }
+        let now = Date.now()
         
         
 		if (this.logMessages) console.log(this.constructor.name + ': onMouseUp');
@@ -508,7 +529,7 @@ export class InputHandler extends THREE.EventDispatcher {
 		e.preventDefault();
         
         let pressDistance = this.mouseDownMouse.distanceTo(this.mouse);
-        let pressTime = Date.now() - this.pointerDownTime;
+        let pressTime = now - this.pointerDownTime;
         
 		let noMovement = this.drag.pointerDelta.length() == 0//this.getNormalizedDrag().length() === 0;
         
@@ -546,8 +567,8 @@ export class InputHandler extends THREE.EventDispatcher {
         
         
 		if (this.drag) { 
-            //拖拽结束  
-            
+            //拖拽结束   
+
 			if (this.drag.object/*  && e.button == THREE.MOUSE.LEFT */) {//add LEFT
 				if (this.logMessages) console.log(`${this.constructor.name}: drop ${this.drag.object.name}`);
 				 
@@ -570,16 +591,17 @@ export class InputHandler extends THREE.EventDispatcher {
                 ));
                   
                 
-			}
-
-            // check for a click 
+			} 
             
-            if(pressDistance < Potree.config.clickMaxDragDis && pressTime<Potree.config.clickMaxPressTime){
+            // check for a click  
+            
+            if(pressDistance < Potree.config.clickMaxDragDis && pressTime<Potree.config.clickMaxPressTime && !e.unableClick){
                 let clickElement
                 if(this.hoveredElements){
                     clickElement = this.hoveredElements.find(e=>e.object._listeners['click']) 
                     if(clickElement){
-                        if (this.logMessages) console.log(`${this.constructor.name}: click ${clickObject.name}`);
+                        //console.log('clickElement',clickElement)
+                        if (this.logMessages) console.log(`${this.constructor.name}: click ${clickElement.name}`);
                         clickElement.object.dispatchEvent($.extend(  
                             this.getEventDesc(e,isTouch),
                             {
@@ -587,21 +609,55 @@ export class InputHandler extends THREE.EventDispatcher {
                                 pressDistance     
                             }
                         )); 
-                    }  
+                    }
+                    
+                    
                 }
                 
-                if(!clickElement){
+                
+                let consumed = false;
+                let consume = () => { return consumed = true; };
+                let desc = this.getEventDesc(e,isTouch)
+                //if(!clickElement){
                     this.viewer.dispatchEvent($.extend(  
-                        this.getEventDesc(e,isTouch),
+                        desc,
                         {
                             type: 'global_click',  
-                            pressDistance
+                            pressDistance,
+                            clickElement,
+                            consume
                         }
                     )); 
-                } 
+                //}
+                
+                
+                
+                //增加 单击:
+                this.needSingleClick = true;
+              
+                
+                consumed || setTimeout(()=>{
+                    if(this.needSingleClick){
+                        this.viewer.dispatchEvent($.extend(  
+                            desc,
+                            {
+                                type: 'global_single_click',  
+                                pressDistance,
+                                clickElement
+                            }
+                        ));
+                    }
+                }, Potree.config.doubleClickTime+1)
+                
+                //自行执行双击:
+                
+                if(now - this.lastClickTime < Potree.config.doubleClickTime){
+                    this.onDoubleClick(e)
+                }
+                
+                this.lastClickTime = now
             }
-            
-			 
+
             this.drag = null;
              
 		}
@@ -633,6 +689,8 @@ export class InputHandler extends THREE.EventDispatcher {
 				this.deselectAll();
 			}
 		}
+        
+        
     }
     
 	onMouseUp (e) {
@@ -697,25 +755,29 @@ export class InputHandler extends THREE.EventDispatcher {
     }
 
 
-    ifBlockedByIntersect(point, margin, usePointcloud, cameraPos, pickWindowSize){//某点是否被遮挡(不允许camera修改位置, 因为depthTex不好置换)
+    ifBlockedByIntersect(point, margin=0, usePointcloud, cameraPos, pickWindowSize, pano){//某点是否被遮挡(不允许camera修改位置, 因为depthTex不好置换)
          
         if(cameraPos){
             usePointcloud = true  //只有使用点云才允许换位置
         }
          
-        let intersectPoint = this.getIntersect(this.hoverViewport, true, pickWindowSize, null, usePointcloud, {point, cameraPos})
-        if(intersectPoint && intersectPoint.distance+margin <= point.distanceTo(cameraPos||this.hoverViewport.view.position)){
-            return intersectPoint //被遮挡
+        let intersect = this.getIntersect(this.hoverViewport, true, pickWindowSize, null, usePointcloud, {point, cameraPos, pano})
+        let cameraPos_ = (!usePointcloud && pano) ? pano.position : (cameraPos||this.hoverViewport.view.position)
+        if(intersect && intersect.distance+margin <= point.distanceTo(cameraPos_)){
+            return intersect //被遮挡
         }
         //点云模式,对没加载出的点云不准确。 尤其是需要修改相机位置时,因临时修改并不能使点云加载。
     }
 
-    getIntersect(viewport,   onlyGetIntersect, pickWindowSize, dontIntersectPointcloud, usePointcloud, prop={}){
+
+
+    getIntersect(viewport,   onlyGetIntersect, pickWindowSize, dontIntersect, usePointcloud, prop={}){// usePointcloud:必须使用点云
         let intersectPoint  
         let camera = viewport.camera
+        let raycaster 
+        
         
-        if(Potree.settings.displayMode == 'showPanos' && viewer.images360.currentPano.pointcloud.hasDepthTex && !usePointcloud && !this.isMeasuring && viewport == viewer.mainViewport ){
-            let raycaster 
+        let getByDepthTex = ()=>{
             /* if(prop.point){
                 raycaster = new THREE.Raycaster() 
                 var dir = new THREE.Vector3().subVectors(prop.point, camera.position).normalize()
@@ -723,14 +785,16 @@ export class InputHandler extends THREE.EventDispatcher {
             }  */
             let intersect
             if(prop.point){
-                let dir = new THREE.Vector3().subVectors(subPano.position, mainPano.position).normalize();
-                intersect = {dir}
+                let cameraPos = prop.pano ? prop.pano.position : camera.position
+                let dir = new THREE.Vector3().subVectors(prop.point, cameraPos).normalize(); 
+                intersect = {dir} 
             }else{
                 intersect = Utils.getIntersect(camera, [viewer.images360.cube], this.pointer, raycaster) 
             } 
-            intersectPoint = viewer.images360.depthSampler.sample(intersect, null, !!prop.point) 
-             
-        }else{
+            intersectPoint = viewer.images360.depthSampler.sample(intersect, prop.pano, !!prop.point)  //可能不准确, 因pano可能未加载depthTex
+        }
+        
+        let getByCloud = ()=>{
             if(prop.point){ 
                 prop.cameraPos && camera.position.copy(prop.cameraPos)
                 camera.lookAt(prop.point)
@@ -741,7 +805,7 @@ export class InputHandler extends THREE.EventDispatcher {
                 this.mouse.set(Math.round(viewport.resolution.x/2), Math.round(viewport.resolution.y/2))
             } 
                 
-            intersectPoint = (viewport.noPointcloud || dontIntersectPointcloud)? null : Utils.getMousePointCloudIntersection(
+            intersectPoint = (viewport.noPointcloud || dontIntersect)? null : Utils.getMousePointCloudIntersection(
                 viewport,
                 this.mouse,
                 this.pointer, 
@@ -757,40 +821,90 @@ export class InputHandler extends THREE.EventDispatcher {
                 this.pointer.copy(prop.pointer)
                 this.mouse.copy(prop.mouse)
             } 
+            
+            
         }
         
+        let canUseDepthTex = Potree.settings.displayMode == 'showPanos' && viewer.images360.currentPano.pointcloud.hasDepthTex  && viewport == viewer.mainViewport && !usePointcloud 
         
+        if(canUseDepthTex && !this.isMeasuring){
+            getByDepthTex()
+        }else{
+            getByCloud() 
+            if(!intersectPoint && canUseDepthTex  ){//得不到的话再使用 getByDepthTex 得一次
+                getByDepthTex()
+            }
+        }
+          
+        //console.log(viewport.name , intersectPoint &&  intersectPoint.location )
+        let intersect
+        let intersectOnModel, allElements
+       
         
-        
-        
+        if(Potree.settings.intersectOnObjs && !dontIntersect){
+            if(prop.point){
+                raycaster = new THREE.Raycaster() 
+                var dir = new THREE.Vector3().subVectors(prop.point, camera.position).normalize()
+                raycaster.set(camera.position, dir) //var origin = new THREE.Vector3(pointer.x, pointer.y, -1).unproject(camera),
+            }  
             
-        //console.log(viewport.name , intersectPoint &&  intersectPoint.location )
+            
+            allElements = this.getHoveredElements(viewer.objs.children, true, raycaster)
+            
+            
+            if(allElements[0]){
+                intersectOnModel = {//模拟点云的intersectPoint的结构写法
+                    hoveredElement : allElements[0] ,
+                    location: allElements[0].point,
+                    //point: {normal: allElements[0].face.normal },
+                    normal: allElements[0].face.normal,
+                    distance: allElements[0].distance,
+                    object: allElements[0].object
+                } 
+            }
+        }
+        
+        if(intersectPoint && intersectOnModel){
+            if(intersectPoint.distance < intersectOnModel.distance){
+                intersect = intersectPoint
+            }else{
+                intersect = intersectOnModel
+            } 
+        }else{
+            intersect = intersectOnModel || intersectPoint
+        }                
+          
         
         if(viewport.camera.type == 'OrthographicCamera'/*  == 'mapViewport' */){ 
             let pos3d = new THREE.Vector3(this.pointer.x,this.pointer.y,-1).unproject(viewport.camera); //z:-1朝外   
              
-            if(!intersectPoint){
-                intersectPoint = {}
+            if(!intersect){
+                intersect = {}
             }   
-            intersectPoint.orthoIntersect = pos3d.clone() 
+            intersect.orthoIntersect = pos3d.clone() 
         } 
+         
+        //记录全部hover到的:
+        if(intersect){
+            intersect.allElements = allElements
+            intersect.pointclouds = intersectPoint ? intersectPoint.pointclouds : []
+        }
+        
+        
         if(onlyGetIntersect){ 
-            return intersectPoint
+            return intersect
         }
         
-        if (intersectPoint) { 
-            
-            
+        if (intersect) {  
             if(viewer.showCoordType){ //显示坐标位置时
-                let pos = intersectPoint.point.position.toArray()
+                let pos = intersect.point.position.toArray()
                 if(viewer.showCoordType == "local"){
                      
                 }else if(viewer.showCoordType == "lonlat"){
                     pos = viewer.transform.lonlatToLocal.inverse(pos)
                 }else{
                     pos = viewer.transform.lonlatToLocal.inverse(pos)
-                    pos = viewer.transform.lonlatTo4550.forward(pos)
-                    
+                    pos = viewer.transform.lonlatTo4550.forward(pos) 
                 }
                  
                 viewer.dispatchEvent({
@@ -800,11 +914,11 @@ export class InputHandler extends THREE.EventDispatcher {
         }
         //console.log('getIntersect', !!intersectPoint)  
         
-        this.intersectPoint = intersectPoint 
+        this.intersect = intersect 
          
-        intersectPoint && (this.hoverViewport.lastIntersect = intersectPoint)
+        intersect && (this.hoverViewport.lastIntersect = intersect)
          
-        return intersectPoint         
+        return intersect         
     }
     
     
@@ -825,25 +939,32 @@ export class InputHandler extends THREE.EventDispatcher {
 		this.hoverViewport = viewport
         if(!viewport)return//刚变化viewport时会找不到
          
-		
-        let intersectPoint
+		let isFlying = this.viewer.viewports.some(e=>e.view.isFlying()) || viewer.scene.cameraAnimations.some(c=>c.onUpdate)
+        let intersect 
         
         
-        if(e.onlyGetIntersect || !this.drag || this.drag.object || viewport.alignment ){ //没有拖拽物体,但按下鼠标了的话,不intersect
+        if(e.onlyGetIntersect ||  Potree.settings.intersectWhenHover && (!this.drag || this.drag.object || viewport.alignment ) ){ //没有拖拽物体,但按下鼠标了的话,不intersect
         
-            let dontIntersectPointcloud =  this.drag && viewport.alignment && Potree.settings.editType == 'pano' || viewer.images360.flying // flying 时可能卡顿
+            let dontIntersect =  this.drag && viewport.alignment/*  && Potree.settings.editType == 'pano'  */|| isFlying/* viewer.images360.flying */ // flying 时可能卡顿
             //console.log('dontIntersectPointcloud',dontIntersectPointcloud)
-            intersectPoint = this.getIntersect(viewport,  e.onlyGetIntersect, e.pickWindowSize, dontIntersectPointcloud, e.whichPointcloud) //数据集多的时候卡顿
+            intersect = this.getIntersect(viewport,  e.onlyGetIntersect, e.pickWindowSize, !!dontIntersect, e.whichPointcloud) //数据集多的时候卡顿
             //console.log('intersectPoint', intersectPoint)
         } 
         
         if(e.onlyGetIntersect){ 
-            return intersectPoint
+            /* if(Potree.settings.intersectOnObjs){
+                let hoveredElements = this.getHoveredElements() //应该不用发送mouseover事件吧
+                let intersect = this.getWholeIntersect(hoveredElements, intersectPoint)
+                return intersect
+            }
+         
+            return intersectPoint */
+            return intersect
         }
         e.preventDefault();
         
         
-        let hoveredElements = []
+        
         
         
         /* if(intersectPoint && intersectPoint.pointcloud){
@@ -872,9 +993,7 @@ export class InputHandler extends THREE.EventDispatcher {
                 
                 
             } else {
-                
-                
-                
+                 
                 if (this.logMessages) console.log(this.constructor.name + ': drag: ');
 
                 let dragConsumed = false; 
@@ -891,16 +1010,19 @@ export class InputHandler extends THREE.EventDispatcher {
         } 
         
         
+        
         if(!isTouch || e.touches.length == 1){ 
         
-            if(!this.drag || this.drag.notPressMouse ){
-                hoveredElements = this.getHoveredElements(); 
+            if((!this.drag || this.drag.notPressMouse || Potree.settings.intersectOnObjs && this.drag.object) && !isFlying){
+                
+                /* let blacklist = this.drag && this.drag */
+                let hoveredElements = this.getHoveredElements( ); 
                 if(hoveredElements.length > 0){
                     let names = hoveredElements.map(h => h.object.name).join(", ");
                     if (this.logMessages) console.log(`${this.constructor.name}: onMouseMove; hovered: '${names}'`);
                 }
             
-                let curr = hoveredElements.map(a => a.object).find(a => true);
+                let curr = hoveredElements.map(a => a.object).find(a => true);//只取第一个
                 let prev = this.lastMouseoverElement //this.hoveredElements.map(a => a.object).find(a => true);
 
                 if(curr !== prev){
@@ -934,9 +1056,15 @@ export class InputHandler extends THREE.EventDispatcher {
                         });
                     }
                 }
-
+                this.hoveredElements = hoveredElements
             }
-  
+            
+            
+            
+            //this.intersect = this.getWholeIntersect()
+            
+            
+            
             
             this.viewer.dispatchEvent($.extend(  
                 this.getEventDesc(e,isTouch),
@@ -945,12 +1073,11 @@ export class InputHandler extends THREE.EventDispatcher {
                     
                 }
             ))
-            this.hoveredElements = hoveredElements
+            
              
         }
         
 		
-
 		
 	}
 	
@@ -1139,60 +1266,73 @@ export class InputHandler extends THREE.EventDispatcher {
 			return null;
 		}
 	}
+  
+    
+     
  
-	getHoveredElements () {
-		let scenes = this.hoverViewport.interactiveScenes || this.interactiveScenes.concat(this.scene);
-
-		let interactableListeners = ['mouseup', 'mousemove', 'mouseover', 'mouseleave', 'drag', 'drop', 'click', 'select', 'deselect'];
-		let interactables = [];
-		for (let scene of scenes) {
-			scene.traverseVisible(node => {//检测加了侦听的object
-				if (node._listeners && node.visible && !this.blacklist.has(node)) {
-					let hasInteractableListener = interactableListeners.filter((e) => {
-						return node._listeners[e] !== undefined;
-					}).length > 0;
-
-					if (hasInteractableListener) {
-						interactables.push(node);
-					}                        
-				}
-			});
-		}
+	getHoveredElements (interactables, dontCheckDis, raycaster) { 
 		
+        if(!interactables){
+             
+             
+            let scenes = this.hoverViewport.interactiveScenes || this.interactiveScenes.concat(this.scene);
+
+            let interactableListeners = ['mouseup', 'mousemove', 'mouseover', 'mouseleave', 'drag', 'drop', 'click', 'select', 'deselect'];
+            interactables = []
+            for (let scene of scenes) {
+                scene.traverseVisible(node => {//检测加了侦听的object
+                    if (node._listeners && node.visible && !this.blacklist.has(node) ) {
+                        let hasInteractableListener = interactableListeners.filter((e) => {
+                            return node._listeners[e] !== undefined;
+                        }).length > 0;
+
+                        if (hasInteractableListener) {
+                            interactables.push(node);
+                        }                        
+                    }
+                });
+            }  
+		}  
+		 
 		let camera = this.hoverViewport.camera
-        let ray = Utils.mouseToRay(this.pointer, camera  );
-		
-		let raycaster = new THREE.Raycaster();
-		raycaster.ray.set(ray.origin, ray.direction);  
-        raycaster.camera = camera //add
-        
-      
+        if(!raycaster){ 
+            let ray = Utils.mouseToRay(this.pointer, camera  );
+            
+            raycaster = new THREE.Raycaster();
+            raycaster.ray.set(ray.origin, ray.direction);  
+            raycaster.camera = camera //add 
+        }
         if(camera.type == "OrthographicCamera"){//使无论多远,threshold区域都是一样宽的
             raycaster.params.Line.threshold = 20/camera.zoom  
         }else{
             raycaster.params.Line.threshold = 0.2; 
-        }
+        } 
         raycaster.params.Line2 = {threshold :20 } //拓宽的lineWidth
         
-        
-        
         //raycaster.layers.enableAll()//add
         viewer.setCameraLayers(raycaster,   //设置能识别到的layers(如空间模型里只有mapViewer能识别到marker)
-            ['sceneObjects','mapObjects','measure',  'transformationTool'],
+            ['sceneObjects','mapObjects','measure',  'transformationTool', 'model'],
             this.hoverViewport && this.hoverViewport.extraEnableLayers
         )
-        
+        //this.hoverViewport.beforeRender && this.hoverViewport.beforeRender()
         
         viewer.dispatchEvent( {type:'raycaster',  viewport: this.hoverViewport})//add
 		let intersections = raycaster.intersectObjects(interactables.filter(o => o.visible), true); //原本是false 检测不到children
-        if(this.intersectPoint && this.intersectPoint.distance != void 0){//add
+    
+        let intersectionsCopy = intersections.slice()
+        
+        
+        
+        if(this.intersect && this.intersect.distance != void 0 && !dontCheckDis){//add
             intersections = intersections.filter(e=>{
                 let material = e.object.material
                 
-                return (material.depthTest == false || material.depthWrite == false) && !material.useDepth  //!material.depthTestWhenPick
-                 || e.distance < this.intersectPoint.distance
+                return (e.object.pickDontCheckDis || material.depthTest == false || material.depthWrite == false) && !material.useDepth  //!material.depthTestWhenPick
+                 || ( material.useDepth ? e.distance < this.intersect.distance + material.uniforms.occlusionDistance.value : e.distance < this.intersect.distance )
             }) 
         }
+     
+       
         intersections = intersections.map(e=>{//add 转化为interactables
             var object = e.object; 
             do{ 
@@ -1207,10 +1347,22 @@ export class InputHandler extends THREE.EventDispatcher {
             return e
         })
         
+        
+      
+        
+        
+        
         //add for测量线,在检测到sphere时优先选中sphere而非线
-        intersections = intersections.sort(function(a,b){return b.object.renderOrder-a.object.renderOrder}) // 降序
- 
-
+         
+        //intersections = intersections.sort(function(a,b){return b.object.renderOrder-a.object.renderOrder}) // 降序
+        intersections = intersections.sort(function(a,b){
+            let order2 = b.object.pickOrder || 0
+            let order1 = a.object.pickOrder || 0
+            return order2-order1
+        }) // 降序
+        
+        
+        //console.log('getHoveredElement ', intersections)
 		return intersections;
 	}
 
@@ -1249,7 +1401,7 @@ export class InputHandler extends THREE.EventDispatcher {
 
 		return mouseDelta;
 	} */
-    
+     
     getMouseDirection(pointer) {//add 
         pointer = pointer || this.pointer
 		let camera = this.hoverViewport.camera

+ 109 - 14
src/navigation/OrbitControls.js

@@ -17,6 +17,8 @@ import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Buttons} from "../defines.js";
 import {Utils} from "../utils.js"; 
 
+let minRadius = 2
+
  
 export class OrbitControls extends THREE.EventDispatcher{
 	
@@ -31,7 +33,7 @@ export class OrbitControls extends THREE.EventDispatcher{
 
 		this.rotationSpeed = 3;  //旋转速度
         
-        
+         
 
 		this.fadeFactor = 100;
 		this.yawDelta = 0;
@@ -45,6 +47,17 @@ export class OrbitControls extends THREE.EventDispatcher{
         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
  
@@ -136,9 +149,9 @@ export class OrbitControls extends THREE.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();
 		};
 
@@ -236,7 +249,12 @@ export class OrbitControls extends THREE.EventDispatcher{
         }) */
         
         
-        
+        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也调小
+            }
+        })
         
 	}
 
@@ -253,7 +271,7 @@ export class OrbitControls extends THREE.EventDispatcher{
 		this.panDelta.set(0, 0);
 	}
 	
-	zoomToLocation(mouse){
+	/* zoomToLocation(mouse){
         if(!this.enabled)return
 		let camera = this.scene.getActiveCamera();
 		
@@ -263,17 +281,17 @@ export class OrbitControls extends THREE.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];
@@ -316,7 +334,37 @@ export class OrbitControls extends THREE.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());
@@ -327,6 +375,46 @@ export class OrbitControls extends THREE.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);
 
@@ -351,8 +439,8 @@ export class OrbitControls extends THREE.EventDispatcher{
 			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;
 
@@ -361,14 +449,21 @@ export class OrbitControls extends THREE.EventDispatcher{
 
 		{ // apply zoom
 			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);
 		}
 

+ 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
+};
+ */

+ 2 - 2
src/objects/Magnifier.js

@@ -16,7 +16,7 @@ const maxFov = THREE.Math.radToDeg(Math.atan(radius_ / magDistance_ )) * 2//提
 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)
+//console.log('width2dPX', width2dPX)
 
 
 
@@ -165,7 +165,7 @@ export default class Magnifier extends THREE.Object3D {//放大镜or望远镜
         var updateVisi = (e)=>{
             if(e.hoverViewport == viewer.mainViewport){
                 viewer.updateVisible(this,"atViewport", true)
-                this.update(e.intersectPoint && e.intersectPoint.location)
+                this.update(e.intersect && e.intersect.location)
             }else{
                 viewer.updateVisible(this,"atViewport", false) //小地图不显示
             } 

+ 32 - 22
src/objects/Reticule.js

@@ -27,12 +27,12 @@ export default class Reticule extends THREE.Mesh{
         this.forbitTex = texLoader.load(Potree.resourcePath+'/textures/pic-forbid.png') 
         
         //this.layers.set(0/* RenderLayers.RETICULE */);
-        this.renderOrder = 0
+        this.renderOrder = 100
         this.layers.set(Potree.config.renderLayers.marker);
         
         
         this.direction = new THREE.Vector3;
-        this.hidden = !0;
+       
         this.mouseLastMoveTime = Date.now();
         this.hoverViewport;
         this.matrixMap = new Map 
@@ -42,7 +42,7 @@ export default class Reticule extends THREE.Mesh{
         this.hide(0)
 
         //viewer.inputHandler.addInputListener(this);
-        viewer.addEventListener('global_mousemove',this.move.bind(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))//主要针对触屏
         
@@ -51,14 +51,19 @@ export default class Reticule extends THREE.Mesh{
         
         this.state = {}
         
-        viewer.addEventListener('measureMovePoint',()=>{
+        let startCrossStyle = ()=>{
             this.state.cross = true
             this.judgeTex()
-        }) 
-        viewer.addEventListener('endMeasureMove',()=>{
+        }
+        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){
@@ -83,7 +88,7 @@ export default class Reticule extends THREE.Mesh{
         }
         
          
-        viewer.mapViewer.dispatchEvent({type:'content_changed'})
+        viewer.mapViewer && viewer.mapViewer.dispatchEvent({type:'content_changed'})
     }
 
 
@@ -96,11 +101,11 @@ export default class Reticule extends THREE.Mesh{
            
         this.mouseLastMoveTime = Date.now()
         
-        this.updatePosition(e.intersectPoint, e.hoverViewport)
+        this.updatePosition(e.intersect, e.hoverViewport)
          
     }
 
-    hide(duration = 500){
+    hide(duration = 500){ 
         if(this.hidden)return
         
  
@@ -117,6 +122,7 @@ export default class Reticule extends THREE.Mesh{
     }
 
     show(duration = 300){
+         
         if(!viewer.getObjVisiByReason(this, 'force'))return
         //console.log("show Reticule")
         this.hidden = !1
@@ -153,7 +159,7 @@ export default class Reticule extends THREE.Mesh{
         }else{
             
             let n = camera.position.distanceTo(this.position)
-            s = 1 + .01 * n;
+            s = 1 + .1 * n;
             n < 1 && (s -= 1 - n)
         }
         this.scale.set(s, s, s);
@@ -189,15 +195,15 @@ export default class Reticule extends THREE.Mesh{
 
     
 
-    updatePosition(intersectPoint, viewport ){ //在地图(当地图融合到viewer时)和场景里都显示且完全相同(大小可能不同)
+    updatePosition(intersect, viewport ){ //在地图(当地图融合到viewer时)和场景里都显示且完全相同(大小可能不同)
          
         if (viewer.getObjVisiByReason(this, 'force')) {//没有被强制隐藏,如进入某个页面后强制不显示
-            if (!intersectPoint /* || !intersectPoint.point.normal */){ 
+            if (!intersect /* || !intersect.point.normal */){ 
                  return //this.hide();   
             }
                 
-            var atMap = !intersectPoint.location
-            let location = intersectPoint.location || intersectPoint.orthoIntersect.clone()
+            var atMap = !intersect.location
+            let location = intersect.location || intersect.orthoIntersect.clone()
             let normal  
             
             
@@ -210,14 +216,18 @@ export default class Reticule extends THREE.Mesh{
                 location.setZ(0);//低于相机高度即可
                 this.direction = normal.clone()
             }else{
-                if(intersectPoint.point){ 
-                    normal = new THREE.Vector3().fromArray(intersectPoint.point.normal ).applyMatrix4(intersectPoint.pointcloud.rotateMatrix);
+                /* 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 = intersectPoint.normal  //when showPanos
-                }
-                
+                    normal = intersect.normal  //when showPanos
+                } */
+                normal = intersect.normal 
                 if(normal){
-                    let ratio = Potree.settings.hasDepthTex ? 1 : 0.2;   
+                    let ratio = /* Potree.settings.useDepthTex ? 1 : */ 0.2;   
                     this.direction = this.direction.multiplyScalar(1-ratio); 
                     this.direction.add(normal.clone().multiplyScalar(ratio)); 
                 }
@@ -227,7 +237,7 @@ export default class Reticule extends THREE.Mesh{
              
             
             
-            this.position.copy(location)/* .add(normal.clone().multiplyScalar(.01)); */
+            this.position.copy(location)/* .add(normal.clone().multiplyScalar(.01));  */
             this.updateMatrix();  //lookAt之前要保证得到matrix
             this.lookAt(this.position.clone().add(this.direction));
             

+ 30 - 6
src/objects/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
         
@@ -58,6 +59,30 @@ export default class Sprite extends THREE.Mesh{
         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){
             (this.viewports || viewer.viewports).forEach(view=>{
@@ -65,7 +90,7 @@ export default class Sprite extends THREE.Mesh{
             })
             return;
         }
-        if(!this.visible || !this.root)return
+        if(!this.root || ! this.realVisible() /* this.visible */ )return
         if(this.viewports && !this.viewports.includes(e.viewport) )return
         if(e.viewport.name == 'magnifier')return
         
@@ -82,16 +107,15 @@ 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()) ,
+                    camera , position:this.root.getWorldPosition(new THREE.Vector3()) ,
                     resolution: e.viewport.resolution//2
                 }))
                 
@@ -112,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.visible || !this.root)return
+        if( !this.root || !this.realVisible()  )return
         
         var matrix = this.matrixMap.get(e.viewport);
           

+ 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
+
+
+
+
+
+
+

+ 17 - 17
src/objects/TextSprite.js

@@ -10,25 +10,25 @@ 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 ,    
-            dontFixOrient: options.dontFixOrient
-        })
+        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,8 +39,8 @@ 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);
         
         
@@ -88,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

+ 273 - 40
src/objects/tool/Compass.js

@@ -1,58 +1,291 @@
-
+ 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 
-import {Utils} from "../../utils.js";
 
-export class Compass{
 
-	constructor(viewer){
-		this.viewer = viewer;
+const initDir = new THREE.Vector3(0,1,0)//指南针模型的北方向 向屏幕里
 
-		this.visible = false;
-		this.dom = this.createElement();
+class Compass extends THREE.EventDispatcher{
+    
+    constructor(dom, viewport){
+        super()
+        this.angle = 0;
+        this.show = false; 
+        if(dom){
+            this.dom = $(dom);
+        }
+        
+        this.viewport = viewport
+        this.init()
+        
 
-		viewer.addEventListener("update", () => {
-			const direction = viewer.scene.view.direction.clone();
-			direction.z = 0;
-			direction.normalize();
+        
 
-			const camera = viewer.scene.getActiveCamera();
+    }
+    init(){ 
+        var width = 100, height = 100
+        if(!this.dom){ 
+            this.dom = $('<div name="compass"></div>')
+            $(viewer.renderArea).append(this.dom)
+        } 
+        this.dom.css({ display:"none",  position:"absolute",right:"1%",top: "60px",width:width+"px",height:height+"px", "z-index":100,"pointer-events":"none" })
 
-			const p1 = camera.getWorldPosition(new THREE.Vector3());
-			const p2 = p1.clone().add(direction);
+        let child = $("<div class='dirText north'><span>"+/* (config.lang=='zh'? */'北'/* :'N') */+"</span></div><div class='center'></div>")
+        this.dom.append(child)  
 
-			const projection = viewer.getProjection();
-			const azimuth = Utils.computeAzimuth(p1, p2, projection);
-			
-			this.dom.css("transform", `rotateZ(${-azimuth}rad)`);
-		});
+         
+        this.dom.find(".dirText").css({textAlign:"center","font-size":"10px","position":"absolute",
+                width: "100%",
+                height: "25px",
+        "line-height": "25px"})
+         
+        this.dom.find(".north").css({"color":"#02a0e9","top":"0"})
+        this.dom.find(".south").css({"color":"#ff1414","bottom":"0"})
+        this.dom.find(".center").css({
+            //"background":`url(${config.getStaticResource('img')}/dire.png)`,
+            width: width/2+"px",
+            height: height/2+"px",
+            "background-size": "contain",
+            "background-position": "center",
+            left: "50%",
+            top: "50%",
+            transform: "translate(-50%,-50%)",
+            position: "absolute" 
+        })
+        this.dom.find(".dirText").css({
+            "text-align": "center",
+            "font-size": "10px", 
+            "color": "rgb(255, 255, 255)",
+            "position": "absolute",
+            "top": "50%",
+            "left": "50%",
+            "width": "45%",
+            "height": "0px",
+            "transform-origin": "left center",
+            
+        })
+        this.dom.find(".dirText span").css({
+            display: "block",
+            position: "absolute",
+            right: "5px",
+            top: "0",
+            width: "20px",
+            height: "20px",
+            "line-height": "20px", 
+           // "font-size": ".75rem ",
+            "margin-top": "-10px",
+            
+        })   
+    
+        try { 
+            this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha:true })//许钟文 添加个抗锯齿,否则添加的线条锯齿严重,
+            this.renderer.autoClear = !0
+            this.renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1)
+            this.renderer.domElement.setAttribute('name','compass')
+            this.renderer.setClearAlpha(0.0)
+            this.renderer.setSize(width/2, height/2, false, window.devicePixelRatio ? window.devicePixelRatio : 1);
+            //this.emit(SceneRendererEvents.ContextCreated)
+        } catch (e) {
+            viewer.dispatchEvent('webglError', {msg:e})
+        }
+        
+        this.dom.find(".center")[0].appendChild(this.renderer.domElement);
+        this.renderer.domElement.style.width = this.renderer.domElement.style.height = '100%'
+        
+        
+        this.camera = new THREE.PerspectiveCamera;
+        this.camera.fov = 50;
+        this.camera.updateProjectionMatrix()
+        this.scene = new THREE.Scene,
+        this.scene.add(this.camera)
+        
+        
+        
+        this.createCompass()
+          
+        viewer.addEventListener('camera_changed', e => {
+            if (e.viewport == this.viewport && (e.changeInfo.positionChanged || e.changeInfo.quaternionChanged)) {
+                 this.update()
+            } 
+        })
+        
+        this.setDomPos()
+        if(this.viewport)this.setDisplay(true)
+    }   
+        
+        
+    createCompass(){
+        //ConeBufferGeometry(radius : Float, height : Float, radialSegments : Integer, heightSegments : Integer, openEnded : Boolean, thetaStart : Float, thetaLength : Float)
+        const height = 2;
+        const geometry1 = new THREE.ConeBufferGeometry( 0.7, height, 4, true );
+        const geometry2 = new THREE.ConeBufferGeometry( 0.7, height, 4, true );
+        const material = new THREE.MeshBasicMaterial({   
+             vertexColors :true
+        })
+        
+        //指南针由两个四棱锥拼成,为了渐变颜色,采用指定vertexColor的方式。
+        var setColor = function(geometry, color1,color2){ 
+            const colors = [];
+            for ( let i = 0, n = geometry.attributes.position.count; i < n; ++ i ) { 
+                colors.push( 1, 1, 1 ); 
+            } 
+            var set = function(index, color){//设置第index个点的颜色
+                colors[index*3+0] = color[0]
+                colors[index*3+1] = color[1]
+                colors[index*3+2] = color[2]
+            }
+            var mid = [(color1[0]+color2[0])/2, (color1[1]+color2[1])/2, (color1[2]+color2[2])/2 ]
+            set(1,color1); set(5,color1);set(6,color1);
+            set(2,mid); set(3,mid);set(7,mid);
+            set(4,color2); set(8,color2);set(9,color2);
+            geometry.setAttribute("color", new THREE.BufferAttribute(new Float32Array(colors), 3))  
+        }
+        var blue1 = [1/255,238/255,245/255] //逐渐变深
+        var blue2 = [20/255,146/255,170/255]
+        var blue3 = [40/255,60/255,103/255]
+        setColor(geometry1, blue1,blue2)
+        setColor(geometry2, blue2,blue3)
+        
+        /*  朝箭头方向看点构成如下  虽然geometry.attributes.position.count = 19  只有1-9设置的颜色是有效的  另外为什么7决定了上下两边的颜色呢…… 5、9可将其分成上下两个颜色
+             6 
+            /|\   
+           / | \
+        7 /_2|1_\ 5
+          \ 3|4 / 9
+           \ | /
+            \|/
+             8
+         */
+        const cone = new THREE.Mesh( geometry1, material );
+        cone.position.setY(height/2)
+        geometry1.computeVertexNormals()//computeFaceNormals
+        geometry2.computeVertexNormals()
+        
+        const cones = new THREE.Object3D();
+        cones.add(cone)
+         
+        let cone2 = new THREE.Mesh( geometry2, material );
+        cone2.rotation.x = Math.PI;
+        cone2.position.setY(-height/2)
+        cones.add(cone2)  
+        //cones.rotation.x = Math.PI / 2;//转向initDir的方向
+        //cones.rotation.z = Math.PI / 2;
+        cones.rotation.z = Math.PI ;//转向initDir的方向
+        cones.scale.set(0.7,0.7,0.7)
+        this.scene.add(cones)
+        this.cones = cones
+    }
+    
+    
+    
+    setNorth(){ //设置北方向,这决定了指南针自身的朝向。 
+        const floors = store.getters['scene/houstFloor'].floors
+        if(!floors || !floors.length){
+            return 
+        }
+        const floor = floors[0] 
+        const metadata = app.store.getters['scene/metadata'] || {}
+         
+        this.angle = (floor && floor.dire || 0) + THREE.Math.radToDeg(parseFloat(metadata.floorPlanAngle || 0))  //基础朝向  
+        this.cones.rotation.y = Math.PI / 2 - THREE.Math.degToRad(this.angle) 
+        //console.log("dir:"+floor.dire+", floorPlanAngle:"+metadata.floorPlanAngle)
+        this.update() 
+   
+    }
+    
+    update(quaternion){
+        if(!this.show)return;
+        if(!quaternion) quaternion = this.viewport.camera.quaternion.clone();
+        this.updateCamera(quaternion)
+        this.updateLabel(quaternion)
+        this.render()
+         
+    }
+    
+    
+    /*updateLabel(quaternion){//更新北标签
+          
+        var dir = viewer.mainViewport.view.direction;
+        var oriDir = initDir.clone()  //指南针最初始时的北方向
+        var extraQua 
+        if(objects.player.mode == "transitioning"){//当transitioning时,相机的quaternion不是用control的lookAt算出来,而是直接由一个quaternion过渡到另一个,这样相机将会是歪的,投影面也就不会是原先的水平面。
+            var tempCamera = new THREE.Camera();   //借用camera的lookAt算出如果正视同样的target, quaternion会是什么值。 将它乘以当前相机quaternion,得到的就是相机歪的旋转值。
+            tempCamera.position.copy(this.camera.position); 
+            tempCamera.lookAt(tempCamera.position.clone().add(dir)) 
+            var q = tempCamera.quaternion.inverse()
+            extraQua = q.premultiply(quaternion) //歪掉的额外旋转值
+            
+        }  
+         
+        //北标签的方向为指南针轮盘方向,也就是要将camera的方向投影到水平面上。 但是如果相机歪了,看到的世界都会歪一定角度,投影面也要歪一定角度。
+        var up = new THREE.Vector3(0,0,1) //投影水平面的法线,也是相机的摆正的up方向
+        extraQua && up.applyQuaternion(extraQua)
+        dir.projectOnPlane(up)   //将方向投影到水平面上; 如果相机不是正视(extraQua不为0001),就要将水平面也转动 
+        oriDir.projectOnPlane(up)//为什么initDir投影了和没有投影angle结果一样 
+        var angle = dir.angleTo(oriDir)
+        if(dir.cross(oriDir).y > 0)angle = -angle
+         
+        var deg = this.angle - 90 + THREE.Math.radToDeg(angle) //因为css写的样式初始是指向右方,和initDir差了90°,所以减去。
+        
+        
+        this.dom.find(".dirText").css( "transform","rotate("+deg+"deg)" )
+        this.dom.find(".dirText span").css("transform","rotate("+(-deg)+"deg)")
+    } */
+    
 
-		this.dom.click( () => {
-			viewer.setTopView();
-		});
 
-		const renderArea = $(viewer.renderArea);
-		renderArea.append(this.dom);
+    updateLabel(quaternion){//更新北标签
+        let deg = THREE.Math.radToDeg(this.viewport.view.yaw) - 90
+        this.dom.find(".dirText").css( "transform","rotate("+deg+"deg)" )
+        this.dom.find(".dirText span").css("transform","rotate("+(-deg)+"deg)")
+    }
 
-		this.setVisible(this.visible);
-	}
+    
+    updateCamera(quaternion){ //更新canvas中的指南针表现,也就是更新相机,和场景中的相机朝向一致。 
+         const radius = 5;  //相机距离
+          
+         this.camera.quaternion.copy(quaternion);
+         var dir = this.viewport.view.direction;  //相机朝向
+         this.camera.position.copy(dir.multiplyScalar(radius).negate())  //相机绕着指南针中心(000)转动
+    } 
 
-	setVisible(visible){
-		this.visible = visible;
+    changeViewport(viewport){
+        this.viewport = viewport;
+        this.update(); //因相机更新了
+    }
 
-		const value = visible ? "" : "none";
-		this.dom.css("display", value);
-	}
 
-	isVisible(){
-		return this.visible;
-	}
 
-	createElement(){
-		const style = `style="position: absolute; top: 10px; right: 10px; z-index: 10000; width: 64px;"`;
-		const img = $(`<img src="${Potree.resourcePath}/images/compas.svg" ${style} />`);
+    render(){
+        this.renderer.render(this.scene, this.camera)
+    }
+    
+    setDisplay(state){
+        this.show = !!state;
+        if(this.show){
+            this.update() 
+            this.dom.fadeIn(100) 
+        }else{
+            this.dom.fadeOut(100)
+        }  
+         
+    }
+    
+    
+    autoJudgeDisplay(){
+         
+    }
+    
+    
+    
+    setDomPos(){
+        if(!this.viewport)return
+        let right = this.viewport.left + this.viewport.width
+        this.dom.css({'right':((1-right)*100 + 1) + '%'})
+         
+    }
+    
+}
 
-		return img;
-	}
 
-};
+export default Compass;

+ 39 - 20
src/objects/tool/Measure.js

@@ -44,13 +44,13 @@ const mainLabelProp = {
     textColor: {r: textColor.r*255, g: textColor.g*255, b: textColor.b*255, a: 1.0},
     fontsize:16, 
     useDepth : true ,
-    renderOrder : 5 
+    renderOrder : 5, pickOrder:5, 
 }
 const subLabelProp = { 
     backgroundColor: {r: 255, g: 255, b: 255, a:1},
     textColor: {r: 0, g: 0, b:0, a: 1.0},
     fontsize:14, 
-    renderOrder : 4
+    renderOrder : 4, pickOrder:4,
 }
 
 
@@ -112,12 +112,12 @@ export class Measure extends ctrlPolygon{
         
         
         this.addEventListener('marker_dropped',(e)=>{ 
-            this.updateDatasetBelong()
+            this.updateDatasetBelong(e.index)
         }) 
 
   
         this.addEventListener('isVisible', ()=>{
-            viewer.mapViewer.dispatchEvent({type:'content_changed'})
+            viewer.mapViewer && viewer.mapViewer.dispatchEvent({type:'content_changed'})
         })
          
 	}
@@ -128,11 +128,27 @@ export class Measure extends ctrlPolygon{
         let makeIt = super.initData(prop)
         if(makeIt){
             this.edges.forEach(edge=>{edge.dispatchEvent('addHoverEvent') })
+        }else{
+            this.failBuilded = true
         }
     }
  
      
-    updateDatasetBelong(){//更新所属数据集
+    updateDatasetBelong(changeIndex){//更新所属数据集
+     
+        if(Potree.settings.editType == "merge"){//无地图
+            /* this.dataset_points = this.points.map((e,i)=>{ 
+                return Potree.Utils.datasetPosTransform({toDataset:true, datasetId:this.points_datasets[i], position:e.clone()})
+            }) */
+            this.dataset_points[changeIndex] = Potree.Utils.datasetPosTransform({toDataset:true, datasetId:this.points_datasets[changeIndex], position:this.points[changeIndex].clone()})
+            
+            
+            return
+        }
+    
+    
+    
+    
         let old = this.datasetId
         
         let maxCount = {id:null,count:0}
@@ -153,25 +169,26 @@ export class Measure extends ctrlPolygon{
         }
         this.datasetId = maxCount.count > 0 ?  maxCount.id : null
          
-        if(this.datasetId != old){
-            this.dispatchEvent({type:'changeDatasetId'})
+        //if(this.datasetId != old){
+            //this.dispatchEvent({type:'changeDatasetId'})
             if(this.datasetId == void 0){
                 this.dataset_points = null //可能为空或[null,null...]
             }else{
+                 
                 this.dataset_points = this.points.map(e=>{ 
                     return Potree.Utils.datasetPosTransform({toDataset:true,datasetId:this.datasetId, position:e.clone()})
-                })
+                }) 
             }  
-        }
+        //}
     }
     
     
      
 
     
-    transformByPointcloud(){//每次移动点云 or 加载测量线时要获取一下当前position
+    transformByPointcloud(){//每次移动点云 or 加载测量线时要获取一下当前position  //有地图时
         if(this.datasetId == void 0)return 
-        this.points = this.dataset_points.map(e=>{ 
+        this.points = this.dataset_points.map(e=>{   
             return Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:this.datasetId, position:e.clone()})
         })
          
@@ -216,7 +233,7 @@ export class Measure extends ctrlPolygon{
             let center = new THREE.Vector3().addVectors(p1,p2).multiplyScalar(0.5);  
             label.setPos(center) 
             distance = distance == void 0 ? p1.distanceTo(p2) : distance; 
-            var text = viewer.unitConvert.convert(distance, 'distance', void 0, this.unitSystem, 0.1 , true)//distance要传0.1 这个factor
+            var text = viewer.unitConvert.convert(distance, 'distance', Potree.settings.precision, this.unitSystem, 0.1 , true)//distance要传0.1 这个factor
             label.setText(text)
             return distance
         }
@@ -279,7 +296,7 @@ export class Measure extends ctrlPolygon{
                 this.areaLabel.setVisible(false)
             }else{ */
                 let area = Math.abs(math.getArea(this.point2dInfo.points2d))//this.getArea();
-                let msg = viewer.unitConvert.convert(area, 'area', void 0, this.unitSystem/* , 0.1 */ )
+                let msg = viewer.unitConvert.convert(area, 'area', Potree.settings.precision, this.unitSystem/* , 0.1 */ )
                 this.area = {value:area, string:msg}
                 
                 this.areaLabel.setPos(this.center);
@@ -298,7 +315,7 @@ export class Measure extends ctrlPolygon{
          
         let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, name:"measure_point"} )
         viewer.setObjectLayers(marker, 'measure' )
-        marker.renderOrder = 3 
+        marker.pickOrder = marker.renderOrder = 3 
         marker.markerSelectStates = {} 
         marker.addEventListener('startDragging',(e)=>{
             if(e.drag.dragViewport.name == 'MainView')viewer.inputHandler.dispatchEvent( {type: 'isMeasuring',v:true, cause:'startDragging'})
@@ -310,6 +327,7 @@ export class Measure extends ctrlPolygon{
         let edge
 		{ // edges 
             edge = LineDraw.createFatLine( [ ],{material:this.getLineMat('edgeDefault')} ) 
+            edge.pickOrder = 0
             viewer.setObjectLayers(edge, 'measure' ) 
 
 
@@ -379,7 +397,7 @@ export class Measure extends ctrlPolygon{
             this.editStateTimer = setTimeout(()=>{
                 if(!this.isEditing){
                     this.dispatchEvent({type:'editStateChange',state:false})   
-                    this.setEdgesDisplay(false)
+                    this.setEdgesDisplay(false) 
                 }
             },100)
         }else{
@@ -417,14 +435,14 @@ export class Measure extends ctrlPolygon{
         
         marker.selected = absoluteState
         
-        viewer.mapViewer.dispatchEvent('content_changed') 
+        viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') 
     }
     
     
     
     setEdgesDisplay(state, ignoreGuideLine){
         this.closed && this.edgeLabels.forEach(e=>e.setVisible(!!(state && e.shouldVisi))  )
-        
+         
         if(!ignoreGuideLine && this.measureType == 'Distance'){
             this.horEdgeLabel.visible = this.verEdgeLabel.visible = this.horGuideEdge.visible = this.verGuideEdge.visible = !!(state && this.shouldShowHorVerGuide)
         }
@@ -432,7 +450,7 @@ export class Measure extends ctrlPolygon{
     
     
     setSelected(state, hoverObject){//add
-        
+        //console.log('setSelected',state, hoverObject)
         hoverObject && (this.selectStates[hoverObject] = state)
         let absoluteState = false
         for(var i in this.selectStates){
@@ -470,9 +488,10 @@ export class Measure extends ctrlPolygon{
         }
            
         this.selected = absoluteState
-        viewer.mapViewer.dispatchEvent('content_changed')
+        viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed')
         if(hoverObject != 'byList'){
-            this.bus && this.bus.emit('highlight', this.selected)//列表高亮
+            //this.bus && this.bus.emit('highlight', this.selected)
+            this.dispatchEvent({type:'highlight',state:this.selected})//列表高亮
         }
     }
     

+ 36 - 17
src/objects/tool/MeasuringTool.js

@@ -186,9 +186,12 @@ export class MeasuringTool extends THREE.EventDispatcher{
 
 
     
-    createMeasureFromData(data){//add
+    createMeasureFromData(data){//add 
+    
         const measure = new Measure(data);
-        
+        if(measure.failBuilded){
+            return 
+        }
         viewer.scene.addMeasurement(measure);
         
         if(measure.guideLine)measure.guideLine.visible = false
@@ -434,11 +437,15 @@ export class MeasuringTool extends THREE.EventDispatcher{
                     } */
                 } 
             }
-            if (!e.finish && measure.markers.length > 3) {
+            if (/* !e.finish &&  */measure.markers.length > 3) {
 				measure.removeMarker(measure.points.length - 1); 
                 measure.markers[0].removeEventListener('mouseover', mouseover);
                 measure.markers[0].removeEventListener('mouseleave', mouseleave);
-                measure.markers[0].removeEventListener('mousedown',Exit) 
+                measure.markers[0].removeEventListener('click'/* 'mousedown' */,Exit) 
+                
+                if(e.byClickMarker && measure.markers.length > 3){//通过点击第一个marker而结束的话,会多一个marker
+                    measure.removeMarker(measure.points.length - 1); 
+                }
 			}
             measure.isNew = false
             let length = measure.points.length 
@@ -478,21 +485,29 @@ export class MeasuringTool extends THREE.EventDispatcher{
             if(e.remove){
                 viewer.scene.removeMeasurement(measure)  
             }
-            if(this.viewer.inputHandler.drag && !e.remove){//还未触发drop的话
-                this.viewer.inputHandler.drag.object.dispatchEvent({
+            
+            measure.editStateChange(false)
+            measure.cannotConfirmNormal = false  //一些dropMarker中的句子
+            measure.guideLine &&(measure.guideLine.visible = false)
+            /*
+            if(this.viewer.inputHandler.drag && !e.remove ){//还未触发drop的话   
+                  this.viewer.inputHandler.drag.object.dispatchEvent({  //这句会导致又增一个marker
                     type: 'drop',
                     drag: this.viewer.inputHandler.drag, 
                     viewer: this.viewer,
                     pressDistance:0,
                     button : THREE.MOUSE.RIGHT  
-                });
+                }); 
                  
-            }else{
-                end({finish:true, remove:e.remove})  //未结束时添加新的measure时会触发
-            }
-            this.viewer.inputHandler.drag = null 
-            measure.editStateChange(false)
+            }   else  {*///未结束时添加新的measure时会触发
+                end({finish:true, remove:e.remove, byClickMarker: e.type == 'click'})  
+            //}
+            this.viewer.inputHandler.drag && (this.viewer.inputHandler.drag.object = null)
+            
         }
+        
+        
+        
         this.viewer.addEventListener('cancel_insertions', Exit);
         
         /*let pressExit
@@ -517,14 +532,15 @@ export class MeasuringTool extends THREE.EventDispatcher{
         let click = (e)=>{//一旦点击就立刻增加两marker  
         
             if(ifAtWrongPlace(e))return  
-            
+            if(e.clickElement)return  //如点击label时focusOnObject
              
             
             if(e.button === THREE.MOUSE.RIGHT)return 
             
             //console.log('measure clicked33', !!e.intersectPoint)
              
-            var I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
+            //var I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
+            var I = e.intersect && (e.intersect.orthoIntersect || e.intersect.location)
             if(!I){
                 return measure.dispatchEvent('intersectNoPointcloud') 
             }
@@ -554,7 +570,7 @@ export class MeasuringTool extends THREE.EventDispatcher{
             if(measure.maxMarkers>2 && !measure.isRect){ 
                 measure.markers[0].addEventListener('mouseover', mouseover);
                 measure.markers[0].addEventListener('mouseleave', mouseleave);
-                measure.markers[0].addEventListener('mousedown',Exit) //点击到第一个marker就结束 
+                measure.markers[0].addEventListener('click'/* 'mousedown' */,Exit) //点击到第一个marker就结束 
             }
             
             
@@ -563,7 +579,7 @@ export class MeasuringTool extends THREE.EventDispatcher{
             
             
             //console.log('measure clicked')
-            
+            e.consume && e.consume()
             
             return {stopContinue:true}//防止继续执行别的侦听,如flytopano
         }
@@ -609,9 +625,12 @@ export class MeasuringTool extends THREE.EventDispatcher{
 	
     
 	render(o={}){
+        if(this.scene.children.length == 0)return
+        
         viewer.setCameraLayers(o.camera, ['measure'])
 		
-        if(o.screenshot){ //抗锯齿
+        if(o.screenshot && this.viewer.ssaaRenderPass.enabled){ //抗锯齿
+            this.viewer.ssaaRenderPass.sampleLevel = 4
             this.viewer.composer.render(this.scene, o.camera );  
             /* viewer.scene.measurements.forEach(e=>{ //隐藏除了label以外的
                 e.children.forEach((c)=>{

+ 95 - 0
src/objects/tool/TagTool.js

@@ -0,0 +1,95 @@
+
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+
+import math from "../../utils/math.js";
+import {Utils} from "../../utils.js"; 
+
+import Tag from '../Tag.js'
+
+
+export class TagTool extends THREE.EventDispatcher{
+	constructor (viewer) {
+		super();
+        
+        
+        
+        
+        this.viewer = viewer
+        
+        
+        
+        this.viewer.addEventListener('start_inserting_tag', e => {
+			this.viewer.dispatchEvent({
+				type: 'cancel_insertions'
+			});
+		});
+    }
+    
+    
+    
+    
+    createTagFromData(data){
+        let tag = new Tag({
+            title: data.title, position: data.position,  normal: data.normal,
+            root: data.root   //e.intersect.pointcloud || e.intersect.object
+        })
+        
+        return tag
+        
+    }
+     
+    
+    startInsertion (args = {}, callback, cancelFun) {
+        let deferred = $.Deferred();
+         
+        this.viewer.dispatchEvent({
+			type: 'start_inserting_tag' 
+			 
+		});
+        this.adding = true
+        
+        let cancel = ()=>{
+            end()
+        }
+        let end = ()=>{
+            this.adding = false
+            viewer.dispatchEvent({type:"endTagMove"})
+            this.viewer.removeEventListener('global_click', click)
+        }
+        let click = (e)=>{
+            
+            
+            var worldPos = e.intersect && (/* e.intersect.orthoIntersect ||  */e.intersect.location)
+            if(!worldPos){
+                return  
+            }
+            
+            let localPos = Potree.Utils.datasetPosTransform({ toDataset: true, pointcloud:e.intersect.pointcloud, object:e.intersect.object,  position:worldPos })
+
+            
+            let tag = new Tag({
+                title: '1', position: localPos,  normal:e.intersect.normal,
+                root: e.intersect.pointcloud || e.intersect.object
+            }) 
+            
+            //pointcloud里加一个normal 的非float32
+            
+            
+            
+            end()
+            e.consume && e.consume()
+            deferred.resolve(tag)
+            
+            
+            return {stopContinue:true}
+            
+        }
+        this.viewer.addEventListener('global_click', click, 10)
+        return deferred.promise()
+    }  
+    
+    
+    
+    
+}
+

File diff suppressed because it is too large
+ 1981 - 0
src/objects/tool/TransformControls.js


+ 1 - 1
src/objects/tool/VolumeTool.js

@@ -83,7 +83,7 @@ export class VolumeTool extends THREE.EventDispatcher{
 			let camera = this.viewer.scene.getActiveCamera();
 			
 			
-            var I = e.intersectPoint 
+            var I = e.intersect 
 			if (I) {
 				volume.position.copy(I.location);
 

+ 50 - 23
src/objects/tool/ctrlPolygon.js

@@ -43,29 +43,47 @@ export class ctrlPolygon extends THREE.Object3D {
     
     initData(prop){
         //开始加数据  
+        if(Potree.settings.editType == 'merge'){ //融合页面没有地图,measure的不需要指定datasetId,每个点都有各自的datasetId,跟着各自的模型走
+            if(this.dataset_points){
+                this.dataset_points = this.dataset_points.map(e=>{
+                    return e && new THREE.Vector3().copy(e) 
+                })
+                prop.points = this.dataset_points.map((p,i)=>{
+                    return Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:this.points_datasets[i], position: p})
+                })
+                if(prop.points.some(e=>e == void 0)){
+                    return false
+                }               
+            }else{
+                this.dataset_points = []
+            }
+        } 
+        
+        
+        
         if(prop.points){ 
         
             for(const p of prop.points){
                 const pos = new THREE.Vector3().copy(p) 
                 this.addMarker({point:pos}); 
             }
-         
-            if(this.datasetId != void 0){//初始化位置
-                if(this.dataset_points){
-                    this.dataset_points = this.dataset_points.map(e=>{
-                        return e && new THREE.Vector3().copy(e) 
-                    })
-                    this.transformByPointcloud() //根据dataset_points生成points  
-                }  
-            }else{
-                if(prop.dataset_points && prop.dataset_points.some(e=>e != void 0)){
-                    console.error('存在测量线的datasetId为空而dataset_points有值,请检查并删除:'+this.sid)//存在过的bug,原因未知,可能是后台处理dataset时替换的错误:http://192.168.0.21/index.php?m=bug&f=view&bugID=23601
-                    console.log(this)
-                }
-            }
             
             
-              
+            if(Potree.settings.editType != 'merge'){ 
+                if(this.datasetId != void 0){//初始化位置
+                    if(this.dataset_points){
+                        this.dataset_points = this.dataset_points.map(e=>{
+                            return e && new THREE.Vector3().copy(e) 
+                        })
+                        this.transformByPointcloud() //根据dataset_points生成points  
+                    }  
+                }else{
+                    if(prop.dataset_points && prop.dataset_points.some(e=>e != void 0)){
+                        console.error('存在测量线的datasetId为空而dataset_points有值,请检查并删除:'+this.sid)//存在过的bug,原因未知,可能是后台处理dataset时替换的错误:http://192.168.0.21/index.php?m=bug&f=view&bugID=23601
+                        console.log(this)
+                    }
+                } 
+            }  
             
             this.getFacePlane()
             this.getPoint2dInfo(this.points)
@@ -155,7 +173,7 @@ export class ctrlPolygon extends THREE.Object3D {
         e.drag.object.isDragging = true 
         
         
-        I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
+        I = e.intersect && (e.intersect.orthoIntersect || e.intersect.location)
         
         //记录数据集
         
@@ -166,10 +184,15 @@ export class ctrlPolygon extends THREE.Object3D {
             if (i !== -1) {  
                 this.dragChange(I.clone(), i, atMap) 
                 
+                /* if(this.points_datasets){
+                    if(e.intersect.pointcloud) this.points_datasets[i] = e.intersect.pointcloud.dataset_id
+                    else this.points_datasets[i] = null
+                } */ 
                 if(this.points_datasets){
-                    if(e.intersectPoint.pointcloud) this.points_datasets[i] = e.intersectPoint.pointcloud.dataset_id
+                    if(e.intersect.pointcloud) this.points_datasets[i] = e.intersect.pointcloud.dataset_id
+                    else if(e.intersect.object) this.points_datasets[i] = e.intersect.object.dataset_id
                     else this.points_datasets[i] = null
-                } 
+                }
             }
             this.editStateChange(true)
             return true
@@ -409,7 +432,7 @@ export class ctrlPolygon extends THREE.Object3D {
                 || this.isAtWrongPlace && this.isNew
                 || !e.isAtDomElement && this.isNew//如果是刚添加时在其他dom点击, 不要响应
                 ||  e.hoverViewport != viewer.mainViewport && this.unableDragAtMap //垂直的测量线不允许在地图上放点
-                || !getDifferentPoint(this.points, this.points.length) //不允许和之前的点相同 
+                || this.isNew && !getDifferentPoint(this.points, this.points.length )   //不允许和之前的点相同, 但这句在点云稀疏时会导致难结束 
             ) 
         ){
             return this.continueDrag(null,e)    
@@ -438,7 +461,7 @@ export class ctrlPolygon extends THREE.Object3D {
         
         e.drag.endDragFun && e.drag.endDragFun(e)//  addmarker
          
-        if(this.changeCallBack)this.changeCallBack()
+        //if(this.changeCallBack)this.changeCallBack()
            
         return true
     };
@@ -469,10 +492,14 @@ export class ctrlPolygon extends THREE.Object3D {
     setPosition (index, position) {//拖拽后设置位置
 		let point = this.points[index];
 		point.copy(position);
-        if(this.datasetId){
+        /* if(this.datasetId){
             this.dataset_points[index] = Potree.Utils.datasetPosTransform({toDataset:true, datasetId:this.datasetId, position:point.clone()})
+        } */
+        
+        /* if(Potree.settings.editType == 'merge'){ 
+            this.dataset_points[index] = Potree.Utils.datasetPosTransform({toDataset:true,this.points_datasets[i], position:point.clone()})
+        } */
         
-        }
         let marker = this.markers[index];  
         this.updateMarker(marker, point)
  
@@ -616,7 +643,7 @@ export class ctrlPolygon extends THREE.Object3D {
         }
         
         //this.dispatchEvent({type:'update'})     
-        viewer.mapViewer.dispatchEvent('content_changed') //暂时先这么都通知
+        viewer.mapViewer && viewer.mapViewer.dispatchEvent('content_changed') //暂时先这么都通知
         
     } 
     

+ 7 - 7
src/objects/tool/mapClipBox.js

@@ -55,15 +55,15 @@ export class mapClipBox extends ctrlPolygon {
             
             let lastPos;
             let drag = (e)=>{
-                let intersectPoint = e.intersectPoint.orthoIntersect 
+                let intersect = e.intersect.orthoIntersect 
                 if(lastPos){ 
-                    let moveVec = new THREE.Vector3().subVectors(intersectPoint, lastPos).setZ(0)
+                    let moveVec = new THREE.Vector3().subVectors(intersect, lastPos).setZ(0)
                     this.center.add(moveVec)
                     this.updatePoints()
                     this.dispatchEvent({type:'repos'})
                      
                 } 
-                lastPos = intersectPoint.clone();                
+                lastPos = intersect.clone();                
             }
             let drop = (e)=>{
                 lastPos = null
@@ -132,7 +132,7 @@ export class mapClipBox extends ctrlPolygon {
         var I, atMap 
          
         atMap = e.dragViewport.name == 'mapViewport'
-        I = e.intersectPoint.orthoIntersect 
+        I = e.intersect.orthoIntersect 
         
         if (I && dragInfo.lastPos) {
             let i = this.edgeMarkers.indexOf(e.drag.object);
@@ -259,17 +259,17 @@ export class mapClipBox extends ctrlPolygon {
         
         let lastPos;
         bar.addEventListener('drag',(e)=>{
-            var intersectPoint = e.intersectPoint.orthoIntersect   
+            var intersect = e.intersect.orthoIntersect   
             if(lastPos){ 
                 let vec1 = new THREE.Vector3().subVectors(lastPos, this.center).setZ(0)
-                let vec2 = new THREE.Vector3().subVectors(intersectPoint, this.center).setZ(0)
+                let vec2 = new THREE.Vector3().subVectors(intersect, this.center).setZ(0)
                 let angle = math.getAngle(vec1,vec2,'z')  
                 this.angle += angle 
                 this.rotateBar.rotation.z = this.angle
                 this.updatePoints() 
                 this.dispatchEvent({type:'rotate', angle: this.angle})
             }
-            lastPos = intersectPoint.clone();       
+            lastPos = intersect.clone();       
         })
         bar.addEventListener('drop',()=>{
             lastPos = null 

+ 49 - 32
src/settings.js

@@ -66,10 +66,12 @@ const config = {//配置参数   不可修改
     
     urls:{
         //localTextures:'../resources/textures/', 
-        prefix: 'https://laser-oss.4dkankan.com',//oss
+        prefix1: 'https://laser-oss.4dkankan.com',//oss
         prefix2: 'https://testlaser.4dkankan.com',
         prefix3: 'https://4dkk.4dage.com',
-        prefix4: 'https://uat-laser.4dkankan.com/',//test.4dkankan
+        prefix4: 'https://uat-laser.4dkankan.com',//test.4dkankan
+        prefix5: 'https://laser.4dkankan.com',
+        prefix6: 'https://mix3d.4dkankan.com/backend',
         
     },
      
@@ -209,7 +211,7 @@ const config = {//配置参数   不可修改
         skybox: 1,
         pointcloud: 11,
         sceneObjects:0,//default
-         
+        model : 2,   
         
         measure:4,  
         magnifier:5, 
@@ -226,9 +228,15 @@ const config = {//配置参数   不可修改
         siteModeOnlyMapVisi:12,//只能mapViewer可见
         siteModelMapUnvisi:13,//只有mapViewer不可见
         siteModeSideVisi:14,//只有侧面可见
+        
+        
+        layer1: 10,// 备用1
+        layer2: 15,// 备用2
     },
     
-    
+    renderOrders:{ //会影响到绘制、pick时的顺序。
+        model:10
+    }, 
     siteModel:{
         names:{
             'building': '建筑',
@@ -268,24 +276,7 @@ const config = {//配置参数   不可修改
         directionFactor: 10,
         distanceFactor: -1,
         optionalityFactor: 3
-    },
-    OrthoCameraLimit:{
-        standard:{ 
-            zoom:{min:0.001, max:500}, //如果camera缩太小,地图会因为数字边界问题而扭曲
-            posBound:{ 
-                min: {x:-1e5, y:-1e5,z:-1 / 0},
-                max: {x:1e5, y:1e5, z:1 / 0  }
-            }  
-        },
-        expand:{ //范围再大些,用于编辑空间模型等(但是万一中心点靠近地图边缘的话,就很容易扭曲了)
-            zoom:{min:0.0004, max:100}, //如果camera缩太小,地图会因为数字边界问题而扭曲
-            posBound:{ 
-                min: {x:-5000000, y:-1000000,z:-1 / 0},
-                max: {x:5000000, y:1000000, z:1 / 0  }
-            }//40075017
-        }
-        
-    }
+    } 
     ,
     axis : {   'x':{color:'#d0021b'/* 'red' */}, 'y':{ color:'#86c542' /* 'green' */},  'z': {color:'#3399c8' /* 'blue' */}},  
     
@@ -295,7 +286,7 @@ const config = {//配置参数   不可修改
     
     clickMaxDragDis:5,
     clickMaxPressTime:500, //ms
-     
+    doubleClickTime:200,//双击间隔时间
      
      
     background: '#232323',
@@ -322,6 +313,24 @@ const config = {//配置参数   不可修改
 }
 
 
+config.OrthoCameraLimit = {
+    standard:{ 
+        zoom:{min:0.001, max:500}, //如果camera缩太小,地图会因为数字边界问题而扭曲
+        posBound:{ 
+            min: {x:-1e5, y:-1e5,z: config.map.cameraHeight},
+            max: {x:1e5, y:1e5, z:1 / 0  }
+        }  
+    },
+    expand:{ //范围再大些,用于编辑空间模型等(但是万一中心点靠近地图边缘的话,就很容易扭曲了)
+        zoom:{min:0.0004, max:100}, //如果camera缩太小,地图会因为数字边界问题而扭曲
+        posBound:{ 
+            min: {x:-5000000, y:-1000000,z:config.map.cameraHeight},
+            max: {x:5000000, y:1000000, z:1 / 0  }
+        }//40075017
+    }
+    
+}
+
 
 
 /* 显示模式:
@@ -356,10 +365,10 @@ let settings = {//设置   可修改
     number: '', //场景序号
     originDatasetId:'',//场景原本的数据集id,应该就是数据集第一个吧
     isOfficial:false,
-    webSite:'testdata',//'data', //不同环境对应的静态文件的地址不同
-    
+    webSite:'testdata',//正式:'datav1', //不同环境对应的静态文件的地址不同
+     
     isLocal:false, //是否本地 局域网版本
-    
+    libsUrl:'../libs/',
     displayMode:'',
     isTest :browser.urlHasValue('test'),
     prefix: getPrefix(),
@@ -402,24 +411,32 @@ let settings = {//设置   可修改
     rotAroundPoint:true,//点云模式是否能绕intersectPoint旋转
     tourTestCameraMove:false, //测试镜头时,不移动真实的镜头, 只移动frustum
     cameraAniSmoothRatio : 20, //镜头动画平滑系数,越高越平滑
-    urls  : $.extend({}, config.urls), 
+    urls  : $.extend({}, config.urls, {
+        prefix : config.urls.prefix4 //主要使用的 是测试环境,根据不同工程更改
+    }), 
     
     
-    hasDepthTex: true,//使用深度贴图,但不代表一定有(得到的intersect更快速准确和稳定)   SS-t-7DUfWAUZ3V  
-    
+    useDepthTex: true,//使用深度贴图,但不代表一定有(得到的intersect更快速准确和稳定)   SS-t-7DUfWAUZ3V  
+    //matUseDepth:false, 
     //panoEdit:
-    datasetsPanos:{}
+    datasetsPanos:{},
     
     //mergeModel:
-    
-}
+    boundAddObjs:false,
+    intersectOnObjs:false,
+    intersectWhenHover:true,
 
+    notAdditiveBlending:false, //点云是否使用普通的blend, 否则会曝光过渡
+    precision:2  // 两位小数
+}
+ 
  
 
 //JSON.parse(localStorage.getItem('setting'))
 
 settings.isLocalhost = settings.prefix.includes('localhost')
 
+
 /* 
     关于maxLevel:
     viewer.scene.pointclouds[0].root.getLevel() 是 0

+ 391 - 16
src/start.js

@@ -8,7 +8,7 @@ import './extensions/three.shim.js'
 import {Utils} from "./utils.js"
  
 
-var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
+var start = function(dom, mapDom, number ){ //t-Zvd3w0m
     /* {
         let obj = JSON.parse(localStorage.getItem('setting'))
         for(let i in obj){
@@ -17,10 +17,10 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     }
      */ 
     Potree.settings.number = number || 't-o5YMR13'// 't-iksBApb'// 写在viewer前
-    Potree.fileServer = fileServer 
-    webSite && (Potree.settings.webSite = webSite)
     
     
+     
+    
     let viewer = new Potree.Viewer(dom , mapDom);
     
     let Alignment = viewer.modules.Alignment
@@ -32,7 +32,6 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     //viewer.setPointBudget(pointDensity.pointBudget);
     viewer.loadSettingsFromURL(); 
     
-  
     
     if(!Potree.settings.isOfficial){ 
         viewer.loadGUI(() => {
@@ -183,7 +182,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
         data.forEach((dataset,index)=>{  
             if(!ifReload){
                 var datasetCode = dataset.sceneCode || dataset.name //对应4dkk的场景码
-                var cloudPath = `${Potree.settings.urls.prefix}/${Potree.settings.webSite}/${datasetCode}/data/${datasetCode}/webcloud/cloud.js` 
+                var cloudPath = `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${datasetCode}/data/${datasetCode}/webcloud/cloud.js` 
                 var timeStamp = dataset.createTime ? dataset.createTime.replace(/[^0-9]/ig,'') : '';  //每重算一次后缀随createTime更新一次 
                 //console.warn(dataset.name, 'timeStamp', timeStamp)
                 Potree.loadPointCloud(cloudPath, dataset.name ,datasetCode, timeStamp, e => {
@@ -192,7 +191,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                     let config = Potree.config.material
                     let material = pointcloud.material; 
                     
-                    pointcloud.hasDepthTex = Potree.settings.hasDepthTex && (!!dataset.has_depth  ||  Potree.settings.isLocalhost && Potree.settings.number == 'SS-t-7DUfWAUZ3V') //test   
+                    pointcloud.hasDepthTex = Potree.settings.useDepthTex && (!!dataset.has_depth  ||  Potree.settings.isLocalhost && Potree.settings.number == 'SS-t-7DUfWAUZ3V') //test   
                     material.minSize =  config.minSize
                     material.maxSize =  config.maxSize   
                     material.pointSizeType = config.pointSizeType //Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED
@@ -204,9 +203,18 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                     pointcloud.timeStamp = timeStamp 
                     transformPointcloud(pointcloud,dataset)
                     scene.addPointCloud(pointcloud);
+                    
+                    if(!Potree.settings.isOfficial){ 
+                        Potree.settings.floorplanEnables[dataset.id] = true
+                        Potree.settings.floorplanType[dataset.id] = 'default'
+                    }
+                    
+                    
                     pointcloudLoaded ++;
                     if(pointcloudLoaded == datasetLength)pointcloudLoadDone()
-                        
+                    
+                    
+                
                     Potree.loadPanos(dataset.id, (data) => { 
                         //console.log('loadPanos',dataset.sceneCode, dataset.id, data)
                         viewer.images360.addPanoData(data, dataset.id )
@@ -317,7 +325,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
 //=======================================================================
 
 
-var panoEditStart = function(dom, number, fileServer, webSite){
+var panoEditStart = function(dom, number, fileServer){
     Potree.settings.editType = 'pano'
     Potree.settings.number = number 
     
@@ -349,7 +357,7 @@ var panoEditStart = function(dom, number, fileServer, webSite){
         viewer.scene.pointclouds.forEach(c=>{
             transformPointcloud(c)
         })
-     viewer.images360.loadDone() 
+        viewer.images360.loadDone() 
         viewer.scene.add360Images(viewer.images360); 
         
         viewer.updateModelBound()
@@ -377,20 +385,22 @@ var panoEditStart = function(dom, number, fileServer, webSite){
     var transformPointcloud = (pointcloud )=>{ //初始化位置  
         viewer.sidebar && viewer.sidebar.addAlignmentButton(pointcloud) 
          
-        let orientation = pointcloud.panos[0].dataRotation.z
-        let location = pointcloud.panos[0].dataPosition.clone().negate()
+        let orientation =  pointcloud.panos[0].dataRotation.z + Math.PI
+        
+        let location = pointcloud.panos[0].dataPosition.clone()//.negate()
+        
         Alignment.rotate(pointcloud, null,  orientation  )   
         Alignment.translate(pointcloud, location )  
         
         pointcloud.updateMatrixWorld()
           
-    }  
+    }   
      
      
      
          
     
-    let loadPanosDone = Potree.loadPanosDone = (datasetId, panoData)=>{ //一个数据集获取到它的panos后
+    let loadPanosDone = Potree.loadPanosDone = (datasetId, panoData )=>{ //一个数据集获取到它的panos后
         
         Potree.settings.datasetsPanos[datasetId] = {panoData, panos:[]}
          
@@ -404,7 +414,7 @@ var panoEditStart = function(dom, number, fileServer, webSite){
        
         panoData.forEach((pano, index)=>{
             //let cloudPath = `${Potree.scriptPath}/data/panoEdit/uuidcloud/${pano.uuid}/cloud.js` 
-            let cloudPath = `https://laser-oss.4dkankan.com/testdata/${Potree.settings.number}/data/bundle_${Potree.settings.number}/building/uuidcloud/${pano.uuid}/cloud.js`
+            let cloudPath = `https://laser-oss.4dkankan.com/${Potree.settings.webSite}/${Potree.settings.number}/data/bundle_${Potree.settings.number}/building/uuidcloud/${pano.uuid}/cloud.js`
             
             let name = datasetId + '-'+pano.uuid
             let timeStamp = 0
@@ -418,7 +428,7 @@ var panoEditStart = function(dom, number, fileServer, webSite){
                 material.minSize =  config.minSize
                 material.maxSize =  config.maxSize   
                 material.pointSizeType = /* 'ADAPTIVE'// */config.pointSizeType //Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED
-                pointcloud.changePointSize( 0.2 /* config.realPointSize  */   )  //material.size =  config.pointSize;
+                pointcloud.changePointSize( 0.1 /* config.realPointSize  */   )  //material.size =  config.pointSize;
                 pointcloud.changePointOpacity(1)
                 material.shape = Potree.PointShape.SQUARE; 
                 pointcloud.color = config.pointColor  
@@ -452,6 +462,371 @@ var panoEditStart = function(dom, number, fileServer, webSite){
     }
     
 }
+
+
+
+  
+
+var mergeEditStart = function(dom){
+    Potree.settings.editType = 'merge' 
+    Potree.settings.intersectOnObjs = true
+    Potree.settings.boundAddObjs = true
+    Potree.settings.unableNavigate = true
+   
+    
+    
+    let viewer = new Potree.Viewer(dom );
+   
+    let Alignment = viewer.modules.Alignment
+     
+	viewer.setEDLEnabled(false);
+    viewer.setFOV(config.view.fov); 
+    viewer.loadSettingsFromURL(); 
+    { 
+        viewer.mainViewport.view.position.set(30,30,30) 
+        viewer.mainViewport.view.lookAt(0,0,0)
+        
+        viewer.updateModelBound()//init
+        //this.bound = new THREE.Box3(new THREE.Vector3(-1,-1,-1),new THREE.Vector3(1,1,1))
+        
+        viewer.transformationTool.setModeEnable('scale',false)
+        viewer.ssaaRenderPass.sampleLevel = 1 //  sampleLevel为1 的话,ground就不会变黑
+        
+        viewer.inputHandler.fixSelection = true //不通过点击屏幕而切换transfrom选中状态
+  
+    }
+    
+    Potree.settings.sizeFitToLevel = true//当type为衰减模式时自动根据level调节大小。每长一级,大小就除以2
+    Potree.loadPointCloudScene = function(sceneCode, type, id, done, onError){//对应4dkk的场景码
+          
+        let loadCloud = (cloudPath, sceneName, sceneCode, timeStamp, color)=>{
+            
+            Potree.loadPointCloud(cloudPath, sceneName , sceneCode, timeStamp, e => {
+                let scene = viewer.scene;
+                let pointcloud = e.pointcloud; 
+                let config = Potree.config.material
+                let material = pointcloud.material; 
+                
+                material.minSize =  config.minSize
+                material.maxSize =  config.maxSize   
+                material.pointSizeType = config.pointSizeType //Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED
+                pointcloud.changePointSize(config.realPointSize)  //material.size =  config.pointSize;
+                pointcloud.changePointOpacity(1)
+                material.shape = Potree.PointShape.SQUARE; 
+                color && (pointcloud.color = pointcloud.material.color = color)     
+                pointcloud.timeStamp = timeStamp 
+                //transformPointcloud(pointcloud, originDataset)
+                scene.addPointCloud(pointcloud);
+                {
+                    
+                    viewer.updateModelBound()
+                    let {boundSize, center} = viewer.bound
+                    viewer.dispatchEvent({type:'loadPointCloudDone'})
+                    if(!Potree.settings.UserPointDensity){
+                        Potree.settings.UserPointDensity = 'high'//'middle' 
+                    }
+                     
+                    Potree.Log('loadPointCloudDone  点云加载完毕', null, 10)    
+                } 
+                    
+                /* Potree.loadPanos(dataset.id, (data) => { //暂时不加载panos了,因为没有id 
+                //console.log('loadPanos',dataset.sceneCode, dataset.id, data)
+                viewer.images360.addPanoData(data, dataset.id ) 
+                viewer.images360.loadDone() 
+                viewer.scene.add360Images(viewer.images360);    */   
+                viewer.dispatchEvent('allLoaded')
+                done(pointcloud)
+            },onError) 
+            
+            
+        }
+        
+        if(type == 'laser'){ 
+            Potree.loadDatasets((data)=>{
+                let originDataset = data.find(e=>e.sceneCode == sceneCode);//只加载初始数据集  
+                let timeStamp = originDataset.createTime ? originDataset.createTime.replace(/[^0-9]/ig,'') : '';  //每重算一次后缀随createTime更新一次 
+                let cloudPath = `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${sceneCode}/data/${sceneCode}/webcloud/cloud.js` 
+                loadCloud(cloudPath, originDataset.sceneName, sceneCode, timeStamp, originDataset.color)
+            }, sceneCode, onError)
+        
+        }else{//las or ply
+            let name = type + '|' + id
+            let cloudPath = sceneCode + '/cloud.js' 
+            loadCloud(cloudPath, name, name, '' )
+        }   
+         
+    } 
+    
+    
+    
+    
+    
+    
+    
+    let setMatrix = (pointcloud)=>{//为了漫游点变换,要算一下 类似setMatrix
+                  
+        /* pointcloud.transformMatrix = new THREE.Matrix4().multiplyMatrices(pointcloud.matrix, pointcloud.pos1MatrixInvert)//还原一点位移
+        pointcloud.transformInvMatrix.copy(pointcloud.transformMatrix).invert()
+        
+        pointcloud.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(pointcloud.rotation);
+        pointcloud.rotateInvMatrix.copy(pointcloud.rotateMatrix).invert()
+        pointcloud.panos.forEach(e=>e.transformByPointcloud()) */
+        //pointcloud.updateBound()
+        //pointcloud.getPanosBound()  
+        viewer.updateModelBound()
+    } 
+
+    let moveModel = (e)=>{//根据鼠标移动的位置改变位置
+          
+        let camera = viewer.mainViewport.camera
+        var origin = new THREE.Vector3(e.pointer.x, e.pointer.y, -1).unproject(camera),
+        end = new THREE.Vector3(e.pointer.x, e.pointer.y, 1).unproject(camera)
+        var dir = end.sub(origin)
+        let planeZ = 0;
+        let r = (planeZ - origin.z)/dir.z
+        let x = r * dir.x + origin.x
+        let y = r * dir.y + origin.y
+        
+        //过后改为根据intersect的点来设置底部高度;这样的话,需要发送高度
+        
+        /*let pos = new THREE.Vector3(x,y,  planeZ  )
+         modelEditing.updateMatrixWorld()   
+        let boundCenter = modelEditing.boundingBox.getCenter(new THREE.Vector3).applyMatrix4(modelEditing.matrixWorld);
+         */
+        MergeEditor.moveBoundCenterTo(modelEditing,new THREE.Vector3(x,y, modelEditing.boundCenter.z))  //使模型中心的xy在鼠标所在位置
+         
+        modelEditing.dispatchEvent("position_changed") 
+         
+    }
+    let cancelMove = ()=>{ 
+        modelEditing = null
+        viewer.removeEventListener('global_mousemove', moveModel); 
+        viewer.removeEventListener('global_click', confirmPos); 
+    }
+    let confirmPos = ()=>{ 
+        MergeEditor.focusOn(modelEditing)
+        cancelMove()  
+        return {stopContinue:true}
+    }
+    
+     
+    
+    let modelType,  modelEditing, MergeEditor = viewer.modules.MergeEditor
+    Potree.addModel = function(prop, done, onProgress, onError){ //加载模型
+         
+    
+        let loadDone = (model)=>{ 
+            model.dataset_id = prop.id //唯一标识
+             
+            if(prop.position){
+                model.position.copy(prop.position)
+            }
+            if(prop.rotation){
+                model.rotation.setFromVector3(prop.rotation) 
+            }
+            if(prop.scale){
+                model.scale.set(prop.scale,prop.scale,prop.scale)
+            }
+              
+            if(model.isPointcloud){
+                model.renderOrder = Potree.config.renderOrders.model;  //same as glb
+            }
+            
+            
+            /* {//transform --------维持离地高度和中心点的版本
+                let updateBound = ()=>{ 
+                    model.updateMatrixWorld()
+                    viewer.updateModelBound() 
+                }  
+                let maintainBtmZAndCenter = ()=>{ 
+                    MergeEditor.maintainBoundXY(model)
+                    MergeEditor.setModelBtmHeight(model) 
+                    updateBound()
+                    model.dispatchEvent('transformChanged')  
+                }
+                model.addEventListener('position_changed', ()=>{
+                    updateBound()
+                    MergeEditor.getBoundCenter(model);//更新boundcenter
+                    MergeEditor.computeBtmHeight(model)
+                    if(prop.bottomRange && (model.btmHeight > prop.bottomRange.max || model.btmHeight < prop.bottomRange.min)){
+                        model.btmHeight = THREE.Math.clamp(model.btmHeight, prop.bottomRange.min, prop.bottomRange.max)
+                        MergeEditor.setModelBtmHeight(model)
+                        updateBound()
+                    }  
+                    model.dispatchEvent('transformChanged') 
+                })
+                model.addEventListener("rotation_changed", maintainBtmZAndCenter )
+                model.addEventListener("scale_changed", maintainBtmZAndCenter )
+                model.addEventListener('transformChanged', ()=>{
+                    MergeEditor.modelTransformCallback(model)
+                })
+                //离地高度只是boundingbox在transform后的最低点的高度,而非模型transform后的最低点的高度,所以旋转过后看起来不太准确
+            } */
+            
+            {//transform --------维持中心点的版本
+                let updateBound = ()=>{ 
+                    model.updateMatrixWorld()
+                    viewer.updateModelBound() 
+                }  
+                let maintainCenter = ()=>{ 
+                    //MergeEditor.maintainBoundXY(model) 
+                    MergeEditor.maintainBoundCenter(model) 
+                    updateBound()
+                    model.dispatchEvent('transformChanged')  
+                }
+                model.addEventListener('position_changed', ()=>{
+                    updateBound()
+                    MergeEditor.getBoundCenter(model);//更新boundcenter
+            
+                    model.dispatchEvent('transformChanged') 
+                })
+                model.addEventListener("rotation_changed", maintainCenter )
+                model.addEventListener("scale_changed", maintainCenter )
+                model.addEventListener('transformChanged', ()=>{
+                    MergeEditor.modelTransformCallback(model)
+                })
+            
+            } 
+            
+            model.updateMatrixWorld()
+            viewer.updateModelBound()
+            
+            MergeEditor.getBoundCenter(model) //初始化
+            model.lastMatrixWorld = model.matrixWorld.clone()
+            
+            done(model) // 先发送成功,因为2d界面会随机执行changePosition等初始化,然后这边再将模型移到中心地面上
+            
+            
+            if(prop.isFirstLoad){
+                
+                MergeEditor.moveBoundCenterTo(model, new THREE.Vector3(0,0,0))  
+                MergeEditor.setModelBtmHeight(model, 0) //初始加载设置离地高度为0
+                
+                if(prop.mode != 'single'){//如果不是模型展示页,模型会随着鼠标位置移动
+                    viewer.addEventListener('global_mousemove', moveModel); 
+                    viewer.addEventListener('global_click', confirmPos, 3);
+                    modelEditing = model;
+                }
+                model.dispatchEvent("position_changed") 
+            }else{
+                //MergeEditor.setModelBtmHeight(model, prop.bottom || 0) //默认离地高度为0
+                modelEditing = null
+            }
+            
+            
+            
+            
+        }
+        
+        
+        
+        
+        
+        
+        
+        if(prop.type == 'glb'){
+            
+            
+            
+            let callback = (object)=>{
+                //focusOnSelect(object, 1000)  
+                object.isModel = true
+                //object.dataset_id = Date.now() //暂时
+                
+                object.traverse(e=>e.material && (e.material.transparent = true))
+              
+                /* object.addEventListener('click',(e)=>{
+                    //只是为了能得到hoverElement识别才加这个侦听
+                }) */
+                 
+                loadDone(object)
+            }
+            
+             
+            let info = { 
+                name: prop.type, 
+                id: prop.id,
+                unlit:true,
+                /* transform : { 
+                    position : prop.position,
+                    rotation : new THREE.Euler().setFromVector3(prop.rotation), 
+                    scale: new THREE.Vector3(prop.scale,prop.scale,prop.scale),        
+                }  */               
+            }
+            
+            if(prop.type == 'glb'){
+                info.glburl = prop.url  
+            }
+                 
+              
+            viewer.loadModel(info , callback, onProgress, onError)
+            
+            
+            
+            
+            
+            
+            
+          }else{  
+            
+             //else if(prop.type == 'las' || prop.type == 'ply')
+ 
+            Potree.loadPointCloudScene(prop.url, prop.type, prop.modelId, (pointcloud)=>{  
+                pointcloud.matrixAutoUpdate = true
+                pointcloud.initialPosition = pointcloud.position.clone()
+                
+                pointcloud.pos1MatrixInvert = new THREE.Matrix4().setPosition(pointcloud.initialPosition).invert()
+                
+                /* let maintainBtmZ = ()=>{
+                    MergeEditor.setModelBtmHeight(pointcloud)
+                    updateMatrix()
+                }
+                let updateMatrix = ()=>{ 
+                    setMatrix(pointcloud) 
+                    pointcloud.dispatchEvent('transformChanged')
+                }
+                pointcloud.addEventListener('position_changed', updateMatrix )  
+                pointcloud.addEventListener("orientation_changed", maintainBtmZ )
+                pointcloud.addEventListener("scale_changed", maintainBtmZ ) */
+                
+                loadDone(pointcloud)
+                /* pointcloud.addEventListener('select',(e)=>{
+                    if(Potree.settings.displayMode == 'showPanos')return
+                    console.log('select',e) 
+                    //viewer.setControls(viewer.orbitControls) 
+                    MergeEditor.focusOnSelect(pointcloud) 
+                    
+                    viewer.outlinePass.selectedObjects = [pointcloud]
+                    return {stopContinue:true}
+                },1)
+                pointcloud.addEventListener('deselect',(e)=>{
+                    console.log('deselect',e) 
+                    //viewer.setControls(viewer.fpControls)  
+                    viewer.outlinePass.selectedObjects = []
+                }) */
+                
+            }, onError)
+        
+             
+            
+            
+        }
+    }
+    return {THREE}
+}
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
  
 /* var changeLog = ()=>{ //如果移动端加了test反而出不来bug的话,用这个
         
@@ -503,7 +878,7 @@ changeLog() */
  
  
  
-export {start, panoEditStart}
+export {start, panoEditStart, mergeEditStart}
 
 
 

+ 23 - 8
src/utils.js

@@ -402,7 +402,7 @@ export class Utils {
 	}
 
 	static getMousePointCloudIntersection (viewport, mouse, pointer, camera, viewer, pointclouds, pickParams = {} ) {
-		if(!pointclouds)return
+		if(!pointclouds || pointclouds.length == 0)return
         
 		let renderer = viewer.renderer;
 		
@@ -514,8 +514,9 @@ export class Utils {
 				location: closestIntersection,
 				distance: closestDistance,
 				pointcloud: selectedPointcloud,
-                pointclouds: allPointclouds, //add
-				point: closestPoint
+                point: closestPoint,
+                pointclouds: allPointclouds, //add 
+                normal: new THREE.Vector3().fromArray(closestPoint.normal )//add
 			};
 		} else {
 			return null;
@@ -1286,18 +1287,32 @@ Utils.QuaternionFactory = {
  
   
 Utils.datasetPosTransform = function(o={}){  
+ 
     let pointcloud = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId)
-    
+    let tranMatrix 
     if(pointcloud){ 
-        let tranMatrix = o.fromDataset ? pointcloud.transformMatrix : pointcloud.transformInvMatrix
+        if(Potree.settings.editType == 'merge'){
+            tranMatrix = o.fromDataset ? pointcloud.matrixWorld : new THREE.Matrix4().copy(pointcloud.matrixWorld).invert() 
+        }else{
+            tranMatrix = o.fromDataset ? pointcloud.transformMatrix : pointcloud.transformInvMatrix 
+        }
+    }else{ 
+        if(Potree.settings.intersectOnObjs){
+            let object = o.object || viewer.objs.children.find(e=>e.dataset_id == o.datasetId)
+            if(object){
+                tranMatrix = o.fromDataset ? object.matrixWorld : new THREE.Matrix4().copy(object.matrixWorld).invert() 
+            }  
+        } 
+    } 
+    if(tranMatrix){
         return (new THREE.Vector3).copy(o.position).applyMatrix4(tranMatrix)
-        
     }else{
         if(o.datasetId != void 0){
-            console.error(`datasetPosTransform找不到datasetId为${o.datasetId}的数据集,请检查(热点?测量线?)数据`)
+            console.error(`datasetPosTransform找不到datasetId为${o.datasetId}的数据集或模型,请检查数据, 模型未创建或删除`)
             //很可能是旧的热点,需要删除
         }
-    }  
+    }
+    
 }
 
 

+ 7 - 0
src/utils/Common.js

@@ -285,7 +285,14 @@ var Common = {
             delete defines[defineName] 
         }
         material.needsUpdate = true;
+    },
+    
+    makeTexDontResize(map){//避免贴图因非2的次方而缩小。小心使用
+        if(!map || map.image && THREE.Math.isPowerOfTwo(map.image.width ) && THREE.Math.isPowerOfTwo(map.image.height ))return
+        map.wrapS = map.wrapT = THREE.ClampToEdgeWrapping; //原默认 RepeatWrapping 
+        map.minFilter = THREE.LinearFilter; // or THREE.NearestFilter  原默认 LinearMipmapLinearFilter
     }
+    
 }  
 
 

+ 2 - 2
src/utils/CursorDeal.js

@@ -7,14 +7,14 @@ import Common from './Common.js'
 
 var CursorDeal = {
     priorityEvent : [//在前面的优先级高
-     
+        {'zoomInCloud':'zoom-in'},
         {'hoverPano':'pointer'}, 
           
         {'connectPano':`url({Potree.resourcePath}/images/connect.png),auto`},
         {'disconnectPano':`url({Potree.resourcePath}/images/connect-dis.png),auto`},
          
         {'hoverLine':'pointer'},
-        {'zoomInCloud':'zoom-in'},
+        
         
         
         {"movePointcloud":'move'}, 

+ 48 - 0
src/utils/History.js

@@ -0,0 +1,48 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+
+
+
+class History extends THREE.EventDispatcher{
+    
+    constructor(o){ 
+        super()
+        
+        this.list = []
+        
+        this.callback = o.callback
+    }
+    
+    undo(){
+        let last = this.list.pop();
+        last && this.callback && this.callback(last) 
+        
+        
+    }
+    
+    
+    redo(){//暂时不写
+        
+        
+        
+    }
+    
+    writeIn(item){
+        
+        this.list.push(item)
+        
+    }
+    
+    
+    clear(){
+        
+        this.list.length = 0
+        
+    }
+    
+    
+    
+}
+
+
+export default History

+ 122 - 376
src/utils/SplitScreen.js

@@ -3,293 +3,158 @@ import Viewport from "../viewer/Viewport.js";
 import * as THREE from "../../libs/three.js/build/three.module.js";
 
 
-const viewportProps = [
-    {
-        left:0.5,
-        bottom:0.5,
-        width: 0.5,height:0.5,
-        name : 'MainView',   
-        //view: viewer.scene.view,
-        active: true,
-    },
-    {
-        left:0,
-        bottom:0.5,
-        width: 0.5,height:0.5,
-        name : 'top',   
-        name2 : 'mapViewport', 
-        axis:["x","y"],
-        //axisSign:[1,1],
-        active: true,
-        //相机位置在z轴正向
-    },
-    {
-        left:0.5,
-        bottom:0,
-        width: 0.5,height:0.5,
-        name : 'right', 
-        axis:["y","z"],
-        //axisSign:[1,1],
-        active: true,
-        //相机位置在x轴正向  右下角屏
-    },
-    {
-        left:0,
-        bottom:0,
-        width: 0.5,height:0.5, 
-        name : 'back', 
-        axis:["x","z"],
-        //axisSign:[-1,1],    // 从镜头方向看  x向左 所以取负 
-        active: true,
-        //相机位置在y轴正向  左下角屏
-    },
-]
+ 
 
-
-
-
-var SplitScreen = {
+class SplitScreen extends THREE.EventDispatcher{
+    constructor (args = {}) {
+		super();
+        
+    }
     
-    //viewer.modules.Alignment.SplitScreen.viewportFitBound(viewer.mapViewer.viewports[0],viewer.bound.boundSize, viewer.bound.center)
     
-    splitScreen4Views : function(o={}){
-        var defaultCamera = viewer.scene.getActiveCamera()
-       
-        let {boundSize, center} = viewer.bound
-          
-        var getOrthographicCamera = function(widthRatio, heightRatio, axis){
-             /*var widthPX = viewer.renderArea.clientWidth * widthRatio;
-            var heightPX = viewer.renderArea.clientHeight * heightRatio;
-              var aspect = widthPX/heightPX
-            //console.log(viewer.renderArea.clientWidth,viewer.renderArea.clientHeight,aspect)
-            var width = Math.max(boundSize[axis[0]],  boundSize[axis[1]] * aspect)   */
-            var orthographicCamera = new THREE.OrthographicCamera(-100, 100, 100, 100, 0.01, 10000);
-            /*  var margin = 50 //px 
-            orthographicCamera.zoom = (widthPX - margin) / width//zoom越大视野越小
-            var moveAtAxis = ['x','y','z'].find(e=>!(axis.includes(e)))
-            orthographicCamera.up.set(0,0,1)
-            orthographicCamera.position.copy(center)
-            orthographicCamera.position[moveAtAxis] += 1000;//偏移一些 在模型外即可  
-             */  
-            return orthographicCamera
-        }
-
-        
-        
-        viewer.setLimitFar(false)
-        
-         
-        
-        
+    splitStart(cameraProps){ 
         let viewports = []
+      
+        let subViewports = [viewer.mainViewport]
+        if(viewer.mapViewer){
+            subViewports.push(viewer.mapViewer.viewports[0])
+        }
         
-        //更改mainViewport参数 
-        viewports.push(viewer.mainViewport) 
-         
-        let mapViewport = viewer.mapViewer.viewports[0]
-        viewports.push(mapViewport)
-        
-        
-        
-        
-        
-        
-        for(let i=0;i<4;i++){
-            let prop = viewportProps[i];
+        let length = cameraProps.length
+        for(let i=0;i<length;i++){
+            let prop = cameraProps[i];
             let viewport;
-            
-             
-            let v = viewports.find(e=>e.name == (prop.name2||prop.name)) 
+            let v = subViewports.find(e=>e.name == (prop.name2||prop.name)) 
             if(v){
                 viewport = v
                 viewport.left = prop.left; viewport.bottom = prop.bottom; viewport.width = prop.width; viewport.height = prop.height;
             }
             
             if(!viewport){
-                viewport = new Viewport( new View(), getOrthographicCamera(prop.width, prop.height, prop.axis), prop )
-                //viewport.unableDepth = true //depthBasicMaterial等在此viewport中不开启depth
-                viewports.push(viewport) 
-            }                
-             
-              
-            if(viewport.name != 'MainView'){
-                viewport.view.setCubeView(viewport.name)
-                viewport.view.position.copy(viewport.camera.position)
+                let view = new View()  
+                if(prop.limitBound)view.limitBound = prop.limitBound
+                prop.direction && (view.direction = prop.direction)
+                
+                viewport = new Viewport(view , this.getOrthoCamera(), prop )
+                if(prop.viewContainsPoints)viewport.viewContainsPoints = prop.viewContainsPoints
                 
+                //viewport.unableDepth = true //depthBasicMaterial等在此viewport中不开启depth 
+                
+            }    
+            if(viewport.camera.type == 'OrthographicCamera'  ){
+                viewport.targetPlane = new THREE.Plane()
+                viewport.shiftTarget = new THREE.Vector3 //project在targetPlane上的位置
             }
-            
-            
-            
-        }
-        
-        
-        viewer.viewports = viewports;
-        
-        
-        viewer.mapViewer.attachToMainViewer(true,'split4Screens','dontSet') 
-        //覆盖在map上、点云等其他物体之下的一层背景
-        
-        mapViewport.noPointcloud = false
-        //隐藏地图游标
-        //viewer.updateVisible(viewer.mapViewer.cursor, 'split4Screens', false)
-        /* viewer.images360.panos.forEach(pano=>{
-            viewer.updateVisible(pano.mapMarker, 'split4Screens', false) //希望这时候mapMarker已经建好了吧
-        }) */
-            
-            
-            
-            
-        //材质 
-        this.statesBefore = { 
-            pointDensity : Potree.settings.pointDensity,
-            displayMode : Potree.settings.displayMode,
-            
-            position: viewer.images360.position,
-			target: viewer.scene.view.getPivot(),
-             
-            
-            //---
-            //ifShowMarker : Potree.settings.ifShowMarker, 
-        }
-        
-        viewer.setPointStandardMat(true,null,true) //切换到标准模式(主要为了mainViewport)  点云使用标准大小 
-        
-        var matBefore = { 
-            opacity : new Map() 
+            viewport.fitMargin = prop.margin
+            viewports.push(viewport) 
         } 
-        var newOpacityMap = new Map() 
-         
-        viewer.scene.pointclouds.forEach(e=>{
-            matBefore.opacity.set(e, e.temp.pointOpacity) 
-            matBefore.colorType = e.material.activeAttributeName
-            
-            /* { 
-                var map = new Map()
-                newOpacityMap.set(e, map )
-                var size = e.bound.getSize()
-                viewports.forEach(viewport=>{//根据bound设置opacity,越小的要靠越近,需要大的opacity。但似乎影响太大了
-                    if(viewport.name == 'MainView')return;
-                    var prop = viewportProps.find(v => viewport.name == v.name2||viewport.name == v.name)
-                    let axis = prop.axis
-                    var width = size[axis[0]]
-                    var height = size[axis[1]]
-                    var area = width * height
-                    map.set(viewport, 5000/area);
-                })
-                
-            }  */ 
-        }) 
-        
-        let beforeRender = function(){
-            viewer.scene.pointclouds.forEach(e=>{ 
-                if(this.name == "MainView"){ 
-                    e.material.activeAttributeName = matBefore.colorType // 'rgba'
-                    
-                    e.material.useFilterByNormal = false 
-                    e.changePointOpacity(matBefore.opacity.get(e)) //1 //恢复下 e.temp.pointOpacity 其实就是1
-                    
-                    Potree.settings.pointDensity = 'fourViewportsMain'/* 'fourViewports' */ //本来想比另外三屏高一点质量,结果发现会闪烁,因为点云加载需要时间 (navvis仿版也是一样,以后看看能否优化)
-                    
-                }else{ 
-                    e.material.activeAttributeName = "color"
-                    e.material.useFilterByNormal = true 
-                    
-                    Potree.settings.pointDensity = 'fourViewports' //强制降低点云质量
-                    
-                    e.changePointOpacity(0.6/* newOpacityMap.get(e).get(viewport), true */);  //多数据集有的数据集很小,放大后显示特别淡
-                    //console.log(e.name, viewport.name, e.temp.pointOpacity, e.material.opacity)
-                }                 
-            })  
-        }    
-        viewports.forEach(viewport=>{viewport.beforeRender = beforeRender})
-         
-         
-         
-        this.enableMap(false)
-        this.enableFloorplan(false)
-        viewer.mapViewer.setViewLimit('expand') //多数据集距离远时可以任意远,所以不限制了。但是这样就会看到地图边界了怎么办?
-        //viewer.dispatchEvent({'type': 'beginSplitView' }) 
-        viewer.updateScreenSize({forceUpdateSize:true})   
-        
-        
+        viewer.viewports = viewports;
+        viewer.updateScreenSize({forceUpdateSize:true})
         viewports.forEach(viewport=>{
             if(viewport.name == 'MainView')return
-            this.viewportFitBound(viewport, boundSize, center)
-        })   
-        //this.viewportFitBound(mapViewport, boundSize, center)
-        //Potree.settings.ifShowMarker = false
-        Potree.settings.displayMode = 'showPointCloud'
-    },
-    
+            this.viewportFitBound(viewport, viewer.bound.boundingBox , viewer.bound.center , 0, viewport.fitMargin)
+        }) 
+        return viewports
+    } 
     
-     
-      
     
+    unSplit(){
+        this.unfocusViewport()
+        viewer.viewports = [viewer.mainViewport] 
+        viewer.mainViewport.width = 1;
+        viewer.mainViewport.height = 1;
+        viewer.mainViewport.left = 0
+        viewer.mainViewport.bottom = 0;   
+        viewer.updateScreenSize({forceUpdateSize:true})        
+    }
     
-    recoverFrom4Views: function(){
+    viewportFitBound(viewport,  bound,  center, duration=0, margin){
+        let view = viewport.view
+        let info = {bound} 
+        let {boundSize, boundCenter} = this.getViewBound(viewport)
         
-        this.unfocusViewport() 
-        const {width, height} = viewer.renderer.getSize(new THREE.Vector2());
-        viewer.renderer.setViewport(0,0,width,height)
-        viewer.renderer.setScissorTest( false );
         
-        viewer.setView({
-            position: SplitScreen.statesBefore.position,
-            target: SplitScreen.statesBefore.target,
-            duration:300,
-            callback:function(){ 
-            }
-        })
         
+        viewport.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), boundCenter )  
+        viewport.targetPlane.projectPoint(center, viewport.shiftTarget)  //target转换到过模型中心的平面,以保证镜头一定在模型外 this.shiftTarget是得到的
         
-        viewer.viewports = [viewer.mainViewport] 
-        viewer.mainViewport.width = 1;
-        viewer.mainViewport.height = 1;
-        viewer.mainViewport.left = 0
-        viewer.mainViewport.bottom = 0;
-        viewer.mainViewport.beforeRender = null 
-        viewer.setLimitFar(true)
+        info.endPosition = this.getPosOutOfModel(viewport, boundSize) 
         
-        let mapViewport = viewer.mapViewer.viewports[0]
-        viewer.mapViewer.attachToMainViewer(false) 
-        //viewer.updateVisible(viewer.mapViewer.cursor, 'split4Screens', true)
-        /* viewer.images360.panos.forEach(pano=>{
-            viewer.updateVisible(pano.mapMarker, 'split4Screens', true)
-        }) */
-        mapViewport.noPointcloud = true
-        { 
-            this.enableMap(Potree.settings.mapEnable)
-            this.enableFloorplan(Potree.settings.floorplanEnable)
-            if(this.floorplanListener){
-                viewer.mapViewer.mapLayer.removeEventListener( 'floorplanLoaded', this.floorplanListener )  
-                this.floorplanListener = null  
-            } 
-        }
+        //if(viewport.name == 'mapViewport')info.endPosition.z = Math.max(Potree.config.map.cameraHeight, info.endPosition.z) 
          
-        Potree.settings.pointDensity = SplitScreen.statesBefore.pointDensity
-        if(!Potree.settings.isOfficial){
-            Potree.settings.displayMode = SplitScreen.statesBefore.displayMode
+        info.margin = margin || {x:30, y:30}    
+        view.moveOrthoCamera(viewport, info ,  duration   )
+    } 
+    
+    
+    getViewBound(viewport){
+        let {boundSize, center} = viewer.bound
+        if(viewport.viewContainsPoints){//视野范围内必须要包含的点,直接算入模型区域。这时候得到的boundCenter和模型中心不重合
+            let boundingBox = viewer.bound.boundingBox.clone()
+            viewport.viewContainsPoints.forEach(point=>{
+                boundingBox.expandByPoint(point)
+            })
+            boundSize = boundingBox.getSize(new THREE.Vector3)
+            center = boundingBox.getCenter(new THREE.Vector3)
         }
+         
+        return {boundSize, boundCenter:center }  
+    }
+    
+    getPosOutOfModel(viewport, boundSize){ 
+        //let {boundSize, center} = viewer.bound
+        boundSize = boundSize || this.getViewBound(viewport).boundSize
+        let expand = 10; 
+        let radius = boundSize.length() //    / 2  
+        let position = viewport.shiftTarget.clone().sub(viewport.view.direction.clone().multiplyScalar(radius + expand))  
+         
+        return position 
+    } 
+    
+    updateCameraOutOfModel(){//因为移动模型导致模型超出相机外,所以更新位置
+        viewer.viewports.forEach((viewport, i )=>{
+            if(viewport != viewer.mainViewport){
+                let {boundSize, boundCenter} = this.getViewBound(viewport)
+                viewport.targetPlane.setFromNormalAndCoplanarPoint( viewport.view.direction.clone(), boundCenter) 
+                viewport.targetPlane.projectPoint(viewport.view.position, viewport.shiftTarget)  //target转换到过模型中心的平面,以保证镜头一定在模型外 this.shiftTarget是得到的
+         
+                 let endPosition = this.getPosOutOfModel(viewport, boundSize) 
+                 //if(viewport.name == 'mapViewport')endPosition.z = Math.max(Potree.config.map.cameraHeight, endPosition.z) 
+                 viewport.view.position.copy(endPosition)
+            } 
+        })  
+    }  
+     
+    rotateSideCamera(viewport, angle){//侧视图绕模型中心水平旋转
+         
+        //let {boundSize, center} = viewer.bound
+        let {boundSize, boundCenter } = this.getViewBound(viewport)  
+        let center = this.focusCenter || boundCenter //旋转中心,一般是所有模型的中心,除非想指定中心点
         
-        viewer.scene.pointclouds.forEach(e=>{ 
-            //e.material.color.set(SplitScreen.statesBefore.mat.color)
-            //e.material.activeAttributeName = SplitScreen.statesBefore.mat.colorType 
-            e.material.useFilterByNormal = false
-            //e.material.opacity = SplitScreen.statesBefore.mat.opacity  
-        }) 
-        viewer.setPointStandardMat(false)
-        viewer.mapViewer.setViewLimit('standard')
+        //找到平移向量
+        viewport.targetPlane.setFromNormalAndCoplanarPoint(viewport.view.direction  , center ) 
+        viewport.targetPlane.projectPoint(viewport.view.position,  viewport.shiftTarget )  //target转换到过模型中心的平面,以保证镜头一定在模型外
+        let vec = new THREE.Vector3().subVectors(center, viewport.shiftTarget)//相对于中心的偏移值,旋转后偏移值也旋转
         
-        //Potree.settings.ifShowMarker = SplitScreen.statesBefore.ifShowMarker
-        //viewer.dispatchEvent({'type': 'finishSplitView' }) 
-        viewer.updateScreenSize({forceUpdateSize:true}) 
+        //旋转
+        var rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle) 
         
+        viewport.view.direction = viewport.view.direction.applyMatrix4(rotMatrix)
+         
+         
+        vec.applyMatrix4(rotMatrix)
+        viewport.shiftTarget.subVectors(center,vec) //新的
         
         
+        viewport.view.position = this.getPosOutOfModel(viewport, boundSize)
         
-    },
+    }
+    
+    getOrthoCamera(){
+        return new THREE.OrthographicCamera(-100, 100, 100, 100, 0.01, 10000)
+    } 
     
-    focusOnViewport : function(name){//全屏
+    focusOnViewport(name){//全屏
         viewer.viewports.forEach((viewport, i )=>{
             if(viewport.name == name){
                 this.focusInfo = {
@@ -304,9 +169,9 @@ var SplitScreen = {
         })
          
         viewer.updateScreenSize({forceUpdateSize:true}) 
-    },
+    } 
     
-    unfocusViewport:function(){
+    unfocusViewport(){
         if(!this.focusInfo)return
         viewer.viewports.forEach((viewport, i )=>{ 
             if(this.focusInfo.name == viewport.name){//全屏的恢复 
@@ -319,128 +184,9 @@ var SplitScreen = {
         }) 
         viewer.updateScreenSize({forceUpdateSize:true}) 
         this.focusInfo = null
-    },
-    
-    
-    
-    
-    updateMapViewerBG(){
-        let mapViewport = viewer.mapViewer.viewports[0]
-        if(this.floorplanEnabled || this.mapEnabled){
-            mapViewport.background = 'overlayColor'
-            mapViewport.backgroundColor = new THREE.Color(0,0,0)
-            mapViewport.backgroundOpacity = 0.5; 
-        }else{
-            mapViewport.background = null
-            mapViewport.backgroundColor = null
-            mapViewport.backgroundOpacity = null
-        }
-    },
-    
-    setFloorplanDisplay: function(e, show=false){ 
-        //viewer.updateVisible(e.floorplan.objectGroup, 'splitScreen', !!show)  
-        //e.floorplan.objectGroup.visible = !!show  
-        //viewer.mapViewer.mapLayer.needUpdate = true
-        e.floorplan.setEnable(show)
-    },
+    } 
     
-     
-    enableMap(enable){ 
-        let map = viewer.mapViewer.mapLayer.maps.find(e=>e.name == 'map')
-        //viewer.updateVisible(map.objectGroup, 'splitScreen', !!enable) 
-        //map.objectGroup.visible = !!enable
-        //if(enable)viewer.mapViewer.mapLayer.needUpdate = true //加载地图
-        map.setEnable(!!enable)
-        
-        
-        //viewer.mapViewer.mapGradientBG = viewer.background == 'gradient' && !enable
-        this.mapEnabled = enable
-        this.updateMapViewerBG()
-       
-        
-        
-    },
-    //直接覆盖原设置
-    
-    enableFloorplan(enable){ //是否让自定义的平面图显示
-        let floorplans = viewer.mapViewer.mapLayer.maps.filter(e=>e.name.includes('floorplan'))
-        
-        if(this.floorplanListener){
-            viewer.mapViewer.mapLayer.removeEventListener( 'floorplanLoaded', this.floorplanListener )  
-        }
-        this.floorplanListener = (e)=>{
-            this.setFloorplanDisplay(e, enable) 
-        }
-        
-        viewer.mapViewer.mapLayer.addEventListener( 'floorplanLoaded', this.floorplanListener ) //万一之后才加载 
-        
-        
-        if(!enable){ 
-            //隐藏平面图 
-            floorplans.forEach(floorplan=>this.setFloorplanDisplay({floorplan},false))  
-             
-        }else{
-             
-            floorplans.forEach(floorplan=>this.setFloorplanDisplay({floorplan},true))  
-            
-        }
-        
-        
-        if (enable && floorplans.length == 0) Potree.loadMapEntity('all',true)
-        
-        this.floorplanEnabled = enable
-        this.updateMapViewerBG()
-    },
-    
-    viewportFitBound:function(viewport, boundSize, center){  //使一个viewport聚焦在某个范围
-        var prop = viewportProps.find(v => viewport.name == v.name2||viewport.name == v.name)
-        let axis = prop.axis 
-        let expand = 10;
-        let position = center.clone()
-        var moveAtAxis = ['x','y','z'].find(e=>!(axis.includes(e))) 
-        
-        if(viewport.name == 'mapViewport'){ 
-            let ori = viewport.view.position[moveAtAxis] 
-            position[moveAtAxis] = ori //不改变这个值,尤其是mapViewer中的z
-        }else{
-            position[moveAtAxis] += boundSize[moveAtAxis]/2+expand//移动到bounding边缘外
-        }
-        
-        viewport.view.position.copy(position)
-        
-        var width = Math.max(boundSize[axis[0]],  boundSize[axis[1]] * viewport.camera.aspect)//视口宽度(米)
-        var margin = 50 //px
-        viewport.camera.zoom = (viewport.resolution.x - margin) / width  
-        viewport.camera.updateProjectionMatrix()
-    },
-    
-     
-    focusOnPointCloud:function(pointcloud){//三个屏都聚焦在这个点云 
-        var boundSize = pointcloud.bound.getSize(new THREE.Vector3);
-	    var center = pointcloud.bound.getCenter(new THREE.Vector3); 
-        let target = pointcloud.panosBound && pointcloud.panosBound.center  //看向pano集中的地方,也就是真正有点云的地方。(因为需要展示所有点云,所以没办法用这个做为center)
-        this.focusOnObject(boundSize,center,target)
-        
-        viewer.flyToDataset({pointcloud, dontMoveMap:true, duration:0})
-    }
-    ,
-    focusOnObject:function(boundSize, center, target, duration=0){
-        viewer.viewports.forEach(e=>{
-            if(e.name == 'MainView'){
-                /* let len = boundSize.length()
-                let distance = THREE.Math.clamp(e.view.position.distanceTo(center),  len * 0.01,  len*0.3 ) //距离限制
-                //viewer.focusOnObject({position:center}, 'point', duration, {distance, direction: e.view.direction,dontMoveMap:true} )//平移镜头
-                //为了方便定位,直接飞到中心位置:
-                e.view.setView({
-                    position:center,  duration,  target   
-                }) */
-                
-                
-            }else{
-                this.viewportFitBound(e, boundSize, center)
-            }
-        })
-    },
 }
 
 export default SplitScreen
+

+ 9 - 2
src/utils/math.js

@@ -53,9 +53,16 @@ var math = {
     getAngle:function(vec1, vec2, axis){//带方向的角度 vector3
         var angle = vec1.angleTo(vec2)
         var axis_ = vec1.clone().cross(vec2);
-        if(axis_[axis] < 0){
-            angle *= -1
+        if(typeof axis == 'string'){
+            if(axis_[axis] < 0){
+                angle *= -1
+            }
+        }else{//vector3
+            if(axis_.dot(axis)< 0){
+                angle *= -1
+            }
         }
+        
         return angle
     }, 
     

+ 7 - 2
src/utils/transitions.js

@@ -457,15 +457,20 @@ var transitions = {
     },
     cancelById: function(e, dealCancelFun) { //xzw add dealDone
         var t = void 0 === e ? 0 : e;
-		 
+		let cancels = []
         this.funcs = this.funcs.filter(function(e) {
 			var is = e.id == t;
 			
 			if(is && dealCancelFun){
-				e.cancelFun && e.cancelFun()
+                e.cancelFun && cancels.push(e.cancelFun)
+				//e.cancelFun && e.cancelFun()
 			} 
             return !is
         })
+        
+        cancels.forEach(e=>{e()}) //先从funcs中去除后再执行
+        
+        
     },
     cancel: function(e) {
         this.funcs = this.funcs.filter(function(t) {

+ 87 - 94
src/viewer/EDLRenderer.js

@@ -188,11 +188,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
         
         //viewer.dispatchEvent({type: "render.pass.begin",viewer: viewer});
 	
-		                                           
-        
-         
-        
- 
+		       
 		let lights = [];
 		/* viewer.scene.scene.traverse(node => {
 			if(node.type === "SpotLight"){
@@ -231,36 +227,35 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
         //skybox  全景图
         if(!params.magnifier){
             viewer.setCameraLayers(camera, ['skybox'])
-            if(Potree.settings.hasDepthTex && Potree.settings.displayMode == 'showPanos' && Features.EXT_DEPTH.isSupported()){//渲染深度图
-                viewer.renderer.setRenderTarget(params.rtEDL || this.getRtEDL(params.viewport))
+            let useDepthTex
+            if(Potree.settings.displayMode == 'showPanos' && viewer.images360.currentPano.pointcloud.hasDepthTex && Features.EXT_DEPTH.isSupported()){//渲染深度图
+                useDepthTex = true
+                viewer.renderer.setRenderTarget(params.rtEDL || this.getRtEDL(params.viewport)) //将带有深度图的skybox画在rtEDL一下,这样就不需要绘制后边的点云了
                 viewer.renderer.render(viewer.scene.scene, camera);
                 viewer.renderer.setRenderTarget(params.target || null);
             } 
             viewer.renderer.render(viewer.scene.scene, camera);
-            if(Potree.settings.hasDepthTex && Potree.settings.displayMode == 'showPanos' )return 
+            if(useDepthTex)return 
         } 
          
-         
         
+		const visiblePointClouds2 = viewer.scene.pointclouds.filter(pc => viewer.getObjVisiByReason(pc,'datasetSelection')  ); //需要绘制到rtEDL的
+		const showPointClouds = params.magnifier ? visiblePointClouds2.length>0 : viewer.scene.pointclouds.some(e=>e.visible) //是否有需要绘制到屏幕的
          
-		//const visiblePointClouds = viewer.scene.pointclouds.filter(pc =>  pc.visible );
-		const visiblePointClouds2 = viewer.scene.pointclouds.filter(pc => viewer.getObjVisiByReason(pc,'datasetSelection')  ); 
-		const showPointClouds = viewer.scene.pointclouds.some(e=>e.visible)
-         
-        viewer.scene.pointclouds.forEach(e=>{//为了绘制到depthTexture,先显示(展示全景图时隐藏了点云,所以需要显示下。且放大镜需要绘制点云)
+        visiblePointClouds2.forEach(e=>{//为了绘制到depthTexture,先显示(展示全景图时隐藏了点云,所以需要显示下。且放大镜需要绘制点云)
             e.oldVisi = e.visible
-            if(viewer.getObjVisiByReason(e, 'datasetSelection') ) e.visible = true; 
+            e.visible = true; 
         })
 
         
-        //pointcloud
+         
         viewer.setCameraLayers(camera, ['pointcloud'])
         camera.layers.set(Potree.config.renderLayers.pointcloud);
 		
         //TODO adapt to multiple lights
 		//this.renderShadowMap(visiblePointClouds2, camera, lights);  //???????
 
-		{ //scenePointCloud   COLOR & DEPTH PASS
+		{ 
 			for (let pointcloud of visiblePointClouds2) {
                 
                 let material = pointcloud.material; 
@@ -290,47 +285,41 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
 			}
             
             
-            if(Features.EXT_DEPTH.isSupported()){   
-                //借用rtEDL存储深度信息 
+            if(Features.EXT_DEPTH.isSupported() && !params.dontRenderRtEDL){  
+                //借用rtEDL存储深度信息  
                 viewer.renderer.setRenderTarget(params.rtEDL || this.getRtEDL(params.viewport)/* this.rtEDL */);
-                 
-                //pointDensity已经使用的是panorama模式,在画面边缘点云会稀疏,遮挡效果差
-                
-                /* if(Potree.settings.displayMode == "showPanos"){ //还原成点云模式的材质,且可能要更大些,因点云变稀疏
-                    var matBefore = { 
-                        pointSizeType : new Map(),
-                        size :  new Map(),    
-                        usePanoMap :  new Map(),
-                    } 
-                    for (let pointcloud of visiblePointClouds2) { 
-                        matBefore.usePanoMap.set(pointcloud, pointcloud.material.usePanoMap)
-                        //matBefore.pointSizeType.set(pointcloud, pointcloud.material.pointSizeType)  
-                        matBefore.size.set(pointcloud, pointcloud.temp.pointSize)  
-                        
-                        //pointcloud.material.pointSizeType = Potree.config.material.pointSizeType //不能修改这项,因为是define,会卡。
-                        //pointcloud.changePointSize(pointcloud.temp.pointSize * (1+Potree.config.material.sizeAddAtPanoRtEDL))
-                        
-                        //pointcloud.changePointSize(Potree.config.material.sizeAtPanoRtEDL, true)
-                        pointcloud.material.usePanoMap = false  //禁止使用absolutePanoramaSize大小
-                        
-                    }
-                }  */   
-                //渲染scenePointCloud到rtEDL
-                viewer.pRenderer.render(viewer.scene.scenePointCloud, camera, params.rtEDL || this.getRtEDL(params.viewport), {
-                    shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
-                    clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)),
-                    transparent: false,
-                });
-                
-                
-                /* if(Potree.settings.displayMode == "showPanos"){ 
-                    for (let pointcloud of visiblePointClouds2) {
-                         
-                        //pointcloud.material.pointSizeType = matBefore.pointSizeType.get(pointcloud)
-                        pointcloud.material.usePanoMap = matBefore.usePanoMap.get(pointcloud)
-                        //pointcloud.changePointSize(matBefore.size.get(pointcloud))
-                    }
-                } */
+               
+               
+                if(visiblePointClouds2.length>0){ 
+                    //渲染scenePointCloud到rtEDL
+                    viewer.pRenderer.render(viewer.scene.scenePointCloud, camera, params.rtEDL || this.getRtEDL(params.viewport), {
+                        shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
+                        clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)),
+                        transparent: false,
+                    });
+                } 
+                if(Potree.settings.intersectOnObjs){// model也要渲染到rtEDL
+                    camera.layers.set(Potree.config.renderLayers.model);
+                    viewer.objs.traverse(e=>{if(e.material)e._OlddepthWrite = e.material.depthWrite, e.material.depthWrite = true}) //否则半透明的mesh无法遮住测量线
+                    viewer.renderer.render(viewer.scene.scene, camera);
+                    viewer.objs.traverse(e=>{if(e.material)e.material.depthWrite = e._OlddepthWrite})
+                    //缺点:半透明的model 就算完全透明, 也会遮住测量线
+                }
+                //test
+                /* {
+                    viewer.objs.traverse((obj)=>{
+                        if(obj.material){
+                            obj.material = obj.depthMat
+                        }
+                    })
+                    viewer.setCameraLayers(camera, ['sceneObjects'])
+                    viewer.renderer.render(viewer.scene.scene, camera)
+                    viewer.objs.traverse((obj)=>{
+                        if(obj.material){
+                            obj.material = obj.standardMat
+                        }
+                    })
+                } */ 
             } 
 		}
         
@@ -341,52 +330,56 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
 		viewer.dispatchEvent({type: "render.pass.scene", viewer: viewer/* , renderTarget: this.rtRegular */});
 		viewer.renderer.setRenderTarget(params.target || null);
         
-        if(!params.magnifier)viewer.scene.pointclouds.forEach(e=>{//放大镜显示点云
+        if(!params.magnifier)visiblePointClouds2.forEach(e=>{//放大镜显示点云
             e.visible = e.oldVisi
         })
         
          
          
-        
-       //设置edlMaterial
-        if(viewer.useEDL && showPointClouds  /* || params.magnifier */) { 
-            //Features.EXT_DEPTH不支持的话不会到这一块
+        if(showPointClouds){ //绘制点云到画布
+            if(viewer.useEDL) {  //设置edlMaterial
+                //Features.EXT_DEPTH不支持的话不会到这一块
+                 
+                const uniforms = this.edlMaterial.uniforms;
+                //if(viewer.useEDL){
+                    /* uniforms.screenWidth.value = width;
+                    uniforms.screenHeight.value = height; */
+                    uniforms.resolution.value.copy(resolution)
+                    
+                    uniforms.edlStrength.value = viewer.edlStrength;
+                    uniforms.radius.value = viewer.edlRadius;
+                    uniforms.useEDL.value = 1;//add
+                /* }else{
+                    uniforms.useEDL.value = 0;//add
+                } */
+                
+                let proj = camera.projectionMatrix;
+                let projArray = new Float32Array(16);
+                projArray.set(proj.elements);
+                uniforms.uProj.value = projArray;
              
-			const uniforms = this.edlMaterial.uniforms;
-            //if(viewer.useEDL){
-                /* uniforms.screenWidth.value = width;
-                uniforms.screenHeight.value = height; */
-                uniforms.resolution.value.copy(resolution)
+                uniforms.uEDLColor.value = (params.rtEDL || this.getRtEDL(params.viewport)).texture;
+                //uniforms.uEDLDepth.value = (params.rtEDL || this.getRtEDL(params.viewport)).depthTexture; //其实没用到
+                 
+                uniforms.opacity.value = viewer.edlOpacity; // HACK
+                 
                 
-                uniforms.edlStrength.value = viewer.edlStrength;
-                uniforms.radius.value = viewer.edlRadius;
-                uniforms.useEDL.value = 1;//add
-            /* }else{
-                uniforms.useEDL.value = 0;//add
-            } */
-            
-            let proj = camera.projectionMatrix;
-            let projArray = new Float32Array(16);
-            projArray.set(proj.elements);
-            uniforms.uProj.value = projArray;
-		 
-			uniforms.uEDLColor.value = (params.rtEDL || this.getRtEDL(params.viewport)).texture;
-			//uniforms.uEDLDepth.value = (params.rtEDL || this.getRtEDL(params.viewport)).depthTexture; //其实没用到
-			 
-			uniforms.opacity.value = viewer.edlOpacity; // HACK
-			 
-			
-            Utils.screenPass.render(viewer.renderer, this.edlMaterial, params.target);
-		}else{
-            //渲染点云 (直接用rtEDL上的会失去抗锯齿)
-            viewer.pRenderer.render(viewer.scene.scenePointCloud, camera, null , {
-                shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
-                clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume))
-            });  
-        }   
+                Utils.screenPass.render(viewer.renderer, this.edlMaterial, params.target);
+            }else{
+                //渲染点云 (直接用rtEDL上的会失去抗锯齿)
+                
+                let prop = {
+                    shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
+                    clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)) ,
+                    notAdditiveBlending: Potree.settings.notAdditiveBlending//add 否则透明的点云会挡住后面的模型。 加上这句后竟然透明不会叠加了!
+                }
+                 
+                viewer.pRenderer.render(viewer.scene.scenePointCloud, camera, null , prop);  
+            }
+        }        
           
 		//viewer.dispatchEvent({type: "render.pass.end",viewer: viewer});
-        viewer.scene.pointclouds.forEach(e=>{
+        visiblePointClouds2.forEach(e=>{
             e.visible = e.oldVisi
         })
         

+ 6 - 2
src/viewer/Scene.js

@@ -16,7 +16,10 @@ export class Scene extends THREE.EventDispatcher{
 		this.scene = new THREE.Scene();
         //this.sceneBG = new THREE.Scene(); //用来放skybox
         //this.sceneOverlay = new THREE.Scene();  
+         
 		this.scenePointCloud = new THREE.Scene();
+        
+        
 
 		this.cameraP = new THREE.PerspectiveCamera(this.fov, 1, Potree.config.view.near, Potree.config.view.near);
 		this.cameraO = new THREE.OrthographicCamera(-1, 1, 1, -1, Potree.config.view.near, Potree.settings.cameraFar);
@@ -59,7 +62,8 @@ export class Scene extends THREE.EventDispatcher{
         viewer.setObjectLayers(this.axisArrow,  'bothMapAndScene' )
         
         
-        
+        this.tags = new THREE.Object3D;
+        this.scene.add(this.tags)
 
 	}
 
@@ -426,7 +430,7 @@ export class Scene extends THREE.EventDispatcher{
         light3.position.set( 10, 10, 10 );
 		light3.lookAt( new THREE.Vector3(0, 0, 0));
         viewer.setObjectLayers(light3, 'bothMapAndScene')
-        this.scene.add(light3)
+        this.scene.add(light3)  
         //--------------------------------------------
 
 		{ // background

+ 30 - 23
src/viewer/View.js

@@ -150,20 +150,23 @@ export class View extends THREE.EventDispatcher{
     pan (x, y) { //发现pan其实就是translate
 		this.translate(x, 0, y)
 	}
-	translate (x, y, z) {
+	translate (x, y, z, forceHorizon ) {
+        //相机方向
 		let dir = new THREE.Vector3(0, 1, 0);
-		dir.applyAxisAngle(new THREE.Vector3(1, 0, 0), this.pitch);
-		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);
+		dir.applyAxisAngle(new THREE.Vector3(1, 0, 0), forceHorizon ? 0 : this.pitch); //上下角度
+		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);//水平角度
 
 		let side = new THREE.Vector3(1, 0, 0);
-		side.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);
-
-		let up = side.clone().cross(dir);
+		side.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);  //垂直于相机当前水平朝向的 左右方向
 
-		let t = side.multiplyScalar(x)
-			.add(dir.multiplyScalar(y))
-			.add(up.multiplyScalar(z));
+		let up = side.clone().cross(dir); //垂直于相机当前水平朝向的 向上方向
 
+		let t = side.multiplyScalar(x)   //x影响 左右分量
+			.add(dir.multiplyScalar(y))  //y影响 前后分量
+			.add(up.multiplyScalar(z));  //z影响 上下分量
+             
+        
+        
 		this.position = this.position.add(t);
         this.restrictPos()
 	}
@@ -249,15 +252,9 @@ export class View extends THREE.EventDispatcher{
         }
         
         let endPosition = new THREE.Vector3().copy(info.position)
-	 
-        const startPosition = this.position.clone();
-		const startTarget = this.getPivot();
-        let startQuaternion = math.getQuaFromPosAim(startPosition,startTarget)
-        
-		let endTarget = null, endQuaternion ;
-        
-        
-        
+        let startPosition = this.position.clone();
+		let startQuaternion, endQuaternion, endTarget = null ;
+         
         
 		if(info.target ){
 			endTarget = new THREE.Vector3().copy(info.target)  
@@ -266,7 +263,14 @@ export class View extends THREE.EventDispatcher{
             endQuaternion = info.quaternion.clone()
         }
          
-         
+        if(endQuaternion){
+            startQuaternion = new THREE.Quaternion().setFromEuler(this.rotation)
+            /*  const startTarget = this.getPivot();
+            let startQuaternion = math.getQuaFromPosAim(startPosition,startTarget) */
+            
+        }
+        
+        
 		if(!info.duration){
 			this.position.copy(endPosition);
             this.restrictPos()
@@ -309,11 +313,14 @@ export class View extends THREE.EventDispatcher{
         if(info.bound){//需要修改boundSize以适应相机的旋转,当相机不在xy水平面上朝向z时
             endPosition = endPosition || info.bound.getCenter(new THREE.Vector3())
             
-            let matrixRot = new THREE.Matrix4().makeRotationFromEuler(this.rotation) 
-            matrixRot.getInverse(matrixRot)  
+            let matrixRot = new THREE.Matrix4().makeRotationFromEuler(this.rotation).invert() 
             let boundingBox = info.bound.clone().applyMatrix4(matrixRot) 
             boundSize = boundingBox.getSize(new THREE.Vector3())
             
+        }           
+        
+        if(boundSize && boundSize.x == 0 && boundSize.y == 0){
+            boundSize.set(1,1)  //避免infinity
         }
         
         this.setView({ position:endPosition,  duration, 
@@ -328,10 +335,10 @@ export class View extends THREE.EventDispatcher{
                         
                         if(camera.aspect > aspect){//视野更宽则用bound的纵向来决定
                             h = boundSize.y 
-                            endZoom = (viewport.resolution.y - margin.x) / h    //注意,要在resolution不为0时执行 
+                            endZoom = (viewport.resolution.y - margin.y) / h    //注意,要在resolution不为0时执行 
                         }else{
                             w = boundSize.x;  
-                            endZoom = (viewport.resolution.x - margin.y) / w
+                            endZoom = (viewport.resolution.x - margin.x) / w
                         }  
                         //onUpdate时更新endzoom是因为画布大小可能更改
                     }  

+ 6 - 6
src/viewer/map/Map.js

@@ -638,14 +638,14 @@ export class TiledMapFromEntity extends TiledMapBase{
         data.id = e.id
         data.globalLocation = Potree.Utils.VectorFactory.fromArray3(e.location),
         data.orientation = Potree.Utils.QuaternionFactory.fromArray(e.orientation) 
-        if(Potree.fileServer){
-            data.filePath = `${Potree.settings.urls.prefix}${e.file_path}`
-        }else{
-            data.filePath = `${Potree.settings.urls.prefix}/data/${Potree.settings.number}/${e.file_path}`
-        }
+        //if(Potree.fileServer){
+            data.filePath = `${Potree.settings.urls.prefix1}${e.file_path}`
+        //}else{
+        //    data.filePath = `${Potree.settings.urls.prefix}/data/${Potree.settings.number}/${e.file_path}`
+        //}
          
         //if(!data.filePath.includes('building_1'))data.filePath = data.filePath.replace('building','building_1')//暂时
-        data.fileName = '$DEPTH/$X/$Y.png',//e.file_name,
+        data.fileName = '$DEPTH/$X/$Y.png' //e.file_name,
         data.type = e.type,
         data.mapSizeM = e.map_size_m,
         data.tileSizePx = e.tile_size_px,

+ 1 - 1
src/viewer/map/MapViewer.js

@@ -72,7 +72,7 @@ export class MapViewer extends ViewerBase{
         
         this.addEventListener('global_click',(e)=>{
             if(!e.isTouch && e.button != THREE.MOUSE.LEFT)return
-            this.updateClosestPano(e.intersectPoint)
+            this.updateClosestPano(e.intersect)
         })
         
         

+ 30 - 4
src/viewer/sidebar.js

@@ -50,6 +50,9 @@ export class Sidebar{
 	init(){
         if(Potree.settings.editType == 'merge'){
             this.initMergeBar() 
+            this.initToolbar();
+            this.initScene();
+            this.initNavigation();
         }else{
             this.initAccordion();
             this.initAppearance();
@@ -137,25 +140,28 @@ export class Sidebar{
     initMergeBar(){//多元融合模块
         var pannel = $('#mergeModel');
         var buttons = pannel.find('button')
-        
+        let MergeEditor = viewer.modules.MergeEditor
         let loading = false;
         
-        pannel.find('li button').on('click',(e)=>{
+        pannel.find('ul[name="model"] li button').on('click',(e)=>{
             if(loading)return console.log('还在加载', loading)
             let $elem = $(e.target)
             
                 
             let parent = $elem.parent()
-            let name = parent.attr('name')
+            let name = parent.attr('name') 
             
             if($elem.attr('name') == 'select'){ 
                 return Potree.selectModel(name)
             }
             
-            if($elem.text() == '添加'){ 
+            if($elem.text() == '添加'){
+                let startTime = Date.now()
                 Potree.addModel(name,()=>{
                     loading = false 
                     $elem.text('删除')
+                    let now = Date.now()
+                    console.log('加载完毕', name, '用时', (now-startTime)/1000, 's')
                 })
                 loading = name
                 
@@ -166,6 +172,26 @@ export class Sidebar{
             
             
         })
+        
+        pannel.find('li button[name="splitScreen"]').on('click',(e)=>{
+            let $elem = $(e.target)
+            if($elem.text() == '分屏'){
+                $elem.text('恢复') 
+                MergeEditor.enterSplit()
+            }else{
+                $elem.text('分屏') 
+                MergeEditor.leaveSplit()
+            }
+        
+        })
+        
+        let addingTag = false
+        pannel.find('li button[name="tag"]').on('click',(e)=>{
+            let $elem = $(e.target)
+             
+            viewer.tagTool.startInsertion()
+        })
+        
     }
     
     

File diff suppressed because it is too large
+ 580 - 194
src/viewer/viewer.js


+ 61 - 1
改bug的历史.txt

@@ -1,8 +1,68 @@
 
 
 
-2022.7.22
 
+
+
+
+
+2022.8.30
+
+融合页面的分屏,为何在多次缩放后易卡顿?  
+
+
+为什么加载点云时,在right设置为active=false时无法渲染?删除点云也不行。但是此时将top.width设置为1,也就是禁止了scissorTest,就没问题
+且reticule所在之处能刷新画面。
+
+
+
+2022.8.22
+
+1
+glb 显示出的颜色比图片原本的颜色暗,但换成颜色没问题, obj图没问题。
+
+自己创建个mesh贴上glb的贴图也是暗。 可是官方编辑器glb没问题。 
+换DepthBasicMaterial可以  why?
+
+最后解决方案:自己写了个简单的BasicMaterial, 只贴贴图
+
+
+
+
+2 glb 大文件经常加载不成功
+
+在本地demo未出现。  Content-Type: application/octet-stream 这和本地加载不同:Content-Type: model/gltf-binary
+
+DOMException: The source image could not be decoded.
+
+
+将 CanvasTextrue ImageBitmapLoader 换成普通的texture后, 又容易崩溃。
+
+为什么要用CanvasTextrue 呢?我直接换成普通Texture了
+
+
+
+
+
+
+
+
+2022.7.22
+闪烁的原因?  之前出现过警告且黑屏的情况,但鼠标滑动后恢复
+[.WebGL-0000074204D3AA00] GL_INVALID_OPERATION: Must have element array buffer bound.
+
+延迟过渡或不修改geo时也会闪烁 
+在外侧 感觉有碎片,碎片是因为修改了深度的原因,但是去掉一样闪烁。
+hasDepthTex明明会改变材质的样子,但是漫游时没感觉?好像因为cameraHeight1不同 vWorldPosition0
+
+在非当前geo的点位下 bump就能有碎片感 why 似乎是从外侧就会 
+放大后还是闪烁,但改善一些。加延迟再好一些。  
+似乎在出门时容易闪烁 
+改为球体 不换geo 也闪烁 
+改为cube 不换不闪 换也闪  
+去掉camera_changed发送就可以了!! 
+今天不闪烁了 但是有时候很卡 (莫名其妙)   
+ 
 似乎会出 贴图碎片加载。 黑块
 
 又警告[.WebGL-000020FC00B72900] GL_INVALID_OPERATION: Must have element array buffer bound.