Browse Source

fix: dataset color

xzw 3 năm trước cách đây
mục cha
commit
3c5a568750
98 tập tin đã thay đổi với 4136 bổ sung2620 xóa
  1. BIN
      build/potree/resources/textures/icon-explode.png
  2. BIN
      build/potree/resources/textures/icon-fire.png
  3. BIN
      build/potree/resources/textures/icon-smoke.png
  4. BIN
      resources/textures/icon-explode.png
  5. BIN
      resources/textures/icon-fire.png
  6. BIN
      resources/textures/icon-smoke.png
  7. 3 3
      src/Actions.js
  8. 2 3
      src/Annotation.js
  9. 14 7
      src/EventDispatcher.js
  10. 14 2
      src/Features.js
  11. 2 1
      src/KeyCodes.js
  12. 44 18
      src/PointCloudOctree.js
  13. 3 4
      src/PointCloudOctreeGeometry.js
  14. 2 4
      src/PointCloudTree.js
  15. 34 19
      src/Potree.js
  16. 1 1
      src/PotreeRenderer.js
  17. 14 9
      src/materials/DepthBasicMaterial.js
  18. 9 6
      src/materials/shaders/depthBasic.fs
  19. 13 4
      src/materials/shaders/pointcloud.vs
  20. 386 107
      src/modules/CameraAnimation/CameraAnimation.js
  21. 240 120
      src/modules/Images360/Images360.js
  22. 112 57
      src/modules/Images360/Panorama.js
  23. 42 29
      src/modules/Images360/tile/PanoRenderer.js
  24. 19 27
      src/modules/Images360/tile/TileDownloader.js
  25. 1 0
      src/modules/Images360/tile/TileUtils.js
  26. 1 2
      src/modules/OrientedImages/OrientedImageControls.js
  27. 2 3
      src/modules/OrientedImages/OrientedImages.js
  28. 137 13
      src/modules/clipModel/Clip.js
  29. 18 6
      src/modules/datasetAlignment/Alignment.js
  30. 60 20
      src/modules/siteModel/BuildingBox.js
  31. 458 191
      src/modules/siteModel/SiteModel.js
  32. 2 3
      src/navigation/DeviceOrientationControls.js
  33. 2 3
      src/navigation/EarthControls.js
  34. 8 8
      src/navigation/FirstPersonControls.js
  35. 63 60
      src/navigation/InputHandler.js
  36. 9 3
      src/navigation/OrbitControls.js
  37. 2 3
      src/navigation/VRControls.js
  38. 2 3
      src/objects/Label.js
  39. 13 4
      src/objects/Magnifier.js
  40. 5 5
      src/objects/Reticule.js
  41. 13 4
      src/objects/Sprite.js
  42. 0 100
      src/objects/fireParticle/explode.html
  43. 399 273
      src/objects/fireParticle/explode/ExplodeParticle.js
  44. 4 3
      src/objects/fireParticle/explode/Particle.js
  45. 0 26
      src/objects/fireParticle/explode/Tween.js
  46. 0 1
      src/objects/fireParticle/explode/shader.js
  47. 0 96
      src/objects/fireParticle/fire.html
  48. 98 35
      src/objects/fireParticle/fire/FireParticle.js
  49. 2 5
      src/objects/fireParticle/fire/shader.js
  50. BIN
      src/objects/fireParticle/images/checkerboard.jpg
  51. BIN
      src/objects/fireParticle/images/circle-particle.png
  52. BIN
      src/objects/fireParticle/images/explode.png
  53. BIN
      src/objects/fireParticle/images/explode1.png
  54. BIN
      src/objects/fireParticle/images/groundcolor.jpg
  55. BIN
      src/objects/fireParticle/images/groundnormal.jpg
  56. BIN
      src/objects/fireParticle/images/plane.png
  57. BIN
      src/objects/fireParticle/images/smoke (1).png
  58. BIN
      src/objects/fireParticle/images/smoke.png
  59. BIN
      src/objects/fireParticle/images/smokeparticle.png
  60. 0 87
      src/objects/fireParticle/smoke.html
  61. 1 1
      src/objects/fireParticle/smoke/Particle.js
  62. 241 100
      src/objects/fireParticle/smoke/SmokeParticle.js
  63. 0 26
      src/objects/fireParticle/smoke/Tween.js
  64. 2 3
      src/objects/tool/AnnotationTool.js
  65. 2 3
      src/objects/tool/ClippingTool.js
  66. 101 129
      src/objects/tool/CurveCtrl.js
  67. 78 0
      src/objects/tool/HandleSprite.js
  68. 155 0
      src/objects/tool/HandleSvg.js
  69. 27 18
      src/objects/tool/Measure.js
  70. 23 17
      src/objects/tool/MeasuringTool.js
  71. 2 3
      src/objects/tool/ProfileTool.js
  72. 2 3
      src/objects/tool/ScreenBoxSelectTool.js
  73. 2 2
      src/objects/tool/TransformationTool.js
  74. 2 3
      src/objects/tool/VolumeTool.js
  75. 67 44
      src/objects/tool/ctrlPolygon.js
  76. 17 7
      src/objects/tool/mapClipBox.js
  77. 38 11
      src/settings.js
  78. 42 31
      src/start.js
  79. 64 6
      src/utils.js
  80. 18 18
      src/utils/Common.js
  81. 4 1
      src/utils/CursorDeal.js
  82. 23 39
      src/utils/DrawUtil.js
  83. 0 7
      src/utils/MathLight.js
  84. 130 70
      src/utils/SplitScreen.js
  85. 2 2
      src/utils/cameraLight.js
  86. 25 68
      src/utils/math.js
  87. 209 205
      src/utils/transitions.js
  88. 4 4
      src/viewer/EDLRenderer.js
  89. 1 0
      src/viewer/PropertyPanels/CameraAnimationPanel.js
  90. 3 4
      src/viewer/Scene.js
  91. 45 107
      src/viewer/View.js
  92. 39 22
      src/viewer/map/Map.js
  93. 108 58
      src/viewer/map/MapViewer.js
  94. 2 3
      src/viewer/profile.js
  95. 5 1
      src/viewer/sidebar.html
  96. 13 7
      src/viewer/sidebar.js
  97. 281 173
      src/viewer/viewer.js
  98. 105 46
      src/viewer/viewerBase.js

BIN
build/potree/resources/textures/icon-explode.png


BIN
build/potree/resources/textures/icon-fire.png


BIN
build/potree/resources/textures/icon-smoke.png


BIN
resources/textures/icon-explode.png


BIN
resources/textures/icon-fire.png


BIN
resources/textures/icon-smoke.png


+ 3 - 3
src/Actions.js

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

+ 2 - 3
src/Annotation.js

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

+ 14 - 7
src/EventDispatcher.js

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

+ 14 - 2
src/Features.js

@@ -1,4 +1,8 @@
 
+import browser from './utils/browser'
+
+
+
 let ftCanvas = document.createElement('canvas');
 
 export const Features = (function () {
@@ -69,8 +73,16 @@ export const Features = (function () {
 		},
         //add:
         EXT_DEPTH:{
-            isSupported: function () {
-                return gl.getExtension('EXT_frag_depth');
+            isSupported: function () { 
+                if(browser.detectIOS()){
+                    let {major,minor,patch} = browser.iosVersion()
+                    if(major == 15 && minor == 4 && patch == 1){
+                        console.warn('检测到是ios15.4.1, 关闭EXT_frag_depth')//该版本ext_depth有问题,导致clear错乱。没有解决办法先关闭。
+                        return false
+                    }
+                }
+
+                return  gl.getExtension('EXT_frag_depth');
             }
         },
         

+ 2 - 1
src/KeyCodes.js

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

+ 44 - 18
src/PointCloudOctree.js

@@ -223,16 +223,16 @@ export class PointCloudOctree extends PointCloudTree {
         
         
         
-        this.pcoGeometry.on('updateNodeMaxLevel', this.updateNodeMaxLevel.bind(this))
+        this.pcoGeometry.addEventListener('updateNodeMaxLevel', this.updateNodeMaxLevel.bind(this))
         
 	}
 
 
-    updateNodeMaxLevel(level){//目前点云包含node的最高level
-        var level = Math.max(level, this.nodeMaxLevel)
+    updateNodeMaxLevel(e){//目前点云包含node的最高level
+        var level = Math.max(e.level, this.nodeMaxLevel)
         if(level != this.nodeMaxLevel){
             this.nodeMaxLevel = level 
-            viewer.emit('updateNodeMaxLevel',  this, level) 
+            viewer.dispatchEvent({type:'updateNodeMaxLevel', pointcloud: this, nodeMaxLevel:level}) 
         }
     }
 
@@ -1384,7 +1384,7 @@ export class PointCloudOctree extends PointCloudTree {
 	 *
 	 */
 	pick(viewer, viewport, camera, ray, params = {}){
-
+  
 		let renderer = viewer.renderer;
 		let pRenderer = viewer.pRenderer;
 
@@ -1827,7 +1827,7 @@ export class PointCloudOctree extends PointCloudTree {
         }else{
             let base = this.material.spacing / Math.pow(2, this.nodeMaxLevel) //点云大小在level为0时设置为spacing,每长一级,大小就除以2
             
-            this.material.size = base * 4 * num /* * window.devicePixelRatio  */
+            this.material.size = base * 5 * num /* * window.devicePixelRatio  */
         }
         
         
@@ -1839,7 +1839,7 @@ export class PointCloudOctree extends PointCloudTree {
     
     
     // 设置点透明度
-    changePointOpacity(num) {
+    changePointOpacity(num, canMoreThanOne) {
         //num:0-1   navvis用的是亮度
         if (num == void 0) {
             num = this.temp.pointOpacity
@@ -1854,32 +1854,58 @@ export class PointCloudOctree extends PointCloudTree {
                 let base = this.material.spacing / Math.pow(1.4, this.maxLevel) //随着level提高,点云重叠几率增多
                 let minBase = this.material.spacing / Math.pow(1.4, this.nodeMaxLevel)
                 let ratio = Math.min(1 / base, 1 / minBase / 3) //ratio为一个能使opacity不大于1 的 乘量,minBase要除以一个数,该数调越大减弱效果越强。level越低opacity和面板越接,level越高效果越弱,以减免过度重叠后的亮度。
-                this.material.opacity = THREE.Math.clamp(base * ratio * num, 0, 0.999) //到1就不透明了(可能出现一段一样)
+                this.material.opacity = base * ratio * num
+                if(!canMoreThanOne){
+                    this.material.opacity = THREE.Math.clamp(this.material.opacity, 0, 0.999) //到1就不透明了(可能出现一段一样)
+                }
+            
             }else{
                 let base = this.material.spacing / Math.pow(1.8, this.maxLevel) 
                 let minBase = this.material.spacing / Math.pow(1.8, this.nodeMaxLevel)
                 //console.log(1 / base, 1 / minBase / 6)
                 let ratio = Math.min(1 / base, 1 / minBase / 6) //ratio为一个能使opacity不大于1 的 乘量,minBase要除以一个数,该数调越大减弱效果越强。level越低opacity和面板越接,level越高效果越弱,以减免过度重叠后的亮度。
-                this.material.opacity = THREE.Math.clamp(base * ratio * num, 0, 0.999) //到1就不透明了(可能出现一段一样)
-                
+                this.material.opacity = base * ratio * num
+                if(!canMoreThanOne){
+                    this.material.opacity = THREE.Math.clamp(this.material.opacity, 0, 0.999) //到1就不透明了(可能出现一段一样)
+                }
             }
             //缺点:防止颜色过亮主要是相机离远时,当在漫游点处由于离点云太近,可能会导致高质量点云看起来很暗。
         }
         //console.log('changePointOpacity ' + this.dataset_id + ', num : ' + num + ' , opacity : ' + this.material.opacity) //检查是否做到了低质量时num==opacity,中质量opacity稍小于num,高质量更小
          
     } 
+
+
+
+    updateBound(){
+        var boundingBox_ = this.pcoGeometry.tightBoundingBox.clone().applyMatrix4(this.matrixWorld)
+        this.bound = boundingBox_
+    }
     
     
-    
-    getUnrotBoundPoint(){//获取没有旋转的tightBounding的水平四个点 
+    getUnrotBoundPoint(type){//获取没有旋转的tightBounding的水平四个点
+        //如果alighment支持任意轴旋转,水平面上看到的可能就是六边形了,失去意义,到时候不能用这个。也可以若只绕z旋转, 使用tightBoundingBox,否则bound
         let bound = this.pcoGeometry.tightBoundingBox 
-        return [new THREE.Vector3(bound.min.x, bound.min.y,0),
-            new THREE.Vector3(bound.max.x, bound.min.y,0),
-            new THREE.Vector3(bound.max.x, bound.max.y,0),
-            new THREE.Vector3(bound.min.x, bound.max.y,0),
-        ].map(e=>e.applyMatrix4(this.matrixWorld)) 
+
+        if(type == 'all'){
+            return [new THREE.Vector3(bound.min.x, bound.min.y, bound.min.z),
+                new THREE.Vector3(bound.max.x, bound.min.y, bound.min.z),
+                new THREE.Vector3(bound.max.x, bound.max.y,bound.min.z),
+                new THREE.Vector3(bound.min.x, bound.max.y,bound.min.z),
+                new THREE.Vector3(bound.min.x, bound.min.y, bound.max.z),
+                new THREE.Vector3(bound.max.x, bound.min.y, bound.max.z),
+                new THREE.Vector3(bound.max.x, bound.max.y,bound.max.z),
+                new THREE.Vector3(bound.min.x, bound.max.y,bound.max.z),
+            ].map(e=>e.applyMatrix4(this.matrixWorld)) 
+
+        }else 
+            return [new THREE.Vector3(bound.min.x, bound.min.y,0),
+                new THREE.Vector3(bound.max.x, bound.min.y,0),
+                new THREE.Vector3(bound.max.x, bound.max.y,0),
+                new THREE.Vector3(bound.min.x, bound.max.y,0),
+            ].map(e=>e.applyMatrix4(this.matrixWorld)) 
     }
-    
+     
     
     //数据集的显示影响到其下的:点云、marker  .不会影响地图上的显示
     

+ 3 - 4
src/PointCloudOctreeGeometry.js

@@ -1,11 +1,10 @@
-
-import { EventDispatcher } from "./EventDispatcher.js";
+ 
 import * as THREE from "../libs/three.js/build/three.module.js";
 import {PointCloudTreeNode} from "./PointCloudTree.js";
 import {XHRFactory} from "./XHRFactory.js";
 import {Utils} from "./utils.js";
 
-export class PointCloudOctreeGeometry extends EventDispatcher{
+export class PointCloudOctreeGeometry extends THREE.EventDispatcher{
 
 	constructor(){
         super()
@@ -200,7 +199,7 @@ export class PointCloudOctreeGeometryNode extends PointCloudTreeNode{
 				let parentName = name.substring(0, name.length - 1);
 				let parentNode = nodes[parentName];
 				let level = name.length - 1;
-                pco.emit('updateNodeMaxLevel',level);//add
+                pco.dispatchEvent({type:'updateNodeMaxLevel',level});//add
                 
 				let boundingBox = Utils.createChildAABB(parentNode.boundingBox, index);
 

+ 2 - 4
src/PointCloudTree.js

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

+ 34 - 19
src/Potree.js

@@ -1,3 +1,8 @@
+export {config, settings} from "./settings.js";
+export {start} from "./start.js";
+
+
+ 
 
 export * from "./Actions.js";
 export * from "./AnimationPath.js";
@@ -78,8 +83,6 @@ export {VRControls} from "./navigation/VRControls.js";
 
 //add:
 export {Alignment} from "./modules/datasetAlignment/Alignment.js";
-export {config, settings} from "./settings.js";
-export {start} from "./start.js";
 
 
 
@@ -146,7 +149,7 @@ export {scriptPath, resourcePath};
 
 export async function loadFile(path, callback){
     if(Potree.fileServer){
-        Potree.fileServer.get(path).then(data=>{
+        Potree.fileServer.get(path).then(data=>{ 
             callback && callback(data)
         })
     }else{
@@ -165,41 +168,51 @@ export async function loadDatasets(callback){//之后直接把path写进来
     if(Potree.fileServer){
         path = `/laser/dataset/${Potree.settings.number}/getDataSet` 
     }else{
-        path = `https://${Potree.config.urls.prefix2}/indoor/${Potree.settings.number}/api/datasets`
+        path = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/datasets`
           
     }
     return loadFile(path, callback)
     
 }
-export async function loadMapEntity(){
-    if((!Potree.settings.floorplanType || !Potree.settings.floorplanEnable) && Potree.fileServer)return /* 等待平面图类型定义好会加载 */
-    let pointcloudNum = viewer.scene.pointclouds.length
+
+
+//目前上传平面图后如果不点击保存按钮,数据还是旧的不生效
+export async function loadMapEntity(datasetId, force){ 
+    if(!Potree.settings.floorplanEnable && !force && Potree.fileServer  )return /* 等待平面图类型定义好会加载 */
+     
     let loaded = 0
     
-    let callback = (datasetId, data )=>{
-        var map = viewer.mapViewer.mapLayer.maps.find(e => e.name == 'floorplan_'+ datasetId)
+    let needLoads = datasetId == 'all' ? viewer.scene.pointclouds.map(e=>e.dataset_id) : [datasetId]
+    
+    
+    let callback = (dataset_id, floorplanType, data  )=>{
+        //要防止旧的比新的先获取到导致覆盖新的,因为两种type随时可能切换
+        if(floorplanType != Potree.settings.floorplanType[dataset_id]) return //如果请求的floorplanType不是当前最新的floorplanType就返回
+        
+        var map = viewer.mapViewer.mapLayer.maps.find(e => e.name == 'floorplan_'+ dataset_id)
         if(map){
             viewer.mapViewer.mapLayer.removeMap(map)
         }else{
             
         }
         
-        var map = viewer.mapViewer.mapLayer.addMapEntity(data.data || data )
-        map.name += "_"+ datasetId
-        loaded ++;
-        
+        var map = viewer.mapViewer.mapLayer.addMapEntity(data.data || data,  dataset_id)
         
+        loaded ++; 
     }
     
-    viewer.scene.pointclouds.forEach(e=>{
+    needLoads.forEach(dataset_id=>{
+        let floorplanType = Potree.settings.floorplanType[dataset_id]
+        if(!floorplanType)return
         var path 
         if(Potree.fileServer){ 
-            path = `/laser/tiledMap/${Potree.settings.number}/tiledMap/${Potree.settings.floorplanType}/${e.dataset_id}` 
+            path = `/laser/tiledMap/${Potree.settings.number}/tiledMap/${floorplanType}/${dataset_id}` 
         }else{
-            path = `https://${Potree.config.urls.prefix2}/indoor/${Potree.settings.number}/api/tiled_maps`
+            path = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/tiled_maps`
             
         }
-        return loadFile(path, callback.bind(this, e.dataset_id)  )
+        Potree.settings.floorplanRequests[dataset_id] = true //开始加载了
+        return loadFile(path, callback.bind(this,  dataset_id, floorplanType)  )
     })
     
      
@@ -212,7 +225,7 @@ export async function loadPanos(datasetId, callback){
     if(Potree.fileServer){
         path = `/laser/filter/${Potree.settings.number}/query` + query
     }else{ 
-        path = `https://${Potree.config.urls.prefix2}/indoor/${Potree.settings.number}/api/images/filter` + query
+        path = `${Potree.settings.urls.prefix2}/indoor/${Potree.settings.number}/api/images/filter` + query
              
     }
     return loadFile(path, callback) 
@@ -277,9 +290,11 @@ export function Log(value, color, fontSize){
 
  
 
-export function loadPointCloud(path, name, timeStamp, callback){
+export function loadPointCloud(path, name, sceneCode, timeStamp, callback){
 	let loaded = function(e){
 		e.pointcloud.name = name;
+        e.pointcloud.sceneCode = sceneCode //对应4dkk的场景码
+        
 		callback(e);
 	};
 

+ 1 - 1
src/PotreeRenderer.js

@@ -233,7 +233,7 @@ class Shader {
             
             if(  !gl.isProgram(this.program  )){//创建失败  开启多个页面可能会,原因是webglcontextlost
                 //console.error('创建program失败');
-                viewer.emit('webglError', 'potreeRenderer创建program失败')
+                viewer.dispatchEvent('webglError', {msg: 'potreeRenderer创建program失败'})
                 console.log(this.vs)
                 console.log(this.fs)
                 

+ 14 - 9
src/materials/DepthBasicMaterial.js

@@ -2,9 +2,9 @@
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Shaders} from "../../build/shaders/shaders.js";
 import {Features} from "../Features.js";
-
+    
  
-export default class DepthBasicMaterial extends THREE.ShaderMaterial{
+export default class DepthBasicMaterial extends THREE.ShaderMaterial{ 
     constructor(o={}){
         let {width, height} = viewer.renderer.getSize(new THREE.Vector2());
         
@@ -16,12 +16,17 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
 			depthTexture:   { type: 't', 	value: null }, 
 			opacity:        { type: 'f',	value: o.opacity == void 0 ? 1 : o.opacity },
 			map:             { type: 't', 	value: o.map }, 
-            baseColor:     {type:'v3',      value: o.color ?  new THREE.Color(o.color) :  new THREE.Color("#ffffff"),
-             
-		}};  
+            baseColor:     {type:'v3',      value: o.color ?  new THREE.Color(o.color) :  new THREE.Color("#ffffff")},
+            backColor:     {type:'v3',      value: o.backColor ?  new THREE.Color(o.backColor) :  new THREE.Color("#ddd")},
+            clipDistance :     { type: 'f', 	value:o.clipDistance || 4}, //消失距离
+            occlusionDistance :     { type: 'f', 	value: o.occlusionDistance || 1 }, //变为backColor距离
+            maxClipFactor :  { type: 'f', 	value: o.maxClipFactor || 1 },  //0-1
+      
+
+		}  
         
         let defines = {};
-        if(o.useDepth)defines.useDepth = ''
+        if(o.useDepth && Features.EXT_DEPTH.isSupported())defines.useDepth = ''
         if(o.map)defines.use_map = ''
          
         super({ 
@@ -35,7 +40,7 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
             defines, 
         } )
         
-        if(o.useDepth) this.useDepth_ = true
+        if(o.useDepth && Features.EXT_DEPTH.isSupported()) this.useDepth_ = true
         
         
         let setSize = (e)=>{//如果出现横条状的异常,往往是viewportOffset出错 
@@ -59,7 +64,7 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
         })  
     
         
-        if(Features.EXT_DEPTH.isSupported()){  
+        if(this.useDepth){  
             
             viewer.addEventListener('camera_changed', (e)=>{
                 if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) 
@@ -96,7 +101,7 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
     
     set useDepth(value){//如果不支持 EXT_DEPTH 的话会失效
         if(this.useDepth_ != value){
-            if(value){
+            if(value && Features.EXT_DEPTH.isSupported()){
                 this.defines.useDepth = ''
                 this.updateDepthParams()
             }else{

+ 9 - 6
src/materials/shaders/depthBasic.fs

@@ -2,6 +2,10 @@ varying vec2 vUv;
 uniform float opacity;
 
 uniform vec3 baseColor;
+uniform vec3 backColor;
+uniform float occlusionDistance;
+uniform float clipDistance;
+uniform float maxClipFactor;
 
 
 #if defined use_map
@@ -51,18 +55,17 @@ void main() {
         float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
 
         // The difference between the two depths
-        float delta = textureDepth - fragDepth;
+        float delta =  fragDepth - textureDepth;
 
-        if (delta < 0.0)//差距
+        if (delta > 0.0)//差距
         {
             // occlusionDistance and clipDistance define the width of the respective zones and
             // mixFactor and clipFactor express the interpolation between the two colors depending on the position
             // of the current fragment withing those zones.
             
-            float occlusionDistance = - 1.0; //1米
-            float clipDistance = - 4.0;
+            
             mixFactor = clamp(delta / occlusionDistance, 0.0, 1.0);
-            clipFactor = clamp(delta / clipDistance, 0.0, 1.0);
+            clipFactor = clamp(delta / clipDistance, 0.0, maxClipFactor);
         }
         
         // If the fragment is totally transparent, don't bother drawing it
@@ -75,8 +78,8 @@ void main() {
                 color = texture2D(map, vUv) * color; 
             #endif
            
-            vec3 backColor = vec3(0.8,0.8,0.8); 
             
+             
             color = vec4(mix(color.rgb, backColor, mixFactor), color.a * (1.0 - clipFactor));
         }
          

+ 13 - 4
src/materials/shaders/pointcloud.vs

@@ -938,12 +938,14 @@ vec3 transformAxis( vec3 direction ) //navvis->4dkk
 }
 
 void main() {
-    
+    bool filtered_by_normal = false; 
+
     #ifdef use_filter_by_normal
         if(abs(getNormal().z) > 0.3) { //ufilterByNormalThreshold 暂定0.3
 			// Move point outside clip space space to discard it.
-			gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
-            return;
+			//gl_Position = vec4(0.0, 0.0, 2.0, 1.0);   
+            //return;
+            filtered_by_normal = true; //标记一下。不直接不绘制,因为有的法线都是垂直向上
 		}
     #endif
      
@@ -1008,7 +1010,14 @@ void main() {
 
     //数据集校准时,相机拉远后随着点云密集需降低透明度 
     #ifdef attenuated_opacity  
-        vOpacity = uOpacity * exp(-length(-mvPosition.xyz) / 1000.0);  //opacityAttenuation = 1000
+        //vOpacity = uOpacity * exp(-length(-mvPosition.xyz) / 1000.0);  //opacityAttenuation = 1000
+        vOpacity = uOpacity * exp(-mvPosition.z / 1000.0); 
+        vOpacity = clamp(vOpacity, 0.001, 1.0);          
+        if(filtered_by_normal){//垂直朝相机时降低透明度
+            vOpacity *= 0.05; 
+            vOpacity = clamp(vOpacity, 0.0001, 0.1);    
+        }  
+        
     #else
         vOpacity = uOpacity;
     #endif

+ 386 - 107
src/modules/CameraAnimation/CameraAnimation.js

@@ -1,12 +1,11 @@
 
-import * as THREE from "../../../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import * as THREE from "../../../libs/three.js/build/three.module.js"; 
 import { Utils } from "../../utils.js";
- 
+import math  from "../../utils/math";
 import {LineDraw} from "../../utils/DrawUtil"; 
 import CurveCtrl from  "../../objects/tool/CurveCtrl";
- 
-
+import HandleSvg from  "../../objects/tool/HandleSvg";
+import {/* transitions,*/ easing,  lerp} from '../../utils/transitions.js'
  
 
 const colors = {
@@ -15,7 +14,7 @@ const colors = {
 }
  
 
-let lineMats
+let lineMats 
 const getLineMat = function(name){
     if(!lineMats){
         lineMats = {
@@ -31,6 +30,7 @@ const getLineMat = function(name){
                 color: colors.position,  
                 lineWidth: 2   
             }),
+            aimAtTarget: new THREE.LineBasicMaterial({color:colors.target})
         }
     }
     return lineMats[name]
@@ -39,7 +39,7 @@ const getLineMat = function(name){
  
 
 
-export class CameraAnimation extends EventDispatcher{
+export class CameraAnimation extends THREE.EventDispatcher{
 
 	constructor(viewer){
 		super();
@@ -58,27 +58,39 @@ export class CameraAnimation extends EventDispatcher{
 
 		this.frustum = this.createFrustum();
 		this.node.add(this.frustum);
+       
 
 		this.name = "Camera Animation";
-		this.duration = 5;
-		this.t = 0;
+		
 		// "centripetal", "chordal", "catmullrom"
 		this.curveType = "centripetal" 
 		this.visible = true;
 
-	 
+        this.targets = [];  
 		this.createPath();
+        this.duration = 5;
+		this.percent = 0;
+        this.currentIndex = 0
+        this.quaternions = [];
         
+        if(!Potree.settings.isTest){
+            this.setVisible(false)
+        }
         
         
         
-        
-        
-        this.addEventListener('dispose', ()=>{ 
-             
+        this.addEventListener('dispose', ()=>{  
             this.dispose()
         })
         
+        
+        this.targetLines = new THREE.Object3D
+        this.node.add(this.targetLines)
+        
+        
+        
+        
+        
 	}
 
 	static defaultFromView(viewer){
@@ -111,21 +123,20 @@ export class CameraAnimation extends EventDispatcher{
 			const dx = r * Math.cos(u);
 			const dy = r * Math.sin(u);
 
-			const cpPos = [
+			const cpPos = new THREE.Vector3(
 				cpCenter.x + dx,
 				cpCenter.y + dy,
 				cpCenter.z,
-			];
+			)
 
-			const targetPos = [
+			const targetPos = new THREE.Vector3(
 				targetCenter.x + dx * 0.1,
 				targetCenter.y + dy * 0.1,
 				targetCenter.z,
-			];
+            )
 
-			animation.createControlPoint();
-			animation.posCurve.points[i].fromArray(cpPos)
-            animation.targetCurve.points[i].fromArray(targetPos)
+			animation.createControlPoint(null,{position:cpPos, target:targetPos});
+			 
              
 		}
 
@@ -135,68 +146,125 @@ export class CameraAnimation extends EventDispatcher{
 
 	 
 
-	createControlPoint(index){ 
+	createControlPoint(index, posInfo ){ 
         const length = this.posCurve.points.length
         const position = new THREE.Vector3
         const target = new THREE.Vector3
         
-		if(index === undefined){
+		if(index == void 0 ){
 			index = length;
 		}
         
-
-		if(length >= 2 && index === 0){ 
-			const dir = new THREE.Vector3().subVectors(this.posCurve.points[0], this.posCurve.points[1] ) 
-			position.copy(this.posCurve.points[0]).add(dir);
-			
-            const tDir = new THREE.Vector3().subVectors(this.targetCurve.points[0], this.targetCurve.points[1] ) 
-			target.copy(this.targetCurve.points[0]).add(dir);
-			 
-		}else if(length >= 2 && index === length){ 
-            const dir = new THREE.Vector3().subVectors(this.posCurve.points[length-1], this.posCurve.points[length-2] ) 
-			position.copy(this.posCurve.points[length-2]).add(dir);
-			 
-            const tDir = new THREE.Vector3().subVectors(this.targetCurve.points[length-1], this.targetCurve.points[length-2] ) 
-			target.copy(this.targetCurve.points[length-2]).add(dir);
-			 
-		}else if(length >= 2){ 
-			position.copy(this.posCurve.points[index-1].clone().add(this.posCurve.points[index]).multiplyScalar(0.5));
-			target.copy(this.targetCurve.points[index-1].clone().add(this.targetCurve.points[index]).multiplyScalar(0.5));
-		}
-
+        if(!posInfo){
+        
+            if(length >= 2 && index === 0){ 
+                const dir = new THREE.Vector3().subVectors(this.posCurve.points[0], this.posCurve.points[1] ) 
+                position.copy(this.posCurve.points[0]).add(dir);
+                
+                const tDir = new THREE.Vector3().subVectors(this.targets[0].position, this.targets[1].position ) 
+                target.copy(this.targets[0].position).add(dir);
+                 
+            }else if(length >= 2 && index === length){ 
+                const dir = new THREE.Vector3().subVectors(this.posCurve.points[length-1], this.posCurve.points[length-2] ) 
+                position.copy(this.posCurve.points[length-2]).add(dir);
+                 
+                const tDir = new THREE.Vector3().subVectors(this.targets[length-1].position, this.targets[length-2].position ) 
+                target.copy(this.targets[length-2].position).add(dir);
+                 
+            }else if(length >= 2){ 
+                position.copy(this.posCurve.points[index-1].clone().add(this.posCurve.points[index]).multiplyScalar(0.5));
+                target.copy(this.targets[length-1].position.clone().add(this.targets[length]).multiplyScalar(0.5));
+            }
+        }else{
+            position.copy(posInfo.position)
+            target.copy(posInfo.target)
+        }
             
-        this.posCurve.addPoint(position, index)
-        this.targetCurve.addPoint(target, index)
-  
-
+        this.posCurve.addPoint(position, index/* , true */)
+        //this.targetCurve.addPoint(target, index/* , true */) 
+        let targetSvg = new HandleSvg(target, colors.target)
+        targetSvg.visible = this.visible 
+        this.targets = [...this.targets.slice(0,index), targetSvg, ...this.targets.slice(index,length)]
+        
+        
 		this.dispatchEvent({
 			type: "controlpoint_added", 
             index
 		});
-
-		 
+        
+        {
+            let targetLine = LineDraw.createLine([position,target] ,{mat: getLineMat('aimAtTarget')})
+            this.targetLines.children = [...this.targetLines.children.slice(0,index), targetLine, ...this.targetLines.children.slice(index,length)]
+            
+            
+            
+            this.targets[index].addEventListener('dragged', (e)=>{ 
+                this.updatePathCallback()
+                this.dragPointCallback(e)
+            })
+        } 
 	}
 
 	 
+    dragPointCallback(e){
+        
+        let index = e.index
+        if(e.index == void 0){
+            index = this.targets.indexOf(e.target)
+        }
+        LineDraw.moveLine(this.targetLines.children[index], [this.posCurve.points[index], this.targets[index].position] )
+         
+        this.updateFrustum()
+    } 
+     
+    updatePathCallback(){
+        {
+            this.quaternions = [];
+            let length = this.posCurve.points.length;
+            for(let i=0; i<length; i++){
+                let quaternion = math.getQuaFromPosAim(this.posCurve.points[i], this.targets[i].position) 
+                this.quaternions.push( quaternion) 
+            }
+        }
+        this.reMapCurvePercent()
+    } 
+     
+     
     removeControlPoint(index){
         this.posCurve.removePoint(index)
-        this.targetCurve.removePoint(index)
+        //this.targetCurve.removePoint(index)
+        this.targets[index].dispose();
+        this.targets.splice(index, 1)
+         
         this.dispatchEvent({
 			type: "controlpoint_removed",
 			index
 		});
+        this.targetLines.remove(this.targetLines.children[index])
+        
         
     }
 
 
 
 	createPath(){
-        this.posCurve = new CurveCtrl([],getLineMat('position'), colors.position);
-        this.targetCurve = new CurveCtrl([], getLineMat('target'), colors.target); 
+        this.posCurve = new CurveCtrl([],getLineMat('position'), colors.position, 'posCurve'); 
+        //this.targetCurve = new CurveCtrl([], getLineMat('target'), colors.target, 'targetCurve', {noLine:true}); 
+        this.posCurve.needsPercent = true
         this.node.add(this.posCurve)
-        this.node.add(this.targetCurve)
+        //this.node.add(this.targetCurve)
+        
+        this.posCurve.addEventListener('dragCurvePoint', this.dragPointCallback.bind(this))
+        this.posCurve.addEventListener('updatePath', this.updatePathCallback.bind(this))
+            
+         
+        
+        
 	}
 
+
+     
+
 	createFrustum(){
 
 		const f = 0.3;
@@ -225,38 +293,156 @@ export class CameraAnimation extends EventDispatcher{
 
 			new THREE.Vector3(-f,  f, +1),
 			new THREE.Vector3(-f, -f, +1),
-		] 
-
+		]
+        
+        positions.forEach(e=>e.z *= -1)  //因为得到的rotation是camera的,作用在物体上要反向,所以这里反向一下
         
 		//geometry.computeBoundingSphere();//? 
 		const line = LineDraw.createFatLine( positions, {material:getLineMat('frustum')})
+         
         //line.scale.set(20, 20, 20); 
+        line.visible = false
 		return line;
 	}
 
-	 
 
-	at(t){
-		
-		if(t > 1){
-			t = 1;
-		}else if(t < 0){
-			t = 0;
-		}
+    reMapCurvePercent(){  //因在不同点在相同位置旋转,由于间隔仅和位置距离相关,导致时间间隔为0,采取重新调整间隔的策略。
+        var length = this.posCurve.points.length
+        
+        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
+            
+            
+            newPercents.push(percent) 
+        }
+        
+        let maxPercent = newPercents[length-1]  //最后一个,若扩充过时间,就会>1
 
-		const camPos = this.posCurve.getPointAt(t);
-		const target = this.targetCurve.getPointAt(t);
 
-		const frame = {
-			position: camPos,
-			target: target,
+        if( !math.closeTo(maxPercent, 1)){
+            let scale = 1 / maxPercent //需要压缩的比例 <1  这一步会让实际得到的间隔更小 
+            newPercents = newPercents.map(e=> e*=scale ) 
+        }
+        this.newPointsPercents = newPercents;
+        //console.log(newPercents)
+    }
+
+
+	at(originPercent, delta, transitionRatio){
+		 
+        originPercent = THREE.Math.clamp(originPercent, 0, 1)
+        //修改第一层:起始时间
+        let percent = originPercent;
+        /* const easePercent = 0.3; //缓动占比  //如果能在所有从静止到运动的中间加缓动就好了呀:lastPos * 0.9 + currentPos *  0.1 ?
+        if(originPercent < easePercent){
+            console.log('easeIn')
+            percent = easing.easeInSine(originPercent, 0, easePercent, easePercent) //currentTime, startY, wholeY, duration   选了一个衔接时接近斜率1的缓动函数
+        }else if(originPercent > 1-easePercent){ 
+            console.log('easeOut')
+            percent = easing.easeOutSine(originPercent-(1-easePercent), 1-easePercent, easePercent, easePercent)
+        } */
+        
+        
+		
+	 
+        let quaternion
+        
+        if(percent < 1){
+            //修改第二层:使用每个点的重定位的 newPointsPercents
+            
+            this.currentIndex = this.newPointsPercents.findIndex(e=> e>percent ) - 1   
+            //假设每个节点的百分比是精确的,那么:
+            let curIndexPercent = this.newPointsPercents[this.currentIndex];
+            let nextIndexPercent = this.newPointsPercents[this.currentIndex+1];
+            let progress = (percent - curIndexPercent) / (nextIndexPercent - curIndexPercent)//在这两个节点间的百分比
+            
+            //投影到原本的 posCurve.pointsPercent上:
+            let curIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex] 
+            let nextIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex+1] 
+            percent = curIndexOriPercent + (nextIndexOriPercent - curIndexOriPercent) * progress
+            
+            let endQuaternion = this.quaternions[this.currentIndex+1]
+            let startQuaternion = this.quaternions[this.currentIndex]       
+            quaternion = (new THREE.Quaternion()).copy(startQuaternion) 
+            lerp.quaternion(quaternion, endQuaternion)(progress) 
+            
+            
+        }else{
+             
+            
+            this.currentIndex = this.posCurve.points.length - 1;
+            
+            quaternion = math.getQuaFromPosAim(this.posCurve.points[this.currentIndex], this.targets[this.currentIndex].position)
+        }
+        
+        
+        const position = this.posCurve.getPointAt(percent); // 需要this.posCurve.points.length>1 否则报错
+        
+        
+        //console.log(this.currentIndex, originPercent)
+        //缓动:
+        var aimQua, aimPos;
+        if(delta != void 0 ){
+            if(Potree.settings.tourTestCameraMove){
+                aimQua = this.frustum.quaternion.clone();
+                aimPos = this.frustum.position.clone();
+            }else{
+                var camera = viewer.scene.getActiveCamera();
+                aimQua = camera.quaternion.clone();
+                aimPos = camera.position.clone();
+            }
+            
+            transitionRatio = transitionRatio || 1 / Potree.settings.cameraAniSmoothRatio//渐变系数,越小缓动程度越高,越平滑
+            transitionRatio *= delta * 60 //假设标准帧率为60fps,当帧率低时(delta大时)要降低缓动
+            //console.log(transitionRatio, delta) //画面ui变化会使delta变大
+            transitionRatio = THREE.Math.clamp(transitionRatio, 0, 1);
+            lerp.quaternion(aimQua, quaternion)(transitionRatio) //每次只改变一点点  
+            lerp.vector(aimPos, position)(transitionRatio)
+  
+        }else{
+            aimQua = quaternion;  aimPos = position
+        }
+        
+        
+        
+        let rotation = new THREE.Euler().setFromQuaternion(aimQua )
+  
+ 
+        const frame = {
+			position: aimPos,  
+            rotation
 		};
 
+
 		return frame;
 	}
 
-	set(t){
-		this.t = t;
+	set(percent){
+		this.percent = percent;
 	}
 
 	 
@@ -265,78 +451,171 @@ export class CameraAnimation extends EventDispatcher{
 		this.node.visible = visible;
  
         this.posCurve.visible = visible
-        this.targetCurve.visible = visible
+        this.targets.forEach(e=>e.visible = visible ) 
 
 		this.visible = visible;
 	}
 
 	setDuration(duration){
-		this.duration = duration;
+        if(duration != this.duration){
+            this.duration = duration;
+            if(this.quaternions.length == this.posCurve.points.length)this.reMapCurvePercent()
+            
+        } 
 	}
 
 	getDuration(duration){
 		return this.duration;
 	}
 
-	play(){
-
-		const tStart = performance.now();
-		const duration = this.duration;
-
-		const originalyVisible = this.visible;
-		this.setVisible(false);
-
-		const onUpdate = (delta) => {
-
-			let tNow = performance.now();
-			let elapsed = (tNow - tStart) / 1000;
-			let t = elapsed / duration;
-
-			this.set(t);
-
-			const frame = this.at(t);
-
-			viewer.scene.view.position.copy(frame.position);
-			viewer.scene.view.lookAt(frame.target);
+	play(startOptions={}){
+        if(this.onUpdate){
+            return console.error('已经开始播放')
+        }
+       
+        
+        
+        let startPercent = 0, currentIndex = 0  
+        if(startOptions.percent != void 0 ){
+            startPercent = startOptions.percent
+        }else if(startOptions.index){
+            currentIndex = index
+            //startPercent = index/(this.posCurve.points.length-1)
+            
+            startPercent = this.posCurve.pointsPercent[index]
+        }
+        
+        
+		//const tStart = performance.now();
+        
+        
 
 
-            this.updateFrustum()
+		const duration = this.duration;
 
+		this.originalyVisible = this.visible;
+		Potree.settings.tourTestCameraMove || this.setVisible(false);
+        
+        
+        let tStart, startTransitionRatio = 0.2
+        let startDelay = 1/startTransitionRatio / 20 ;//因为缓动所以延迟开始,前面前都是at(0),使过渡到开始点位(但是依旧不能准确停在起始点,因为缓动是乘百分比有残留。所以直接平滑衔接到开始后的位置)
+        let hasPlayedTime = 0
+        
+        let finishDelay = Potree.settings.cameraAniSmoothRatio / 60 * 3//结束后还需要多久时间才能大致达到缓动的最终目标
+        let hasStoppedTime = 0
+
+		this.onUpdate = (e) => {
+            if(this.posCurve.points.length<2){
+                if(this.posCurve.points.length == 1){
+                    viewer.scene.view.position.copy(this.posCurve.points[0]);
+                    viewer.scene.view.rotation = new THREE.Euler().setFromQuaternion(this.quaternions[0])
+                }
+                this.pause()
+                return 
+            }
+             
+            let percent, transitionRatio
+            
+            if(tStart){ 
+                let tNow = performance.now(); 
+                let elapsed = (tNow - tStart) / 1000;
+                percent = elapsed / duration + startPercent;
+            }else{//从当前位置过渡到开始位置 
+                percent = 0
+                hasPlayedTime += e.delta
+                transitionRatio = startTransitionRatio;
+                console.log('延迟开始') 
+                if(hasPlayedTime > startDelay){
+                    tStart = performance.now(); 
+                }
+                
+            }
+             
+			this.set(percent);
+             
+			const frame = this.at(percent, e.delta, transitionRatio);
+            
+            if(currentIndex != this.currentIndex){
+                currentIndex = this.currentIndex
+                console.log('updateCurrentIndex', currentIndex)
+                this.dispatchEvent({type:'updateCurrentIndex', currentIndex  })
+            }
+            
+            
+            
+            if(!Potree.settings.tourTestCameraMove){
+                viewer.scene.view.position.copy(frame.position);
+                //viewer.scene.view.lookAt(frame.target);
+                viewer.scene.view.rotation = frame.rotation; 
+            }
 
-			if(t > 1){
-				this.setVisible(originalyVisible);
+            this.updateFrustum(frame)
 
-				this.viewer.removeEventListener("update", onUpdate);
+            
+            
+            
+			if(percent >= 1){ 
+                if(hasStoppedTime > finishDelay){
+                    this.pause()
+                }else{ 
+                    hasStoppedTime += e.delta
+                    console.log('延迟结束') 
+                }
+                
 			}
+             
             
             
 		};
 
-		this.viewer.addEventListener("update", onUpdate);
-
+		this.viewer.addEventListener("update", this.onUpdate);
+         
+         
 	}
 
-    updateFrustum(){ // 
-        const frame = this.at(this.t);
-        const frustum = this.frustum;
+    pause(){ 
+        this.setVisible(this.originalyVisible); 
+        this.viewer.removeEventListener("update", this.onUpdate);
+        this.dispatchEvent('playDone')
+        this.onUpdate = null
+    }
 
-        frustum.position.copy(frame.position);
-        frustum.lookAt(...frame.target.toArray());
+
+
+    updateFrustum(frame){  
+        const frustum = this.frustum;
+        if(this.posCurve.points.length>1){
+            frustum.visible = true
+        }else{
+            frustum.visible = false;
+            return
+        }
         
+        frame = frame || this.at(this.percent);
+        frustum.position.copy(frame.position);
+        //frustum.lookAt(...frame.target.toArray());
+        frustum.rotation.copy(frame.rotation)
     }
 
     changeCallback(){
         this.posCurve.update()
-        this.targetCurve.update()
+        //this.targetCurve.update()
+        this.targets.forEach(e=>e.update()) 
         this.updateFrustum()
     }
 
     dispose(){//add
-        this.posCurve.dispatchEvent({type:'dispose'}) 
-        this.targetCurve.dispatchEvent({type:'dispose'}) 
+        this.posCurve.dispose()
+        //this.targetCurve.dispatchEvent({type:'dispose'}) 
+        this.targets.forEach(e=>e.dispose())
+        
         this.node.parent.remove(this.node);
     }
 }
 
 
-//scene.removeCameraAnimation
+//scene.removeCameraAnimation
+
+
+//修改:不使用targetCurve作为target曲线,因为播放时posCuve的节点和targetCurve并没有对应,且使用target的曲线会使角度变化大的情况过渡生硬。
+// 改完旋转了。但是位置也有问题。速度完全和路程相关,当在同一位置设置多点时,这段的总时长为0. (是否要设置最小时长?不过也做不到 - -)

+ 240 - 120
src/modules/Images360/Images360.js

@@ -1,6 +1,5 @@
 
-import * as THREE from "../../../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import * as THREE from "../../../libs/three.js/build/three.module.js"; 
 import {TextSprite} from "../../objects/TextSprite.js";
 import ModelTextureMaterial from "./ModelTextureMaterial.js";
 import Common from "../../utils/Common.js";
@@ -40,7 +39,7 @@ const HighMapCubeWidth = 1
 
  
  
-export class Images360 extends EventDispatcher{
+export class Images360 extends THREE.EventDispatcher{
 
 	constructor(viewer ){
 		super();
@@ -89,12 +88,12 @@ export class Images360 extends EventDispatcher{
         
         {//高分辨率cube 放大
             this.addHighMapCube()
-            viewer.on(PanoramaEvents.Enter,(e)=>{
+            viewer.addEventListener(PanoramaEvents.Enter,(e)=>{
                 this.setHighMap(e.newPano)
             })
-            viewer.on('panoSetZoom',(pano, zoomed)=>{
+            viewer.addEventListener('panoSetZoom',(e)=>{ 
                 if(Potree.settings.displayMode == 'showPanos'){
-                    zoomed ? this.showHighMap() : this.hideHighMap() //add
+                    e.zoomed ? this.showHighMap() : this.hideHighMap() //add
                 } 
             }) 
              
@@ -113,7 +112,7 @@ export class Images360 extends EventDispatcher{
             } */
         }
         
-        viewer.fpControls.on('dollyStopCauseUnable',(e)=>{ 
+        viewer.fpControls.addEventListener('dollyStopCauseUnable',(e)=>{ 
             if(/* e.hoverViewport != viewer.mainViewport ||  */!Potree.settings.zoom.enabled)return 
             
             if(e.scale != void 0){//触屏
@@ -132,12 +131,19 @@ export class Images360 extends EventDispatcher{
         
 
         let click = (e) => {//不用"mouseup" 是因为 mouseup有drag object时也会触发
-            if(Potree.settings.unableNavigate || this.flying  || !e.isTouch && e.button != THREE.MOUSE.LEFT || e.hoverViewport != viewer.mainViewport )return //
+            if( e.hoverViewport == viewer.mapViewer.viewports[0]){
+                return viewer.mapViewer.dispatchEvent(e/* {type:'global_click',e } */) 
+            }
+            
+            
+            
+            
+            if(Potree.settings.unableNavigate || this.flying  || !e.isTouch && e.button != THREE.MOUSE.LEFT || e.drag &&  e.drag.object)return //拖拽结束时不算
              
             /* if(currentlyHovered && currentlyHovered.pano){
 				this.focusPano(currentlyHovered.pano);
 			}else{//add */
-                if(!Potree.settings.dblToFocusPoint && this.currentPano){//双击不会focus点云 或者 已经focusPano了
+                if(!Potree.settings.dblToFocusPoint/*  && this.currentPano */){//双击不会focus点云 或者 已经focusPano了
                     this.flyToPanoClosestToMouse()   
                 }
             //}  
@@ -224,17 +230,19 @@ export class Images360 extends EventDispatcher{
                     //console.log('Request setMode: ' + mode)  
                                    
                     if(mode != displayMode){
-                        this.modeChanging = true //主要是因为到全景图不会立刻成功
+                        
                            
                         let config = Potree.config.displayMode[mode]
                         let config2 
                         let camera = viewer.scene.getActiveCamera()
-                        if(mode == 'showPanos' && this.flying){//飞完才能切换全景
-                            this.once('cameraMoveDone', ()=>{
+                        if(mode == 'showPanos' && this.flying){//飞完才能切换全景 
+                            let f = ()=>{
                                 if(latestRequestMode == mode){//如果ui还是停在这个模式的话
                                     Potree.settings.displayMode = mode 
-                                } 
-                            })
+                                }
+                                this.removeEventListener('cameraMoveDone', f)
+                            } 
+                            this.addEventListener('cameraMoveDone', f) //once
                             return
                         }
                         if(this.isAtPano() ){//this.currentPano
@@ -245,9 +253,11 @@ export class Images360 extends EventDispatcher{
                             if(mode == 'showPanos'){//自动飞入一个pano
                                 //要改成飞进最近的。。。 
                                 if(this.panos.length == 0)return
+                                //this.modeChanging = true //主要是因为到全景图不会立刻成功
                                 this.flyToPano({
-                                    pano: /* this.currentPano ||  */Common.sortByScore(this.panos,null,[e=>-e.position.distanceTo(this.position)])[0].item,   
+                                    pano: this.findNearestPano(),   
                                     callback: ()=>{
+                                        //this.modeChanging = false
                                         if(latestRequestMode == mode ){
                                             Potree.settings.displayMode = mode 
                                         } 
@@ -330,7 +340,7 @@ export class Images360 extends EventDispatcher{
                             viewer.scene.pointclouds.forEach(e=>{
                                 e.material.pointSizeType = 'FIXED'   
                             })
-                            
+                            this.updateCube(this.currentPano)
                             
                         }else{
                             if(camera.limitFar)   camera.far = Potree.settings.cameraFar;//修改far
@@ -351,12 +361,12 @@ export class Images360 extends EventDispatcher{
                             this.elDisplayModel.value = mode == 'showPointCloud' ? ">>全景" : '>>点云'
                         }
                          
-                        this.modeChanging = false
-                        this.emit('endChangeMode',mode)  
+                         
+                        //this.dispatchEvent({type:'endChangeMode',mode})  
                         console.log('setModeSuccess: ' + mode)       
                     }else{
-                        this.modeChanging = false  //取消modeChanging
-                        this.emit('endChangeMode',mode)    
+                        
+                        //this.dispatchEvent({type:'endChangeMode',mode})    
                     }                        
                 }
             }) 
@@ -443,23 +453,21 @@ export class Images360 extends EventDispatcher{
                     } 
                 } 
             }
-        })
-        
-        
-        
-		 
+        }) 
   
 	}; 
-    
-    
-    updateCube(params){ 
-        let size = params.boundSize  
-        this.cube.scale.set(Math.max(size.x, HighMapCubeWidth), Math.max(size.y, HighMapCubeWidth), Math.max(size.z, HighMapCubeWidth) )
-        this.cube.position.copy(params.center)
      
+    
+    findNearestPano(pos){
+        pos = pos ? new THREE.Vector3().copy(pos) : this.position
+        let result = Common.sortByScore(this.panos,[Images360.filters.isEnabled()],[e=>-e.position.distanceTo(pos)])
+        let pano = result && result[0] && result[0].item
+        return pano
+        
     }
+    
 
-    set flying(v){
+    set flying(v){//正在飞向pano
         this.flying_ = !!v
         //this.emit('flying', this.flying_)
         let config = Potree.config.displayMode[Potree.settings.displayMode]
@@ -552,10 +560,13 @@ export class Images360 extends EventDispatcher{
 
 
     flyToPano(toPano) {  //飞向漫游点
+        if(!toPano)return
         if(toPano instanceof Panorama){
             toPano = {pano: toPano}
         }
         
+        if(!toPano.pano.enabled)return
+
         if(!this.currentPano){
             return this.focusPano(toPano) 
         }
@@ -563,10 +574,13 @@ export class Images360 extends EventDispatcher{
         let done = (makeIt)=>{
             //console.log('done '+ !!toPano.deferred)
             toPano.deferred && toPano.deferred.resolve(makeIt)
-            makeIt && toPano.callback && toPano.callback()
+            if(makeIt) {
+                toPano.callback && toPano.callback()
+                this.flying = false 
+            }
         }
         if(this.currentPano == toPano.pano && this.isAtPano() && !toPano.target ){
-            this.emit('flyToPano', toPano)
+            this.dispatchEvent({type:'flyToPano', toPano})
             return done(true);
         }
         if(this.flying){
@@ -648,35 +662,38 @@ export class Images360 extends EventDispatcher{
         }
         
         
-        viewer.scene.view.setView(endPosition, target ,duration,  ()=>{//done
-            
-            if(!config.atPano.pointUsePanoTex){ 
-                viewer.scene.pointclouds.forEach(e=>{
-                    e.material.stopProjectedPanos()
-                })
-            }
-            //this.currentPano.exit()
-            //pano.enter()
-            this.currentPano = pano;
-            this.flying = false 
-            this.nextPano = null;
-            if(Potree.settings.displayMode == 'showPanos'){
+        viewer.scene.view.setView({position:endPosition, target ,duration,  
+            callback:()=>{ 
+                
+                if(!config.atPano.pointUsePanoTex){ 
+                    viewer.scene.pointclouds.forEach(e=>{
+                        e.material.stopProjectedPanos()
+                    })
+                }
+                //this.currentPano.exit()
+                //pano.enter()
+                this.currentPano = pano;
+                
+                this.nextPano = null;
+                if(Potree.settings.displayMode == 'showPanos'){
+                    viewer.scene.pointclouds.forEach(e=>{
+                        viewer.updateVisible(e, 'displayMode',pointcloudVisi) 
+                    })
+                }
+                done(true);
+                this.updateCube(this.currentPano)
+                this.dispatchEvent('cameraMoveDone')
+                
+            }, onUpdate:(progress)=>{ 
+                this.cube.material.uniforms.progress.value = progress 
+                
                 viewer.scene.pointclouds.forEach(e=>{
-                    viewer.updateVisible(e, 'displayMode',pointcloudVisi) 
+                    e.material.uniforms.progress.value = progress 
                 })
-            }
-            done(true);
-            
-            this.emit('cameraMoveDone')
-            
-        },(progress)=>{//onUpdate
-            this.cube.material.uniforms.progress.value = progress 
-            
-            viewer.scene.pointclouds.forEach(e=>{
-                e.material.uniforms.progress.value = progress 
-            })
-        }, easeName )
-        
+            },
+            cancelFun:()=>{this.flying = false},
+            Easing:easeName  
+        })
         
         
     }
@@ -691,12 +708,12 @@ export class Images360 extends EventDispatcher{
         if(Potree.settings.displayMode == 'showPanos'){
             this.resetHighMap() 
         }
-        
+        this.updateCube(this.currentPano, toPano.pano)
         this.smoothZoomTo(1, toPano.duration / 2);  
          
          
              
-        this.emit('flyToPano', toPano)
+        this.dispatchEvent({type:'flyToPano', toPano})
          
     }
 
@@ -713,7 +730,7 @@ export class Images360 extends EventDispatcher{
             toPano = {pano: toPano}
         }
         let done = (makeIt)=>{
-            toPano.deferred && toPano.deferred.resolve(makeIt)
+            toPano.deferred && toPano.deferred.resolve(makeIt) 
             makeIt && toPano.callback && toPano.callback()
         }
         
@@ -772,22 +789,25 @@ export class Images360 extends EventDispatcher{
         let target = newCamPos.clone().add(viewer.scene.view.direction)  
         this.flying = true
         
-		viewer.scene.view.setView( newCamPos,  target, dur , ()=>{//done 
-            //pano.enter()
-            this.flying = false  
-            /* viewer.scene.pointclouds.forEach(e=>{
-                e.visible = pointcloudVisi
-            }) */
-            this.currentPano = pano;
-            done(true)
-        },(progress)=>{//onUpdate
-            /* this.cube.material.uniforms.progress.value = progress 
-            
-            viewer.scene.pointclouds.forEach(e=>{
-                e.material.uniforms.progress.value = progress 
-            }) */
-        } )
-		 
+		viewer.scene.view.setView( {position:newCamPos,  target, duration:dur ,
+            callback:()=>{//done 
+                //pano.enter()
+                this.flying = false  
+                /* viewer.scene.pointclouds.forEach(e=>{
+                    e.visible = pointcloudVisi
+                }) */
+                this.currentPano = pano;
+                this.updateCube(this.currentPano)
+                done(true)
+            },onUpdate:(progress)=>{ 
+                /* this.cube.material.uniforms.progress.value = progress 
+                
+                viewer.scene.pointclouds.forEach(e=>{
+                    e.material.uniforms.progress.value = progress 
+                }) */
+            }, 
+            cancelFun:()=>{this.flying = false}  
+        })
 
 		
 
@@ -813,7 +833,7 @@ export class Images360 extends EventDispatcher{
 		viewer.orbitControls.doubleClockZoomEnabled = true;
 		 
 
-		viewer.scene.view.setView(
+		/* viewer.scene.view.setView(
 			o.position || previousView.position, 
 			o.target || previousView.target,
 			o.duration || 500,
@@ -824,7 +844,23 @@ export class Images360 extends EventDispatcher{
                 }
                 o.callback && o.callback()            
             }
-		);
+		); */
+        
+        viewer.scene.view.setView($.extend({
+            position: previousView.position,
+            target:previousView.target,
+            duration:500
+        },o,{
+            callback:()=>{ //done
+                for(let pano of this.panos){
+                    pano.mesh.visible = true;
+                    //pano.marker.material.depthTest = true
+                }
+                o.callback && o.callback()            
+            }
+            ,
+            cancelFun:()=>{this.flying = false},
+        })) 
 
         //this.currentPano.exit()
 		this.currentPano = null;
@@ -833,28 +869,83 @@ export class Images360 extends EventDispatcher{
 	}
 
 
+    updateCube(pano0, pano1){
+        if(Potree.settings.displayMode != 'showPanos')return
+        
+       
+        let f = (bound, size)=>{
+            size = size || bound.getSize(new THREE.Vector3) 
+            let center = bound.getCenter(new THREE.Vector3)
+            size.max(new THREE.Vector3(HighMapCubeWidth,HighMapCubeWidth,HighMapCubeWidth))
+            this.cube.scale.copy(size)  
+            this.cube.position.copy(center) 
+            
+        }
+        
+        let getDis = (bound1, bound2)=>{ //获取bound1边界到bound2边界距离
+            if(bound1.intersectsBox(bound2))return 0
+            let center1 = bound1.getCenter(new THREE.Vector3) 
+            let center2 = bound2.getCenter(new THREE.Vector3) 
+            let dis = center1.distanceTo(center2)
+            let dis1 = bound1.distanceToPoint(center2)
+            let dis2 = bound2.distanceToPoint(center1)
+            return dis1 + dis2 - dis
+        }
+        
+        if(pano1){//过渡
+            if(pano0.pointcloud == pano1.pointcloud){//同一个数据集内的过渡
+                f(pano0.pointcloud.bound)
+                //console.log('updateCube1' )
+            }else{//非同一个数据集内的过渡 
+                let bound = pano0.pointcloud.bound.clone().union(pano1.pointcloud.bound)
+                if(getDis(pano0.pointcloud.bound, pano1.pointcloud.bound) < 100){ 
+                    f(bound)
+                }else{//如果两个数据集boundingbox距离大于这个距离,扩大一下,防止精度问题导致失真//对很远的数据集似乎没有什么用
+                    let size = bound.getSize(new THREE.Vector3) 
+                    let max = Math.max(size.x, size.y, size.z)
+                    size.set(max,max,max)
+                    f(bound, size)
+                    //console.log('updateCube2', size)
+                    //far可能要修改下
+                }
+            }
+        }else{
+            f(pano0.pointcloud.bound) //假定一个数据集不会太大,否则会造成失真,且bump时移动距离看起来很小。或者可以考虑用固定大小
+            //console.log('updateCube3' )
+        } 
+        
+    }
 
-
- 
+  /*   updateCube(params){ 
+        let size = params.boundSize  
+        this.cube.scale.set(Math.max(size.x, HighMapCubeWidth), Math.max(size.y, HighMapCubeWidth), Math.max(size.z, HighMapCubeWidth) )
+        this.cube.position.copy(params.center)
+     
+    } */
 
     bump(direction) {//撞墙弹回效果
         if (!this.flying) {  
-            this.flying = !0 
+            this.flying = true
             
             let distance = Potree.settings.displayMode == 'showPanos' ? 0.4 : 0.2;//感觉点云模式比全景模式更明显,所以降低
             let currentPos = this.position.clone()
             let endPosition = new THREE.Vector3().addVectors(this.position, direction.clone().multiplyScalar(distance)) 
             
             let duration = 150 
-            viewer.scene.view.setView(endPosition, null , duration,  ()=>{//done
-                viewer.scene.view.setView(currentPos, null , duration*5,  ()=>{//done
-                    this.flying = !1 
-                    this.emit('cameraMoveDone')
-                },null,'easeInOutSine')
-             
-            } ,(progress)=>{//onUpdate
-                 
-            },  'easeInOutSine' ) 
+            viewer.scene.view.setView({position:endPosition,   duration,  
+                callback:()=>{ 
+                    viewer.scene.view.setView({position:currentPos, duration: duration*5, 
+                        callback: ()=>{ 
+                            this.flying = false
+                            this.dispatchEvent('cameraMoveDone')
+                        }, 
+                        Easing:'easeInOutSine',
+                        cancelFun:()=>{this.flying = false}
+                    }) 
+                }, 
+                cancelFun:()=>{this.flying = false},  
+                Easing:'easeInOutSine'
+            })            
         }
         //备注:将4dkk中的‘前后方向变化fov、左右方向移动镜头’ 都改为移动镜头。 因为这里无法判断左右离壁距离。
  
@@ -932,10 +1023,11 @@ export class Images360 extends EventDispatcher{
         var request = [//必要条件 
             Images360.filters.inPanoDirection( this.position, direction, option1), 
             //Images360.filters.isNeighbourPanoTo(this.currentPano), 
-            Images360.filters.not(this.currentPano)
+            Images360.filters.not(this.currentPano),
+            Images360.filters.isEnabled()
         ] 
         var list = [//决胜项目
-            Images360.scoreFunctions.distanceSquared(this.currentPano),
+            Images360.scoreFunctions.distanceSquared(this.position),
             Images360.scoreFunctions[o]( this.position, direction)
         ]; 
          
@@ -958,7 +1050,7 @@ export class Images360 extends EventDispatcher{
             //当静止在漫游点时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{
 			 
         }
@@ -1178,14 +1270,18 @@ export class Images360 extends EventDispatcher{
 
 
     fitPanoTowardPoint(o){  //寻找最适合的点位
-		var point = o.point, 
+		var point = o.point,     //相机最佳位置
+            target = o.target,   //实际要看的位置 
 			require = o.require || [],
 			rank = o.rank || [],
 			force = o.force,
 			getAll = o.getAll,  
             bestDistance = o.bestDistance || 0
         let camera = viewer.scene.getActiveCamera()
-        
+        if(target){
+            var vec = new THREE.Vector3().subVectors(target,point).normalize()
+        }
+         
         
         //if(o.floor)require.push(Panorama.filters.atFloor(o.floor))
             
@@ -1198,13 +1294,7 @@ export class Images360 extends EventDispatcher{
             }else{
                 let hfov = cameraLight.getHFOVForCamera(camera , true  );
                 dis = /* size.x */ o.boundSphere.radius /* / 2 */ /  (hfov / 2) 
-            }
-             
-
-            
-
-
-
+            } 
              
             bestDistance = dis//*0.8 
             
@@ -1213,7 +1303,17 @@ export class Images360 extends EventDispatcher{
         
         let bestDisSquared = bestDistance * bestDistance
         rank.push((pano)=>{
-            return -Math.abs(pano.position.distanceToSquared(point) - bestDisSquared)
+            let dis1 = Math.abs(pano.position.distanceToSquared(point) - bestDisSquared); //距离最佳位置
+            if(!target){
+                return -dis1 
+            }else{
+                let dis2 = pano.position.distanceToSquared(target);  //距离目标点
+                let vec2 = new THREE.Vector3().subVectors(target,pano.position).normalize()
+                let cos = vec.dot(vec2)  
+                let result = (- dis1  - Math.pow(dis2 , 1.5)) / (cos + 2)  // cos+2是为了调整到1-3, 尽量贴近最佳位置的角度;
+                //console.log(pano.id, dis1,dis2,  cos,  result)
+                return result
+            } 
         }) 
      
 		/* var temp = {position:point}
@@ -1527,7 +1627,7 @@ export class Images360 extends EventDispatcher{
         
     }
     
-    showHighMap(){ 
+    showHighMap(){  
         if(!this.highMapCube)   return 
         //console.warn('showHighMap')
         this.highMapCube.visible = true; 
@@ -1540,11 +1640,11 @@ export class Images360 extends EventDispatcher{
     //缩小后继续显示cube呢还是不显示?  不显示的话,就要把cube上的复制到renderTarget上……会不会又崩溃,or没加载的显示???
     
     
-    addPanoData(data, datasetId){
+    addPanoData(data, dataset ){
         //data[0].file_id = '00019'
          
         if(data.data) data = data.data 
-        if(data.length == 0)console.error(datasetId + ' 没有漫游点') 
+        if(data.length == 0)console.error(dataset.name , dataset.id + ' 没有漫游点') 
         //data = data.sort(function(a,b){return a.id-b.id})
         
         data.forEach((info)=>{  
@@ -1565,7 +1665,7 @@ export class Images360 extends EventDispatcher{
     
     loadDone(){
         viewer.setObjectLayers(this.node, 'marker'/* 'sceneObjects' */)
-        this.updateCube(viewer.bound)
+        //this.updateCube(/* viewer.bound */)
         
         this.panos.forEach(e=>{
             e.label && viewer.setObjectLayers(e.label, 'bothMapAndScene') 
@@ -1590,6 +1690,14 @@ export class Images360 extends EventDispatcher{
         }
         
         
+
+        if(viewer.scene.pointclouds.some(e=>e.panos.length == 0)){
+            //console.warn('存在数据集没有pano');
+            viewer.hasNoPanoDataset = true
+        } 
+
+
+
     }
     
             
@@ -1658,9 +1766,11 @@ Images360.prototype.checkAndWaitForPanoLoad = function() {
 Images360.filters = { 
     inPanoDirection : function(pos, dir, i) { 
         return function(pano) { 
-            var r = pano.floorPosition.clone().sub(pos).setZ(0).normalize()    //忽略上下角度,这样即使看得很低也能走
-			  , o = pano.position.clone().sub(pos).normalize(); 
-			return r.dot(dir.clone().setZ(0).normalize()) > i || o.dot(dir) > i
+            var r = pano.floorPosition.clone().sub(pos).normalize() 
+            var o = pano.position.clone().sub(pos).normalize()
+            return r.dot(dir) > i || o.dot(dir) > i 
+            
+            
         }
     },
     inFloorDirection: function(pos, e, o) {//许钟文 改 for鱼眼
@@ -1684,10 +1794,20 @@ Images360.filters = {
     },
     
     not: function(e) {
-        return function(t) {
+        return function(t) { 
             return t !== e
         }
-    }   
+    } , 
+    isEnabled:function() {
+        return function(t) {
+            return t.enabled
+        }
+    },
+    isVisible:function() {
+        return function(t) {
+            return t.visible
+        }
+    }    
 }
 
 
@@ -1700,10 +1820,10 @@ Images360.scoreFunctions = {
         }
     },
      
-    distanceSquared: function(e) {
-        var pos1 = e.position.clone()
-        return  function(i) {//许钟文 改
-            var pos2 = i.position.clone()
+    distanceSquared: function(pos1) { 
+        if(pos1.position)pos1 = pos1.position
+        return  function(pano) {//许钟文 改
+            var pos2 = pano.position.clone()
             return pos1.distanceToSquared(pos2) * -1 
         }
     },

+ 112 - 57
src/modules/Images360/Panorama.js

@@ -2,8 +2,7 @@ import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {transitions, easing, lerp} from '../../utils/transitions.js'
 import TileUtils from './tile/TileUtils'
 import { PanoRendererEvents, PanoramaEvents, PanoSizeClass} from '../../defines'
-import math from '../../utils/math'
-import { EventDispatcher } from "../../EventDispatcher.js";
+import math from '../../utils/math' 
 import {TextSprite} from '../../objects/TextSprite'
 
 var texLoader = new THREE.TextureLoader()
@@ -52,7 +51,7 @@ var a = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.
  */
 
 //暂时直接用4dkkconsole输出的数据 
-class Panorama extends EventDispatcher{
+class Panorama extends THREE.EventDispatcher{
 
 	constructor(o, transform, images360){//file, time, longitude, latitude, altitude, course, pitch, roll
         super()
@@ -60,7 +59,8 @@ class Panorama extends EventDispatcher{
         this.images360 = images360
         this.transform =  transform
         this.visible = true  //for viewer updateVisible
-         
+        this.enabled = true//是否可以走
+
         this.originPosition = new THREE.Vector3().fromArray(o.dataset_location) 
         this.originFloorPosition = new THREE.Vector3().fromArray(o.dataset_floor_location)
         
@@ -136,22 +136,22 @@ class Panorama extends EventDispatcher{
             
             
            
-            images360.panoRenderer.on(PanoRendererEvents.TileRenderSuccess, this.onTileRendered.bind(this));
-            images360.panoRenderer.on(PanoRendererEvents.PanoRenderComplete, this.onPanoRendered.bind(this));
-            images360.panoRenderer.on(PanoRendererEvents.TileRenderFailure, this.onTileRenderFail.bind(this));
-            images360.panoRenderer.on(PanoRendererEvents.UploadAttemptedForAllTiles, this.onUploadAttemptedForAllTiles.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderSuccess, this.onTileRendered.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.PanoRenderComplete, this.onPanoRendered.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderFailure, this.onTileRenderFail.bind(this));
+            images360.panoRenderer.addEventListener(PanoRendererEvents.UploadAttemptedForAllTiles, this.onUploadAttemptedForAllTiles.bind(this));
             
         }
         
         
         
-        this.on('hoverOn', (e)=>{//from Map
+        this.addEventListener('hoverOn', (e)=>{//from Map
             if(!e.byMainView){ 
                 this.hoverOn(e) 
             } 
         })
         
-        this.on('hoverOff', (e)=>{
+        this.addEventListener('hoverOff', (e)=>{
             if(!e.byMainView){
                 this.hoverOff(e) 
             } 
@@ -160,9 +160,14 @@ class Panorama extends EventDispatcher{
     
 
 
+    setEnable(enable){//是否可以走
+        viewer.updateVisible(this, 'isEnabled', enable) //令所有marker不可见
 
+        this.enabled = enable 
+        //如果当前在全景模式且在这个点,需要切换显示吗? 目前用不到 
+    }
 
-
+ 
 
 
     
@@ -223,6 +228,7 @@ class Panorama extends EventDispatcher{
         this.panoMatrix = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix  ) 
         //this.panoMatrix2 =  Potree.Utils.datasetRotTransform({fromDataset:true, pointcloud:this.pointcloud,  matrix:this.oriPanoMatrix, getMatrix:true}) //和上一行结果一样
         //quaternion也变下  
+        this.dispatchEvent('rePos')
     }
     
     setPosition(position, floorPosition){
@@ -248,7 +254,7 @@ class Panorama extends EventDispatcher{
     hoverOn(e={}) { 
         //console.log("hoverOn  " + this.id  )
         transitions.start(lerp.property(this.marker.material, "opacity", 1), 250)  
-		if(!e.byMap) this.emit('hoverOn', {byMainView:true})
+		if(!e.byMap) this.dispatchEvent({type:'hoverOn', byMainView:true})
     }
 
  
@@ -257,7 +263,7 @@ class Panorama extends EventDispatcher{
     hoverOff(e={}){
         //console.log("hoverOff  " + this.id  )
         transitions.start(lerp.property(this.marker.material, "opacity", 0.5), 250) 
-        if(!e.byMap) this.emit('hoverOff', {byMainView:true})
+        if(!e.byMap) this.dispatchEvent({type:'hoverOff',  byMainView:true})
     }
     
     
@@ -265,14 +271,14 @@ class Panorama extends EventDispatcher{
     setZoomed(zoomed){
         this.zoomed = zoomed;
         Potree.settings.displayMode == 'showPanos' && this.updateSkyboxForZoomLevel(); //放大后换成zoomTarget贴图
-        viewer.emit('panoSetZoom', this, zoomed)
+        viewer.dispatchEvent({type:'panoSetZoom', zoomed})
         
     }
     
     
     enter(){ 
         this.setZoomed(!1),
-        viewer.emit(PanoramaEvents.Enter,  {oldPano:old, newPano:this  }  )
+        viewer.dispatchEvent({type:PanoramaEvents.Enter,  oldPano:old, newPano:this  }  )
         old = this 
         //console.log("enter pano "+ this.id)
     } 
@@ -297,7 +303,7 @@ class Panorama extends EventDispatcher{
         
         //console.log("exit pano "+ this.id)
         
-        viewer.emit(PanoramaEvents.Exit, this); 
+        viewer.dispatchEvent({type:PanoramaEvents.Exit, pano:this}); 
     }
     
     
@@ -332,15 +338,11 @@ class Panorama extends EventDispatcher{
     
    
     
-    isLoaded(e){
-        //if (this.tiled) {
-            if (e && "string" == typeof e)
-                console.error("Wrong panoSize given to Panorama.isLoaded(); a tiled pano uses PanoSizeClass");
-            return !!this.minimumTiledPanoLoaded && (!e || this.highestPartialTileRenderOpCompleted >= e)
-        //}
-        /* if (e && "number" == typeof e)
-            throw new BasicException("Wrong panoSize given to Panorama.isLoaded(); a non-tiled pano uses high/low.");
-        return !!this.solidSkybox.high || e in this.solidSkybox */
+    isLoaded(e){ 
+        if (e && "string" == typeof e)
+            console.error("Wrong panoSize given to Panorama.isLoaded(); a tiled pano uses PanoSizeClass"); 
+        return !!this.minimumTiledPanoLoaded && (!e || this.highestFullTileRenderOpCompleted >= e)//改:原本是:this.highestPartialTileRenderOpCompleted >= e, 希望这代表全部加载完
+     
     }
 
     getWaitDeferred(size){//获取不同size的tile贴图的promiss 
@@ -371,30 +373,33 @@ class Panorama extends EventDispatcher{
         t.active = !1;
         t.deferred = $.Deferred();
     }
-    onTileRendered(e, t, i, n){
-        e === this.id && this.dispatchEvent({type:PanoramaEvents.TileLoaded,  size:t, index:i, count:n});
+    onTileRendered(ev){  
+        ev.id === this.id && this.dispatchEvent({
+            type:PanoramaEvents.TileLoaded, 
+            size:ev.panoSize, index:ev.tileIndex, count:ev.totalTiles
+        });
     }
 
-    onPanoRendered(e, t, i, n) {
-        if(e === this.id)
+    onPanoRendered(ev) { 
+        if(ev.id === this.id)  
         {
             this.minimumTiledPanoLoaded = !0;
             this.updateSkyboxForZoomLevel();//更新贴图 setProjected
-            t > this.highestPartialTileRenderOpCompleted && (this.highestPartialTileRenderOpCompleted = t);//应该是更新最高获取到的Partial size
-            !n && t > this.highestFullTileRenderOpCompleted && (this.highestFullTileRenderOpCompleted = t); //应该是更新最高获取到的Full size
-            //this.emit("load", t);
+            ev.panoSize > this.highestPartialTileRenderOpCompleted && (this.highestPartialTileRenderOpCompleted = ev.panoSize);//应该是更新最高获取到的Partial size
+            ev.updateFullComplete && ev.panoSize > this.highestFullTileRenderOpCompleted && (this.highestFullTileRenderOpCompleted = ev.panoSize); //应该是更新最高获取到的Full size
+            //this.dispatchEvent("load", ev.panoSize);
             viewer.ifAllLoaded( this);
-            this.dispatchEvent({type:PanoramaEvents.LoadComplete, size:t, count:i});
+            this.dispatchEvent({type:PanoramaEvents.LoadComplete, size:ev.panoSize, count:ev.totalTiles});
         }
     }
  
-    onTileRenderFail(e, t, i) {
-        e === this.id && this.dispatchEvent({type:PanoramaEvents.LoadFailed, t});
+    onTileRenderFail(ev) { 
+        ev.id === this.id && this.dispatchEvent({type:PanoramaEvents.LoadFailed   });
     }
-    onUploadAttemptedForAllTiles(e, t, i) {
-        if (e === this.id) {
+    onUploadAttemptedForAllTiles(ev) { 
+        if (ev.id === this.id) {        
             var n = this.images360.qualityManager.getPanoSize(PanoSizeClass.BASE);
-            if(t === n && this.shouldRedrawOnBaseLoaded) //shouldRedrawOnBaseLoaded一直是false。在4dkk里只有初始点在quickstart后变为true。
+            if(ev.panoSize === n && this.shouldRedrawOnBaseLoaded) //shouldRedrawOnBaseLoaded一直是false。在4dkk里只有初始点在quickstart后变为true。
             {
                 this.shouldRedrawOnBaseLoaded = !1;
                 this.panoRenderer.resetRenderStatus(this.id, !0, !1);
@@ -430,8 +435,9 @@ class Panorama extends EventDispatcher{
  
 
 Panorama.prototype.loadTiledPano = function() {
-    var downloads = []  , t = [];
-     
+    //var downloads = []  , t = [];
+    var downloaded = {}  , eventAdded = {}, latestPartialRequest = {}; //每个pano对应一组这些
+         
     return function(size, dirs, fov, o, a, download) {
         var dir = dirs.datasetsLocal.find(e=>e.datasetId == this.pointcloud.dataset_id).direction;
         //var dir = dirs
@@ -444,28 +450,47 @@ Panorama.prototype.loadTiledPano = function() {
           , h = null
           , u = null; 
         fov && ("number" == typeof fov ? h = fov : (h = fov.hFov, u = fov.vFov))  
+        
         if (!this.isLoaded(size)) {
+            //console.log('loadTiledPano', this.id, size, fov)
             if (!l.active) {
                 l.active = !0 
+                let name = this.id + ":" + size
+                downloaded[name] = downloaded[name] || []
+                /* 
+                this.downloaded = downloaded
+                this.latestPartialRequest = latestPartialRequest 
+                 */
+                latestPartialRequest[name] = null
+                     
                 if (fov) {
-                    var d = TileUtils.matchingTilesInDirection(this, size, dir, h, u);
-                    downloads[this.id + ":" + size] = {
-                        tileCount: 0,
-                        targetTileCount: d
-                    } 
-                    //console.log("Loading partial pano: " + this.id + " with " + d + " tiles")
-                }else{
-                    this.downloads =  this.downloads || {}
-                       
+                    let tileArr = []//add 
+                    var d = TileUtils.matchingTilesInDirection(this, size, dir, h, u, tileArr);
                     
+                    latestPartialRequest[name] = tileArr
+                    downloaded[name].forEach((e)=>{
+                         let item = latestPartialRequest[name].find(a=>e.faceTileIndex == a.faceTileIndex && e.face == a.face)  
+                         if(item){
+                             item.loaded = true
+                         }
+                    })
+                    if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的全部加载成功
+                        //let total = TileUtils.getTileCountForSize(size)
+                        //this.onPanoRendered(this.id, size, total, !0);
+                        c.resolve(size/* , total */);
+                        this.resetWaitDeferred(size)
+                        //console.log('该部分早已经加载好了'+size, this.id)
+                        latestPartialRequest[name] = null
+                    }
+                     
+                    //console.log("Loading partial pano: " + this.id + " with " + d + " tiles")
                 }
-                if(!t[this.id]) {
-                    t[this.id] = !0 
+                if(!eventAdded[this.id]) {
+                    eventAdded[this.id] = !0 
                     
                     this.addEventListener(PanoramaEvents.LoadComplete, function(ev/* e, t */) {//本次任务全部加载完毕 
                         
-                        console.warn('点位下载完成 ', 'id:'+this.id,  'size:'+ev.size )
-                        
+                        //console.warn('点位(可能部分)下载完成 ', 'id:'+this.id,  'size:'+ev.size ) 
                         
                         var i = this.getWaitDeferred(ev.size).deferred;//"pending"为还未完成
                         i && "pending" === i.state() && this.highestPartialTileRenderOpCompleted >= ev.size && (i.resolve(ev.size, ev.count),
@@ -479,11 +504,38 @@ Panorama.prototype.loadTiledPano = function() {
                     }.bind(this)) 
                     
                     this.addEventListener(PanoramaEvents.TileLoaded, function(ev/* t, i, n */) {//每张加载完时
-                       
-                        //console.log('tileLoaded', 'id:'+this.id,  'size:'+ev.size, 'index:'+ev.index )
                         
+                        //console.log('tileLoaded', 'id:'+this.id,  'size:'+ev.size, 'tileIndex:'+ev.index )
+                        let tileIndex = ev.index
+                        let total = ev.count
+                        let size = ev.size
+                        let name = this.id + ":" + size 
+                        downloaded[name] = downloaded[name] || [] //不是所有的加载都是从loadTiledPano获取的所以会有未定义的情况
+                        
+                        let {faceTileIndex,face} = TileUtils.getTileLocation(size, tileIndex, {}) 
+                        downloaded[name].push({faceTileIndex,face})    
+                        var r = this.getWaitDeferred(size).deferred;
+                        if (r && "pending" === r.state()) { 
+                            r.notify(size, tileIndex, total);
+                            if(latestPartialRequest[name]){
+                                let item = latestPartialRequest[name].find(e=>e.faceTileIndex == faceTileIndex && e.face == face)    
+                                item && (item.loaded = true ) 
+                                
+                                if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的局部tiles全部加载成功
+                                    this.onPanoRendered(this.id, size, total, !0); //onPanoRendered还会触发 PanoramaEvents.LoadComplete   
+                                    r.resolve(size, total);
+                                    this.resetWaitDeferred(size)
+                                    //console.log('该部分加载好了'+size, this.id)
+                                    latestPartialRequest[name] = null
+                                }
+                                
+                            } 
+                        } 
+
+
+    
                         
-                        var r = this.getWaitDeferred(ev.size).deferred;
+                        /* var r = this.getWaitDeferred(ev.size).deferred;
                         if (r && "pending" === r.state()) {
                             r.notify(ev.size, ev.index, ev.count);
                              
@@ -497,7 +549,7 @@ Panorama.prototype.loadTiledPano = function() {
                                     this.resetWaitDeferred(ev.size)
                                 }
                             }
-                        }
+                        } */
                     }.bind(this))
                 }
             }
@@ -505,6 +557,9 @@ Panorama.prototype.loadTiledPano = function() {
             this.images360.tileDownloader.forceQueueTilesForPano(this, size, dir, h, u, download) 
             this.tiledPanoRenderTarget = this.images360.panoRenderer.activateTiledPano(this, this.images360.qualityManager.getMaxNavPanoSize(), o) 
             this.images360.panoRenderer.renderPanoTiles(this.id, dirs, a)
+        }else{
+            //console.log('早已经全加载好了' +size, this.id)
+            c.resolve(size)
         }
         return c.promise()
     }

+ 42 - 29
src/modules/Images360/tile/PanoRenderer.js

@@ -7,8 +7,7 @@ import TilePrioritizer from './TilePrioritizer'
 import TileUtils from './TileUtils' 
 import {settings,config} from '../../../settings'
 /* import config from '../../config' */
-import * as THREE from "../../../../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "../../../EventDispatcher.js";
+import * as THREE from "../../../../libs/three.js/build/three.module.js"; 
 import math from '../../../utils/math' 
 
 function createDescriptor() {
@@ -48,7 +47,7 @@ var b = !1,
     }/* ,
     M = []; */
 
-class PanoRenderer extends EventDispatcher{
+class PanoRenderer extends THREE.EventDispatcher{
     constructor(viewer, tileDownloader, qualityManager) {
         super()
 		this.tileDirectory = {};
@@ -382,6 +381,7 @@ class PanoRenderer extends EventDispatcher{
             (!i || i && i === r.tile.panoId) && r.level >= t ? (r.uploadQueued = !1,
                 e.splice(n, 1)) : n++
         }
+        //若报错, r.tile.panoId改为 r.panoId
     }
 
         
@@ -483,7 +483,8 @@ class PanoRenderer extends EventDispatcher{
             if (t.hasOwnProperty(i)) {
                 var n = t[i];
                 n.uploadCount = 0,
-                    n.uploadAttempts = 0
+                n.uploadAttempts = 0  
+                n.uploaded = [] 
             }
     }
 
@@ -492,7 +493,8 @@ class PanoRenderer extends EventDispatcher{
             n = i[t];
         return n || (n = {
                     uploadCount: 0,
-                    uploadAttempts: 0
+                    uploadAttempts: 0, 
+                    uploaded:[],//add
                 },
                 i[t] = n),
             n
@@ -865,6 +867,7 @@ PanoRenderer.prototype.uploadTile = function () {//重写
             tileY = info.tileY,
             p = !0,
             g = !1,
+            ignore = false, //add   
             LodDescripor = (this.getPanoDescriptor(id), this.getPanoLODDescriptor(id, panoSize)),
             activeDescripor = this.getActiveRenderTargetDescriptor(id),
             renderTarget = activeDescripor.renderTarget,
@@ -876,7 +879,17 @@ PanoRenderer.prototype.uploadTile = function () {//重写
             size =  this.zoomRenderTarget.width   //this.qualityManager.getMaxZoomPanoSize(); //放大后可能2048或4096
         } 
         
-      
+        let done = ()=>{ 
+            if(!LodDescripor.uploaded.includes(tileIndex)){//已经upload过(本来这时候直接返回,但发现缩放后这不会归零,导致清晰度不更新,所以还是redraw且emit吧)
+                //console.log('try to reupload and return',tileIndex) 
+                LodDescripor.uploaded.push(tileIndex)
+                LodDescripor.uploadCount++;
+            }   
+            this.dispatchEvent({type:PanoRendererEvents.TileRenderSuccess, id, panoSize, tileIndex, totalTiles});
+            LodDescripor.uploadCount === totalTiles && this.dispatchEvent({type:PanoRendererEvents.PanoRenderComplete, id, panoSize, totalTiles, updateFullComplete:true});
+            this.setUploaded(info, !0);
+            this.addCoverageForNode(info.node);
+        }
         
         
         
@@ -885,18 +898,18 @@ PanoRenderer.prototype.uploadTile = function () {//重写
                 p = !1; g = !1
             } 
             if(!n){
-                this.anyUploaded(info.node) && (p = !1, g = !0) //包括子集也uploadTile了
-                this.isTileUploaded(info) && (p = !1, g = !1) //当前tile uploadTile了
+                this.anyUploaded(info.node) && (p = !1, g = !0,ignore = true  ) //包括子集也uploadTile了
+                this.isTileUploaded(info) && (p = !1, g = !1,ignore = true ) //当前tile uploadTile了 
             }
         }
         
         
         if (p) {
              
-             if(failHistory[''+id+ panoSize+ tileIndex]){
+            /*if(failHistory[id+':'+ panoSize+ ':' +tileIndex]){
                 console.log('uploadTile retry',id, panoSize, tileIndex)
             } 
-            /*console.log('uploadTile 成功', id, panoSize, tileIndex) */  
+            console.log('uploadTile 成功', id, panoSize, tileIndex) */  
 
             var C = tileX * tileSize,
                 I = tileY * tileSize,
@@ -926,29 +939,29 @@ PanoRenderer.prototype.uploadTile = function () {//重写
                     this.renderToCubeMap(tex, renderTarget, tileSize, tileSize, 0, 0, tileSize, tileSize, b, w, E, E, info.cubeFace);
                 }
             }
+            done()
+           
+        }else if(ignore){ 
+            //console.log('finish because anyUploaded',id,panoSize,tileIndex)
+            done() //改: 如果因为这部分更高清的贴图已加载所以才不绘制的话,直接完成
             
-            LodDescripor.uploadCount++;
-            this.emit(PanoRendererEvents.TileRenderSuccess, id, panoSize, tileIndex, totalTiles);
-            LodDescripor.uploadCount === totalTiles && this.emit(PanoRendererEvents.PanoRenderComplete, id, panoSize, totalTiles);
-            this.setUploaded(info, !0);
-            this.addCoverageForNode(info.node);
-        } else {
+        }else{
             //console.log('uploadTile  失败', id, panoSize, tileIndex)
             if(panoSize == 512){
-                console.log("512 失败!!!!!!!!!!!!!")
+                //console.log("!!!!!!!!!!!!!")
             } 
-            failHistory[''+id+ panoSize+ tileIndex] = true; 
-            
-            
-            this.setUploaded(info, !1);
-            
-            //发现如果不预加载512,512很可能在1024加载了之后才加载,然后g = true,就不会发送TileRenderSuccess,导致该pano一直loading。但现在应该很难出现这种情况
-            
             
+            failHistory[id+':'+ panoSize+ ':' +tileIndex] = true;   
+            this.setUploaded(info, !1); 
         }
-        info.uploadAttempted || (LodDescripor.uploadAttempts++, this.emit(PanoRendererEvents.TileUploadAttempted, id, panoSize, tileIndex, totalTiles)),
-            info.uploadAttempted = !0;
-        LodDescripor.uploadAttempts === totalTiles && this.emit(PanoRendererEvents.UploadAttemptedForAllTiles, id, panoSize, totalTiles);
+
+
+
+
+
+        info.uploadAttempted || (LodDescripor.uploadAttempts++, this.dispatchEvent({type:PanoRendererEvents.TileUploadAttempted, id, panoSize, tileIndex, totalTiles})),
+        info.uploadAttempted = !0;
+        LodDescripor.uploadAttempts === totalTiles && this.dispatchEvent({type:PanoRendererEvents.UploadAttemptedForAllTiles, id, panoSize, totalTiles});
         return g;
     }
 }()
@@ -975,7 +988,7 @@ PanoRenderer.prototype.renderToCubeMap = function() {
         plane = null,
         l = 1;
     return function(texture, renderTarget, tileWidth, tileHeight, startXinTile, startYinTile, widthinTile, heightinTile, startX, startY, width, height, cubeFace, E, b, w) {
-        
+         
           
         var renderer =  this.viewer.renderer; 
         
@@ -1070,7 +1083,7 @@ PanoRenderer.prototype.renderToCubeMap = function() {
         renderer.autoClear = !1
         
         
-        
+         
          
         
         renderer.setRenderTarget(renderTarget, cubeFace);

+ 19 - 27
src/modules/Images360/tile/TileDownloader.js

@@ -8,8 +8,7 @@ import TileUtils from './TileUtils'
 import {settings, config} from '../../../settings' 
 import {
     http
-} from '../../../utils/request'
-import { EventDispatcher } from "../../../EventDispatcher.js";
+} from '../../../utils/request' 
 
 
 
@@ -19,7 +18,7 @@ window.startdownloads = [];
 
 
 
-class TileDownloader extends EventDispatcher{
+class TileDownloader extends THREE.EventDispatcher{
     constructor( ) {
         super()
         this.panos = null;
@@ -43,9 +42,9 @@ class TileDownloader extends EventDispatcher{
         
         this.visible = true //add   借用viewer.updateVisible来判断是否start
          
-        viewer.on('pageVisible', (state)=>{//不可见时不refreshUpdateInterval 
+        viewer.addEventListener('pageVisible', (e)=>{//不可见时不refreshUpdateInterval 
             //console.log('visibilitychange:', state)
-            viewer.updateVisible(this,  'pageVisible', state) 
+            viewer.updateVisible(this,  'pageVisible', e.v) 
             this.judgeStart() 
         }) 
          
@@ -339,7 +338,7 @@ class TileDownloader extends EventDispatcher{
     
 
     getTiles(d, sceneNum){
-        return `https://4dkk.4dage.com/images/images${sceneNum}/${d}`    
+        return `${Potree.settings.urls.prefix3}/images/images${sceneNum}/${d}`    
     }
 
     loadImage(e, t, i, n) {
@@ -419,7 +418,7 @@ TileDownloader.prototype.getTileUrl = function() {
             panoSize = o.panoSize,
             tileSize = o.tileSize,
             tileIndex = o.tileIndex,
-            datasetName = o.pano.pointcloud.name
+            sceneCode = o.pano.pointcloud.sceneCode
         var metadata = {sceneScheme:10}  
         
         
@@ -429,9 +428,17 @@ TileDownloader.prototype.getTileUrl = function() {
             h = Math.floor(tileIndex / l),
             u = "",
             d = '',  g = '';
-        1 === config.tiling.customCompression && (u = "_" + config.tiling["q" + e[panoSize]]);
-         
-        /* if (metadata.sceneScheme == 10)  */{//阿里云oss的规则
+        
+        
+        
+        if(Potree.settings.isLocal){//原始规则
+            //1 === config.tiling.customCompression && (u = "_" + config.tiling["q" + e[panoSize]]);
+            //1 === o.tiling.customCompression && (u = "_" + o.tiling["q" + e[n]]);
+            d = "tiles/" + id + "/" + e[panoSize] + u + "_face" + h + "_" + t.tileX + "_" + t.tileY + ".jpg" 
+            d =  this.getTiles(d, sceneCode);
+            g = "?"  
+          
+        }else{//阿里云oss的规则   if (metadata.sceneScheme == 10) 
             
             d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
             if (e[panoSize] == '512') {
@@ -454,25 +461,10 @@ TileDownloader.prototype.getTileUrl = function() {
                     d += 'y_0';
                 } else {
                     d += 'y_' + (512 * t.tileY - 1);
-                }
-                /* 
-                if (t.tileX == 0) {
-                    d += 'x_1,';
-                } else {
-                    d += 'x_' + (512 * t.tileX) + ',';
-                }
-
-                if (t.tileY == 0) {
-                    d += 'y_1';
-                } else {
-                    d += 'y_' + (512 * t.tileY);
-                } */
-                
-                
-                
+                } 
             }
             
-            d = this.getTiles(d, datasetName);
+            d = this.getTiles(d, sceneCode);
             g = "&" 
         } 
         

+ 1 - 0
src/modules/Images360/tile/TileUtils.js

@@ -111,6 +111,7 @@ TileUtils.getTileLocation = function(size, t, result) {
     result.tileY = Math.floor(l / a);
     result.face = r;
     result.faceTileIndex = l;
+    return result
 }
 ,
 

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

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

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

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

+ 137 - 13
src/modules/clipModel/Clip.js

@@ -3,14 +3,30 @@ import {BoxVolume} from '../../objects/tool/Volume'
 import {  ClipTask, ClipMethod} from "../../defines.js"
 import {mapClipBox} from '../../objects/tool/mapClipBox'
 import Common from '../../utils/Common'
-import {Images360} from '../Images360/Images360'
-
+import math from '../../utils/math' 
+import {Images360} from '../Images360/Images360' 
 
 const defaultBoxWidth = 6;  //navvis:  10
-                           //navvis position: si {x: 0, y: 0, z: 0}
-
-var Clip = {
+                            //navvis position: si {x: 0, y: 0, z: 0}
    
+var Clip = {
+    bus : new THREE.EventDispatcher,
+    selectedDatasets : [],    
+    changeCallback(force){ 
+        if(Potree.settings.isOfficial){  
+            Common.intervalTool.isWaiting('clipSelectedDatasets', ()=>{ //延时update,防止卡顿
+                let pointclouds = this.getIntersectPointcloud()
+                let selectedDatasets = pointclouds.map(e=>e.dataset_id) 
+                if(force || Common.getDifferenceSet(selectedDatasets,this.selectedDatasets).length){  
+                    this.selectedDatasets = selectedDatasets 
+                    //console.error('clipSelectedDatasets',selectedDatasets)
+                    this.bus.dispatchEvent({type:'updateSelectedDatasets', selectedDatasets})
+                    return true 
+                } 
+            },  300)  
+        }
+    },
+
     enter:function(){
         this.previousView = { 
 			position: viewer.images360.position,
@@ -20,11 +36,16 @@ var Clip = {
             ifShowMarker : Potree.settings.ifShowMarker,
             
 		} 
-        let bound = viewer.scene.pointclouds[0].bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。
+
+        let pointcloud = this.getPointcloud() 
+        let bound = pointcloud.bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。
         let boundSize = bound.getSize(new THREE.Vector3())
         let target = this.getTarget(bound.getCenter(new THREE.Vector3())); //navvis的位置xy是用相机位置 this.ViewService.mainView.getCamera().position  我觉得也可以用第一个漫游点的,或者最接近bound中心的漫游点
         let scale = new THREE.Vector3(defaultBoxWidth,defaultBoxWidth, boundSize.z)//z和navvis一样
-        let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3) 
+        
+        let eyeDir = viewer.scene.view.direction.clone().setZ(-boundSize.z/3).multiplyScalar(-defaultBoxWidth) 
+
+        //let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3) 
         let position = new THREE.Vector3().addVectors(target, eyeDir)
         
         Potree.settings.displayMode = 'showPointCloud'
@@ -53,21 +74,24 @@ var Clip = {
                 this.mapBox.center.setX(this.box.position.x)
                 this.mapBox.center.setY(this.box.position.y)
                 this.mapBox.updatePoints() 
+                this.changeCallback()
             })
             this.box.addEventListener('scale_changed',e=>{
                 var scale = this.box.scale 
-                this.mapBox.updatePoints(scale) 
+                this.mapBox.updatePoints(scale)
+                this.changeCallback()
             })
             this.box.addEventListener('orientation_changed',e=>{
                 this.mapBox.angle = this.box.rotation.z
                 this.mapBox.rotateBar.rotation.z = this.mapBox.angle
                 this.mapBox.updatePoints()
+                this.changeCallback()
             })
             viewer.scene.addVolume(this.box);
             
         }
         
-        {
+        {//map
             let boxRotateBack = ()=>{//不知道是不是这么写。 因为可能z的旋转不一定都在z 
                 this.box.rotation.x = 0;
                 this.box.rotation.y = 0;
@@ -79,6 +103,7 @@ var Clip = {
                 this.box.position.setX(this.mapBox.center.x)
                 this.box.position.setY(this.mapBox.center.y)
                 boxRotateBack()
+                this.changeCallback()
             })
             this.mapBox.addEventListener('dragChange',e=>{
                 var scale = this.mapBox.getScale() 
@@ -87,10 +112,12 @@ var Clip = {
                 this.box.position.setX(this.mapBox.center.x)
                 this.box.position.setY(this.mapBox.center.y)
                 boxRotateBack()
+                this.changeCallback()
             })
             this.mapBox.addEventListener('rotate',e=>{ 
                 this.box.rotation.z = this.mapBox.angle 
                 boxRotateBack()
+                this.changeCallback()
             })
         }
         
@@ -104,11 +131,29 @@ var Clip = {
         
         Potree.settings.unableNavigate = true
         Potree.settings.ifShowMarker = false
-        viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', false)  
+        viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', false)   
+        viewer.updateVisible(viewer.mapViewer.cursor, 'clipModel', false)//隐藏地图游标
         viewer.inputHandler.toggleSelection(this.box);
         viewer.inputHandler.fixSelection = true
         viewer.transformationTool.frame.material.color.set(Potree.config.clip.color)//navvis 15899953 
         viewer.setPointStandardMat(true) 
+        
+        {
+            this.events = {
+                flyToDataset : ()=>{
+                    
+                },
+                focusEntity : ()=>{
+                    
+                },
+            }
+            
+            this.bus.addEventListener('flyToDataset',this.events.flyToDataset)
+            this.bus.addEventListener('focusEntity',this.events.focusEntity)
+        }
+        this.editing = true
+        
+        setTimeout(()=>{this.changeCallback(true)},1)
     },
     
     leave:function(){
@@ -121,18 +166,49 @@ var Clip = {
         Potree.settings.unableNavigate = false
         Potree.settings.ifShowMarker = this.previousView.ifShowMarker
         viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', true)  
-        
+        viewer.updateVisible(viewer.mapViewer.cursor, 'clipModel', true) 
         viewer.setView(this.previousView)
         viewer.setLimitFar(true)
         viewer.setPointStandardMat(false) 
+        
+        
+        {
+            this.bus.removeEventListener('flyToDataset',this.events.flyToDataset)
+            this.bus.removeEventListener('focusEntity',this.events.focusEntity)
+            this.events = null 
+        }
+        this.editing = false
     },
     
+
+
+    getPointcloud:function(){ //找一个离当前最近的点云,且最好有漫游点
+        let pointclouds = viewer.scene.pointclouds.filter(e=>e.panos.length>0)
+        if(pointclouds.length == 0)pointclouds = viewer.scene.pointclouds;
+
+
+        let result = Common.sortByScore(pointclouds,[],[e=>{
+            let center = e.bound.getCenter(new THREE.Vector3)
+            let size = e.bound.getSize(new THREE.Vector3).length() / 2 
+            let posToCenter = viewer.images360.position.distanceTo(center)
+            return size / posToCenter 
+        }])
+        
+        return result[0].item
+    },
+    
+
     getTarget:function(boundCenter){//box位置。要找一个有点云的地方。方案1相机位置, 方案2接近相机的漫游点, 方案3接近中心的漫游点。选择方案2,因最大概率有点云
         var target = new THREE.Vector3()
         var cameraPos = viewer.images360.position;
         var pano = Common.find(viewer.images360.panos , [], [Images360.sortFunctions.floorDistanceToPoint(cameraPos)]);
-        target.copy(pano.position) 
-        target.setZ(boundCenter.z)
+        if(pano){
+            target.copy(pano.position) 
+            target.setZ(boundCenter.z)
+        }else{
+            target.copy(boundCenter)
+        }
+        
         return target
     },
     /* switchMap:function(state){
@@ -161,6 +237,54 @@ var Clip = {
     getTransformationMatrix:function(pointcloud) {//剪裁矩阵
         var invMatrix = new THREE.Matrix4().getInverse(this.box.matrixWorld) 
         return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose()
+    },
+
+
+    getIntersectPointcloud(){ 
+        var boxBound = new THREE.Box3(
+            new THREE.Vector3(-0.5,-0.5,-0.5), new THREE.Vector3(0.5,0.5,0.5),
+        ).applyMatrix4(this.box.matrixWorld)    //large boundingbox
+        
+       /*  var boxTightPoints = this.box.children[0].geometry.vertices.map(e=>e.clone().applyMatrix4(this.matrixWorld)) 
+            console.log(boxTightPoints) 
+        */
+        
+        let boxMatrixInverse = new THREE.Matrix4().copy(this.box.matrixWorld).invert();
+
+        let boxPoints = [
+            new THREE.Vector3(boxBound.min.x, boxBound.min.y,0),
+            new THREE.Vector3(boxBound.max.x, boxBound.min.y,0),
+            new THREE.Vector3(boxBound.max.x, boxBound.max.y,0),
+            new THREE.Vector3(boxBound.min.x, boxBound.max.y,0)
+        ]
+
+        var intersect = (pointcloud)=>{
+        
+            if(!pointcloud.bound.intersectsBox(boxBound))return false
+            //判断box和点云的tight bound是否相交(因为box可以任意旋转,且实在找不到三维中的立方体相交的函数,所以直接用boxBound) 
+            var points = pointcloud.getUnrotBoundPoint('all') 
+            let rings = math.getPolygonsMixedRings([points.slice(0,4), boxPoints] , true) 
+            //console.log(pointcloud.dataset_id, pointcloud.name, rings.length) 
+            if(rings.length > 1 )return false
+
+            {//再用frustum和数据集的sphere相交试试,能排除一些错误
+                let a = Potree.Utils.isInsideBox(points,  boxMatrixInverse) 
+                if(!a){
+                    console.log('没能经过isInsideBox测试')
+                }
+                return a
+            }
+            return true 
+            
+        }
+
+        
+
+
+        return viewer.scene.pointclouds.filter(e=>intersect(e)) 
+        
+       
+
     }
 }
 

+ 18 - 6
src/modules/datasetAlignment/Alignment.js

@@ -2,27 +2,33 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import SplitScreen from "../../utils/SplitScreen"
 import math from "../../utils/math"
-
-
+import {EventDispatcher} from "../../EventDispatcher.js";
+ 
 
 var Alignment = {
     SplitScreen, 
     handleState:null,  //操作状态 'translate'|'rotate'
+    bus: new THREE.EventDispatcher(), 
     init:function(){ 
         let rotateInfo  
         
         viewer.fpControls.addEventListener("transformPointcloud",(e)=>{ 
+            if(e.pointcloud.dataset_id == Potree.settings.originDatasetId){//禁止手动移动初始数据集
+                return this.bus.dispatchEvent('forbitMoveOriginDataset') 
+            }
+ 
+        
             if(this.handleState == 'translate'){
                 Alignment.translate(e.pointcloud,e.moveVec)
             }else if(this.handleState == 'rotate'){
                  
                 let center = e.pointcloud.translateUser //移动到的位置就是中心
-                if(!rotateInfo){
+                if(!rotateInfo){  
                     rotateInfo = {
                         orientationUser : e.pointcloud.orientationUser,
                         vecStart : new THREE.Vector3().subVectors(e.intersectStart, center).setZ(0),
                         pointcloud: e.pointcloud
-                    } 
+                    }  
                 }else{ 
                     let vec = new THREE.Vector3().subVectors(e.intersectPoint, center).setZ(0)
                     let angle = math.getAngle(rotateInfo.vecStart,vec,'z')   
@@ -72,7 +78,13 @@ var Alignment = {
         
         if(pointcloud.spriteNodeRoot){
             pointcloud.spriteNodeRoot.matrixWorld.copy(pointcloud.matrixWorld)//.multiplyMatrices(pointcloud.matrixWorld, pointcloud.matrixWorld);	
-        }
+        } 
+
+        //viewer.updateModelBound();
+        pointcloud.updateBound()
+         
+
+
     },
     rotate:function(pointcloud, deg, angle){//假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
         var angle = angle != void 0 ? angle : THREE.Math.degToRad(deg)   //正逆负顺
@@ -148,7 +160,7 @@ var Alignment = {
         let callback = ()=>{//保存成功后
             this.saveTemp();
             //需要修改 测量线的position。漫游点已经实时修改了
-            viewer.updateModelBound();
+            
             viewer.scene.measurements.forEach(e=>e.transformByPointcloud())
             viewer.images360.updateCube(viewer.bound)
         }

+ 60 - 20
src/modules/siteModel/BuildingBox.js

@@ -112,7 +112,7 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         
         if(this.buildType=='floor'){
             
-            this.points = this.buildParent.points;//完全等于建筑的点
+            this.points = prop.points = this.buildParent.points;//完全等于建筑的点
             this.buildParent.holes.forEach(hole=>{//从building获取holes
                 let floorHole = new BuildingBox({
                     buildType : 'hole', 
@@ -145,7 +145,7 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
             
             
             this.addEventListener('dragChange',(e)=>{ //修改中点
-                this.isNew || this.updateTwoMidMarker(e.index)
+                this.updateTwoMidMarker(e.index)
             }) 
             
         }
@@ -205,7 +205,7 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
                   
                 let holes_ = holes.map(e=>e.points)
                 
-                outHoles = math.getPolygonsMixedRings(holes_,  true)
+                outHoles = math.getPolygonsMixedRings(holes_,  true )
                 outHoles.forEach(e=>{ 
                     holesArea+=e.area
                 }) 
@@ -338,6 +338,21 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         
         if(!this.selected)viewer.updateVisible(marker,'select',false) 
          
+        let addClickEvent = (e)=>{ 
+            let click = (e) => {   
+                this.dispatchEvent({type:'clickMarker', marker } )  //由entity发送给sitemodel统一处理
+            }; 
+            marker.addEventListener('click', click); 
+            marker.addEventListener('clickSelect', (e)=>{
+                 this.setMarkerSelected(marker, e.state ? 'select' : 'unselect' );  
+            }); 
+            marker.removeEventListener('addHoverEvent',addClickEvent) 
+        }
+        marker.addEventListener('addHoverEvent',addClickEvent)//当非isNew时才添加事件
+        if(!this.isNew){
+            marker.dispatchEvent('addHoverEvent')
+        }
+     
         return marker
     }
     
@@ -368,23 +383,23 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         viewer.setObjectLayers(marker, 'siteModeOnlyMapVisi' ) 
         { // Event Listeners  
             let mouseover = (e) => {
-                this.setMarkerSelected(e.object, true, 'single');
+                this.setMarkerSelected(e.object, 'hover', 'single'); 
                 viewer.dispatchEvent({
                     type : "CursorChange", action : "add",  name:"markerMove"
                 }) 
             };
             let mouseleave = (e) => {
-                this.setMarkerSelected(e.object, false, 'single');
+                this.setMarkerSelected(e.object, 'unhover', 'single');
                 viewer.dispatchEvent({
                     type : "CursorChange", action : "remove",  name:"markerMove"
                 })
             }
             let drag = (e) => {
                 let index = this.midMarkers.indexOf(marker)
-                let newMarker = this.addMarker({index:(index+1), point:marker.position.clone() }) 
+                let newMarker = this.addMarker({index:(index+1), point:marker.position.clone()  }) 
                 this.addMidMarker(index+1, new THREE.Vector3 )
                 this.updateTwoMidMarker(index+1) 
-                this.setMarkerSelected(marker, false) 
+                this.setMarkerSelected(marker, 'unhover') 
                 viewer.inputHandler.startDragging(newMarker , {/* dragViewport:viewer.mapViewer.viewports[0],  */   } ); //notPressMouse代表不是通过按下鼠标来拖拽.  dragViewport指定了只能在地图上拖拽
             }
             marker.addEventListener('drag', drag );
@@ -412,6 +427,7 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
     }
     
     updateTwoMidMarker(index){//更新第index个marker两边的midMarker
+        if(!this.midMarkers.length)return
         let length = this.points.length
         let last = this.points[(index-1+length)%length] //它之前的marker位置
         let next = this.points[(index+1)%length];//它之后的marker位置
@@ -434,7 +450,8 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
         this.lineMesh && this.lineMesh.geometry.dispose();
         this.holes.forEach(e=>e.dispose()) 
         this.parentHoles.forEach(e=>e.dispose()) 
-        this.buildChildren.forEach(e=>e.dispose())
+        //this.buildChildren.forEach(e=>e.dispose())
+        this.dispatchEvent('dispose')
     }
     
     
@@ -560,8 +577,8 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
             //building的zMax和zMin一样的所以要算
             let top = this.buildChildren[this.buildChildren.length - 1]
             let btm = this.buildChildren[0] 
-            if(btm) zMin = btm.zMin 
-            if(top)zMax = top.zMax 
+            zMin = btm ? btm.zMin : 0  //建好的建筑不加楼的话是0
+            zMax = top ? top.zMax : 0
         }else if(this.buildType == 'hole'){
             return this.buildParent.getRealZ()
         }else{
@@ -594,7 +611,8 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
          
         let {zMin , zMax} = this.getRealZ()
         
-        this.points.forEach(p=>{
+        let points = this.buildType == 'floor' ? this.buildParent.points : this.points
+        points.forEach(p=>{
             bound.expandByPoint(p.clone().setZ(zMin))
             bound.expandByPoint(p.clone().setZ(zMax))
         }) 
@@ -620,13 +638,21 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
                     map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
                     depthTest:false,
                 }),  
-                select:    new THREE.MeshBasicMaterial({   
+                hover:    new THREE.MeshBasicMaterial({   
                     transparent: !0,
                     color,
                     opacity: 1,
                     map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
                     depthTest:false,
                      
+                }),
+                select:    new THREE.MeshBasicMaterial({   
+                    transparent: !0,
+                    color:new THREE.Color('#00C8AF'),
+                    opacity: 1,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
+                    depthTest:false,
+                     
                 }),   
             }
             
@@ -638,14 +664,26 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
     
     setMarkerSelected(marker, state, hoverObject){ 
         //console.warn(marker.id , state, hoverObject)
-        if(state){
+         
+        
+        if(state == 'select'){
+            marker.selected = true
             marker.material = this.getMarkerMaterial('select')
+        }else if(state == 'unselect'){
+            marker.selected = false
+            marker.material = this.getMarkerMaterial('default')
         }else{
-            if(marker.name.includes('mid')){
-                marker.material = this.getMarkerMaterial('midPrepare')
-            }else{
-                marker.material = this.getMarkerMaterial('default')
-            } 
+            if(marker.selected)return //选中时不允许修改为除了'unselect'以外的状态
+            
+            if(state == 'hover'){
+                marker.material = this.getMarkerMaterial('hover')
+            }else if(state == 'unhover'){
+                if(marker.name.includes('mid')){
+                    marker.material = this.getMarkerMaterial('midPrepare')
+                }else{
+                    marker.material = this.getMarkerMaterial('default')
+                } 
+            }
         }
     }
     
@@ -666,7 +704,7 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
          */  //注:自己的就代表定包括hole,如果有parentHoles的也(building上的hole的对应)
         
         
-        
+        //console.log('select '+this.name,   this.selected)
         
         if(this.selected)return
         
@@ -705,12 +743,14 @@ export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, fl
     
     
     unselect(){
+        if(!this.selected)return
+        console.log('unselect '+this.name  )
         if(this.box){
             this.box.material = this.mats.boxDefault;
         }
         
         if(this.buildType == 'building' || this.buildType == 'floor'){ 
-            this.buildChildren.forEach(e=>{
+            this.buildChildren.forEach(e=>{  //(这里要保证选中前要先取消选中,否则如选中房间后取消了楼层,房间线就隐藏了)
                 e.lineMesh.visible = false
             })  
             

+ 458 - 191
src/modules/siteModel/SiteModel.js

@@ -3,19 +3,24 @@ import SplitScreen from "../../utils/SplitScreen"
 import {BuildingBox} from "./BuildingBox"
 import Common from "../../utils/Common.js";
 import {Images360} from '../Images360/Images360'
-
-
-
-
+import {KeyCodes} from '../../KeyCodes' 
+import {config } from "../../settings.js";
+import math from "../../utils/math.js";
 const minFloorHeight = 0.5
 const ifDrawDatasetBound = true //显示一下数据集的tightBound线框
+const minMarkers = 3
+
+
+const Limit = {zMin:-config.map.cameraHeight,  zMax:config.map.cameraHeight,} //不能超过camera的高度,为了对称所以也限制了最低
 
 var SiteModel = {
-    
+    bus: new THREE.EventDispatcher(), 
     entities:[],  //所有实体
     buildings:[], //所有建筑父集
     meshGroup: new THREE.Object3D,
-     
+    inEntity : null,
+    lastPos: new THREE.Vector3(Infinity,Infinity,Infinity),
+    
     
     init: function(){
         
@@ -26,9 +31,9 @@ var SiteModel = {
         
         this.createHeightPull();
         
-        
         if(Potree.settings.isTest && ifDrawDatasetBound){
-            viewer.on('allLoaded',()=>{ 
+            viewer.addEventListener('allLoaded',()=>{
+ 
                 viewer.scene.pointclouds.forEach(pointcloud=>{
                     let boxPoints = pointcloud.getUnrotBoundPoint();
                       
@@ -46,44 +51,111 @@ var SiteModel = {
                 })   
             })
         }
+
+
         if(Potree.settings.isOfficial){
-            let lastPos = new THREE.Vector3
-            let lastEntity
+             
             viewer.addEventListener('camera_changed', e => {
-                if(!this.entities.length || this.editing) return
-                Common.intervalTool.isWaiting('sitemodelCameraInterval', ()=>{ //延时update,防止卡顿
-                    let currPos = viewer.scene.getActiveCamera().position
-                 
-                    if(!currPos.equals(lastPos)){
-                        lastPos.copy(currPos)
-                        let entity;
-                        if(Potree.settings.displayMode == 'showPanos'){
-                            entity = this.entities.find(e=>e.panos.includes(viewer.images360.currentPano))
-                            if(!entity)console.log('没找到entity')
-                        }
-                        if(!entity){
-                            entity = this.pointInWhichEntity(currPos, 'room');
-                        }
+                this.updateEntityAt()
+            })
+
+
+
+            /* viewer.addEventListener('allLoaded',()=>{
+                viewer.images360.panos.forEach(pano=>{//初始化为不可见
+                    viewer.updateVisible(pano, 'buildingChange', false)  
+                }) 
+            }) */
+        }
+        
+        
+        { 
+            let pressDelete = (e)=>{ 
+                if(e.keyCode == KeyCodes.BACKSPACE || e.keyCode == KeyCodes.DELETE){ 
+                    if(this.selectedMarker){
+                        let entity = this.selectedMarker.parent
+                        let index = entity.markers.indexOf(this.selectedMarker) 
+                        entity.removeMarker(index) 
                         
-                        if(lastEntity != entity ){
-                            console.log('buildingChange', entity)
-                            entity && Potree.sdk.scene.emit('buildingChange', entity.polygon)
-                            lastEntity = entity
+                        if(entity.points.length<2){//删到只剩一个点时重新画(如果是hole的点,直接删除hole吧?)
+                            this.startInsertion('resume',entity)       
                         }
-                        return true 
                     }
-                }, 1000)  
-            })
+                }
+            } 
+            viewer.inputHandler.addEventListener('keydown', pressDelete) 
+            
+            
         }
         
-    },
+        
+    }, 
     
+    updateEntityAt(force){
+        if(!this.entities.length || this.editing) return
+        Common.intervalTool.isWaiting('sitemodelCameraInterval', ()=>{ //延时update,防止卡顿
+            let currPos = viewer.scene.getActiveCamera().position
+         
+            if(force || !currPos.equals(this.lastPos)){
+                this.lastPos.copy(currPos)
+                let entity;
+                
+                let searchPos = Potree.settings.displayMode == 'showPanos' ? viewer.images360.currentPano : currPos
+                entity = this.pointInWhichEntity(searchPos, 'room');
+ 
+                if(force || this.inEntity != entity ){
+                    console.log('buildingChange', entity) 
+                    this.bus.dispatchEvent({type:'buildingChange',entity})
+                     
+                    this.updatePanosVisible(this.inEntity, entity)
+                    this.inEntity = entity
+                }
+                force = false
+                return true 
+            }
+        }, 500)  
+
+        
+        
+    },
+
+    updatePanosVisible(lastEntity, entity, force){//根据所在楼层更新marker可见性。当在楼层中时,只显示当前楼层的marker。
+        if(!entity ){//暂定:不在任何一个实体中时显示全部漫游点
+            viewer.images360.panos.forEach(pano=>{
+                viewer.updateVisible(pano, 'buildingChange', true)  
+            })
+        }else{
+            let lastFloor = lastEntity ? lastEntity.buildType == 'floor' ? lastEntity : lastEntity.buildType == 'room' ? lastEntity.buildParent : null : null; //基本只会是floor或room
+            let currentFloor = entity ? entity.buildType == 'floor' ? entity : entity.buildType == 'room' ? entity.buildParent : null : null; //基本只会是floor或room
+            if(currentFloor != lastFloor || force){
+                console.log('改变了floor',lastFloor,currentFloor)
+                if(lastFloor){
+                    lastFloor.panos.forEach(pano=>{
+                        viewer.updateVisible(pano, 'buildingChange', false)  
+                    })
+                }else{//重置为全部不可见
+                    viewer.images360.panos.forEach(pano=>{
+                        viewer.updateVisible(pano, 'buildingChange', false)  
+                    })
+                }
+                if(currentFloor){
+                    currentFloor.panos.forEach(pano=>{
+                        viewer.updateVisible(pano, 'buildingChange', true)  
+                    })
+                }
+
+            }
+        } 
+    },
+
+
+
     enter:function(){
         
         Potree.Log('sitemodel enter')
         this.clear()  //确保全部清空
         this.editing = true
-        
+        this.updatePanosVisible(null, null, true)//show all
         
         let mapViewport = viewer.mapViewer.viewports[0]
         SplitScreen.splitScreen4Views({siteModel:true/* , viewports:[{name:'Top',viewport : mapViewport  }] */})
@@ -117,9 +189,18 @@ var SiteModel = {
         
         let mapViewport = viewer.mapViewer.viewports[0]
         SplitScreen.recoverFrom4Views()
-        
+
+        viewer.viewports.forEach(e=>{
+            if(e.name != 'mapViewport'){
+                e.layersRemove('siteModelMapUnvisi') 
+            } 
+            if(e.name == 'Right' || e.name == 'Back'){
+                e.layersRemove('siteModeSideVisi') 
+            }
+        })
+
         viewer.images360.panos.forEach(pano=>{
-            viewer.setObjectLayers(pano.marker, 'mapObjects' ) 
+            viewer.setObjectLayers(pano.marker, 'sceneObjects' ) 
         })
         
         mapViewport.layersRemove('siteModeOnlyMapVisi') 
@@ -172,10 +253,12 @@ var SiteModel = {
         this.entities.push(floor) */
         floor.update()
         this.addEntity(floor,parent)
-        this.selectEntity(floor)
-        
-        this.updateBuildingZ(parent)
-        
+        //this.selectEntity(floor)
+        if(this.selected == parent){//重新选择下,为了显示新楼层线框
+            parent.unselect()  
+            parent.select()    
+        }
+ 
         return floor
     },
      
@@ -183,43 +266,64 @@ var SiteModel = {
     
     startInsertion:function(buildType, parent, sid, name, callback, cancelFun){
         
-        let zMin, zMax
-        
-        if(buildType == 'hole' || buildType == 'room'){
-            zMin = parent.zMin
-            zMax = parent.zMax 
-        }else if(buildType == 'building'){ 
-            parent = null
-            zMin = viewer.bound.boundingBox.min.z
-            zMax = viewer.bound.boundingBox.min.z
-        } 
-        let minMarkers = 3
+        let zMin, zMax, entity, resume 
         let mapViewport = viewer.mapViewer.viewports[0]
-       
-        let entity    
-        if(buildType == 'hole'){ 
-            entity = parent.addHole()
-            this.selectEntity(parent)
-            entity.select()
-            console.log('挖洞 ',entity.uuid)
-        }else{ 
+         
+        if(buildType == 'resume'){//继续画(使用最后一个点或者新加的点)
+            resume = true
+            entity = parent
+            buildType = parent.buildType
+             
+            //删除原先所有的点,因为它们已经添加了事件,会很麻烦:
+            entity.reDraw(0)
+            entity.isNew = true //当作新的来画
+        }
+        
+        if(!resume){
+            if(buildType == 'hole' || buildType == 'room'){
+                zMin = parent.zMin
+                zMax = parent.zMax 
+            }else if(buildType == 'building'){ 
+                parent = null
+                zMin = viewer.bound.boundingBox.min.z
+                zMax = viewer.bound.boundingBox.min.z
+            } 
         
-            let prop = { 
-                buildType,
-                //name : Potree.config.siteModel.names[buildType],//'building',
-                zMin, 
-                zMax,
-                buildParent:parent,
-                sid, name,
-                ifDraw:true
+        
+       
+           
+            if(buildType == 'hole'){ 
+                entity = parent.addHole()
+                entity.isNew = true
+                this.selectEntity(parent)
+                entity.select()
+                console.log('挖洞 ',entity.uuid)
+            }else{ 
+            
+                let prop = { 
+                    buildType,
+                    //name : Potree.config.siteModel.names[buildType],//'building',
+                    zMin, 
+                    zMax,
+                    buildParent:parent,
+                    sid, name,
+                    ifDraw:true
+                }
+                   
+                entity = new BuildingBox(prop); 
+                entity.isNew = true   
+                this.selectEntity(entity)
             }
-               
-            entity = new BuildingBox(prop); 
-            this.selectEntity(entity)
-        }
         
         
-        entity.isNew = true   
+            
+            this.addEntity(entity, parent)
+              
+        
+        
+        
+        }
+        
         
          
         
@@ -229,7 +333,7 @@ var SiteModel = {
         let endDragFun = (e) => {  
             if (e.button == THREE.MOUSE.LEFT ) { 
                 var marker = entity.addMarker({point:entity.points[entity.points.length - 1].clone()})
-                  
+                   
                 //entity.editStateChange(true) //重新激活reticule状态
                 entity.continueDrag(marker, e)  
             } else if (e.button === THREE.MOUSE.RIGHT ) {
@@ -239,10 +343,33 @@ var SiteModel = {
             }
         };
 
-        let end = (e={}) => {//确定、结束
- 
-            if(!e.finish && entity.markers.length<=minMarkers){//右键  当个数不够时取消
-                 
+
+        let finish = ()=>{//结束绘画 
+            viewer.removeEventListener('cancel_insertions', Exit);
+            entity.removeEventListener('unselect', Exit);
+            clearTimeout(timer) 
+            entity.editStateChange(false)  
+            //pressExit && viewer.inputHandler.removeEventListener('keydown', pressExit);
+            callback && callback(entity) 
+        }
+
+
+        let end = (e={}) => {//尝试结束
+             /* 退出的三种形式:
+                1 普通:如果大于三个marker,结束且保留;否则重新画。()
+                2 删除:直接结束且删除。(remove)
+                3 结束:如果大于三个marker,结束且保留;否则结束且删除。 (finish)
+                4 保留:无论几个marker,都保留着,结束。(remain)
+             */
+            
+            if(e.remove){ 
+                finish()
+                return this.removeEntity(entity)
+            }
+             
+             
+             
+            if(!e.remain && !e.finish && !e.remove && entity.markers.length<=minMarkers){//右键  当个数不够时取消 
                 //重新开始画
                 entity.reDraw(1)
                  
@@ -253,30 +380,22 @@ var SiteModel = {
                 }
                 entity.addEventListener('dragChange',f) 
                 
-                console.log('waitcontinue')
+                //console.log('waitcontinue')
                 entity.continueDrag(entity.markers[0], e)
-                return
-                
-                 
+                return  
             } 
-            viewer.removeEventListener('cancel_insertions', Exit);
-            //entity.removeEventListener('unselect', Exit);
-            clearTimeout(timer) 
-            entity.editStateChange(false)  
             
-            
-            if (!e.finish && entity.markers.length > 3) {
+            finish()
+              
+            if (e.remain || !e.remove && entity.markers.length >  3) {//保留 
                 entity.removeMarker(entity.points.length - 1); 
-                entity.addHoverEvent()
+                entity.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') })
                 if(buildType == 'room'){
                     this.fitPullBox()
                 } 
                 entity.isNew = false 
-                entity.addMidMarkers()
-                 
+                entity.addMidMarkers() 
                 
-                pressExit && viewer.inputHandler.removeEventListener('keydown', pressExit);
-                callback && callback(entity) 
             }else{
                 this.removeEntity(entity) //直接删除没画好的,比较简单。这样就不用担心旧的continueDrag仍旧触发了
         
@@ -286,8 +405,9 @@ var SiteModel = {
         };
 
         
-        let Exit = (e)=>{ 
-            //entity.removeEventListener('unselect', Exit);
+        let Exit = (e)=>{ //强制结束
+         
+            entity.removeEventListener('unselect', Exit);
             
             if(viewer.inputHandler.drag){//还未触发drop的话
                 viewer.inputHandler.drag.object.dispatchEvent({
@@ -299,47 +419,31 @@ var SiteModel = {
                 });
                 viewer.inputHandler.drag = null 
             }else{
-                end({finish:true, remove:e.remove})  //未结束时添加新的polygon时会触发
+                end({remain:true})   
             }
             viewer.inputHandler.drag = null 
-            
-            
         }
+         
         
-        viewer.dispatchEvent({
-            type: 'cancel_insertions'  //取消之前的
-        });
+        viewer.dispatchEvent( 'cancel_insertions'  );//取消之前的 
         viewer.addEventListener('cancel_insertions', Exit);
-        //entity.addEventListener('unselect', Exit);  //这个太难了,创建时也会被取消选中的
+        entity.addEventListener('unselect', Exit);   
+        
+        
         
-        let pressExit
-        if(!Potree.settings.isOfficial){
-            pressExit = (e)=>{ 
-                if(e.keyCode == 27){//Esc
-                    Exit()
-                }
-            } 
-            viewer.inputHandler.addEventListener('keydown', pressExit) 
-        }
-          
-          
         var marker = entity.addMarker({point:new THREE.Vector3(0, 0, 0)})
-        viewer.inputHandler.startDragging(marker , {dragViewport:mapViewport, endDragFun, notPressMouse:true} ); //notPressMouse代表不是通过按下鼠标来拖拽.  dragViewport指定了只能在地图上拖拽
         viewer.updateVisible(marker,'unMove',false);//这时候的位置是假的(0,0,0)所以先不可见
-         
         var f = ()=>{
             viewer.updateVisible(marker,'unMove',true); 
             entity.removeEventListener('dragChange',f)
         }
         entity.addEventListener('dragChange',f)  
-          
-        
-        if(buildType!='hole'){
-            this.addEntity(entity, parent)
-        } 
-        
-      
+            
+             
+        marker.isDragging = true 
+        viewer.inputHandler.startDragging(marker , {dragViewport:mapViewport, endDragFun, notPressMouse:true} ); //notPressMouse代表不是通过按下鼠标来拖拽.  dragViewport指定了只能在地图上拖拽
         
+          
         return entity;
     
         
@@ -347,36 +451,88 @@ var SiteModel = {
         
     },
     
-   
-    createFromData:function( buildType, parent ,sid, name, points=[], holes=[], zMin, zMax, initial,panos,flagPano){ 
-        if(buildType != 'building' && buildType != 'floor' && buildType != 'room' ) return
+    
+    
+    getPreDealData(points,  zMin, zMax, initial, buildType, parent){
+        /* if( buildType == 'building' ){
+            zMax = zMin //强制变得一样,作为基底。如果有必要,保存时再算真实的zMax。目前zMin没有保存所以数据是错的,会直接根据floor计算
+        } */ 
         
-        {
-            if(initial){//初始数据错的,要自己建(只有一个building和floor)    原posIsLonlat
-                var bound = viewer.bound.boundingBox
-                points = [
-                    new THREE.Vector3(bound.min.x, bound.min.y,0),
-                    new THREE.Vector3(bound.max.x, bound.min.y,0),
-                    new THREE.Vector3(bound.max.x, bound.max.y,0),
-                    new THREE.Vector3(bound.min.x, bound.max.y,0),
-                ]
+        var bound = viewer.bound.boundingBox
+        
+        if(buildType == 'building' && initial){//初始数据错的,要自己建(只有一个building和floor)    原posIsLonlat
+            console.log('空间模型未编辑过, 初始化了一个')
+            
+            points = [
+                new THREE.Vector3(bound.min.x, bound.min.y,0),
+                new THREE.Vector3(bound.max.x, bound.min.y,0),
+                new THREE.Vector3(bound.max.x, bound.max.y,0),
+                new THREE.Vector3(bound.min.x, bound.max.y,0),
+            ]
+            zMin = bound.min.z
+            zMax = bound.max.z
+            /* points = points.map(e=>{
+                return  viewer.transform.lonlatToLocal.forward(e) 
+            })  */
+            
+        }else{//相对于初始数据集的模型内坐标
+            points = points.map(e=> this.transform(e, 'fromDataset'))
+            if(buildType == 'floor' && initial){
                 zMin = bound.min.z
                 zMax = bound.max.z
-                /* points = points.map(e=>{
-                    return  viewer.transform.lonlatToLocal.forward(e) 
-                })  */
-            }else{//相对于初始数据集的模型内坐标
-                points = points.map(e=> this.transform(e, 'fromDataset'))
-                    
-            } 
+            }                
+        } 
+        return {points, zMax, zMin }
+    },
+    
+    
+   
+    resetFromData:function(entity,  points=[], holes=[], zMin, zMax ){
+         
+         
+        var {points, zMax, zMin}  = this.getPreDealData(points,  zMin, zMax , this.autoBuild , entity.buildType, entity.buildParent )
+        
+        if(entity.buildType != 'floor' )entity.points = points
+        
+        
+        if(entity.buildType == 'room'){
+            entity.zMin = zMin
+            entity.zMax = zMax
+        }else if(entity.buildType == 'floor'){//改楼高
+            let height = zMax - zMin
+            let zMax2 = entity.zMin + height 
+            SiteModel.changeZ(entity, 'zMax', zMax2)
         }
         
+        { 
+            //删除旧的holes重新添加
+            let holesOld = entity.holes 
+            holesOld.forEach(e=>{
+                entity.removeHole(e)
+            })
+            
+            holes.forEach(points =>{
+                let ps = points.map(e=> this.transform(e, 'fromDataset'))
+                let hole = entity.addHole(ps)
+                hole.addMidMarkers()
+            })
+        }
         
-        if(buildType == 'building' ){
-            zMax = zMin //强制变得一样,作为基底。如果有必要,保存时再算真实的zMax。目前zMin没有保存所以数据是错的,会直接根据floor计算
-        }  
         
         
+        entity.update()
+        return entity
+    }
+   
+   
+    ,
+   
+    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 )
+        
         
         
         {
@@ -407,7 +563,8 @@ var SiteModel = {
             zMax,
             buildParent:parent, 
             ifDraw:this.editing  || Potree.settings.drawEntityData,
-            panos,flagPano
+            panos, flagPano,
+            autoBuild : initial
         }
         
         let entity = new BuildingBox(prop)
@@ -426,9 +583,9 @@ var SiteModel = {
         })
          
         
-        if(buildType == 'floor'){
+        /* if(buildType == 'floor'){
             this.updateBuildingZ(parent)
-        } 
+        }  */
          
          
         return entity
@@ -436,12 +593,12 @@ var SiteModel = {
     
     transform:function(pos, type){
         if(type == 'toDataset'){
-            let point = Potree.Utils.datasetPosTransform({ toDataset: true, position: pos.clone(), datasetId: Potree.datasetData[0].id })
+            let point = Potree.Utils.datasetPosTransform({ toDataset: true, position: pos.clone(), datasetId: Potree.settings.originDatasetId })
             return new THREE.Vector2().copy(point)
             
         }else{
             let position = new THREE.Vector3().copy(pos).setZ(0)
-            return Potree.Utils.datasetPosTransform({ fromDataset: true, position, datasetId: Potree.datasetData[0].id })
+            return Potree.Utils.datasetPosTransform({ fromDataset: true, position, datasetId: Potree.settings.originDatasetId })
             
         }
     },
@@ -466,17 +623,48 @@ var SiteModel = {
                 this.fitPullBox()
             })   
         }else if(entity.buildType == 'floor'){
+            this.updateBuildingZ(parent)
             parent.dispatchEvent({type:'addFloor'})
         }
         
-        console.log('添加实体:', entity.buildType, entity.sid, entity.uuid)
+        {//仅能存在一个marker被选中。选中的点可以被删除
+            entity.addEventListener('clickMarker', (e)=>{
+                if(this.selectedMarker == e.marker){
+                    this.selectedMarker.dispatchEvent({type:'clickSelect',state:false})
+                    this.selectedMarker = null 
+                }else{
+                    if(this.selectedMarker){
+                        this.selectedMarker.dispatchEvent({type:'clickSelect',state:false})
+                    }
+                    this.selectedMarker = e.marker
+                    this.selectedMarker.dispatchEvent({type:'clickSelect',state:true})
+                } 
+            })
+            entity.addEventListener('removeMarker', (e)=>{ 
+                if(this.selectedMarker == e.marker){
+                    this.selectedMarker = null
+                }
+            })
+            
+            let unselect = (e)=>{//取消选中实体或删除后
+                if(this.selectedMarker && entity.markers.includes(this.selectedMarker)){
+                    this.selectedMarker.dispatchEvent({type:'clickSelect',state:false})
+                    this.selectedMarker = null
+                } 
+            } 
+            entity.addEventListener('dispose', unselect)
+            entity.addEventListener('unselect', unselect)
+                
+            
+        }
+        //console.log('添加实体:', entity.buildType, entity.sid, entity.uuid)
     
     },
     
     removeEntity : function(entity){
         if(!this.entities.includes(entity))return
         
-        
+        console.log('删除实体:', entity.buildType, entity.sid)
         
         if(this.selected == entity){
             this.height_pull_box.visible = false 
@@ -496,6 +684,8 @@ var SiteModel = {
             }
         }
         
+        
+        
         var index = this.entities.indexOf(entity);
         if(index>-1){
             this.entities.splice(index,1) 
@@ -503,10 +693,10 @@ var SiteModel = {
         
         
         entity.dispose()
-        entity.dispatchEvent({type:'delete'})
-        
-        console.log('删除实体:', entity.buildType, entity.sid)
+        let buildChildren = entity.buildChildren.slice()
+        buildChildren.forEach(e=>this.removeEntity(e))
         
+         
     },
     
     
@@ -516,11 +706,19 @@ var SiteModel = {
         building.zMin = building.zMax = building.buildChildren[0].zMin  //基底高度 
         //building.zMax = building.buildChildren[building.buildChildren.length-1].zMax
         if(this.editing) building.update({dontUpdateChildren:true})
+        building.dispatchEvent('updateBuildingZ')    
     },
    
     
     
-    selectEntity : function(entity){
+    selectEntity : function(entity, state=true){
+        if(state === false){
+            entity.unselect()
+            if(this.selected == entity)this.selected = null
+            
+            return 
+        }
+        
         if(this.selected == entity || entity && entity.buildType == 'hole')return
         //this.buildings.forEach(e=>e.unselect())
         this.selected && this.selected.unselect()
@@ -540,8 +738,10 @@ var SiteModel = {
             this.height_pull_box.visible = true 
             this.fitPullBox()
         } 
-         
-         
+        
+        if(entity && !entity.isNew && (entity.buildType == 'building' || entity.buildType == 'room' ) && entity.points.length<2){
+            this.startInsertion('resume',entity)   //继续画     
+        }
     },
     
     
@@ -567,6 +767,7 @@ var SiteModel = {
     
     
     fitPullBox: function(){ //自适应拖拽楼层的pullMesh
+        if(!this.selected || this.selected.buildType!= 'floor' && this.selected.buildType!= 'room')return
         let bound = new THREE.Box3();
         bound.expandByObject(this.selected.box)
         let center = bound.getCenter(new THREE.Vector3() )  
@@ -583,10 +784,10 @@ var SiteModel = {
         let max, min //limit
         
         if(entity.buildType == 'floor'){//楼层 
-            let index = entity.buildParent.buildChildren.indexOf(this.selected)
+            let index = entity.buildParent.buildChildren.indexOf(entity)
             if(dirType == 'zMax'){
                 let upper = entity.buildParent.buildChildren[index+1];
-                entity.zMax = value
+                entity.zMax = Math.min(Limit.zMax, value)
                 min = entity.zMin + minFloorHeight
                 if(entity.zMax < min){
                     entity.zMax = min
@@ -606,7 +807,7 @@ var SiteModel = {
             }else{
                 let lower = entity.buildParent.buildChildren[index-1];
               
-                entity.zMin = value
+                entity.zMin = Math.max(Limit.zMin, value)   
                 max = entity.zMax - minFloorHeight
                 if(entity.zMin > max){
                     entity.zMin = max
@@ -623,7 +824,7 @@ var SiteModel = {
                     lower.update()
                     lower.dispatchEvent({type:'changeHeight'}) 
                 } 
-                if(index == 0)this.updateBuildingZ(this.selected.buildParent) 
+                if(index == 0)this.updateBuildingZ(entity.buildParent) 
             }
         }else if(entity.buildType == 'room'){//房间
             //按照navvis的是不一定限制在当前楼层,只要高度不超过当前楼层即可。
@@ -652,9 +853,10 @@ var SiteModel = {
         let boxGeo = new THREE.BoxBufferGeometry( 1, 1, 1/4 )
         let boxMat = new THREE.MeshBasicMaterial({
             color:"#F00", 
-            opacity:0 ,
+            opacity:0,
             transparent:true,
-            depthTest:false
+            depthTest:false,
+            side:2
         }) 
         
         let height_pull_box_up = new THREE.Mesh(boxGeo,boxMat)
@@ -667,8 +869,8 @@ var SiteModel = {
         this.height_pull_box.add(height_pull_box_down) 
         this.height_pull_box.visible = false
         this.meshGroup.add(this.height_pull_box) 
-        height_pull_box_up.position.set(0,0,3/8)
-        height_pull_box_down.position.set(0,0,-3/8)
+        height_pull_box_up.position.set(0,0,1/2/* 3/8 */)
+        height_pull_box_down.position.set(0,0,-1/2/* -3/8 */)
         viewer.setObjectLayers(this.height_pull_box, 'siteModeSideVisi' )
         
         
@@ -731,16 +933,22 @@ var SiteModel = {
     },
     
     
-    pointInWhichEntity(position, buildType, ifIgnoreHole){//返回第一个符合标准的实体,buildType是要找的建筑类型
-        
+    pointInWhichEntity(location, buildType, ifIgnoreHole){//返回第一个符合标准的实体,buildType是要找的建筑类型
+        //location 可以是pano或者坐标
         //由于房间可能在building外,所以房间要另外单独识别。
         
         let lastResult; //最接近的上一层结果,如果没有result返回这个
         let result
-        
+         
         
         let traverse = (parent)=>{
-            if(parent.ifContainsPoint(position)){
+            let contains;
+            if(location instanceof THREE.Vector3){
+                contains = parent.ifContainsPoint(location)
+            }else{//is pano
+                contains = parent.panos.includes(location)
+            }
+            if(contains){
                 lastResult = parent
                 if(parent.buildType == buildType){
                     return parent
@@ -779,15 +987,22 @@ var SiteModel = {
     
     findPanos: function(){
         
-        {//清空:
+        {
             this.entities.forEach(entity=>{
-                entity.panos = []
+                //清空:
+                entity.panos = []   
                 entity.flagPano = null
+
+                viewer.images360.panos.forEach(pano=>{
+                    if(entity.ifContainsPoint(pano.position)){
+                        entity.panos.push(pano)
+                    } 
+                })
             }) 
         } 
         
         
-        viewer.images360.panos.forEach(pano=>{
+        /* viewer.images360.panos.forEach(pano=>{  //一个漫游点只对应一个实体的话
             let result = this.pointInWhichEntity(pano.position, 'room');
             
             {//get panos for every entities
@@ -797,18 +1012,25 @@ var SiteModel = {
                     entity = entity.buildParent
                 } 
             } 
-        })
+        }) */
         
+
+
+
+
         {//search center pano
              this.entities.forEach(entity=>{
+                let panos = entity.panos
+                if(panos.length == 0)return   
                 let bound = entity.getBound();
                 let center = bound.getCenter(new THREE.Vector3)
                 let request = []
                 let rank = [
                     Images360.scoreFunctions.distanceSquared({position: center}) 
                 ]
-                let panos = entity.panos && entity.panos.length ? entity.panos : viewer.images360.panos
-                let r = Common.sortByScore(panos, request, rank);//entity没有panos的话,就扩大到所有panos
+                //let panos = entity.panos && entity.panos.length ? entity.panos : viewer.images360.panos //entity没有panos的话,就扩大到所有panos
+                
+                let r = Common.sortByScore(panos, request, rank);
 
                 if(r && r.length){
                     entity.flagPano = r[0].item 
@@ -825,7 +1047,7 @@ var SiteModel = {
     ,
     findEntityForDataset:function(){
         var entities = this.entities.filter(e=>e.buildType == 'room' || e.buildType == 'floor' && e.buildChildren.length == 0)
-        entities.length && viewer.scene.pointclouds.forEach(pointcloud=>{
+        viewer.scene.pointclouds.forEach(pointcloud=>{
              
             let volumes = []
             entities.forEach(entity=>{
@@ -833,8 +1055,13 @@ var SiteModel = {
                 volumes.push({entity, volume})
             })
             volumes.sort((a,b)=>{ return b.volume-a.volume }) 
-            console.log(volumes)
-            pointcloud.belongToEntity = volumes[0].entity; //如果约等于0怎么办???
+            //console.log(volumes)
+            if(volumes.length == 0 || volumes[0].volume < 10e-4){//如果约等于0 
+                pointcloud.belongToEntity = null
+            }else{
+                pointcloud.belongToEntity = volumes[0].entity; 
+            }
+            
         })
         
         /*
@@ -858,14 +1085,14 @@ var SiteModel = {
         meshGroup: new THREE.Object3D, */
         this.selectEntity(null)
         
-        let length = this.buildings.length;
+        let length = this.entities.length;
         for(let i=0;i<length;i++){
-            this.buildings[i].dispose()
+            this.entities[i].dispose()
         }
         
         this.entities = []
         this.buildings = []
-        
+        this.inEntity = null
             
          
     }
@@ -876,22 +1103,49 @@ var SiteModel = {
         if (!entity) {
             return console.error('没找到entity ')
         }
+       
+        if(Potree.settings.displayMode == 'showPanos'){
+            if (isNearBy && entity.panos.length) {
+                if(entity.panos.includes(viewer.images360.currentPano)) return 'posNoChange' //已在当前实体中
 
-        if (isNearBy && entity.panos.length) {
-            let position = viewer.scene.getActiveCamera().position
-            let request = []
-            let rank = [Images360.scoreFunctions.distanceSquared({ position })]
-            let r = Common.sortByScore(entity.panos, request, rank)
+                let position = viewer.scene.getActiveCamera().position
+                let request = []
+                let rank = [Images360.scoreFunctions.distanceSquared({ position })]
+                let r = Common.sortByScore(entity.panos, request, rank)
 
-            aimPano = r[0].item
-        } else {
-            if (!entity.flagPano) {
-                return console.error('没找到flagPano')
+                aimPano = r[0].item
+            } else {
+                if (!entity.flagPano) {
+                    return console.log('没有flagPano')
+                }
+                aimPano = entity.flagPano
             }
-            aimPano = entity.flagPano
-        }
+            if(aimPano == viewer.images360.currentPano) return 'posNoChange'
+            viewer.images360.flyToPano(aimPano)
+        }else{  
+            if(isNearBy && entity.ifContainsPoint(viewer.images360.position)  ){
+                return 'posNoChange' //已在当前实体中
+            }
+
+            let boundingBox = entity.getBound()
+            let position = boundingBox.getCenter(new THREE.Vector3())
+             
+            if(viewer.controls == viewer.orbitControls){ 
+                let dis = 2
+                let target = position
+                position = new THREE.Vector3().subVectors(target, viewer.scene.view.direction)
+                viewer.scene.view.setView({position,  duration: 1000,  target})
+            }else{
+                if(math.closeTo(position, viewer.images360.position)) return 'posNoChange'  
 
-        viewer.images360.flyToPano(aimPano)
+                viewer.scene.view.setView({position,  duration: 1000})
+            }
+            
+            
+            
+            
+        }
+        return true
     },
 
     focusEntity(id){
@@ -901,6 +1155,19 @@ var SiteModel = {
         let center = boundingBox.getCenter(new THREE.Vector3())
         this.SplitScreen.focusOnObject(boundSize, center) 
     },
+    
+    removeIlligalArchi(){//删除marker数量小于3个的建筑,当保存时
+        let needDelete = []
+        
+        this.entities.forEach(e=>{
+            if(e.points.length<3){
+                needDelete.push(e)
+            }
+        })
+        
+        needDelete.forEach(e=>this.removeEntity(e))
+        
+    },
 }
 
 /* 

+ 2 - 3
src/navigation/DeviceOrientationControls.js

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

+ 2 - 3
src/navigation/EarthControls.js

@@ -1,10 +1,9 @@
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Buttons} from "../defines.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Utils} from "../utils.js"; 
 
-export class EarthControls extends EventDispatcher {
+export class EarthControls extends THREE.EventDispatcher {
 	constructor (viewer) {
 		super(viewer);
 

+ 8 - 8
src/navigation/FirstPersonControls.js

@@ -15,14 +15,13 @@
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Buttons} from "../defines.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Utils} from "../utils.js"; 
 import cameraLight from "../utils/cameraLight.js";
 
 
  
 
-export class FirstPersonControls extends EventDispatcher {
+export class FirstPersonControls extends THREE.EventDispatcher {
 	constructor (viewer, viewport) {
 		super();
         
@@ -230,6 +229,7 @@ export class FirstPersonControls extends EventDispatcher {
             
             
             if(mode.includes('scale')){
+ 
                 this.dollyEnd.subVectors(e.touches[0].pointer, e.touches[1].pointer);
                 //if(!this.dollyStart)return
                 var scale = this.dollyEnd.length() / this.dollyStart.length()
@@ -257,7 +257,7 @@ export class FirstPersonControls extends EventDispatcher {
         let dolly = (e={})=>{
                        
             if(this.currentViewport.unableChangePos){//全景时 
-                this.emit('dollyStopCauseUnable',e)
+                this.dispatchEvent({type:'dollyStopCauseUnable',delta:e.delta, scale:e.scale})
                 return 
             }
             
@@ -279,8 +279,8 @@ export class FirstPersonControls extends EventDispatcher {
                 }
                 
                 let zoom = camera.zoom * ratio
-                let limit = Potree.config.OrthoCameraLimit.zoom
-                zoom = THREE.Math.clamp(zoom, limit.min,limit.max )
+                let limit = camera.zoomLimit
+                if(limit) zoom = THREE.Math.clamp(zoom, limit.min,limit.max )
                 
                 
                 let pointerPos = new THREE.Vector3(e.pointer.x, e.pointer.y,0.5); 
@@ -357,7 +357,7 @@ export class FirstPersonControls extends EventDispatcher {
         
         
         
-        let prepareScale = (e)=>{//触屏的scale
+        let prepareScale = (e)=>{//触屏的scale 
             this.dollyStart.subVectors(e.touches[0].pointer, e.touches[1].pointer);
         }
         let prepareRotate = (e)=>{ 
@@ -369,7 +369,7 @@ export class FirstPersonControls extends EventDispatcher {
               
             e.drag.z = void 0  //清空    
             drag(e) //触屏点击时更新的pointer直接用一次drag
-            console.log('preparePan '   )
+            //console.log('preparePan '   )
         }
         
         this.viewer.addEventListener('global_mousedown'/* 'startDragging' */, (e)=>{

+ 63 - 60
src/navigation/InputHandler.js

@@ -7,11 +7,10 @@
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {KeyCodes} from "../KeyCodes.js";
 import {Utils} from "../utils.js";
-import {Buttons} from "../defines.js"; 
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Buttons} from "../defines.js";  
 import Common from "../utils/Common.js";
 
-export class InputHandler extends EventDispatcher {
+export class InputHandler extends THREE.EventDispatcher {
 	constructor (viewer,scene) {
 		super();
 
@@ -85,9 +84,9 @@ export class InputHandler extends EventDispatcher {
         
         
         {
-            this.on('isMeasuring',(v, cause)=>{
-                console.log('isMeasuring',v,cause)
-                this.isMeasuring = v 
+            this.addEventListener('isMeasuring',(e)=>{ 
+                console.log('isMeasuring',e.v,e.cause)
+                this.isMeasuring = e.v 
             }) 
         }
         
@@ -112,10 +111,12 @@ export class InputHandler extends EventDispatcher {
 	} */
     //统一跟第一个触碰的viewport相同
     updateTouchesInfo(e){
-        var   viewport, pointer, camera  
+        var viewport, pointer, camera  
         let oldTouches = this.touches
-        
-        this.touches = Array.from(e.touches).map(touch=>{
+        let changedTouches = Array.from(e.changedTouches)
+        let touches = Array.from(e.touches)
+
+        this.touches = touches.map(touch=>{
             let touch_ = oldTouches.find(a=>a.touch.identifier == touch.identifier)
             let pointer = touch_ && touch_.pointer  //复制原先的值
             return { 
@@ -123,14 +124,21 @@ export class InputHandler extends EventDispatcher {
             }
         }) 
         if(e.touches.length > 0){ 
-            Array.from(e.changedTouches).forEach(touch=>{   //修改changedTouches的 
+            
+            let newTouches = touches.filter(e=>!
+                oldTouches.some(a=>a.touch.identifier == e.identifier) && !changedTouches.some(a=>a.identifier == e.identifier)  
+            ) //从按钮处划过时e.touches中会出现this.touches和changedTouches中都没有的identifier
+            if(newTouches.length>0){
+                console.warn('has new',newTouches.map(e=>e.identifier))
+            }
+            
+            newTouches.concat(changedTouches).forEach(touch=>{   //修改changedTouches的 
                 let touch_ = this.touches.find(a=>a.touch.identifier == touch.identifier)
                 if(touch_){
                     let a = this.getPointerInViewport(touch.pageX, touch.pageY, this.dragViewport||viewport, new THREE.Vector2) 
-                    touch_.pointer = a.pointer.clone()
+                    touch_.pointer = a.pointer.clone() 
                     viewport = a.viewport;  camera = a.camera
-                }
-                
+                } 
             })
             
             
@@ -144,18 +152,23 @@ export class InputHandler extends EventDispatcher {
                 //console.log('updateTouchesInfo', this.pointer.clone())
                 
             }else{
-                this.pointer = this.touches[0].pointer.clone() //更新,使用当前touches中的第一个
-                
-                 
+                this.pointer = this.touches[0].pointer.clone() //更新,使用当前touches中的第一个 
             }
                 
             
-            
+            /* if(this.touches.find(e=>!e.pointer)){
+                console.error('  touches has no pointer', oldTouches.map(e=>e.touch.identifier),
+                Array.from(e.touches).map(e=>e.identifier),  Array.from(e.changedTouches).map(e=>e.identifier) ) 
+            } */ 
+            //console.log(this.touches)
             
             //console.log('更新pointer1',this.pointer.toArray())
             return  {viewport,  camera/* , pointer:this.pointer  */}
         }
-        //console.log(this.touches)
+
+
+        
+        
     }
     
 	onTouchStart (e) {
@@ -555,47 +568,36 @@ export class InputHandler extends EventDispatcher {
                         pressDistance
                     }
                 ));
-                 
-                 
-                
-                // check for a click 
-                if(pressDistance < Potree.config.clickMaxDragDis && pressTime<Potree.config.clickMaxPressTime){
-                    if(this.hoveredElements && this.hoveredElements[0]){
-                        if (this.logMessages) console.log(`${this.constructor.name}: click ${clicked.name}`);
-                        this.hoveredElements[0].object.dispatchEvent($.extend(  
-                            this.getEventDesc(e,isTouch),
-                            {
-                                type: 'click',
-                                pressDistance     
-                            }
-                        )); 
-                      
-                    }else{
-                        this.viewer.dispatchEvent($.extend(  
-                            this.getEventDesc(e,isTouch),
-                            {
-                                type: 'global_click',  
-                                pressDistance
-                            }
-                        )); 
-                       
-                    }
-                     
-                }
+                  
                 
 			}
 
-			
+            // check for a click 
+            if(pressDistance < Potree.config.clickMaxDragDis && pressTime<Potree.config.clickMaxPressTime){
+                if(this.hoveredElements && this.hoveredElements[0]){
+                    if (this.logMessages) console.log(`${this.constructor.name}: click ${clicked.name}`);
+                    this.hoveredElements[0].object.dispatchEvent($.extend(  
+                        this.getEventDesc(e,isTouch),
+                        {
+                            type: 'click',
+                            pressDistance     
+                        }
+                    )); 
+                  
+                }else{
+                    this.viewer.dispatchEvent($.extend(  
+                        this.getEventDesc(e,isTouch),
+                        {
+                            type: 'global_click',  
+                            pressDistance
+                        }
+                    )); 
+                   
+                }
+                 
+            }
             
-			/* let clicked = this.hoveredElements.map(h => h.object).find(v => v === this.drag.object) !== undefined;
-			if(clicked){
-				if (this.logMessages) console.log(`${this.constructor.name}: click ${this.drag.object.name}`);
-				this.drag.object.dispatchEvent({
-					type: 'click',
-					viewer: this.viewer,
-					consume: consume,
-				});
-			} */
+			 
             this.drag = null;
              
 		}
@@ -644,11 +646,11 @@ export class InputHandler extends EventDispatcher {
         pointer = pointer ||this.pointer
         //if(this.viewer.viewports || viewForceAt){
             var getDimension = (view)=>{
-                var left = Math.floor(this.domElement.clientWidth * view.left)
-                 , bottom = this.domElement.clientHeight * view.bottom
-                 , width = Math.floor(this.domElement.clientWidth * view.width)
-                 , height = this.domElement.clientHeight * view.height
-                 ,   top = Math.floor(this.domElement.clientHeight - bottom - height) 
+                var left = Math.ceil(this.domElement.clientWidth * view.left)
+                 , bottom = Math.ceil(this.domElement.clientHeight * view.bottom)
+                 , width = Math.ceil(this.domElement.clientWidth * view.width)
+                 , height = Math.ceil(this.domElement.clientHeight * view.height)
+                 ,   top = this.domElement.clientHeight - bottom - height
                 return {left, bottom, width, height, top}
             }
             var getView = (view, left, bottom, width, height, top)=>{
@@ -903,6 +905,7 @@ export class InputHandler extends EventDispatcher {
                 }
             ))
             this.hoveredElements = hoveredElements
+             
         }
         
 		

+ 9 - 3
src/navigation/OrbitControls.js

@@ -15,11 +15,10 @@
 
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Buttons} from "../defines.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Utils} from "../utils.js"; 
 
  
-export class OrbitControls extends EventDispatcher{
+export class OrbitControls extends THREE.EventDispatcher{
 	
 	constructor(viewer){
 		super();
@@ -48,6 +47,13 @@ export class OrbitControls extends EventDispatcher{
         
 		let drag = (e) => {
             if(!this.enabled)return
+ 
+            let viewport = e.dragViewport;
+            if(!viewport || viewport.camera.type == "OrthographicCamera" )return
+            //let camera = viewport.camera 
+          
+
+
 			if (e.drag.object !== null) {
 				return;
 			}

+ 2 - 3
src/navigation/VRControls.js

@@ -1,6 +1,5 @@
 
-import * as THREE from "../../libs/three.js/build/three.module.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import * as THREE from "../../libs/three.js/build/three.module.js"; 
 import { XRControllerModelFactory } from '../../libs/three.js/webxr/XRControllerModelFactory.js';
 import {Line2} from "../../libs/three.js/lines/Line2.js";
 import {LineGeometry} from "../../libs/three.js/lines/LineGeometry.js";
@@ -277,7 +276,7 @@ class RotScaleMode{
 };
 
 
-export class VRControls extends EventDispatcher{
+export class VRControls extends THREE.EventDispatcher{
 
 	constructor(viewer){
 		super(viewer);

+ 2 - 3
src/objects/Label.js

@@ -1,10 +1,9 @@
 import * as THREE from "../../libs/three.js/build/three.module.js";
-import {Utils} from "../utils.js"; 
-import { EventDispatcher } from "../EventDispatcher.js";
+import {Utils} from "../utils.js";  
 
 
 
-class Label  extends EventDispatcher{
+class Label  extends THREE.EventDispatcher{
     constructor(o={}){
         super()
         

+ 13 - 4
src/objects/Magnifier.js

@@ -174,7 +174,7 @@ export default class Magnifier extends THREE.Object3D {//放大镜or望远镜
         
         viewer.addEventListener('global_mousemove', updateVisi)
         viewer.addEventListener('global_touchstart', updateVisi)
-       
+        
         
         /* viewer.addEventListener("beginSplitView",()=>{
             this.updateVisible("splitView", false) 
@@ -204,6 +204,14 @@ export default class Magnifier extends THREE.Object3D {//放大镜or望远镜
                 viewer.updateVisible(this, "measure", false) 
             })
         }
+        
+        
+        viewer.scene.view.addEventListener('setViewDone',()=>{
+            if(!this.visible)return
+            let pickWindowSize = 100
+            let intersect = viewer.inputHandler.getIntersect(viewer.mainViewport, viewer.mainViewport.camera, true, pickWindowSize )
+            this.update(intersect && intersect.location)
+        })
     }
     
     
@@ -233,10 +241,11 @@ export default class Magnifier extends THREE.Object3D {//放大镜or望远镜
         
 
          
-        //自身位置
-        let pointer = viewer.inputHandler.pointer.clone();
+        //自身位置 
+        //let pos2d = viewer.inputHandler.pointer.clone();   //跟随鼠标 
+        let pos2d = Potree.Utils.getPos2d(aimPos, playerCamera, viewer.renderArea, viewer.mainViewport).vector   //更新目标点的实时二维位置
         let margin = 0.4, maxY = 0.4
-        let screenPos = pointer.clone().setY(pointer.y + (pointer.y>maxY ? -margin : margin ))
+        let screenPos = pos2d.clone().setY(pos2d.y + (pos2d.y>maxY ? -margin : margin ))
         
         let newPos = new THREE.Vector3(screenPos.x,screenPos.y,0.8).unproject(playerCamera); //z:-1朝外       
         let dir = newPos.clone().sub(playerPos).normalize().multiplyScalar(10);//这个数值要大于playerCamera.near

+ 5 - 5
src/objects/Reticule.js

@@ -60,11 +60,11 @@ export default class Reticule extends THREE.Mesh{
             this.judgeTex()
         }) 
         
-        viewer.on('reticule_forbit',(e)=>{
-            if(this.state.forbit != e){
-                console.log('change forbit ',e)
+        viewer.addEventListener('reticule_forbit',(e)=>{
+            if(this.state.forbit != e.v){
+                console.log('change forbit ',e.v)
             }
-            this.state.forbit = e
+            this.state.forbit = e.v
             this.judgeTex() 
         })
         
@@ -149,7 +149,7 @@ export default class Reticule extends THREE.Mesh{
         let s, camera = viewport.camera
         if(camera.type == "OrthographicCamera"){
             
-            var sizeInfo = this.state.cross ? {width2d:400} : {minSize : 100,  maxSize : 400,   nearBound : 100 , farBound :  700}
+            var sizeInfo = this.state.cross ? {width2d:500} : {minSize : 100,  maxSize : 400,   nearBound : 100 , farBound :  700}
             s = math.getScaleForConstantSize($.extend(   sizeInfo ,  
             {position:this.position, camera, resolution:viewport.resolution/* 2 */} ))
             

+ 13 - 4
src/objects/Sprite.js

@@ -18,7 +18,7 @@ export default class Sprite extends THREE.Mesh{
         this.name = options.name || 'sprite'
         this.useViewport = null
         this.viewports = options.viewports//指定更新的viewports
-        
+        this.visible_ = true
         
         
         let update = (e)=>{
@@ -48,7 +48,15 @@ export default class Sprite extends THREE.Mesh{
          
     }
     
-    
+    set visible(v){
+        this.visible_ = v  
+        if(v){
+            this.update()
+        }
+    }
+    get visible(){
+        return this.visible_ 
+    }
     
     update(e){
         if(!e){
@@ -57,6 +65,7 @@ export default class Sprite extends THREE.Mesh{
             })
             return;
         }
+        if(!this.visible || !this.root)return
         if(this.viewports && !this.viewports.includes(e.viewport) )return
         if(e.viewport.name == 'magnifier')return
         
@@ -103,7 +112,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
         
         var matrix = this.matrixMap.get(e.viewport);
           
@@ -128,6 +137,6 @@ export default class Sprite extends THREE.Mesh{
      
     dispose(){
         this.removeAllListeners()
-        
+        this.parent && this.parent.remove(this)
     }
 }

+ 0 - 100
src/objects/fireParticle/explode.html

@@ -1,100 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - custom attributes [particles]</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<link type="text/css" rel="stylesheet" href="../main.css">
-	</head>
-
-	<body>
-		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - custom attributes example - particles</div>
-		<div id="container"></div>
-		<script type="module">
-			import * as THREE from '../../build/three.module.js';
-            
-			import { OrbitControls } from '../jsm/controls/OrbitControls.js';
-			import Stats from '../jsm/libs/stats.module.js';
-			import { GUI } from '../jsm/libs/dat.gui.module.js';
-            import ExplodeParticleSystem from './explode/ExplodeParticleSystem.js';
-			import ExplodeEmitter from './explode/ExplodeEmitter.js'
-
-			let renderer, scene, camera, light,stats,clock;
-			let explode;
-
-			const width = window.innerWidth;
-			const height = window.innerHeight;
-
-			init();
-			animate();
-
-			function init() {
-				stats = new Stats();
-				clock = new THREE.Clock(),
-				scene  = new THREE.Scene();
-
-                var VIEW_ANGLE = 45, ASPECT = width / height, NEAR = 0.1, FAR = 10000;
-	            camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
-				camera.position.set(0, 50, 200);
-	            camera.lookAt(new THREE.Vector3())	
-
-				//https://github.com/imokya/ParticleSystem
-				// const light = new THREE.PointLight(0xffffff, 2, 100)
-				// light.position.set(0, 30, 10)
-				// this.scene.add(light)
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setSize( width, height );
-				renderer.setClearColor( 0x333333 );
-				document.body.appendChild( renderer.domElement );
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.update();
-
-				scene.add( new THREE.DirectionalLight( 0xffffff ) );
-				scene.add( new THREE.AmbientLight( 0x666666 ) );
-
-				initParticles()
-			}
-
-			function initParticles() {
-				explode = new ExplodeParticleSystem({
-					emitter: new ExplodeEmitter()
-				})
-				scene.add(explode.mesh)
-				explode.start()
-			}
-
-			function updateExplode(){
-				var delta = clock.getDelta();
-	            explode.update( delta * 0.5 );	
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			function animate() {
-
-				requestAnimationFrame( animate );
-				render();
-				stats.update();
-
-			}
-
-			function render() {
-                updateExplode()
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-</body>
-
-</html>

+ 399 - 273
src/objects/fireParticle/explode/ExplodeParticle.js

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

+ 4 - 3
src/objects/fireParticle/explode/Particle.js

@@ -19,9 +19,10 @@ class Particle {
     this.color = new THREE.Color()
     this.opacity = 1
 
+    this.rebornCount = 0//重生次数
     this.age = 0
-    this.alive = 0
-
+    this.alive = 0 //注意,一开始时是未出生的
+    this.deadAge = 0//已死亡时间
     this.sizeTween = null
     this.colorTween = null
     this.opacityTween = null
@@ -29,7 +30,7 @@ class Particle {
 
   update(dt) { 
     //s = s0 + (v0 + at) * t 或 lastS + delta(vt)
-  
+    
     this.position.add(this.velocity.clone().multiplyScalar(dt))
     this.velocity.multiplyScalar( 1+this.acceleration*dt )
     

+ 0 - 26
src/objects/fireParticle/explode/Tween.js

@@ -1,26 +0,0 @@
-import * as THREE from "../../../../libs/three.js/build/three.module.js";
-class Tween {
-
-    constructor(times, values) {
-      this.times = times || []
-      this.values = values || []
-    }
-  
-    lerp(t) {
-      if(this.times.length == 0) return
-      let i = 0, n = this.times.length
-      while(i < n && t > this.times[i]) i++
-      if(i == 0) return this.values[0]
-      if(i == n) return this.values[n-1]
-      const ratio = (t - this.times[i-1]) / (this.times[i] - this.times[i-1])
-      if(this.values[0] instanceof THREE.Vector3) {
-        return this.values[i-1].clone().lerp(this.values[i], ratio)
-      } else {
-        return this.values[i-1] + ratio * (this.values[i] - this.values[i-1])
-      }
-      
-    }
-  
-  }
-  
-  export default Tween

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

@@ -16,7 +16,6 @@ export const vertexShader = `
     }
     vAngle = angle;
     vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
-    //gl_PointSize = size * (300.0 / length(mvPosition.xyz));
     gl_Position = projectionMatrix * mvPosition;
     
     gl_PointSize = ( heightOfNearPlane * size ) / gl_Position.w;

+ 0 - 96
src/objects/fireParticle/fire.html

@@ -1,96 +0,0 @@
-
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - custom attributes [particles]</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<link type="text/css" rel="stylesheet" href="../main.css">
-	</head>
-
-	<body>
-		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - custom attributes example - particles</div>
-		<div id="container"></div>
-
-		<script type="module">
-			import * as THREE from '../../build/three.module.js';
-			import { OrbitControls } from '../jsm/controls/OrbitControls.js';
-			import Stats from '../jsm/libs/stats.module.js';
-			import { GUI } from '../jsm/libs/dat.gui.module.js';
-			import FireParticleSystem from './fire/FireParticleSystem.js';
-
-			let renderer, scene, camera, stats,clock;
-
-			let sphere;
-			let fireParticleSystem;
-			const width = window.innerWidth;
-			const height = window.innerHeight;
-
-			init();
-			animate();
-
-			function init() {
-				stats = new Stats();
-				clock = new THREE.Clock(),
-				scene  = new THREE.Scene();
-				scene.fog = new THREE.Fog( 0x333333, 8, 20 );
-				camera = new THREE.PerspectiveCamera( 40, width / height, 0.1, 100 );
-				camera.position.z = 5;
-               
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setSize( width, height );
-				renderer.setClearColor( 0x333333 );
-				document.body.appendChild( renderer.domElement );
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.maxPolarAngle = 2*Math.PI;
-				controls.minDistance = 1;
-				controls.maxDistance = 100;
-				controls.update();
-
-				scene.add( new THREE.DirectionalLight( 0xffffff ) );
-				scene.add( new THREE.AmbientLight( 0x666666 ) );
-				//https://yomotsu.github.io/three-particle-fire/examples/basic.html
-				initParticles()
-			}
-
-			function initParticles() {
-				let fireRadius = 1;
-				let fireHeight = 3;
-				let particleCount = 400;
-				fireParticleSystem = new FireParticleSystem()
-				let geometry = fireParticleSystem.createGeometry( fireRadius, fireHeight, particleCount );
-				let material = fireParticleSystem.createMaterial(  camera.fov, height, 0x00338f );//0xff2200
-				fireParticleSystem.mesh = new THREE.Points( geometry, material );
-                fireParticleSystem.mesh.position.y = -0.6//add
-                
-				scene.add( fireParticleSystem.mesh );
-			}
-
-			function updateFire(){
-				let delta = clock.getDelta();
-				let elapsed = clock.getElapsedTime();
-				fireParticleSystem.updateMaterial(delta * 0.75) //更改速度
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-				renderer.setSize( window.innerWidth, window.innerHeight );
-			}
-
-			function animate() {
-				requestAnimationFrame( animate );
-				updateFire()
-				render();
-				stats.update();
-			}
-
-			function render() {
-				renderer.render( scene, camera );
-			}
-		</script>
-</body>
-
-</html>

+ 98 - 35
src/objects/fireParticle/fire/FireParticle.js

@@ -14,7 +14,8 @@ const getTexture = ()=>{
     return texture
 }
 
-
+const boxGeo = new THREE.BoxBufferGeometry(1,1,1,1);
+const boxMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"})
 
 
 
@@ -29,19 +30,19 @@ class FireParticle extends THREE.Points{
         
         
          
-        this.density = this.density || 1
+        this.strength = this.strength || 1
         
         
          
-        this.fireRadius = prop.fireRadius || 1;
-        this.fireHeight = prop.fireHeight || 5;  
+        this.radius = prop.radius || 1;
+        this.height = prop.height || 5;  
         
         this.computeParams()
-        this.geometry = this.createGeometry( this.fireRadius, this.fireHeight, this.particleCount );
+        this.geometry = this.createGeometry( this.radius, this.height, this.particleCount );
          
         
-        
-        this.material = this.createMaterial(  prop.fov, 0xff3200 );  //小蓝火:0x00338f
+        if(this.color == void 0)   this.color = 0xff3200
+        this.createMaterial( );  //小蓝火:0x00338f
         
       
          
@@ -53,7 +54,7 @@ class FireParticle extends THREE.Points{
         this.angleVelocity = 0
         this.angleAcceleration = 0
         this.size = 16
-        this.color = new THREE.Color()
+        
         this.opacity = 1
 
         this.age = 0
@@ -67,28 +68,73 @@ class FireParticle extends THREE.Points{
          
         this.setSize({viewport:viewer.mainViewport})
         this.setFov(viewer.fov)
-        viewer.addEventListener('resize',(e)=>{ 
+        
+        let setSize = (e)=>{
             if(e.viewport.name != "MainView")return
-            this.setSize(e) 
-        })  
-        viewer.addEventListener('fov_changed',(e)=>{ 
+            this.setSize(e)
+        }
+        let setFov = (e)=>{
             this.setFov(e.fov) 
+        }
+            
+        viewer.addEventListener('resize',setSize) 
+        viewer.addEventListener('fov_changed',setFov)
+          
+        this.addEventListener('dispose',()=>{
+            viewer.removeEventListener('resize',setSize) 
+            viewer.removeEventListener('fov_changed',setFov)
         })  
+          
     }
 
  
     computeParams(){  
-        let length = (this.curve ? this.curve.wholeLength : 0) + this.fireRadius * 2 //加上首尾的半径
-        this.particleCount =  Math.ceil( this.fireRadius * this.fireHeight * length * this.density *  5  )        
-        console.log('fire particleCount',this.particleCount)
+        let length = (this.curve ? this.curve.wholeLength : 0) + this.radius * 2 //加上首尾的半径
+        
+        
+        const minSize = 0.3, maxSize = 3, minRadiusBound = 0.3, maxRadiusBound = 10;
+        this.size = minSize + (maxSize - minSize) * THREE.Math.smoothstep(this.radius, minRadiusBound, maxRadiusBound);
+        //console.log('fire material  particle size:', size )
+        
+        this.particleCount =  Math.ceil(   length * Math.sqrt(this.strength  * this.height )   * this.radius / (this.size * this.size) *  25  )        
+        //console.log('fire particleCount',this.particleCount)
+    }
+    getPointsForBound(){
+        return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距
     }
 
-    createGeometry( radius, height, particleCount){
-        let geometry = new THREE.BufferGeometry()
+    getBound(points){ // points为生成点(圆心)
+        this.boundPoints = []  
+        let boundingBox = new THREE.Box3()
         
+         
+        let margin = this.size * 0.13 + 0.3; 
         
+        points.forEach(bottom=>{ 
+            let top = bottom.clone()
+            top.z +=  this.height 
+            boundingBox.expandByPoint(bottom);
+            boundingBox.expandByPoint(top);
+            this.boundPoints.push(bottom,top)            
+        })
+        let xyExpand = this.radius+margin 
+        boundingBox.expandByVector(new THREE.Vector3(xyExpand,xyExpand,margin)) 
+        this.boundingBox = boundingBox
         
-        var halfHeight = height * 0.5;
+        /* if(!this.debugBox){
+            this.debugBox = new THREE.Mesh(boxGeo, boxMat)
+            this.add(this.debugBox)
+        }
+        
+        this.debugBox.scale.copy(boundingBox.getSize(new THREE.Vector3))
+        this.debugBox.position.copy(boundingBox.getCenter(new THREE.Vector3))  */
+         
+    }
+
+
+    createGeometry( radius, height, particleCount){
+        let geometry = new THREE.BufferGeometry()
+         
         
         let count , points
         if(this.positions.length>1){
@@ -96,12 +142,14 @@ class FireParticle extends THREE.Points{
             const spaceDis = 0.2;//间隔距离
             
             count = Math.ceil(this.curve.wholeLength / spaceDis) + 1 
-            console.log('count', count)
+            //console.log('count', count)
             points = this.curve.getSpacedPoints( count );  //得到的数量会比count多一个
             count = points.length  
             //得到的点不太均匀,两端容易点少。
+            this.getBound(points) 
             
-            
+        }else{
+            this.getBound(this.positions) 
         }
          
         
@@ -125,7 +173,7 @@ class FireParticle extends THREE.Points{
                 var angle = Math.random() * 2 * Math.PI;
                 position[i * 3 + 0] = center.x + Math.cos(angle) * r; 
                 position[i * 3 + 1] = center.y + Math.sin(angle) * r;
-                position[i * 3 + 2] = center.z + (radius - r) / radius * halfHeight + halfHeight; //不太明白这句为什么能达到height高度
+                position[i * 3 + 2] = center.z + (radius - r) / radius * height/2 + height/2; //不太明白这句为什么能达到height高度
                
                 sprite[i] = 0.25 * (Math.random() * 4 | 0);
                 randam[i] = Math.random();
@@ -148,21 +196,24 @@ class FireParticle extends THREE.Points{
     updateGeometry(){ 
         this.computeParams()
         this.geometry.dispose() 
-        this.geometry = this.createGeometry( this.fireRadius, this.fireHeight, this.particleCount )
-        
+        this.geometry = this.createGeometry( this.radius, this.height, this.particleCount )
+        this.material.uniforms.size.value = this.size
     }
 
 
-    createMaterial(fov, color){
+
+
+    createMaterial(){
+         
         
         const material = new THREE.ShaderMaterial( {
             uniforms:{
-                color: { type: "c", value: new THREE.Color(color) },
-                size: { type: "f", value: THREE.Math.clamp(this.fireRadius * 1.2, 0.5, 5)},
+                color: { type: "c", value: new THREE.Color(this.color) },
+                size: { type: "f", value: this.size},
                 u_sampler: { type: "t", value: getTexture() },
                 time: { type: "f", value: 0.0 },
                 heightOfNearPlane: { type: "f", value:0},  //相对far ,以确保画面缩放时点的大小也会缩放
-                fireHeight :{ type: "f", value:this.fireHeight}  ,
+                height :{ type: "f", value:this.height}  ,
             },
             vertexShader,
             fragmentShader,
@@ -171,10 +222,9 @@ class FireParticle extends THREE.Points{
             depthWrite: false,
             transparent: true
 
-        } );
-        
-        
-        return material
+        } ); 
+        this.material = material
+        this.setPerspective(this.fov, this.screenHeight)
     }
 
 
@@ -199,12 +249,25 @@ class FireParticle extends THREE.Points{
     
     
     update(delta){
-        
+        if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了
+            return
+        }
+        if(!Potree.Utils.isInsideFrustum(this.boundingBox, viewer.scene.getActiveCamera())){
+            viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围
+            //console.log('unvi')
+            return
+        }else{
+            viewer.updateVisible(this,'isInsideFrustum', true )
+        } 
         delta *= 1//更改速度
         
-        this.material.uniforms.time.value = (this.material.uniforms.time.value + delta) % 1;
-        
-      
+        this.material.uniforms.time.value = (this.material.uniforms.time.value + delta) % 1; 
+    }
+    
+    dispose(){
+        this.geometry.dispose();
+        this.material.dispose();
+        this.dispatchEvent('dispose') 
     }
 }
 

+ 2 - 5
src/objects/fireParticle/fire/shader.js

@@ -53,12 +53,9 @@ export const fragmentShader = `
     void main() 
     {
         
-        //const vec3 smokeColor = vec3(0.1,0.1,0.1); 
+       
         vec2 texCoord = vec2(gl_PointCoord.x * 0.25 + vSprite, gl_PointCoord.y);
-        
-        
-        //vec3 mixColor = mix(color, smokeColor, 1.0);
-        
+         
         gl_FragColor = vec4( texture2D( u_sampler, texCoord ).xyz * color * vOpacity, 1.0 );
           
          

BIN
src/objects/fireParticle/images/checkerboard.jpg


BIN
src/objects/fireParticle/images/circle-particle.png


BIN
src/objects/fireParticle/images/explode.png


BIN
src/objects/fireParticle/images/explode1.png


BIN
src/objects/fireParticle/images/groundcolor.jpg


BIN
src/objects/fireParticle/images/groundnormal.jpg


BIN
src/objects/fireParticle/images/plane.png


BIN
src/objects/fireParticle/images/smoke (1).png


BIN
src/objects/fireParticle/images/smoke.png


BIN
src/objects/fireParticle/images/smokeparticle.png


+ 0 - 87
src/objects/fireParticle/smoke.html

@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - custom attributes [particles]</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<link type="text/css" rel="stylesheet" href="../main.css">
-	</head>
-
-	<body>
-		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - custom attributes example - particles</div>
-		<div id="container"></div>
-		<script type="module">
-			import * as THREE from '../../build/three.module.js';
-            
-			import { OrbitControls } from '../jsm/controls/OrbitControls.js';
-			import Stats from '../jsm/libs/stats.module.js';
-			import { GUI } from '../jsm/libs/dat.gui.module.js';
-            import SmokeParticle from './smoke/SmokeParticle.js';
-             
-			let renderer, scene, camera, stats,clock;
-			let smoke;
-
-			const width = window.innerWidth;
-			const height = window.innerHeight;
-
-			init();
-			animate();
-
-			function init() {
-				stats = new Stats();
-				clock = new THREE.Clock()
-				scene  = new THREE.Scene();
-                var VIEW_ANGLE = 45, ASPECT = width / height, NEAR = 0.1, FAR = 20000;
-	            camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
-				camera.position.set(0,200,400);
-	            camera.lookAt(scene.position);	
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setSize( width, height );
-				renderer.setClearColor( 0x333333 );
-				document.body.appendChild( renderer.domElement );
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.update();
-
-				scene.add( new THREE.DirectionalLight( 0xffffff ) );
-				scene.add( new THREE.AmbientLight( 0x666666 ) );
-				
-				initParticles()
-				//http://stemkoski.github.io/Three.js/Particle-Engine.html
-			}
-
-			function initParticles() {
-                let position = new THREE.Vector3(0,-100,0)
-				smoke = new SmokeParticle(position)
-                smoke.init()
-                smoke.createMesh() 
-				scene.add( smoke.particleMesh );
-			}
-
-			function updateSmoke(){
-				var delta = clock.getDelta();
-	            smoke.update( delta * 0.5 );	
-			}
-
-			function onWindowResize() {
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-				renderer.setSize( window.innerWidth, window.innerHeight );
-			}
-
-			function animate() {
-				requestAnimationFrame( animate );
-				updateSmoke()
-				render();
-				stats.update();
-			}
-
-			function render() {
-				renderer.render( scene, camera );
-			}
-		</script>
-
-</body>
-
-</html>

+ 1 - 1
src/objects/fireParticle/smoke/Particle.js

@@ -1,5 +1,5 @@
 import * as THREE from "../../../../libs/three.js/build/three.module.js";
-import Tween from './Tween.js'
+import Tween from '../Tween.js'
 export default class Particle{
     constructor(prop={}){
         this.position     = new THREE.Vector3();

+ 241 - 100
src/objects/fireParticle/smoke/SmokeParticle.js

@@ -1,6 +1,6 @@
 import * as THREE from "../../../../libs/three.js/build/three.module.js";
 import Particle from './Particle.js'
-import Tween from './Tween.js'
+import Tween from '../Tween.js'
 import { vertexShader, fragmentShader } from './shader.js'
 
 const Type = Object.freeze({ "CUBE":1, "SPHERE":2 });
@@ -12,7 +12,8 @@ const getTexture = ()=>{
     }
     return particleTexture
 }
-
+const boxGeo = new THREE.BoxBufferGeometry(1,1,1,1);
+const boxMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"})
 
 
 
@@ -25,17 +26,17 @@ const defaults =
  
     positionSpread   : new THREE.Vector3( 1, 1, 0), //cube
      
-    positionRadius   :   1,       // sphere
+    radius   :   1,       // sphere
         
     velocityStyle    : 'cube',
      
-    velocityBase     : new THREE.Vector3( 0,  0,  1),     // cube  基础速度
-    velocitySpread   : new THREE.Vector3( 0.2, 0.2, -0.3), 
-    
-    accelerationBase : 0.7,             //基础加速度
-    accelerationSpread : 1,	
+    velocityBase     : new THREE.Vector3( 0,  0,  0.5),     // cube  基础速度
+    velocitySpread   : new THREE.Vector3( 1, 1, 0.3), 
     
+    accelerationBase : 0.3,             //基础加速度
+    accelerationSpread : 0.6,	
     
+    //没使用
     speedBase  : 0.1,       //sphere
     speedSpread : 0.5,
           
@@ -50,35 +51,32 @@ const defaults =
         
     sizeBase    :   0,  
     sizeSpread  :   0,
-    sizeTween    : [[0, 0.3,   1], [0.3, 1.4,  3 ]], 
+    sizeTween    : [[0, 0.3,   1], [0.3, 1.4,  6 ]], 
     
     
     colorBase   :   new THREE.Vector3(0.0, 1.0, 0.5), 
     colorSpread :   new THREE.Vector3(0.0, 0.0, 0.0),
-    colorTween   : new Tween( [0.4, 1], [ new THREE.Vector3(0,0,0.4), new THREE.Vector3(0, 0, 0.2) ] ),
+    colorTween   : new Tween( [0.2, 1], [ new THREE.Vector3(0,0,0.4), new THREE.Vector3(0, 0, 0.1) ] ),
 
     opacityBase     :   0.1,//1.0,
     opacitySpread   :   0.2,
-    opacityTween :[ [0, 0.3,  0.7, 0.95, 1], [0, 0.1, 0.4 , 0.1, 0 ] ], 
+    opacityTween :[ [0, 0.1, 0.9, 1], [0.1, 0.4 , 0.03, 0 ] ], 
      
     //particlesPerSecond : 20,
-    density : 1,
-    particleDeathAge   : 1.5,		
+    strength : 1,
+    particleDeathAge   : 3,  //从底下升起后能持续的时间		
     //emitterDeathAge    : 60 // time (seconds) at which to stop creating particles.
+    height : 3,
 };
 
-const positions = [];
-const colors = [];
-const alives = [];
-const opacitys = [];
-const sizes = [];
-const angles = [];
+
+const debugSphere = new THREE.Mesh(new THREE.SphereBufferGeometry(0.03, 5,5), new THREE.MeshBasicMaterial({color:'white',depthTest:false}))
 
 export default class SmokeParticle extends THREE.Points{
     constructor(prop={}) {
         super()
         
-        this.particleArray = [];   
+         
         this.blendStyle = THREE.NormalBlending; // false; 
         this.emitterAge = 0.0;
         //this.emitterAlive = true;
@@ -87,10 +85,18 @@ export default class SmokeParticle extends THREE.Points{
         for ( var key in prop ){
             let value = prop[key] 
             if(value instanceof Array && value[0] instanceof Array ) this[ key ] = new Tween(...value)
-            else this[ key ] = value
+            else if(value instanceof THREE.Vector3 || value instanceof THREE.Color){
+                this[ key ] = value.clone()
+            }else{
+                this[ key ] = value
+            }
         }
-        this.curve = prop.curve
         
+        this.defaultSizeTween = this.sizeTween.clone()
+        this.defaultOpacityTween = this.opacityTween.clone()
+        
+        
+        this.geometry = new THREE.BufferGeometry()
         this.computeParams()
         this.createMaterial()
         this.createGeometry()
@@ -107,71 +113,81 @@ export default class SmokeParticle extends THREE.Points{
         //---------------------------------------
         this.setSize({viewport:viewer.mainViewport})
         this.setFov(viewer.fov)
-        viewer.addEventListener('resize',(e)=>{ 
+        
+        let setSize = (e)=>{
             if(e.viewport.name != "MainView")return
-            this.setSize(e) 
-        })  
-        viewer.addEventListener('fov_changed',(e)=>{ 
+            this.setSize(e)
+        }
+        let setFov = (e)=>{
             this.setFov(e.fov) 
-        })
-        
-        
-        viewer.on('pageVisible', (state)=>{   
-            if(state){//重新一个个放出粒子,否则会一股脑儿全部出来,因为同时大于粒子周期了一起重新生成出现。
-                setTimeout(()=>{//会先update一次delta为pageUnvisile的时间才触发
-                    //归零
-                    //console.log('归零')
-                    
-                    this.reStart()
+        }
+        /* let reStart = (e)=>{
+            if(e.v){//重新一个个放出粒子,否则会一股脑儿全部出来,因为同时大于粒子周期了一起重新生成出现。
+                setTimeout(()=>{//会先update一次delta为pageUnvisile的时间才触发 
+                    //console.log('归零') 
+                    //this.reStart()
                 },1) 
-            }  
-        })  
-        
-    }
-
-    reStart(){
-        this.emitterAge = 0;
-        
-        this.geometry.dispose()
+            } 
+        } */
+        viewer.addEventListener('resize',setSize) 
+        viewer.addEventListener('fov_changed',setFov)
+        //viewer.addEventListener('pageVisible', reStart)
+        
+        this.addEventListener('dispose',()=>{
+            viewer.removeEventListener('resize',setSize) 
+            viewer.removeEventListener('fov_changed',setFov)
+            //viewer.removeEventListener('pageVisible', reStart)
+        })   
         
-        this.createGeometry()  
     }
-
-    createMaterial(){
-        this.material = new THREE.ShaderMaterial( 
-        {
-            uniforms: 
-            {
-                u_sampler:   { type: "t", value: getTexture() },
-                heightOfNearPlane: { type: "f", value:0}  //相对far ,以确保画面缩放时点的大小也会缩放
-            },
-            vertexShader:   vertexShader,vertexShader,
-            fragmentShader: fragmentShader,
-            transparent: true,
-            alphaTest: 0.5, // if having transparency issues, try including: alphaTest: 0.5, 
-            blending: this.blendStyle,
-            depthTest: this.blendStyle != THREE.NormalBlending
-        });
+    
+    
+    
+    computeParams(){   
+         
+        let length = (this.curve ? this.curve.wholeLength : 0) + this.radius * 2 //加上首尾的半径
+        //注意:烟最低高度一米, 0<strength<1
+        if(this.positionStyle == 'cube'){
+            this.positionSpread.set(this.radius,this.radius,0)
+        } 
+        this.velocityBase.set(0,0, (this.height - 0.5 * this.accelerationBase * this.particleDeathAge * this.particleDeathAge) / this.particleDeathAge )
+        //let height = this.velocityBase.z * this.particleDeathAge + 0.5 * this.accelerationBase * this.particleDeathAge * this.particleDeathAge;//s = V0 * t + 0.5 * a * t*t ;     
+        this.velocityBase.z = Math.max(0,this.velocityBase.z);
+        this.particleCount =  Math.ceil(  length * Math.sqrt(this.strength * this.height * this.radius )   )  
+        this.particleCount = Math.max(5,this.particleCount)
+        { 
+            const minSize = 1, maxSize = 2, minBound = 0.01, maxBound = 1;
+            let size = minSize + (maxSize - minSize) * THREE.Math.smoothstep( this.strength, minBound, maxBound);
+                
+            this.sizeTween.values = this.defaultSizeTween.values.map(e=> e*size)
+        }
+        { 
+            const minSize = 1 , maxSize = 1.5, minBound = 0.01, maxBound = 1;
+            let opac = minSize + (maxSize - minSize) * THREE.Math.smoothstep( this.strength, minBound, maxBound);
+                
+            this.opacityTween.values = this.defaultOpacityTween.values.map(e=> e*opac )
+        }
+         
+        //console.log('smoke  particleCount',this.particleCount)
         
         
         
         
     }
 
+    reStart(){
+        this.emitterAge = 0; 
+        this.createGeometry()  
+    }
 
-    computeParams(){  
-        this.density = 1
-        let length = (this.curve ? this.curve.wholeLength : 0) + this.positionRadius * 2 //加上首尾的半径
-        
-         
-        
-        let maxHeight = this.velocityBase.z * this.particleDeathAge + 0.5 * this.accelerationBase * this.particleDeathAge * this.particleDeathAge;//s = V0 * t + 0.5 * a * t*t ;     
-        this.particleCount =  Math.ceil( this.positionRadius * maxHeight * length * this.density  )  
-        this.particleCount = Math.max(5,this.particleCount)
+
+    updateGeometry(){ 
+        this.computeParams()
+        this.reStart() 
         
-        console.log('smoke  particleCount',this.particleCount)
     }
-
+    
+    
     createParticle(center)
     {
         var particle = new Particle({
@@ -191,14 +207,16 @@ export default class SmokeParticle extends THREE.Points{
             var t = Math.PI * 2 * Math.random();
             var r = Math.sqrt( 1 - z*z ) ;
             var vec3 = new THREE.Vector3( r * Math.cos(t), r * Math.sin(t), z );
-            particle.position = new THREE.Vector3().addVectors( this.positionBase, vec3.multiplyScalar( this.positionRadius ) );
+            particle.position = new THREE.Vector3().addVectors( this.positionBase, vec3.multiplyScalar( this.radius ) );
            */
             //怎么改半径
-            var y = 2 * Math.random() - 1    
-            var t = Math.PI * 2 * Math.random();
-            var r = Math.sqrt( 1 - y*y ) ;
-            var vec3 = new THREE.Vector3( r * Math.cos(t), y, r * Math.sin(t)  );
-            particle.position = new THREE.Vector3().addVectors( this.positionBase, vec3.multiplyScalar( this.positionRadius ) );
+            let y = 2 * Math.random() - 1    
+            let t = Math.PI * 2 * Math.random();
+            let r = Math.sqrt( 1 - y*y ) ; //因为 r*r = 1-y*y = x*x + z*z = r*r(cos^2 + sin^2 );
+            let lowDownRatio = 0.2 //压低近平面
+            let vec3 = new THREE.Vector3( r * Math.cos(t), y, Math.abs(r * Math.sin(t) ) * lowDownRatio);
+            particle.position = new THREE.Vector3().addVectors( this.positionBase, vec3.multiplyScalar( this.radius ) );
+           
           
         } 
          
@@ -210,7 +228,7 @@ export default class SmokeParticle extends THREE.Points{
          
         if ( this.velocityStyle == 'cube' )
         {
-            particle.velocity     = this.randomVector3( this.velocityBase,     this.velocitySpread ); 
+            particle.velocity  = this.randomVector3( this.velocityBase,  this.velocitySpread ); 
         }
         if ( this.velocityStyle == 'sphere' )  
         {
@@ -239,11 +257,55 @@ export default class SmokeParticle extends THREE.Points{
     }			
 
 
+    getPointsForBound(){
+        return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距
+    }
 
-    createGeometry(){
-        this.geometry = new THREE.BufferGeometry()
-        var debugSphere = new THREE.Mesh(new THREE.SphereBufferGeometry(0.03, 5,5), new THREE.MeshBasicMaterial({color:'white',depthTest:false}))
+    getBound(points){ // points为生成点(圆心)
+        this.boundPoints = [] 
+        let boundingBox = new THREE.Box3()
+        
+        
+        let maxSize = this.sizeTween.values.slice().sort((a,b)=>b-a)[0]            
+        let margin0 = maxSize * 0.11
+        let margin1 = margin0 + 0.5   ;//保守估计还会飘出这么多距离吧: size + 飘动  
+        
+         
+        points.forEach(bottom=>{ 
+            let top = bottom.clone()
+            top.z +=  this.height 
+            boundingBox.expandByPoint(bottom);
+            boundingBox.expandByPoint(top); 
+            this.boundPoints.push(bottom,top)
+        })
+        let xyExpand = this.radius+margin1 
+        boundingBox.expandByVector(new THREE.Vector3(xyExpand,xyExpand,0))
+        boundingBox.min.z -= margin0 
+        boundingBox.max.z += margin1 
+       
+       
+       
+        this.boundingBox = boundingBox
+        
+        /* if(!this.debugBox){
+            this.debugBox = new THREE.Mesh(boxGeo, boxMat)
+            this.add(this.debugBox)
+        }
+        
+        this.debugBox.scale.copy(boundingBox.getSize(new THREE.Vector3))
+        this.debugBox.position.copy(boundingBox.getCenter(new THREE.Vector3))  */  
+         
+    }
 
+    createGeometry(){
+        this.particleArray = []
+        const positions = [];
+        const colors = [];
+        const alives = [];
+        const opacitys = [];
+        const sizes = [];
+        const angles = [];
+               
         let count, points;
         if(this.positions.length>1){
              
@@ -252,13 +314,14 @@ export default class SmokeParticle extends THREE.Points{
             count = Math.ceil(this.curve.wholeLength / spaceDis) + 1 
              
             points = this.curve.getSpacedPoints( count );  
+            
             count = points.length
             
-            points.forEach(e=>  { 
+            /* points.forEach(e=>  { 
                 var sphere = debugSphere.clone();
                 sphere.position.copy(e)
                 viewer.scene.scene.add(sphere)
-            })
+            }) */
             let haventGetPoints = points.slice() 
             var getRanPoints = function(i){
                 var a = Math.random()
@@ -271,6 +334,11 @@ export default class SmokeParticle extends THREE.Points{
                 }
                 return point
             }
+            
+            
+            this.getBound(points)
+        }else{
+            this.getBound(this.positions)
         }
         
         
@@ -308,33 +376,90 @@ export default class SmokeParticle extends THREE.Points{
         this.geometry.setAttribute( 'customAngle', new THREE.BufferAttribute( new Float32Array(angles), 1 ) );
     }
     
-    
-    updateGeometry(){ 
-        this.computeParams()
-        this.reStart() 
+    createMaterial(){
+        this.material = new THREE.ShaderMaterial( 
+        {
+            uniforms: 
+            {
+                u_sampler:   { type: "t", value: getTexture() },
+                heightOfNearPlane: { type: "f", value:0}  //相对far ,以确保画面缩放时点的大小也会缩放
+            },
+            vertexShader:   vertexShader,vertexShader,
+            fragmentShader: fragmentShader,
+            transparent: true,
+            alphaTest: 0.5, // if having transparency issues, try including: alphaTest: 0.5, 
+            blending: this.blendStyle,
+            depthTest: this.blendStyle != THREE.NormalBlending
+        });
+        
+        
+        this.setPerspective(this.fov, this.screenHeight)
+        
         
     }
     
 
     update(dt){
+        if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了
+            return
+        }
+        if(!Potree.Utils.isInsideFrustum(this.boundingBox, viewer.scene.getActiveCamera())){
+            viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围
+            //console.log('unvi')
+            return
+        }else{
+            viewer.updateVisible(this,'isInsideFrustum', true )
+        } 
+        
+        
+        
+        if(dt > 1){
+            console.log('update dt>1', dt)
+        }
          
-        dt *= 0.5;
+        //dt *= 0.5;
+        
+        const recycleIndices = [];
+        const recycleAges = []
+        
+        
+        const positions = [];
+        const colors = [];
+        const alives = [];
+        const opacitys = [];
+        const sizes = [];
+        const angles = [];
+    
+       
+        
+        
+        
         
-        var recycleIndices = [];
-        // update particle data
         for (var i = 0; i < this.particleCount; i++)
         {
             if ( this.particleArray[i].alive )
             {
                   
                 if ( this.velocityStyle == 'cube' )
-                {    
-                    if(this.particleArray[i].age - this.particleArray[i].lastChangeVage > this.particleDeathAge*0.4  ){
-                        if( Math.random()>0.3){//一定几率改变下方向
-                            this.particleArray[i].velocity = this.randomVector3( this.velocityBase,     this.velocitySpread ); 
-                        }
+                {        //一定几率改变下方向
+                    let ratio = Math.random()
+                    if(this.particleArray[i].age - this.particleArray[i].lastChangeVage > this.particleDeathAge*ratio  ){
+                        
+                        this.particleArray[i].velocity = this.randomVector3( this.velocityBase, this.velocitySpread ); 
+                        
                         this.particleArray[i].lastChangeVage = this.particleArray[i].age
                     }
+                }else{
+                    /* if(this.particleArray[i].age - this.particleArray[i].lastChangeVage > this.particleDeathAge*0.3  ){
+                        if( Math.random()>0.1){//一定几率改变下方向
+                            var speed  = this.randomValue( this.speedBase, this.speedSpread ); 
+                            this.particleArray[i].velocity = this.randomVector3( new THREE.Vector3,   new THREE.Vector3(1,1,1) ); 
+                            this.particleArray[i].velocity.normalize().multiplyScalar( speed );
+                        }
+                        this.particleArray[i].lastChangeVage = this.particleArray[i].age
+                    } */
+                    
+                    
                 }
                  
                 
@@ -346,6 +471,7 @@ export default class SmokeParticle extends THREE.Points{
                 {
                     this.particleArray[i].alive = 0.0;
                     recycleIndices.push(i);
+                    recycleAges.push((this.particleArray[i].age - this.particleDeathAge)%(this.particleDeathAge ))
                 } 
                 
                 
@@ -399,10 +525,10 @@ export default class SmokeParticle extends THREE.Points{
         // if any particles have died while the emitter is still running, we imediately recycle them
         for (var j = 0; j < recycleIndices.length; j++)
         {
-            var i = recycleIndices[j];
+            var i = recycleIndices[j]; 
             this.particleArray[i] = this.createParticle(this.particleArray[i].center);
             this.particleArray[i].alive = 1.0; // activate right away
-
+            this.particleArray[i].age = recycleAges[j]
             positions[3*i] = this.particleArray[i].position.x
             positions[3*i+1] = this.particleArray[i].position.y
             positions[3*i+2] = this.particleArray[i].position.z
@@ -450,4 +576,19 @@ export default class SmokeParticle extends THREE.Points{
         this.material.uniforms.heightOfNearPlane.value = far 
     }
     
-}
+    dispose(){
+        this.geometry.dispose();
+        this.material.dispose();
+        this.dispatchEvent('dispose') 
+    }
+}
+
+
+/* 
+    改进:如果有必要
+    
+    根据curve中分成的点,分成多个簇,每个簇掌管该部分的可见性和particle的数量。
+    在camera_changed时根据远近修改每个簇的particle的数量,当然不会大于初始创建的个数。多出的随机隐藏。
+
+
+ */

+ 0 - 26
src/objects/fireParticle/smoke/Tween.js

@@ -1,26 +0,0 @@
-import * as THREE from "../../../../libs/three.js/build/three.module.js";
-export default class Tween{
-    constructor(timeArray, valueArray){
-        this.times  = timeArray || [];
-        this.values = valueArray || [];
-    }
-
-    lerp(t)
-    {
-        var i = 0;
-        var n = this.times.length;
-        while (i < n && t > this.times[i])  
-            i++;
-        if (i == 0) {
-            return this.values[0];
-        }
-        if (i == n)	{
-            return this.values[n-1];
-        }
-        var p = (t - this.times[i-1]) / (this.times[i] - this.times[i-1]);
-        if (this.values[0] instanceof THREE.Vector3)
-            return this.values[i-1].clone().lerp( this.values[i], p );
-        else // its a float
-            return this.values[i-1] + p * (this.values[i] - this.values[i-1]);
-    }
-}

+ 2 - 3
src/objects/tool/AnnotationTool.js

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

+ 2 - 3
src/objects/tool/ClippingTool.js

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

+ 101 - 129
src/objects/tool/CurveCtrl.js

@@ -3,39 +3,46 @@
  
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {LineDraw} from "../../utils/DrawUtil";
+import math  from "../../utils/math";
+import HandleSvg from  "./HandleSvg";
+import HandleSprite from  "./HandleSprite";
+
+const sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(0.08,0.08,3,2), new THREE.MeshBasicMaterial({color:'#f88'}))
+
+
 
- 
 export default class CurveCtrl extends THREE.Object3D {
     
-    constructor(points, material, color ){
+    constructor(points, lineMat, color, name, options={}){
         super()
         this.curve = new THREE.CatmullRomCurve3(points, false, "centripetal"    /* , tension */)
-        this.name = 'curveNode'        
-        this.material = material
+        this.name = name || 'curveNode'
+        this.handleMat = options.handleMat
+        this.lineMat = lineMat
         this.createPath(); 
         this.color = color;
         this.handles = []; 
-        this.wholeLength = 0
-       /*  setTimeout(()=>{
-            
-            
-        })
-        
+        this.wholeLength = 0 
+        this.viewports = options.viewports || [viewer.mainViewport] //for HandleSprite
         for(let i=0,j=this.points.length; i<j;i++){
-            this.handles.push[this.createHandle(this.points[i])]
-        }  */
+            this.handles.push(this.createHandle(this.points[i]))
+        }   
         this.visible_ = true
         
+         
         
-        let updateHandles = ()=>{
-            this.updateHandles()
+        if(Potree.settings.isTest){   
+            this.spheres = new THREE.Object3D;
+            /* let i = Count+1;
+            while(i>0){
+                this.spheres.add(sphere.clone());
+                i--;
+            } */
+            this.add(this.spheres)
         }
-        viewer.addEventListener("camera_changed", updateHandles)
         
-        this.addEventListener('dispose', ()=>{ 
-            viewer.removeEventListener("camera_changed",  updateHandles)  
-            this.dispose()
-        })
+        this.updatePath()
+        
         
     }
     
@@ -43,7 +50,7 @@ export default class CurveCtrl extends THREE.Object3D {
     addPoint(position, index, ifUpdate){
         let length = this.points.length 
         
-        if(index === undefined){
+        if(index == void 0 ){
 			index = length;
 		}
         
@@ -53,12 +60,12 @@ export default class CurveCtrl extends THREE.Object3D {
       
         this.points = [...this.points.slice(0,index), position, ...this.points.slice(index,length)]
         
-        ifUpdate && this.updatePath()
+        ifUpdate && (this.updatePath(), this.updateHandle(index))
     
     }
     removePoint(index){
         let handle = this.handles[index]
-        handle.svg.remove();
+        handle.dispose();
         
         this.handles.splice(index,1)
         this.points.splice(index,1)
@@ -67,139 +74,101 @@ export default class CurveCtrl extends THREE.Object3D {
     
     createPath(){ 
    
-        const line = LineDraw.createFatLine( [ ],this.material)
+        const line = LineDraw.createFatLine( [ ],this.lineMat)
         this.line = line;
         this.add(line);
     }
     
     
-    updatePath(){
-        let points 
-
+    updatePath(){ 
+        this.curve.needsUpdate = true; //如果不更新,得到的点不均匀,开头点少。 
+        
+        let points, length = this.points.length
+        
         this.wholeLength = this.points.reduce((total, currentValue, currentIndex, arr)=>{ //所有端点的距离总和
             if(currentIndex == 0)return 0
             return total + currentValue.distanceTo(arr[currentIndex-1]);
         },0)
         
-        if(this.points.length > 1){ 
+        if(length > 1){ 
             const count = THREE.Math.clamp(Math.ceil(this.wholeLength * 5), 30, 500);
+         
+            points = this.curve.getSpacedPoints( count ); 
+              
+              
+            if(this.needsPercent){ //获取每个节点在整条中的百分比,便于定位(但不精确)
+                this.pointsPercent = [0];
+                let sums = [0]
+                let sum = 0, last = points[0]
+                for(let i=1;i<length;i++){
+                    let point = this.points[i];
+                    sum += point.distanceTo(last);  //参考getLengths函数,根据长度得到百分比
+                    last = point;
+                    sums.push(sum) 
+                }
+                for(let i=1;i<length;i++){
+                    this.pointsPercent.push(sum == 0 ? i/length : sums[i] / sum);
+                }
+                
+                
+            }
+              
             
-            points = this.curve.getSpacedPoints( count ); //得到的点不太均匀,两端容易点少。
-                   
+              
+              
+              
+            if(Potree.settings.isTest){
+                this.spheres.children.forEach(e=>e.visible = false);
+                points.forEach((e,i)=>{
+                    let sphere1 = this.spheres.children[i]
+                    if(!sphere1){
+                        sphere1 = sphere.clone();
+                        this.spheres.add(sphere1);
+                    }                        
+                    sphere1.position.copy(e)
+                    sphere1.visible = true
+                })
+            }
         }else{
-            points = [this.points[0], this.points[0]]
+            points = []
         } 
-
-        LineDraw.updateLine(this.line, points)      
         
         
+        LineDraw.updateLine(this.line, points)      
+        
         
+        this.dispatchEvent('updatePath')
         
     }
     
     
     createHandle(position){
+        if(this.handleMat){
+            var handle = new HandleSprite(position,  {mat:this.handleMat, viewports:this.viewports})
+            this.add(handle)
+        }else{
+            var handle = new HandleSvg(position,  this.color)
+        }
 		
-		const svgns = "http://www.w3.org/2000/svg";
-		const svg = document.createElementNS(svgns, "svg");
-
-		svg.setAttribute("width", "2em");
-		svg.setAttribute("height", "2em");
-		svg.setAttribute("position", "absolute");
-
-		svg.style.left = "50px";
-		svg.style.top = "50px";
-		svg.style.position = "absolute";
-		svg.style.zIndex = "10000";
-        svg.style.cursor = 'grab'
-        svg.style.transform = 'translate(-50%,-50%)'
-        
-		const circle = document.createElementNS(svgns, 'circle');
-		circle.setAttributeNS(null, 'cx', "1em");
-		circle.setAttributeNS(null, 'cy', "1em");
-		circle.setAttributeNS(null, 'r', "0.5em");
-		circle.setAttributeNS(null, 'style', 'fill: '+this.color+'; stroke: black; stroke-width: 0.2em;' );
-		svg.appendChild(circle);
-
-
-		const element = viewer.renderer.domElement.parentElement;
-		element.appendChild(svg);
-
-
-		const startDrag = (evt) => {
-			this.selectedElement = svg;
-
-			document.addEventListener("mousemove", drag);
-		};
-
-		const endDrag = (evt) => {
-			this.selectedElement = null;
-
-			document.removeEventListener("mousemove", drag);
-		};
-
-		const drag = (evt) => {
-			if (this.selectedElement) {
-                let index = this.handles.indexOf(handle)
-                
-				evt.preventDefault();
-
-				const rect = viewer.renderer.domElement.getBoundingClientRect();
-
-				const x = evt.clientX - rect.x;
-				const y = evt.clientY - rect.y;
-
-				const {width, height} = viewer.renderer.getSize(new THREE.Vector2());
-				const camera = viewer.scene.getActiveCamera();
-				 
-                const projected = position.clone().project(camera);
-          
-
-				projected.x = ((x / width) - 0.5) / 0.5;
-				projected.y = (-(y - height) / height - 0.5) / 0.5;
-
-				const unprojected = projected.clone().unproject(camera);
-				position.set(unprojected.x, unprojected.y, unprojected.z);
+        handle.visible = this.visible 
+        handle.addEventListener('dragged',(e)=>{
+            let index = this.handles.indexOf(handle) 
+            this.points[index].copy(e.position) 
             
-                this.updateHandle(index)
-                this.updatePath()
-                
-                this.dispatchEvent({type:'dragCurvePoint'})
-			}
-		};
-
-		svg.addEventListener('mousedown', startDrag);
-		svg.addEventListener('mouseup', endDrag);
-        svg.style.display = this.visible ? "" : "none" 
-        
-		const handle = {
-			svg: svg,
-		};
-        
-        this.addEventListener('dispose',()=>{
-            svg.removeEventListener('mousedown', startDrag);
-            svg.removeEventListener('mouseup', endDrag); 
+            this.updatePath()
+            
+            this.dispatchEvent({type:'dragCurvePoint', index})
         })
-		return handle;
+        
+        return handle
 	}
     
     
+    
     updateHandle(index){
         if(!this.visible)return
         
-        let handle = this.handles[index]
-        var position = this.points[index]
-        
-        var p = Potree.Utils.getPos2d(position, viewer.scene.getActiveCamera(), viewer.renderArea, viewer.mainViewport);
-        if(!p.trueSide){
-            handle.svg.style.display = 'none';  return;
-        }
-        handle.svg.style.left =  p.posInViewport.x  
-        handle.svg.style.top = p.posInViewport.y 
-          
-        
-         
-        handle.svg.style.display = ''
+        this.handles[index].update() 
             
     }
     
@@ -219,8 +188,8 @@ export default class CurveCtrl extends THREE.Object3D {
         if(v != this.visible_ ){
             this.visible_ = v 
             this.visible = v 
-            if(this.handles){
-                this.handles.forEach(e=>e.svg.style.display = v ? "" : "none" )
+            if(this.handles){ 
+                this.handles.forEach(e=>e.visible = v  )
                 if(v) this.updateHandles() //因为不可见时没更新位置
             }
         }
@@ -247,11 +216,14 @@ export default class CurveCtrl extends THREE.Object3D {
         return this.curve.getSpacedPoints(t)
     }
     dispose(){
-        this.parent.remove(this);
+        this.parent && this.parent.remove(this);
         
-        this.handles.forEach(e=>e.svg.remove() )
+        this.handles.forEach(e=>e.dispose() )
         
-        this.line.geometry.dispose()
+        this.line.geometry && this.line.geometry.dispose()
         
     }
+    
+    
+    
 }

+ 78 - 0
src/objects/tool/HandleSprite.js

@@ -0,0 +1,78 @@
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import Sprite from  "../Sprite";
+/* 
+两种拖拽方式:
+1 只依附在点云上
+2 平行于镜头view移动 */
+
+
+
+
+
+
+const geo = new THREE.PlaneBufferGeometry(1,1)
+export default class HandleSprite extends Sprite{
+    constructor(position,options={}){
+        
+        options.sizeInfo = {width2d:60} 
+        super(options)
+        this.position.copy(position);
+         
+        this.dragStyle =  options.dragStyle || 'default'    //'default'||'onPointCloud'  
+        
+        
+        this.bindEvent()
+        
+        
+    }
+    
+    
+    bindEvent(){
+        let projectedStart, pointerStart
+        
+        const drag = (e)=>{ 
+            /* if(e.hoverViewport != e.drag.dragViewport){//不能使用e.dragViewport,要使用drag中的,因为drag中存储的要一直继承下来,不因mouseup了而改变。
+                viewer.dispatchEvent({
+                    type : "CursorChange", action : "add",  name:"polygon_AtWrongPlace"
+                })
+                return
+            } */ 
+            const camera = viewer.scene.getActiveCamera();
+            if(projectedStart){
+                let move2d = new THREE.Vector2().subVectors(e.pointer, pointerStart)
+                let projectNow = projectedStart.clone()
+                projectNow.x += move2d.x;
+                projectNow.y += move2d.y; 
+                let unprojected = projectNow.clone().unproject(camera);
+                this.position.set(unprojected.x, unprojected.y, unprojected.z);
+            }else{
+                projectedStart = this.position.clone().project(camera);
+                pointerStart = e.pointer.clone()
+            }
+             
+            this.update()
+            this.dispatchEvent({type:'dragged', position: this.position }) 
+        }
+        
+        const drop = (e)=>{
+            projectedStart = null, pointerStart = null
+        }
+        
+        const mouseover = (e) => {  
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"markerMove"
+            }) 
+        };
+        const mouseleave = (e) => {  
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"markerMove"
+            })
+        } 
+        
+        this.addEventListener('drag', drag) 
+        this.addEventListener('drop', drop)  
+        this.addEventListener('mouseover', mouseover);
+        this.addEventListener('mouseleave', mouseleave);
+    }
+    
+}

+ 155 - 0
src/objects/tool/HandleSvg.js

@@ -0,0 +1,155 @@
+
+import math  from "../../utils/math";
+ 
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+
+ 
+export default class HandleSvg extends THREE.EventDispatcher{
+    constructor(position, color){
+        super()
+        this.position = position
+		this.color = '#'+new THREE.Color(color).getHexString()
+        this.svg = this.create() 
+        this.visible_ = true  
+        
+        let update = ()=>{
+            this.update()
+        }
+        viewer.addEventListener("camera_changed", update)
+        
+        this.addEventListener('dispose', ()=>{ 
+            viewer.removeEventListener("camera_changed",  update)  
+        })
+    }
+    
+    
+    create(){
+        
+        const svgns = "http://www.w3.org/2000/svg";
+		const svg = document.createElementNS(svgns, "svg");
+
+		svg.setAttribute("width", "2em");
+		svg.setAttribute("height", "2em");
+		svg.setAttribute("position", "absolute");
+
+		svg.style.left = "50px";
+		svg.style.top = "50px";
+		svg.style.position = "absolute";
+		svg.style.zIndex = "10000";
+        svg.style.cursor = 'grab'
+        svg.style.transform = 'translate(-50%,-50%)'
+        
+		const circle = document.createElementNS(svgns, 'circle');
+		circle.setAttributeNS(null, 'cx', "1em");
+		circle.setAttributeNS(null, 'cy', "1em");
+		circle.setAttributeNS(null, 'r', "0.5em");
+		circle.setAttributeNS(null, 'style', 'fill: '+this.color+'; stroke: black; stroke-width: 0.2em;' );
+		svg.appendChild(circle);
+         
+		const element = viewer.renderer.domElement.parentElement;
+		element.appendChild(svg);
+
+
+		const startDrag = (evt) => {
+            /* if(evt.button === THREE.MOUSE.RIGHT){
+                return
+            } */
+			this.selectedElement = svg;
+
+			document.addEventListener("mousemove", drag);
+		};
+
+		const endDrag = (evt) => {
+			this.selectedElement = null;
+
+			document.removeEventListener("mousemove", drag);
+		};
+
+		const drag = (evt) => {
+			if (this.selectedElement) {
+                 
+				evt.preventDefault();
+
+				const rect = viewer.renderer.domElement.getBoundingClientRect();
+
+				const x = evt.clientX - rect.x;
+				const y = evt.clientY - rect.y;
+
+				const {width, height} = viewer.renderer.getSize(new THREE.Vector2());
+				const camera = viewer.scene.getActiveCamera();
+				 
+                const projected = this.position.clone().project(camera);
+          
+
+				projected.x = ((x / width) - 0.5) / 0.5;
+				projected.y = (-(y - height) / height - 0.5) / 0.5;
+
+				const unprojected = projected.clone().unproject(camera);
+				this.position.set(unprojected.x, unprojected.y, unprojected.z);
+                
+                this.update()
+                this.dispatchEvent({type:'dragged', position: this.position})
+                
+			}
+		};
+
+		svg.addEventListener('mousedown', startDrag);
+		svg.addEventListener('mouseup', endDrag);
+        svg.style.display = this.visible ? "" : "none" 
+         
+        
+        this.addEventListener('dispose',()=>{
+            svg.removeEventListener('mousedown', startDrag);
+            svg.removeEventListener('mouseup', endDrag); 
+        })
+        
+        
+        
+        return svg
+    }
+    
+    
+    
+   
+   
+    set visible(v){
+        this.visible_ = v  
+        if(v){
+            this.update()
+        }else{
+            this.svg.style.display = "none"
+        } 
+    }
+    get visible(){
+        return this.visible_ 
+    }
+   
+   
+    update(){
+        if(!this.visible)return
+        
+        let camera = viewer.scene.getActiveCamera() 
+          
+        
+        var p = Potree.Utils.getPos2d( this.position, camera , viewer.renderArea, viewer.mainViewport);
+        if(!p.trueSide){
+            return this.svg.style.display = 'none'; 
+        }
+        this.svg.style.left =  p.posInViewport.x  
+        this.svg.style.top = p.posInViewport.y 
+          
+        
+         
+        this.svg.style.display = ''
+            
+    }
+    
+    
+    
+    dispose(){
+        this.svg.remove()
+        this.dispatchEvent('dispose')
+    }
+}
+
+ 	

+ 27 - 18
src/objects/tool/Measure.js

@@ -110,6 +110,14 @@ export class Measure extends ctrlPolygon{
 	}
  
  
+ 
+    initData(prop){
+        let makeIt = super.initData(prop)
+        if(makeIt){
+            this.edges.forEach(edge=>{edge.dispatchEvent('addHoverEvent') })
+        }
+    }
+ 
      
     updateDatasetBelong(){//更新所属数据集
         let old = this.datasetId
@@ -272,18 +280,7 @@ export class Measure extends ctrlPolygon{
 	};
 
  
-    addHoverEvent(){ //当非isNew时才添加事件
-        super.addHoverEvent()
-        this.edges.forEach(edge=>{ 
-            let mouseover = (e) => {this.setSelected(true, 'edge')};
-			let mouseleave = (e) => {this.setSelected(false, 'edge')};
-
-			 
-			edge.addEventListener('mouseover', mouseover);
-			edge.addEventListener('mouseleave', mouseleave);
-        }) 
-    }
-
+    
 	addMarker (o={}) {
          
         let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, name:"measure_point"} )
@@ -291,17 +288,29 @@ export class Measure extends ctrlPolygon{
         marker.renderOrder = 3 
         marker.markerSelectStates = {} 
         marker.addEventListener('startDragging',(e)=>{
-            if(e.drag.dragViewport.name == 'MainView')viewer.inputHandler.emit('isMeasuring', true, 'startDragging')
+            if(e.drag.dragViewport.name == 'MainView')viewer.inputHandler.dispatchEvent('isMeasuring', {v:true, cause:'startDragging'})
         })
         marker.addEventListener('drop',(e)=>{
-            viewer.inputHandler.emit('isMeasuring', false, 'stopDragging')
+            viewer.inputHandler.dispatchEvent('isMeasuring', {v:false, cause:'stopDragging'}  )
         })
         
         let edge
 		{ // edges 
             edge = LineDraw.createFatLine( [ ],{material:this.getLineMat('edgeDefault')} ) 
-            viewer.setObjectLayers(edge, 'measure' )   
+            viewer.setObjectLayers(edge, 'measure' ) 
+
+
+                        
+            let addHoverEvent = ()=>{ //当非isNew时才添加事件 
             
+                let mouseover = (e) => {this.setSelected(true, 'edge')};
+                let mouseleave = (e) => {this.setSelected(false, 'edge')};
+ 
+                edge.addEventListener('mouseover', mouseover);
+                edge.addEventListener('mouseleave', mouseleave); 
+                edge.removeEventListener('addHoverEvent', addHoverEvent);
+            }
+            edge.addEventListener('addHoverEvent', addHoverEvent);
 		}
         
         super.addMarker({point:o.point, marker:marker,  edge})
@@ -396,7 +405,7 @@ export class Measure extends ctrlPolygon{
         
         marker.selected = absoluteState
         
-        viewer.mapViewer.emit('content_changed') 
+        viewer.mapViewer.dispatchEvent('content_changed') 
     }
     
     
@@ -449,7 +458,7 @@ export class Measure extends ctrlPolygon{
         }
            
         this.selected = absoluteState
-        viewer.mapViewer.emit('content_changed')
+        viewer.mapViewer.dispatchEvent('content_changed')
         if(hoverObject != 'byList'){
             this.bus && this.bus.emit('highlight', this.selected)//列表高亮
         }
@@ -858,7 +867,7 @@ export class Measure extends ctrlPolygon{
             this.area = {value:0};
             this.areaLabel && this.areaLabel.setVisible(false)
         }
-         viewer.inputHandler.emit('isMeasuring', true, 'reDraw')
+         viewer.inputHandler.dispatchEvent('isMeasuring', {v:true, cause:'reDraw'}  )
 	            
     }
     

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

@@ -3,8 +3,7 @@ import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {Measure} from "./Measure.js";
 import {Utils} from "../../utils.js"; 
 import math from "../../utils/math.js";
-import {CameraMode} from "../../defines.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import {CameraMode} from "../../defines.js"; 
  
 function updateAzimuth(viewer, measure){
     if(!measure.showAzimuth)return
@@ -121,7 +120,7 @@ function updateAzimuth(viewer, measure){
 	azimuth.label.scale.set(scale, scale, scale);
 }
 
-export class MeasuringTool extends EventDispatcher{
+export class MeasuringTool extends THREE.EventDispatcher{
 	constructor (viewer) {
 		super();
 
@@ -380,10 +379,7 @@ export class MeasuringTool extends EventDispatcher{
 			if (e.button == THREE.MOUSE.LEFT || e.isTouch) { 
 				if (length >= measure.maxMarkers) {
                     end({finish:true});
-				}else{ 
-                     
-                    
-                    
+				}else{  
                     var marker = measure.addMarker({point:measure.points[length - 1].clone()})
                      
                     if(args.isRect && measure.markers.length == 3){//marker全可见
@@ -398,6 +394,8 @@ export class MeasuringTool extends EventDispatcher{
                     measure.markers[length-1].visible = true;
                     
                     measure.editStateChange(true) //重新激活reticule状态
+                    
+                    marker.isDragging = true 
                     measure.continueDrag(marker, e)    
                 } 
 				 
@@ -442,9 +440,11 @@ export class MeasuringTool extends EventDispatcher{
             let length = measure.points.length 
             if(length){
                 measure.markers[length-1].visible = true; 
-                measure.edges[length-1].visible = true   
+                measure.edges[length-1].visible = true  
                 
-                measure.addHoverEvent()
+                measure.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') })
+                measure.edges.forEach(edge=>{edge.dispatchEvent('addHoverEvent') })
+              
             }
             clearTimeout(timer) 
 			this.viewer.removeEventListener('cancel_insertions', Exit);
@@ -455,7 +455,7 @@ export class MeasuringTool extends EventDispatcher{
                 type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"
             });
             
-            viewer.inputHandler.emit('isMeasuring', false, 'stopInsertion')
+            viewer.inputHandler.dispatchEvent('isMeasuring', {v:false, cause:'stopInsertion'}  ) 
             
             e.remove || callback && callback()  
             /* this.viewer.dispatchEvent({
@@ -521,12 +521,18 @@ export class MeasuringTool extends EventDispatcher{
             //console.log('measure clicked33', !!e.intersectPoint)
              
             var I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
-            if(!I)return
-            
-            
+            if(!I){
+                return measure.dispatchEvent('intersectNoPointcloud') 
+            }
+            var atMap = e.drag.dragViewport.name == 'mapViewport'
+            //在地图上测量的首个点按楼层高度(暂时先只按mainViewport相机高度吧,但navvis是按楼层,画在楼层的地面上,可能因为平面图显示的是楼层近地面),
             
+            if(atMap){ 
+                I = I.clone().setZ(viewer.mainViewport.camera.position.z ) 
+            }
              
-            var marker = measure.addMarker({point:new THREE.Vector3(0, 0, 0)})
+            var marker = measure.addMarker({point:I})
+            marker.isDragging = true 
             this.viewer.inputHandler.startDragging(marker , {endDragFun, notPressMouse:true} ); //notPressMouse代表不是通过按下鼠标来拖拽
             e.drag = this.viewer.inputHandler.drag
             e.drag.endDragFun = endDragFun
@@ -560,14 +566,14 @@ export class MeasuringTool extends EventDispatcher{
         
         //点击第n下拥有n+1个marker, n>0
         
-        viewer.inputHandler.emit('isMeasuring', true, 'startInsertion')
+        viewer.inputHandler.dispatchEvent('isMeasuring',{v: true, cause:'startInsertion'})
         
         this.viewer.addEventListener('global_click', click, 10)//add importance:10
             
         let ifAtWrongPlace = (e)=>{
             if(measure.unableDragAtMap && e.hoverViewport.name == 'mapViewport' ){ 
                 if(e.isTouch){
-                    viewer.emit('reticule_forbit', true)
+                    viewer.dispatchEvent('reticule_forbit', {v:true})
                 }else{
                     viewer.dispatchEvent({
                         type : "CursorChange", action : "add",  name:"polygon_AtWrongPlace"
@@ -576,7 +582,7 @@ export class MeasuringTool extends EventDispatcher{
                 return true
             }else{ 
                 if(e.isTouch){
-                    viewer.emit('reticule_forbit', false)
+                    viewer.dispatchEvent('reticule_forbit', {v:false})
                 }else{
                     viewer.dispatchEvent({
                         type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"

+ 2 - 3
src/objects/tool/ProfileTool.js

@@ -1,11 +1,10 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {Profile} from "./Profile.js";
-import {Utils} from "../../utils.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import {Utils} from "../../utils.js"; 
 
 
-export class ProfileTool extends EventDispatcher {
+export class ProfileTool extends THREE.EventDispatcher {
 	constructor (viewer) {
 		super();
 

+ 2 - 3
src/objects/tool/ScreenBoxSelectTool.js

@@ -2,11 +2,10 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {BoxVolume} from "./Volume.js";
 import {Utils} from "../../utils.js";
-import {PointSizeType} from "../../defines.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import {PointSizeType} from "../../defines.js"; 
 
 
-export class ScreenBoxSelectTool extends EventDispatcher{
+export class ScreenBoxSelectTool extends THREE.EventDispatcher{
 
 	constructor(viewer){
 		super();

+ 2 - 2
src/objects/tool/TransformationTool.js

@@ -159,7 +159,7 @@ export class TransformationTool {
 
 			let pickSphere = new THREE.Mesh(sgLowPolySphere, pickMaterial);
 			pickSphere.name = `${handleName}.pick_volume`;
-			pickSphere.scale.set(1.5, 1.5, 1.5);
+			pickSphere.scale.set(2, 2, 2);
 			sphere.add(pickSphere);
 			pickSphere.handle = handleName;
 			this.pickVolumes.push(pickSphere);
@@ -385,7 +385,7 @@ export class TransformationTool {
 		let adjust = 1.5;
 		let torusGeometry = new THREE.TorusGeometry(1, adjust * 0.015, 8, 64, Math.PI / 2);
 		let outlineGeometry = new THREE.TorusGeometry(1, adjust * 0.018, 8, 64, Math.PI / 2);
-		let pickGeometry = new THREE.TorusGeometry(1, adjust * 0.07, 6, 4, Math.PI / 2);
+		let pickGeometry = new THREE.TorusGeometry(1, adjust * 0.04, 6, 4, Math.PI / 2);
 
 		for(let handleName of Object.keys(this.rotationHandles)){
 			let handle = this.handles[handleName];

+ 2 - 3
src/objects/tool/VolumeTool.js

@@ -1,10 +1,9 @@
 
 import * as THREE from "../../../libs/three.js/build/three.module.js";
 import {Volume, BoxVolume} from "./Volume.js";
-import {Utils} from "../../utils.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+import {Utils} from "../../utils.js"; 
 
-export class VolumeTool extends EventDispatcher{
+export class VolumeTool extends THREE.EventDispatcher{
 	constructor (viewer) {
 		super();
 

+ 67 - 44
src/objects/tool/ctrlPolygon.js

@@ -73,36 +73,14 @@ export class ctrlPolygon extends THREE.Object3D {
             this.update({ifUpdateMarkers:true})
             //this.dragChange(new THREE.Vector3().copy(prop.points[prop.points.length-1]), prop.points.length-1); 
             this.setSelected(false )
-            this.addHoverEvent()
-            
+            this.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') })
+            return true
         }
     }
     
     
     
-    addHoverEvent(){ //当非isNew时才添加事件
-        this.markers.forEach(marker =>{
-            
-            let mouseover = (e) => { 
-                this.setMarkerSelected(e.object, true, 'single');/* console.log('hover')  */
-                viewer.dispatchEvent({
-                    type : "CursorChange", action : "add",  name:"markerMove"
-                }) 
-            };
-            let mouseleave = (e) => { 
-                this.setMarkerSelected(e.object, false, 'single');/* console.log('hoveroff')  */
-                viewer.dispatchEvent({
-                    type : "CursorChange", action : "remove",  name:"markerMove"
-                })
-            }
-
-            
-            marker.addEventListener('mouseover', mouseover);
-            marker.addEventListener('mouseleave', mouseleave);
-        
-        })
-            
-    }
+    
     
     addMarker(o={}){
         var index = o.index == void 0 ? this.points.length : o.index  //要当第几个
@@ -117,6 +95,29 @@ export class ctrlPolygon extends THREE.Object3D {
             o.marker.addEventListener('drag', this.dragMarker.bind(this));
             o.marker.addEventListener('drop', this.dropMarker.bind(this));
             
+            
+            let addHoverEvent = (e)=>{
+                let mouseover = (e) => { 
+                    this.setMarkerSelected(e.object, true, 'single'); 
+                    viewer.dispatchEvent({
+                        type : "CursorChange", action : "add",  name:"markerMove"
+                    }) 
+                };
+                let mouseleave = (e) => { 
+                    this.setMarkerSelected(e.object, false, 'single'); 
+                    viewer.dispatchEvent({
+                        type : "CursorChange", action : "remove",  name:"markerMove"
+                    })
+                } 
+                o.marker.addEventListener('mouseover', mouseover);
+                o.marker.addEventListener('mouseleave', mouseleave);
+                o.marker.removeEventListener('addHoverEvent',addHoverEvent) 
+            }
+            o.marker.addEventListener('addHoverEvent',addHoverEvent)//当非isNew时才添加事件
+            if(!this.isNew){
+                o.marker.dispatchEvent('addHoverEvent')
+            }
+            
         } 
         
         if(o.edge){
@@ -134,13 +135,25 @@ export class ctrlPolygon extends THREE.Object3D {
         
         var I, atMap 
         
-        if(e.hoverViewport != e.drag.dragViewport)return //不能使用e.dragViewport,要使用drag中的,因为drag中存储的要一直继承下来,不因mouseup了而改变。
+        if(e.hoverViewport != e.drag.dragViewport){//不能使用e.dragViewport,要使用drag中的,因为drag中存储的要一直继承下来,不因mouseup了而改变。
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"polygon_AtWrongPlace"
+            })
+            return
+        }
+        
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"
+        })
+        
         atMap = e.drag.dragViewport.name == 'mapViewport' 
         
         if(atMap && this.unableDragAtMap){ 
             e.drag.object = null //取消拖拽
             return 
         }
+        e.drag.object.isDragging = true 
+        
         
         I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
         
@@ -148,11 +161,10 @@ export class ctrlPolygon extends THREE.Object3D {
         
         //在三维中脱离点云(在map中拉到周围都没有点云的地方)的顶点,无法拖拽怎么办
          
-        if (I) { 
-            atMap && (I.z = 0)
+        if (I) {  
             let i = this.markers.indexOf(e.drag.object);
-            if (i !== -1) { 
-                this.dragChange(I, i, atMap) 
+            if (i !== -1) {  
+                this.dragChange(I.clone(), i, atMap) 
                 
                 if(this.points_datasets){
                     if(e.intersectPoint.pointcloud) this.points_datasets[i] = e.intersectPoint.pointcloud.dataset_id
@@ -174,9 +186,15 @@ export class ctrlPolygon extends THREE.Object3D {
     
     
     dragChange(intersectPos, i, atMap){
-        var len = this.markers.length
-        var location = intersectPos.clone()
-
+        let len = this.markers.length 
+        let oldPoint = this.points[i]; 
+        if(atMap){
+            intersectPos.setZ(oldPoint.z) //在地图上拖拽,不改变其高度。
+        }
+        let location = intersectPos.clone()
+        
+        
+        
         if(this.faceDirection && this.maxMarkers == 2 && len == 2){//add 固定方向的点不直接拖拽
             var p1 = this.markers[0].position
             if(this.faceDirection == 'horizontal'){
@@ -367,18 +385,18 @@ export class ctrlPolygon extends THREE.Object3D {
     }
     
     dropMarker(e){
-        console.log('dropMarker')
+        //console.log('dropMarker')
         if (this.isNew && e.pressDistance>Potree.config.clickMaxDragDis){//拖拽的话返回
             return this.continueDrag(null,e)   
         } 
         
         if(e.isTouch){ 
             if(e.hoverViewport != viewer.mainViewport && this.unableDragAtMap){
-                viewer.emit('reticule_forbit', true)
+                viewer.dispatchEvent({type:'reticule_forbit', v:true})
                 //console.log('reticule_forbit',true)
                 return this.continueDrag(null,e)    
             }else{
-                viewer.emit('reticule_forbit', false)
+                viewer.dispatchEvent({type:'reticule_forbit', v:false})
                 //console.log('reticule_forbit',false)
             }
             this.dragMarker(e) //触屏时必须先更新下点 
@@ -421,7 +439,7 @@ export class ctrlPolygon extends THREE.Object3D {
         e.drag.endDragFun && e.drag.endDragFun(e)//  addmarker
          
         if(this.changeCallBack)this.changeCallBack()
-            
+           
         return true
     };
     
@@ -497,7 +515,7 @@ export class ctrlPolygon extends THREE.Object3D {
 		this.points.splice(index, 1); 
         
         const marker = this.markers[index]
-		this.remove(marker); 
+		//this.remove(marker); 
         this.markers.splice(index, 1);   
         marker.dispatchEvent({type:'dispose'})
         
@@ -511,7 +529,7 @@ export class ctrlPolygon extends THREE.Object3D {
         }
         this.point2dInfo && this.point2dInfo.points2d.splice(index, 1); //add
 
-
+        this.dispatchEvent({type:'removeMarker',index,marker})
 		
         
 	} 
@@ -598,7 +616,7 @@ export class ctrlPolygon extends THREE.Object3D {
         }
         
         //this.dispatchEvent({type:'update'})     
-        viewer.mapViewer.emit('content_changed') //暂时先这么都通知
+        viewer.mapViewer.dispatchEvent('content_changed') //暂时先这么都通知
         
     } 
     
@@ -630,7 +648,9 @@ export class ctrlPolygon extends THREE.Object3D {
             viewer.dispatchEvent({
                 type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"
             })
-            viewer.emit('reticule_forbit', false)
+            viewer.dispatchEvent({type:'reticule_forbit', v:false})
+            
+            this.markers.forEach(e=>e.isDragging = false )
         }
     }
     
@@ -640,9 +660,12 @@ export class ctrlPolygon extends THREE.Object3D {
     
     
     continueDrag(marker, e){
-        var timer = setTimeout(()=>{//等 drag=null之后 //右键拖拽结束后需要重新得到drag
-            if(this.parent){//没被删的话
-                viewer.inputHandler.startDragging(  marker || e.drag.object,
+        let object = marker || e.drag.object
+        object.isDragging = true 
+        var timer = setTimeout(()=>{//等 drag=null之后 //右键拖拽结束后需要重新得到drag 
+            if(this.parent && object.isDragging){
+                //console.log('continueDrag')        
+                viewer.inputHandler.startDragging( object ,
                     {endDragFun: e.drag.endDragFun, notPressMouse:e.drag.notPressMouse, dragViewport:e.drag.dragViewport} 
                 )
             } 

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

@@ -101,8 +101,18 @@ export class mapClipBox extends ctrlPolygon {
         
         let edge = LineDraw.createLine([new THREE.Vector3,new THREE.Vector3],{color  })
         let edgeMarker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, dontFixOrient: true, viewports:viewer.mapViewer.viewports, name:"mapClipBox_edgePoint"} )
-        let mouseover = (e) => {this.setMarkerSelected(e.object, true, 'single');/* console.log('hover')  */};
-        let mouseleave = (e) => {this.setMarkerSelected(e.object, false, 'single');/* console.log('hoveroff')  */}
+        let mouseover = (e) => {
+            this.setMarkerSelected(e.object, true, 'single');
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"markerMove"
+            }) 
+        };
+        let mouseleave = (e) => {
+            this.setMarkerSelected(e.object, false, 'single'); 
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"markerMove"
+            })
+        }
         edgeMarker.addEventListener('mouseover', mouseover);
         edgeMarker.addEventListener('mouseleave', mouseleave);
         let dragInfo = {lastPos:null};
@@ -110,8 +120,8 @@ export class mapClipBox extends ctrlPolygon {
         edgeMarker.addEventListener('drop', this.dropEdge.bind(this,dragInfo));   
         this.edgeMarkers.push(edgeMarker)
         this.add(edgeMarker)
-        
-        
+        marker.dispatchEvent('addHoverEvent')  
+      
         super.addMarker({point:o.point, marker:marker,  edge})
         
     }
@@ -203,7 +213,7 @@ export class mapClipBox extends ctrlPolygon {
         
         marker.selected = absoluteState */
         
-        viewer.mapViewer.emit('content_changed') 
+        viewer.mapViewer.dispatchEvent('content_changed') 
     }
     
     createRotateBar(){
@@ -232,14 +242,14 @@ export class mapClipBox extends ctrlPolygon {
             viewer.dispatchEvent({
                 type : "CursorChange", action : "add",  name:"mapClipRotate"
             })
-            viewer.mapViewer.emit('content_changed') 
+            viewer.mapViewer.dispatchEvent('content_changed') 
         })
         bar.addEventListener('mouseleave',()=>{
             bar.material.opacity = barOpacity
             viewer.dispatchEvent({
                 type : "CursorChange", action : "remove",  name:"mapClipRotate"
             })
-            viewer.mapViewer.emit('content_changed') 
+            viewer.mapViewer.dispatchEvent('content_changed') 
         })
         let lastPos;
         bar.addEventListener('drag',(e)=>{

+ 38 - 11
src/settings.js

@@ -65,9 +65,10 @@ const config = {//配置参数   不可修改
     
     
     urls:{
-        //localTextures:'../resources/textures/',
-        prefix: 'laser-oss.4dkankan.com',//oss
-        prefix2: 'testlaser.4dkankan.com'
+        //localTextures:'../resources/textures/', 
+        prefix: 'https://laser-oss.4dkankan.com',//oss
+        prefix2: 'https://testlaser.4dkankan.com',
+        prefix3: 'https://4dkk.4dage.com',
     },
      
     /* transitionsTime:{
@@ -92,7 +93,10 @@ const config = {//配置参数   不可修改
         far: 10000,
     },
     
-     
+    map:{//mapViewer
+        mapHeight : -1000,//要比点云低。最低
+        cameraHeight : 1000,  //最高  ,注意(如sitemodel)其他的物体不能超过这个高度
+    },
     
     
     pointDensity:{
@@ -140,7 +144,7 @@ const config = {//配置参数   不可修改
         color: '#FFC266', //map
         
     },
-    
+    panoFieldRadius : 10, //当前位置多远范围内可以切全景模式
     
     measure:{
         color:'#00C8AF',
@@ -169,7 +173,7 @@ const config = {//配置参数   不可修改
         minSize: 0.1,
         maxSize: 10000,
         pointSizeType: 'ATTENUATED', //'ADAPTIVE'//'ADAPTIVE' \ FIXED //ADAPTIVE的在小房间里大小会不太匹配,但在远景似乎更好
-        absolutePanoramaSize: 2 ,//全景漫游时的size  是fixed的模式
+        absolutePanoramaSize: 1.3 ,//全景漫游时的size  是fixed的模式
         //ADAPTIVE : 字会失真扭曲
         //'ATTENUATED' 往旁边看有缝隙、点在浮动
         //navvis的shader在哪里 为什么不会抖动
@@ -242,11 +246,21 @@ const config = {//配置参数   不可修改
         optionalityFactor: 3
     },
     OrthoCameraLimit:{
-        zoom:{min:0.001, max:100}, //如果camera缩太小,地图会因为数字边界问题而扭曲
-        posBound:{ 
-            min: {x:-1e5, y:-1e5,z:-1 / 0},
-            max: {x:1e5, y:1e5, z:1 / 0  }
+        standard:{ 
+            zoom:{min:0.001, max:100}, //如果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' */}},  
@@ -318,14 +332,20 @@ let settings = {//设置   可修改
     originDatasetId:'',//场景原本的数据集id,应该就是数据集第一个吧
     isOfficial:false,
     webSite:'data', //不同环境对应的静态文件的地址不同
+    
+    isLocal:false, //是否本地 局域网版本
+    
     displayMode:'',
     isTest :browser.urlHasValue('test'),
     prefix: getPrefix(),
     pointDensity: '',    UserPointDensity:'',//pointDensity会随着进入不同的模块而自动改变,UserPointDensity记录了用户的设置
     UserDensityPercent:null,//点云密度百分比 
     ifShowMarker:true,//显示漫游点
-    floorplanType:null,//平面图类型 'default' | 'diy'
+    floorplanType:{},//平面图类型 'default' | 'diy'  不同数据集不同{datasetId:...}
     floorplanEnable:false,
+    floorplanEnables:{},
+    floorplanRequests:{},//开始加载了的
+    
     mapEnable:true,//地图区域是否加载地图
     cameraFar : config.view.far, //相机最远范围 1-300
     //limitFar: true, //是否使用setting的cameraFar来限制(如在点云裁剪时为false)
@@ -354,8 +374,15 @@ let settings = {//设置   可修改
         whenPointCloud:true,
         map:true,
     },
+    
+    tourTestCameraMove:false, //测试镜头时,不移动真实的镜头, 只移动frustum
+    cameraAniSmoothRatio : 20, //镜头动画平滑系数,越高越平滑
+    urls  : $.extend({}, config.urls), 
+        
 }
 
+ 
+
 //JSON.parse(localStorage.getItem('setting'))
 
 settings.isLocalhost = settings.prefix.includes('localhost')

+ 42 - 31
src/start.js

@@ -32,7 +32,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     //viewer.setPointBudget(pointDensity.pointBudget);
     viewer.loadSettingsFromURL(); 
     
-    
+  
     
     if(!Potree.settings.isOfficial){ 
         viewer.loadGUI(() => {
@@ -48,6 +48,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     }
 
     Potree.loadDatasetsCallback = function(data, ifReload){
+        if(!data || data.length == 0)return console.error('getDataSet加载的数据为空')
         Potree.datasetData = data
         viewer.transform = null
         var datasetLength = data.length 
@@ -58,12 +59,17 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             let {boundSize, center} = viewer.bound
            
             Potree.Log(`中心点: ${math.toPrecision(center.toArray(),2)}, boundSize: ${math.toPrecision(boundSize.toArray(),2)} ` , null, 12)
-            Potree.loadMapEntity() //加载floorplan,不一定成功 
+            
+            if(!Potree.settings.isOfficial){
+                Potree.loadMapEntity('all') //加载floorplan 
+            }
+            
+            
             if(!ifReload){   
-                viewer.scene.view.setView(//position, target
-                    center.clone().add(new THREE.Vector3(10,5,10)), 
-                    center
-                )
+                viewer.scene.view.setView({ 
+                    position:center.clone().add(new THREE.Vector3(10,5,10)), 
+                    target:center
+                })
                  
                 viewer.dispatchEvent({type:'loadPointCloudDone'})
             
@@ -89,7 +95,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             {//初始位置 
                 var urlFirstView = false
                 var panoId = browser.urlHasValue('pano',true);
-                if(panoId != void 0){
+                if(panoId !== ''){
                     var pos
                     var pano = viewer.images360.panos.find(e=>e.id==panoId);
                     if(pano){
@@ -106,7 +112,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             viewer.addFire()
             
             console.log('allLoaded')
-            viewer.emit('allLoaded')
+            viewer.dispatchEvent('allLoaded')
         }
         
         var transformPointcloud = (pointcloud, dataset)=>{
@@ -136,31 +142,35 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
             //viewer.mapView.showSources(false);  
         }
         
-        data.forEach((dataset,index)=>{ 
-            if(!viewer.transform){//拿任意一个数据集作为基准。它的位置就会是000  (第一个数据集应该一直就是初始数据集吧?)
-                var locationLonLat = dataset.location.slice(0,2)
-                proj4.defs("NAVVIS:TMERC", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15));
-                proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
-                 
-                let transform1 = proj4("WGS84", "NAVVIS:TMERC"); //这个ok  TMERC是展开的平面投影
-                let transform2 = proj4("+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs;");
-                
-                
-                viewer.transform = {
-                    lonlatToLocal : transform1,
-                    lonlatTo4550 : transform2       // 转大地坐标EPSG:4550  
-                } 
-                
-                viewer.mapViewer && viewer.mapViewer.mapLayer.maps[0].updateProjection()
-                
+        if(!Potree.settings.originDatasetId)Potree.settings.originDatasetId = data[0].id
+        var originDataset =  data.find(e=>e.id == Potree.settings.originDatasetId)  
+        
+        {//拿初始数据集作为基准。它的位置是000
+            var locationLonLat = originDataset.location.slice(0,2)
+            proj4.defs("NAVVIS:TMERC", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15));
+            proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
+             
+            let transform1 = proj4("WGS84", "NAVVIS:TMERC"); //这个ok  TMERC是展开的平面投影
+            let transform2 = proj4("+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs;");
+            
+            
+            viewer.transform = {
+                lonlatToLocal : transform1,
+                lonlatTo4550 : transform2       // 转大地坐标EPSG:4550  
             } 
             
+            viewer.mapViewer && viewer.mapViewer.mapLayer.maps[0].updateProjection()
+            
+        }
+        
+        
+        data.forEach((dataset,index)=>{  
             if(!ifReload){
-                var datasetCode = dataset.sceneCode || dataset.name
-                var cloudPath = `https://${Potree.config.urls.prefix}/${Potree.settings.webSite}/${datasetCode}/data/${datasetCode}/webcloud/cloud.js` 
+                var datasetCode = dataset.sceneCode || dataset.name //对应4dkk的场景码
+                var cloudPath = `${Potree.settings.urls.prefix}/${Potree.settings.webSite}/${datasetCode}/data/${datasetCode}/webcloud/cloud.js` 
                 var timeStamp = dataset.createTime ? dataset.createTime.replace(/[^0-9]/ig,'') : '';  //每重算一次后缀随createTime更新一次 
-                
-                Potree.loadPointCloud(cloudPath, datasetCode , timeStamp, e => {
+                //console.warn(dataset.name, 'timeStamp', timeStamp)
+                Potree.loadPointCloud(cloudPath, dataset.name ,datasetCode, timeStamp, e => {
                     let scene = viewer.scene;
                     let pointcloud = e.pointcloud; 
                     let config = Potree.config.material
@@ -181,8 +191,9 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
                     pointcloudLoaded ++;
                     if(pointcloudLoaded == datasetLength)pointcloudLoadDone()
                         
-                    Potree.loadPanos(dataset.id, (data) => {
-                        viewer.images360.addPanoData(data, dataset.id)
+                    Potree.loadPanos(dataset.id, (data) => { 
+                        console.log('loadPanos',dataset.sceneCode, dataset.id, data)
+                        viewer.images360.addPanoData(data, dataset )
                         panosLoaded ++; 
                         if(panosLoaded == datasetLength){
                             panosLoadDone() 

+ 64 - 6
src/utils.js

@@ -5,6 +5,11 @@ import {Volume} from "./objects/tool/Volume.js";
 import {Profile} from "./objects/tool/Profile.js";
 import {Measure} from "./objects/tool/Measure.js";
 import {PolygonClipVolume} from "./objects/tool/PolygonClipVolume.js";
+import math from "./utils/math.js";
+
+
+
+
 
 export class Utils {
 	static async loadShapefileFeatures (file, callback) {
@@ -441,7 +446,9 @@ export class Utils {
             Potree.updatePointClouds(pointclouds,  camera, viewport.resolution );
         }else{
             if(viewer.viewports.filter(e=>!e.noPointcloud).length>1){
+                viewport.beforeRender && viewport.beforeRender()
                 Potree.updatePointClouds(pointclouds,  camera, viewport.resolution ); //不加这句的话hover久了会不准 因node是错的
+            
             }
             
         }
@@ -473,6 +480,10 @@ export class Utils {
             pointclouds.forEach(e=>{
                 e.material.pointSizeType = sizeType
             })
+        }else{
+            /* if(viewer.viewports.filter(e=>!e.noPointcloud).length>1){
+                viewport.afterRender && viewport.afterRender() 
+            } */
         }
 
 
@@ -575,9 +586,14 @@ export class Utils {
 	}
     
     static getPos2d(point, camera, dom, viewport){//获取一个三维坐标对应屏幕中的二维坐标
-		 
-		var pos = point.clone().project(camera)	//比之前hotspot的计算方式写得简单  project用于3转2(求法同shader); unproject用于2转3 :new r.Vector3(e.x, e.y, -1).unproject(this.camera);
-		
+        var pos
+        if(math.closeTo(camera.position, point, 1e-5) ){ //和相机位置重合时显示会四处飘,看是要改成一直显示中间还是隐藏?
+            pos = new THREE.Vector3(0,0,1.5); //1.5是为了不可见
+        }else{ 
+            pos = point.clone().project(camera)	//比之前hotspot的计算方式写得简单  project用于3转2(求法同shader); unproject用于2转3 :new r.Vector3(e.x, e.y, -1).unproject(this.camera);
+		}
+        
+        
 		var x,y,left,top;
 		x = (pos.x + 1) / 2 * dom.clientWidth * viewport.width;
 		y = (1 - (pos.y + 1) / 2) * dom.clientHeight * viewport.height; 
@@ -1146,9 +1162,8 @@ Utils.screenPass = new function () {
 Utils.computePointcloudsBound = function(pointclouds){
     var boundingBox = new THREE.Box3();
     pointclouds.forEach(pointcloud=>{
-        var boundingBox_ = pointcloud.pcoGeometry.tightBoundingBox.clone().applyMatrix4(pointcloud.matrixWorld)
-        pointcloud.bound = boundingBox_
-        boundingBox.union(boundingBox_)
+        pointcloud.updateBound()
+        boundingBox.union(pointcloud.bound)
     })
     var boundSize = boundingBox.getSize(new THREE.Vector3)
     var center = boundingBox.getCenter(new THREE.Vector3)
@@ -1296,5 +1311,48 @@ Utils.datasetRotTransform = function(o={}){
     
 }
 
+Utils.isInsideFrustum = function(bounding, camera){// boundingBox在视野范围内有可见部分
+    let frustumMatrix = new THREE.Matrix4
+    frustumMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
+    
+    let frustum = new THREE.Frustum();
+    frustum.setFromProjectionMatrix(frustumMatrix) 
+     
+    if(bounding instanceof THREE.Sphere){
+        return frustum.intersectsSphere(bounding)  
+    }else{
+        return frustum.intersectsBox(bounding)  
+    }
+}
+
+Utils.isInsideBox = function(object,  boxMatrixInverse){//object可以是点或者bounding, box原为1*1*1,但可能形变
+    let frustum = new THREE.Frustum();
+    frustum.setFromProjectionMatrix(boxMatrixInverse) 
+     
+    if(object instanceof THREE.Box3){
+        return frustum.intersectsSphere(object)  
+    }else if(object instanceof Array){//点合集,先求Sphere setFromPoints
+        let sphere = new THREE.Sphere()
+        sphere.setFromPoints(object) 
+        return this.isInsideBox(sphere, boxMatrixInverse)
+
+    }else if(object instanceof THREE.Sphere){
+        return frustum.intersectsSphere(object)  
+    }else if(object instanceof THREE.Vector3){
+        return frustum.containsPoint(object)  
+    }
+
+    /* containsPoint: ƒ containsPoint( point ) 
+    intersectsBox: ƒ intersectsBox( box )
+    intersectsObject: ƒ intersectsObject( object )//geo
+    intersectsSphere: ƒ intersectsSphere( sphere )
+    intersectsSprite: ƒ intersectsSprite( sprite )
+     */
+
+}
+ 
+
+
+
 
 

+ 18 - 18
src/utils/Common.js

@@ -98,24 +98,21 @@ var Common = {
     
     ,
      
-    CloneObject : function(copyObj, result, isSimpleCopy, extraReplace) {
+    CloneObject : function(copyObj, result, isSimpleCopy, simpleCopyList=[]) {
         //isSimpleCopy 只复制最外层
         //复制json		result的可能:普通数字或字符串、普通数组、复杂对象
-        if(!copyObj)return copyObj //0 null undefined ''
+         
+        simpleCopyList.push(THREE.Object3D) //遇到simpleCopyList中的类直接使用不拷贝
+        
+        if(!copyObj || typeof copyObj == 'number' || typeof copyObj == 'string' || copyObj instanceof Function || simpleCopyList.some(className => copyObj instanceof className)){
+            return copyObj 
+        }
+        
         result = result || {};
         if (copyObj instanceof Array) {
-            /*  if (copyObj[0]instanceof Object) {
-                //不支持含有 [[Object]] 这样二级数组里面还是复杂数据的,普通和复杂的数据混合可能也不支持
-                console.error("不支持含有 [[Object]] 这样二级数组里面还是复杂数据的...")
-            }  
-            
-            return copyObj.slice(0);*/ //如果是数组,直接复制返回(排除数组内是object 
-            return copyObj.map(e=>{
-                if(e instanceof Object){
-                    return this.CloneObject(e)
-                }else return e 
-            })
-           
+            return copyObj.map(e=>{ 
+                return this.CloneObject(e) 
+            }) 
         }else{
             if(copyObj.clone instanceof Function ){ //解决一部分
                 return copyObj.clone()
@@ -143,13 +140,16 @@ var Common = {
     CopyClassObject :function(targetObj,  copyObj){//复杂类对象
         for(let i in copyObj){
             if(i in copyObj.__proto__)break; //到函数了跳出 
-            if(!copyObj[i]){
-                targetObj[i] = copyObj[i];
-            }else if(copyObj[i].clone instanceof Function ){
+              
+            targetObj[i] = this.CloneObject(copyObj[i], null )  
+            
+                
+            
+            /* else if(copyObj[i].clone instanceof Function ){
                 targetObj[i] = copyObj[i].clone()
             }else{
                 targetObj[i] = copyObj[i];
-            } 
+            } */ 
         }
     }
     ,

+ 4 - 1
src/utils/CursorDeal.js

@@ -13,8 +13,10 @@ var CursorDeal = {
         {"markerMove":'grab'},
         {'mapClipMove':'move'},
         {'mapClipRotate':`url({Potree.resourcePath}/images/rotate-cursor.png), url({Potree.resourcePath}/images/rotate-cursor.cur),auto`},
-        {'rotatePointcloud':`url({Potree.resourcePath}/images/rotate-cursor.png), url({Potree.resourcePath}/images/rotate-cursor.cur),auto`},
+        {'rotatePointcloud':`url({Potree.resourcePath}/images/rotate-cursor-white.png),auto`},
         {'siteModelFloorDrag':'row-resize'},
+        {'addSth':'cell'},//or  crosshair
+        
     ], 
     list:[], //当前存在的cursor状态
     currentCursorIndex:null,
@@ -73,6 +75,7 @@ var CursorDeal = {
     },
     
     judge:function(o={}){
+        //console.log(o,this.list)
         if(o.addItem){
             var addIndex = this.priorityEvent.indexOf(o.addItem) 
             if(addIndex < this.currentCursorIndex || this.currentCursorIndex == void 0){ 

+ 23 - 39
src/utils/DrawUtil.js

@@ -63,52 +63,29 @@ var LineDraw = {
 	createFatLineMat : function(o){ 
     
         var supportExtDepth = !!Features.EXT_DEPTH.isSupported()  
-    
         
-    
-    
-		var mat = new LineMaterial( { 
-			color: o.color || 0xffffff,
-			lineWidth: o.lineWidth || 5, // in pixels
-			//vertexColors: THREE.VertexColors,//THREE.VertexColors,
-			resolution:  o.resolution || new THREE.Vector2(100,100),// to be set by renderer, eventually
-			//viewportOffset:  o.viewportOffset,
-			transparent:true, //o.dontAlwaysSeen ? false : true,
-			depthTest:   false,//  o.alwaysShow ? false : true,//o.dontAlwaysSeen ? true : false
-            depthWrite:false,
-            dashSize : o.dashSize || 0.1,
-            gapSize: o.gapSize || 0.1,
-               
-            useDepth: !!o.useDepth,  
-            dashed: o.dashWithDepth ? supportExtDepth && !!o.dashed : !!o.dashed,
+        let params = $.extend({}, {
+            //默认
+            lineWidth : 5,  
+            color:0xffffff,
+            transparent : true, depthWrite:false,  depthTest:false,
+            dashSize : 0.1, gapSize:0.1, 
+        }, o, {
+            //修正覆盖:
+            dashed: o.dashWithDepth ? supportExtDepth && !!o.dashed : !!o.dashed ,
             dashWithDepth:!!o.dashWithDepth,//只在被遮住的部分显示虚线
-            
+            useDepth: !!o.useDepth,  
             supportExtDepth,
             
-            
-            
-           /*  transparent:o.dontAlwaysSeen ? false : true,
-			depthTest:o.dontAlwaysSeen ? true : false
-             */
-            /* polygonOffset : true,//是否开启多边形偏移	for not cover the lineMesh
-            polygonOffsetFactor : -o.width*2.5 || -5 ,//多边形偏移因子
-            polygonOffsetUnits : -4.0,//多边形偏移单位 */  
-             
-		} ); 
+        })
         
-        //if(o.dashed)(mat.defines.USE_DASH = "") 
         
-        var opa = 0
-        Object.defineProperty( mat, "opacity", {
-            get: function () {
-                return opa;
-            },
-            set: function(o){
-                mat.uniforms.opacity.value = opa = o; 
-            }
-        })
-        mat.opacity = o.opacity != void 0 ? o.opacity : 1;
         
+		var mat = new LineMaterial(params)
+         
+        
+        //if(o.dashed)(mat.defines.USE_DASH = "") 
+         
 		return 	mat;			
 	},
     
@@ -142,6 +119,9 @@ var LineDraw = {
         
         
 		if(positions.length > 0){
+            if(!geometry){
+                geometry = line.geometry = new LineGeometry(); 
+            }
             if(geometry.attributes.instanceEnd && geometry.attributes.instanceEnd.data.array.length != positions.length){//positions个数改变会有部分显示不出来,所以重建
                 geometry.dispose();
                 geometry = new LineGeometry();
@@ -154,6 +134,10 @@ var LineDraw = {
                 //line.geometry.computeBoundingSphere();
                 line.computeLineDistances(); 
             } 
+        }else{
+            geometry.dispose()
+            line.geometry = new LineGeometry(); 
+             
         }
         
         

+ 0 - 7
src/utils/MathLight.js

@@ -310,13 +310,6 @@ MathLight.angleBetweenVectors = function(e, t) {
     return Math.acos(MathLight.dot(e, t))
 };
 
-MathLight.closeTo = function(e1,e2, precision){//xzw add   判断e1,e2是否接近
- 	var prec = Math.pow(10,  - (precision || 4));
-	var s1 = Math.abs(e1.x - e2.x) < prec && Math.abs(e1.y - e2.y) < prec && Math.abs(e1.z - e2.z) < prec
-	if(e1.w){
-		return s1 && Math.abs(e1.w - e2.w) < prec
-	}else return s1
-}
 
 
 export default MathLight

+ 130 - 70
src/utils/SplitScreen.js

@@ -19,6 +19,7 @@ const viewportProps = [
         axis:["x","y"],
         //axisSign:[1,1],
         active: true,
+        //相机位置在z轴正向
     },
     {
         left:0.5,
@@ -28,6 +29,7 @@ const viewportProps = [
         axis:["y","z"],
         //axisSign:[1,1],
         active: true,
+        //相机位置在x轴正向
     },
     {
         left:0,
@@ -37,6 +39,7 @@ const viewportProps = [
         axis:["x","z"],
         //axisSign:[-1,1],    // 从镜头方向看  x向左 所以取负 
         active: true,
+        //相机位置在y轴正向
     },
 ]
 
@@ -75,49 +78,7 @@ var SplitScreen = {
         
         
         
-        //材质 
-        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) //切换到标准模式(主要为了mainViewport)
-        
-        var matBefore = { 
-            opacity : new Map()
-        } 
-         
-        viewer.scene.pointclouds.forEach(e=>{
-            matBefore.opacity.set(e, e.temp.pointOpacity) 
-            matBefore.colorType = e.material.activeAttributeName
-        }) 
         
-        let beforeRender = function(viewport){
-            viewer.scene.pointclouds.forEach(e=>{ 
-                if(viewport.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.5); 
-                     
-                }                 
-            })  
-        }
          
         
         
@@ -150,9 +111,7 @@ var SplitScreen = {
                 viewports.push(viewport) 
             }                
              
-           
-            viewport.beforeRender = beforeRender;
-            
+              
             if(viewport.name != 'MainView'){
                 viewport.view.setCubeView(viewport.name)
                 viewport.view.position.copy(viewport.camera.position)
@@ -176,11 +135,77 @@ var SplitScreen = {
         }) */
             
             
+            
+            
+        //材质 
+        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) //切换到标准模式(主要为了mainViewport)
+        
+        var matBefore = { 
+            opacity : new Map() 
+        } 
+        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.5/* 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})   
         this.viewportFitBound(mapViewport, boundSize, center)
@@ -224,13 +249,19 @@ var SplitScreen = {
             viewer.updateVisible(pano.mapMarker, 'split4Screens', true)
         }) */
         mapViewport.noPointcloud = true
-         
-        this.enableMap(true)
-        this.enableFloorplan(true)
+        { 
+            this.enableMap(Potree.settings.mapEnable)
+            this.enableFloorplan(Potree.settings.floorplanEnable)
+            if(this.floorplanListener){
+                viewer.mapViewer.mapLayer.removeEventListener( 'floorplanLoaded', this.floorplanListener )  
+                this.floorplanListener = null  
+            } 
+        }
          
         Potree.settings.pointDensity = SplitScreen.statesBefore.pointDensity
-        Potree.settings.displayMode = SplitScreen.statesBefore.displayMode
-        
+        if(!Potree.settings.isOfficial){
+            Potree.settings.displayMode = SplitScreen.statesBefore.displayMode
+        }
         
         viewer.scene.pointclouds.forEach(e=>{ 
             //e.material.color.set(SplitScreen.statesBefore.mat.color)
@@ -239,11 +270,15 @@ var SplitScreen = {
             //e.material.opacity = SplitScreen.statesBefore.mat.opacity  
         }) 
         viewer.setPointStandardMat(false)
-         
+        viewer.mapViewer.setViewLimit('standard')
         
         //Potree.settings.ifShowMarker = SplitScreen.statesBefore.ifShowMarker
         //viewer.dispatchEvent({'type': 'finishSplitView' }) 
         viewer.updateScreenSize({forceUpdateSize:true}) 
+        
+        
+        
+        
     },
     
     focusOnViewport : function(name){//全屏
@@ -295,51 +330,76 @@ var SplitScreen = {
     },
     
     setFloorplanDisplay: function(e, show=false){ 
-        viewer.updateVisible(e.floorplan.objectGroup, 'splitScreen', !!show)  
-        
-        viewer.mapViewer.mapLayer.needUpdate = true
+        //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) 
+        //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()
-        if(enable)viewer.mapViewer.mapLayer.needUpdate = true //加载地图
+       
         
         
     },
+    //直接覆盖原设置
+    
     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)) 
-              
-            viewer.mapViewer.mapLayer.addEventListener( 'floorplanLoaded', this.setFloorplanDisplay ) //万一之后才加载 
+            //隐藏平面图 
+            floorplans.forEach(floorplan=>this.setFloorplanDisplay({floorplan},false))  
              
         }else{
              
-            floorplans.forEach(floorplan=>this.setFloorplanDisplay({floorplan},true)) 
-            viewer.mapViewer.mapLayer.removeEventListener( 'floorplanLoaded', this.setFloorplanDisplay ) 
+            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
-        var moveAtAxis = ['x','y','z'].find(e=>!(axis.includes(e)))  
-        let ori = viewport.view.position[moveAtAxis]
-        viewport.view.position.copy(center)
-        viewport.view.position[moveAtAxis] = ori //不改变这个值,尤其是mapViewer中的z
+        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  
@@ -360,7 +420,7 @@ var SplitScreen = {
             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} )//平移镜头
+                viewer.focusOnObject({position:center}, 'point', duration, {distance, direction: e.view.direction,dontMoveMap:true} )//平移镜头
             }else{
                 this.viewportFitBound(e, boundSize, center)
             }

+ 2 - 2
src/utils/cameraLight.js

@@ -6,10 +6,10 @@ var cameraLight = {
         return r > maxHFov ? cameraLight.getVFOVFromHFOV(maxHFov, width, height) : currentFov
     },
     getHFOVForCamera: function(camera,  getRad) {
-        return cameraLight.getFOVByScreenPrecent(camera.fov, camera.aspect, getRad)
+        return cameraLight.getHFOVByScreenPrecent(camera.fov, camera.aspect, getRad)
     }, 
     //add
-    getFOVByScreenPrecent: function(fov, percent, getRad) { //当fov为占比百分百时,percent代表在屏幕上从中心到边缘的占比
+    getHFOVByScreenPrecent: function(fov, percent, getRad) { //当fov为占比百分百时,percent代表在屏幕上从中心到边缘的占比
         let rad = 2 * Math.atan(percent * Math.tan(fov * MathLight.RADIANS_PER_DEGREE / 2));
         if(getRad)return rad 
         else return rad * MathLight.DEGREES_PER_RADIAN;

+ 25 - 68
src/utils/math.js

@@ -55,10 +55,25 @@ var math = {
         return angle
     }, 
     
-    closeTo : function(a,b, num){ 
-        if(num != void 0)return Math.abs(a-b) < num;
-        return Math.abs(a-b) < 1e-6;
+    closeTo : function(a,b, precision=1e-6){ 
+        let f = (a,b)=>{
+            return Math.abs(a-b) < precision;
+        } 
+          
+        if(typeof (a) == 'number'){
+            return f(a, b);
+        }else{
+            let judge = (name)=>{
+                if(a[name] == void 0)return true //有值就判断,没值就不判断
+                else return f(a[name],b[name])
+            }
+            return judge('x') && judge('y') && judge('z') && judge('w')  
+        } 
+        
     }, 
+     
+
+    
 	toPrecision: function (e, t) {//xzw change 保留小数
 		var f = function (e, t) {
 			var i = Math.pow(10, t);
@@ -572,74 +587,16 @@ var math = {
         
         return rings 
         
-    }  
+    },
+
+    getQuaFromPosAim( position, target ){ 
+        let matrix = (new THREE.Matrix4).lookAt(position, target, new THREE.Vector3(0,0,1))
+        return (new THREE.Quaternion).setFromRotationMatrix(matrix)
+        
+    }
 };
 
  
-/* 
-   var e = this;
-            this.UnitService = t,
-            this.scopedConvert = function(t, n, i, r, o) {
-                return void 0 === i && (i = 2),
-                void 0 === r && (r = void 0),
-                void 0 === o && (o = void 0),
-                e.convert(t, n, i, r, o)
-            }
-        }
-        return t.prototype.convert = function(e, n, i, r, o, a) {
-            if (void 0 === i && (i = 2),
-            void 0 === r && (r = void 0),
-            void 0 === o && (o = void 0),
-            void 0 === a && (a = !1),
-            !e)
-                return "";
-            var s = t.getMostRelevantMeasurement(n, r || this.UnitService.currentSystem, e, o);
-            return t.getFormattedMeasurementString(s[0], s[1], i, a)
-        }
-        ,
-        t.getFormattedMeasurementString = function(t, e, n, i) {
-            return i && e.name === o.UnitsOfMeasurement.FOOT[0] ? this.formatImperialDistance(t * this.FEET_TO_INCHES_FACTOR) : i && e.name === o.UnitsOfMeasurement.INCH[0] ? this.formatImperialDistance(t) : t.toLocaleString(void 0, {
-                minimumFractionDigits: n,
-                maximumFractionDigits: n
-            }) + " " + e.symbol
-        }
-        ,
-        t.formatImperialDistance = function(e) {
-            var n = Math.round(8 * e)
-              , i = Math.floor(n / 8)
-              , r = Math.floor(i / t.FEET_TO_INCHES_FACTOR)
-              , o = i - r * t.FEET_TO_INCHES_FACTOR
-              , a = this.EIGHTHS_SYMBOLS[n % 8]
-              , s = 0 === o && "" !== a ? "" : o;
-            return "" !== s && "" !== a && (a = " " + a),
-            0 !== r ? r + "' " + s + a + '"' : "" + s + a + '"'
-        }
-        ,
-        t.getMostRelevantMeasurement = function(t, e, n, i) {
-            void 0 === i && (i = 0);
-            var a = r.values(o.UnitsOfMeasurement.getUnitsOfMeasurementByDomainAndSystem(t, e))
-              , s = r.filter(a, function(t) {
-                return t.factor >= i
-            })
-              , c = r.reduce(s, function(t, e) {
-                return e.fromBase(n) < t.fromBase(n) && e.fromBase(n) >= 1 ? e : t
-            });
-            return c ? [c.fromBase(n), c] : void 0
-        }
-        ,
-        t.FEET_TO_INCHES_FACTOR = 12,
-        t.EIGHTHS_SYMBOLS = ["", "⅛", "¼", "⅜", "½", "⅝", "¾", "⅞"],
-        t.ɵfac = function(e) {
-            return new (e || t)(c.ɵɵinject(l.UnitService))
-        }
-        ,
-        t.ɵprov = c.ɵɵdefineInjectable({
-            token: t,
-            factory: t.ɵfac,
-            providedIn: "root"
-        }),
-        t
- */
 
 
 

+ 209 - 205
src/utils/transitions.js

@@ -1,229 +1,233 @@
  
 var easing = {};
 //渐变曲线函数,反应加速度的变化
-easing.linearTween = function(e, t, i, n) {
-    return i * e / n + t
+
+
+//currentTime:x轴当前时间(从0-到duration), startY:起始点, duration:总时长, wholeY:路程 (即endY-startY)
+
+easing.linearTween = function(currentTime, startY, wholeY, duration) {
+    return wholeY * currentTime / duration + startY
 }
 ,
-easing.easeInQuad = function(e, t, i, n) {
-    return e /= n,
-    i * e * e + t
+easing.easeInQuad = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    wholeY * currentTime * currentTime + startY
 }
 ,
-easing.easeOutQuad = function(e, t, i, n) {
-    return e /= n,
-    -i * e * (e - 2) + t
+easing.easeOutQuad = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    -wholeY * currentTime * (currentTime - 2) + startY
 }
 ,
-easing.easeInOutQuad = function(e, t, i, n) {
-    return e /= n / 2,
-    e < 1 ? i / 2 * e * e + t : (e--,
-    -i / 2 * (e * (e - 2) - 1) + t)
+easing.easeInOutQuad = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration / 2,
+    currentTime < 1 ? wholeY / 2 * currentTime * currentTime + startY : (currentTime--,
+    -wholeY / 2 * (currentTime * (currentTime - 2) - 1) + startY)
 }
 ,
-easing.easeInCubic = function(e, t, i, n) {
-    return e /= n,
-    i * e * e * e + t
+easing.easeInCubic = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    wholeY * currentTime * currentTime * currentTime + startY
 }
 ,
-easing.easeOutCubic = function(e, t, i, n) {
-    return e /= n,
-    e--,
-    i * (e * e * e + 1) + t
+easing.easeOutCubic = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    currentTime--,
+    wholeY * (currentTime * currentTime * currentTime + 1) + startY
 }
 ,
-easing.easeInOutCubic = function(e, t, i, n) {
-    return e /= n / 2,
-    e < 1 ? i / 2 * e * e * e + t : (e -= 2,
-    i / 2 * (e * e * e + 2) + t)
+easing.easeInOutCubic = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration / 2,
+    currentTime < 1 ? wholeY / 2 * currentTime * currentTime * currentTime + startY : (currentTime -= 2,
+    wholeY / 2 * (currentTime * currentTime * currentTime + 2) + startY)
 }
 ,
-easing.easeInQuart = function(e, t, i, n) {
-    return e /= n,
-    i * e * e * e * e + t
+easing.easeInQuart = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    wholeY * currentTime * currentTime * currentTime * currentTime + startY
 }
 ,
-easing.easeOutQuart = function(e, t, i, n) {
-    return e /= n,
-    e--,
-    -i * (e * e * e * e - 1) + t
+easing.easeOutQuart = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    currentTime--,
+    -wholeY * (currentTime * currentTime * currentTime * currentTime - 1) + startY
 }
 ,
-easing.easeInOutQuart = function(e, t, i, n) {
-    return e /= n / 2,
-    e < 1 ? i / 2 * e * e * e * e + t : (e -= 2,
-    -i / 2 * (e * e * e * e - 2) + t)
+easing.easeInOutQuart = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration / 2,
+    currentTime < 1 ? wholeY / 2 * currentTime * currentTime * currentTime * currentTime + startY : (currentTime -= 2,
+    -wholeY / 2 * (currentTime * currentTime * currentTime * currentTime - 2) + startY)
 }
 ,
-easing.easeInQuint = function(e, t, i, n) {
-    return e /= n,
-    i * e * e * e * e * e + t
+easing.easeInQuint = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    wholeY * currentTime * currentTime * currentTime * currentTime * currentTime + startY
 }
 ,
-easing.easeOutQuint = function(e, t, i, n) {
-    return e /= n,
-    e--,
-    i * (e * e * e * e * e + 1) + t
+easing.easeOutQuint = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    currentTime--,
+    wholeY * (currentTime * currentTime * currentTime * currentTime * currentTime + 1) + startY
 }
 ,
-easing.easeInOutQuint = function(e, t, i, n) {
-    return e /= n / 2,
-    e < 1 ? i / 2 * e * e * e * e * e + t : (e -= 2,
-    i / 2 * (e * e * e * e * e + 2) + t)
+easing.easeInOutQuint = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration / 2,
+    currentTime < 1 ? wholeY / 2 * currentTime * currentTime * currentTime * currentTime * currentTime + startY : (currentTime -= 2,
+    wholeY / 2 * (currentTime * currentTime * currentTime * currentTime * currentTime + 2) + startY)
 }
 ,
-easing.easeInSine = function(e, t, i, n) {
-    return -i * Math.cos(e / n * (Math.PI / 2)) + i + t
+easing.easeInSine = function(currentTime, startY, wholeY, duration) {
+    return -wholeY * Math.cos(currentTime / duration * (Math.PI / 2)) + wholeY + startY
 }
 ,
-easing.easeOutSine = function(e, t, i, n) {
-    return i * Math.sin(e / n * (Math.PI / 2)) + t
+easing.easeOutSine = function(currentTime, startY, wholeY, duration) {
+    return wholeY * Math.sin(currentTime / duration * (Math.PI / 2)) + startY
 }
 ,
-easing.easeInOutSine = function(e, t, i, n) {
-    return -i / 2 * (Math.cos(Math.PI * e / n) - 1) + t
+easing.easeInOutSine = function(currentTime, startY, wholeY, duration) {
+    return -wholeY / 2 * (Math.cos(Math.PI * currentTime / duration) - 1) + startY
 }
 ,
-easing.easeInExpo = function(e, t, i, n) {
-    return i * Math.pow(2, 10 * (e / n - 1)) + t
+easing.easeInExpo = function(currentTime, startY, wholeY, duration) {
+    return wholeY * Math.pow(2, 10 * (currentTime / duration - 1)) + startY
 }
 ,
-easing.easeOutExpo = function(e, t, i, n) {
-    return i * (-Math.pow(2, -10 * e / n) + 1) + t
+easing.easeOutExpo = function(currentTime, startY, wholeY, duration) {
+    return wholeY * (-Math.pow(2, -10 * currentTime / duration) + 1) + startY
 }
 ,
-easing.easeInOutExpo = function(e, t, i, n) {
-    return e /= n / 2,
-    e < 1 ? i / 2 * Math.pow(2, 10 * (e - 1)) + t : (e--,
-    i / 2 * (-Math.pow(2, -10 * e) + 2) + t)
+easing.easeInOutExpo = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration / 2,
+    currentTime < 1 ? wholeY / 2 * Math.pow(2, 10 * (currentTime - 1)) + startY : (currentTime--,
+    wholeY / 2 * (-Math.pow(2, -10 * currentTime) + 2) + startY)
 }
 ,
-easing.easeInCirc = function(e, t, i, n) {
-    return e /= n,
-    -i * (Math.sqrt(1 - e * e) - 1) + t
+easing.easeInCirc = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    -wholeY * (Math.sqrt(1 - currentTime * currentTime) - 1) + startY
 }
 ,
-easing.easeOutCirc = function(e, t, i, n) {
-    return e /= n,
-    e--,
-    i * Math.sqrt(1 - e * e) + t
+easing.easeOutCirc = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration,
+    currentTime--,
+    wholeY * Math.sqrt(1 - currentTime * currentTime) + startY
 }
 ,
-easing.easeInOutCirc = function(e, t, i, n) {
-    return e /= n / 2,
-    e < 1 ? -i / 2 * (Math.sqrt(1 - e * e) - 1) + t : (e -= 2,
-    i / 2 * (Math.sqrt(1 - e * e) + 1) + t)
+easing.easeInOutCirc = function(currentTime, startY, wholeY, duration) {
+    return currentTime /= duration / 2,
+    currentTime < 1 ? -wholeY / 2 * (Math.sqrt(1 - currentTime * currentTime) - 1) + startY : (currentTime -= 2,
+    wholeY / 2 * (Math.sqrt(1 - currentTime * currentTime) + 1) + startY)
 }
 ,
-easing.easeInElastic = function(e, t, i, n) {
+easing.easeInElastic = function(currentTime, startY, wholeY, duration) {
     var r = 1.70158
       , o = 0
-      , a = i;
-    return 0 === e ? t : 1 === (e /= n) ? t + i : (o || (o = .3 * n),
-    a < Math.abs(i) ? (a = i,
-    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(i / a),
-    -(a * Math.pow(2, 10 * (e -= 1)) * Math.sin((e * n - r) * (2 * Math.PI) / o)) + t)
+      , a = wholeY;
+    return 0 === currentTime ? startY : 1 === (currentTime /= duration) ? startY + wholeY : (o || (o = .3 * duration),
+    a < Math.abs(wholeY) ? (a = wholeY,
+    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(wholeY / a),
+    -(a * Math.pow(2, 10 * (currentTime -= 1)) * Math.sin((currentTime * duration - r) * (2 * Math.PI) / o)) + startY)
 }
 ,
-easing.easeOutElastic = function(e, t, i, n) {
+easing.easeOutElastic = function(currentTime, startY, wholeY, duration) {
     var r = 1.70158
       , o = 0
-      , a = i;
-    return 0 === e ? t : 1 === (e /= n) ? t + i : (o || (o = .3 * n),
-    a < Math.abs(i) ? (a = i,
-    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(i / a),
-    a * Math.pow(2, -10 * e) * Math.sin((e * n - r) * (2 * Math.PI) / o) + i + t)
+      , a = wholeY;
+    return 0 === currentTime ? startY : 1 === (currentTime /= duration) ? startY + wholeY : (o || (o = .3 * duration),
+    a < Math.abs(wholeY) ? (a = wholeY,
+    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(wholeY / a),
+    a * Math.pow(2, -10 * currentTime) * Math.sin((currentTime * duration - r) * (2 * Math.PI) / o) + wholeY + startY)
 }
 ,
-easing.easeInOutElastic = function(e, t, i, n) {
+easing.easeInOutElastic = function(currentTime, startY, wholeY, duration) {
     var r = 1.70158
       , o = 0
-      , a = i;
-    return 0 === e ? t : 2 === (e /= n / 2) ? t + i : (o || (o = n * (.3 * 1.5)),
-    a < Math.abs(i) ? (a = i,
-    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(i / a),
-    e < 1 ? -.5 * (a * Math.pow(2, 10 * (e -= 1)) * Math.sin((e * n - r) * (2 * Math.PI) / o)) + t : a * Math.pow(2, -10 * (e -= 1)) * Math.sin((e * n - r) * (2 * Math.PI) / o) * .5 + i + t)
+      , a = wholeY;
+    return 0 === currentTime ? startY : 2 === (currentTime /= duration / 2) ? startY + wholeY : (o || (o = duration * (.3 * 1.5)),
+    a < Math.abs(wholeY) ? (a = wholeY,
+    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(wholeY / a),
+    currentTime < 1 ? -.5 * (a * Math.pow(2, 10 * (currentTime -= 1)) * Math.sin((currentTime * duration - r) * (2 * Math.PI) / o)) + startY : a * Math.pow(2, -10 * (currentTime -= 1)) * Math.sin((currentTime * duration - r) * (2 * Math.PI) / o) * .5 + wholeY + startY)
 }
 ,
-easing.easeInBack = function(e, t, i, n, r) {
+easing.easeInBack = function(currentTime, startY, wholeY, duration, r) {
     return void 0 === r && (r = 1.70158),
-    i * (e /= n) * e * ((r + 1) * e - r) + t
+    wholeY * (currentTime /= duration) * currentTime * ((r + 1) * currentTime - r) + startY
 }
 ,
-easing.easeOutBack = function(e, t, i, n, r) {
+easing.easeOutBack = function(currentTime, startY, wholeY, duration, r) {
     return void 0 === r && (r = 1.70158),
-    i * ((e = e / n - 1) * e * ((r + 1) * e + r) + 1) + t
+    wholeY * ((currentTime = currentTime / duration - 1) * currentTime * ((r + 1) * currentTime + r) + 1) + startY
 }
 ,
-easing.easeInOutBack = function(e, t, i, n, r) {
+easing.easeInOutBack = function(currentTime, startY, wholeY, duration, r) {
     return void 0 === r && (r = 1.70158),
-    (e /= n / 2) < 1 ? i / 2 * (e * e * (((r *= 1.525) + 1) * e - r)) + t : i / 2 * ((e -= 2) * e * (((r *= 1.525) + 1) * e + r) + 2) + t
+    (currentTime /= duration / 2) < 1 ? wholeY / 2 * (currentTime * currentTime * (((r *= 1.525) + 1) * currentTime - r)) + startY : wholeY / 2 * ((currentTime -= 2) * currentTime * (((r *= 1.525) + 1) * currentTime + r) + 2) + startY
 }
 ,
-easing.easeOutBounce = function(e, t, i, n) {
-    return (e /= n) < 1 / 2.75 ? i * (7.5625 * e * e) + t : e < 2 / 2.75 ? i * (7.5625 * (e -= 1.5 / 2.75) * e + .75) + t : e < 2.5 / 2.75 ? i * (7.5625 * (e -= 2.25 / 2.75) * e + .9375) + t : i * (7.5625 * (e -= 2.625 / 2.75) * e + .984375) + t
+easing.easeOutBounce = function(currentTime, startY, wholeY, duration) {
+    return (currentTime /= duration) < 1 / 2.75 ? wholeY * (7.5625 * currentTime * currentTime) + startY : currentTime < 2 / 2.75 ? wholeY * (7.5625 * (currentTime -= 1.5 / 2.75) * currentTime + .75) + startY : currentTime < 2.5 / 2.75 ? wholeY * (7.5625 * (currentTime -= 2.25 / 2.75) * currentTime + .9375) + startY : wholeY * (7.5625 * (currentTime -= 2.625 / 2.75) * currentTime + .984375) + startY
 }
 ,
-easing.easeInBounce = function(e, t, i, r) {
-    return i - easing.easeOutBounce(r - e, 0, i, r) + t
+easing.easeInBounce = function(currentTime, startY, wholeY, r) {
+    return wholeY - easing.easeOutBounce(r - currentTime, 0, wholeY, r) + startY
 }
 ,
-easing.easeInOutBounce = function(e, t, i, r) {
-    return e < r / 2 ? .5 * easing.easeInBounce(2 * e, 0, i, r) + t : .5 * easing.easeOutBounce(x, 2 * e - r, 0, i, r) + .5 * i + t
+easing.easeInOutBounce = function(currentTime, startY, wholeY, r) {
+    return currentTime < r / 2 ? .5 * easing.easeInBounce(2 * currentTime, 0, wholeY, r) + startY : .5 * easing.easeOutBounce(x, 2 * currentTime - r, 0, wholeY, r) + .5 * wholeY + startY
 }
 
  
 
 
 var lerp = {
-	vector: function(e, t, f) {//xzw change, add f
-		var i = e.clone();
-		return t = t.clone(),
-		function(n) {
-			e.set(i.x * (1 - n) + t.x * n, i.y * (1 - n) + t.y * n, i.z * (1 - n) + t.z * n)
-			f && f(e,n);
+	vector: function(currentTime, startY, f) {//xzw change, add f
+		var wholeY = currentTime.clone();
+		return startY = startY.clone(),
+		function(duration) {
+			currentTime.set(wholeY.x * (1 - duration) + startY.x * duration, wholeY.y * (1 - duration) + startY.y * duration, wholeY.z * (1 - duration) + startY.z * duration)
+			f && f(currentTime,duration);
 		}
 	},
-    quaternion: function(e, t, f) {//xzw change, add f
-        var i = e.clone();
-        return function(n) {
-            e.copy(i).slerp(t, n);
-			f && f(e,n);
+    quaternion: function(currentTime, startY, f) {//xzw change, add f
+        var wholeY = currentTime.clone();
+        return function(duration) {
+            currentTime.copy(wholeY).slerp(startY, duration);
+			f && f(currentTime,duration);
         }
     },
-    property: function(e, t, i, n) {
-        var r = e[t];
+    property: function(currentTime, startY, wholeY, duration) {
+        var r = currentTime[startY];
         return function(o) {
-            e[t] = r * (1 - o) + i * o,
-            n && n(e[t])
+            currentTime[startY] = r * (1 - o) + wholeY * o,
+            duration && duration(currentTime[startY])
         }
     },
-    uniform: function(e, t, i) {
-        var n = e.material.uniforms[t].value;
+    uniform: function(currentTime, startY, wholeY) {
+        var duration = currentTime.material.uniforms[startY].value;
         return function(r) {
             try{
-                e.material.uniforms[t] && (e.material.uniforms[t].value = n * (1 - r) + i * r)
-            }catch(e){
+                currentTime.material.uniforms[startY] && (currentTime.material.uniforms[startY].value = duration * (1 - r) + wholeY * r)
+            }catch(currentTime){
                 console.log(1)
             }
             
         }
     },
-    matrix4: function(e, t) {
-        var i = e.clone();
-        return function(n) {
-            for (var r = e.elements, o = i.elements, a = t.elements, s = 0; s < 16; s++)
-                r[s] = o[s] * (1 - n) + a[s] * n
+    matrix4: function(currentTime, startY) {
+        var wholeY = currentTime.clone();
+        return function(duration) {
+            for (var r = currentTime.elements, o = wholeY.elements, a = startY.elements, s = 0; s < 16; s++)
+                r[s] = o[s] * (1 - duration) + a[s] * duration
         }
     },
-    allUniforms: function(e, t, i) {
-        var n = e.map(function(e) {
-            return this.uniform(e, t, i)
+    allUniforms: function(currentTime, startY, wholeY) {
+        var duration = currentTime.map(function(currentTime) {
+            return this.uniform(currentTime, startY, wholeY)
         }
         .bind(this));
-        return function(e) {
-            n.forEach(function(t) {
-                t(e)
+        return function(currentTime) {
+            duration.forEach(function(startY) {
+                startY(currentTime)
             })
         }
     }
@@ -244,44 +248,44 @@ var transitions = {
     funcs: [],
     counter: 0,
     uniqueID: 0,
-    start: function(e, t, i, r, o, a, s, cancelFun) {
+    start: function(currentTime, startY, wholeY, r, o, a, s, cancelFun) {
         return r = r || 0,
         this.funcs.push({
-            func: e,
-            current: -r * Math.abs(t),                      //当前时间
-            duration: (1 - Math.max(r, 0)) * Math.abs(t),   //总时长
-            done: i,
+            func: currentTime,
+            current: -r * Math.abs(startY),                      //当前时间
+            duration: (1 - Math.max(r, 0)) * Math.abs(startY),   //总时长
+            done: wholeY,
             easing: o || easing.linearTween,                //渐变曲线
-            cycling: t < 0,
+            cycling: startY < 0,
             running: !0,
             debug: r < 0,
-            name: a || "T" + this.counter,
+            name: a || "startY" + this.counter,
             id: void 0 === s ? this.counter : s,
             paused: !1,
 			cancelFun : cancelFun,   //取消时执行的函数
         }),
-        e(0, 16),
+        currentTime(0, 16),
         this.counter += 1,
-        e
+        currentTime
     },
-    trigger: function(e) {
-        var t = void 0 === e.delayRatio ? 0 : e.delayRatio
-            , i = e.func || function() {}
-            , r = void 0 === e.duration ? 0 : e.duration;
-        void 0 !== e.cycling && e.cycling && (r = -Math.abs(r));
-        var o = e.done || null
-            , a = e.easing || easing.linearTween
-            , s = e.name || "R" + this.counter
-            , l = void 0 === e.id ? this.counter : e.id;
-        return this.start(i, r, o, t, a, s, l)
+    trigger: function(currentTime) {
+        var startY = void 0 === currentTime.delayRatio ? 0 : currentTime.delayRatio
+            , wholeY = currentTime.func || function() {}
+            , r = void 0 === currentTime.duration ? 0 : currentTime.duration;
+        void 0 !== currentTime.cycling && currentTime.cycling && (r = -Math.abs(r));
+        var o = currentTime.done || null
+            , a = currentTime.easing || easing.linearTween
+            , s = currentTime.name || "R" + this.counter
+            , l = void 0 === currentTime.id ? this.counter : currentTime.id;
+        return this.start(wholeY, r, o, startY, a, s, l)
     },
-    setTimeout: function(e, t, i) {
-        var n = void 0 === i ? this.counter : i;
+    setTimeout: function(currentTime, startY, wholeY) {
+        var duration = void 0 === wholeY ? this.counter : wholeY;
         return this.trigger({
-            done: e,
-            duration: void 0 === t ? 0 : t,
+            done: currentTime,
+            duration: void 0 === startY ? 0 : startY,
             name: "O" + this.counter,
-            id: n
+            id: duration
         })
     },
     pause: function() {
@@ -290,83 +294,83 @@ var transitions = {
     resume: function() {
         this.paused = !1
     },
-    update: function(e) {
-        this.funcs.forEach(function(t) {
-            if (!(t.paused || (t.current += 1e3 * e,
-            t.current < 0)))
-                if (t.current >= t.duration && !t.cycling) {
-                    var i = t.easing(1, 0, 1, 1);
-                    t.func(i, 1e3 * e),
-                    t.done && t.done(),
-                    t.running = !1
+    update: function(currentTime) {
+        this.funcs.forEach(function(startY) {
+            if (!(startY.paused || (startY.current += 1e3 * currentTime,
+            startY.current < 0)))
+                if (startY.current >= startY.duration && !startY.cycling) {
+                    var wholeY = startY.easing(1, 0, 1, 1);
+                    startY.func(wholeY, 1e3 * currentTime),
+                    startY.done && startY.done(),
+                    startY.running = !1
                 } else {
-                    var n = t.easing(t.current % t.duration / t.duration, 0, 1, 1)
-                        , r = t.func(n, 1e3 * e) || !1;
-                    r && (t.done && t.done(),
-                    t.running = !1)
+                    var duration = startY.easing(startY.current % startY.duration / startY.duration, 0, 1, 1)
+                        , r = startY.func(duration, 1e3 * currentTime) || !1;
+                    r && (startY.done && startY.done(),
+                    startY.running = !1)
                 }
         });
-        var t = this.funcs.length;
-        this.funcs = this.funcs.filter(function(e) {
-            return e.running
+        var startY = this.funcs.length;
+        this.funcs = this.funcs.filter(function(currentTime) {
+            return currentTime.running
         });
-        var i = this.funcs.length;
-        if (t > 0 && 0 === i && this.globalDone) {
-            var n = this.globalDone;
+        var wholeY = this.funcs.length;
+        if (startY > 0 && 0 === wholeY && this.globalDone) {
+            var duration = this.globalDone;
             this.globalDone = null,
-            n()
+            duration()
         }
     },
-    adjustSpeed: function(e, t) {
-        for (var i = this.getById(e), n = 0; n < i.length; n++) {
-            var r = i[n];
-            r.duration /= t,
-            r.current /= t
+    adjustSpeed: function(currentTime, startY) {
+        for (var wholeY = this.getById(currentTime), duration = 0; duration < wholeY.length; duration++) {
+            var r = wholeY[duration];
+            r.duration /= startY,
+            r.current /= startY
         }
     },
-    getById: function(e) {
-        return this.funcs.filter(function(t) {
-            return e === t.id
+    getById: function(currentTime) {
+        return this.funcs.filter(function(startY) {
+            return currentTime === startY.id
         })
     },
-    get: function(e) {
-        for (var t = 0; t < this.funcs.length; t += 1)
-            if (this.funcs[t].func === e)
-                return this.funcs[t];
+    get: function(currentTime) {
+        for (var startY = 0; startY < this.funcs.length; startY += 1)
+            if (this.funcs[startY].func === currentTime)
+                return this.funcs[startY];
         return null
     },
-    isRunning: function(e) {
-        var t = this.get(e);
-        return null !== t && t.running
+    isRunning: function(currentTime) {
+        var startY = this.get(currentTime);
+        return null !== startY && startY.running
     },
     countActive: function() {
-        for (var e = 0, t = 0; t < this.funcs.length; t += 1)
-            e += this.funcs[t].running;
-        return e
+        for (var currentTime = 0, startY = 0; startY < this.funcs.length; startY += 1)
+            currentTime += this.funcs[startY].running;
+        return currentTime
     },
     listActive: function() {
-        for (var e = [], t = 0; t < this.funcs.length; t += 1)
-            this.funcs[t].running && e.push(this.funcs[t].name);
-        return e
+        for (var currentTime = [], startY = 0; startY < this.funcs.length; startY += 1)
+            this.funcs[startY].running && currentTime.push(this.funcs[startY].name);
+        return currentTime
     },
-    done: function(e) {
-        this.globalDone = e
+    done: function(currentTime) {
+        this.globalDone = currentTime
     },
-    cancelById: function(e, dealCancelFun) { //xzw add dealDone
-        var t = void 0 === e ? 0 : e;
+    cancelById: function(currentTime, dealCancelFun) { //xzw add dealDone
+        var startY = void 0 === currentTime ? 0 : currentTime;
 		 
-        this.funcs = this.funcs.filter(function(e) {
-			var is = e.id == t;
+        this.funcs = this.funcs.filter(function(currentTime) {
+			var is = currentTime.id == startY;
 			
 			if(is && dealCancelFun){
-				e.cancelFun && e.cancelFun()
+				currentTime.cancelFun && currentTime.cancelFun()
 			} 
             return !is
         })
     },
-    cancel: function(e) {
-        this.funcs = this.funcs.filter(function(t) {
-            return t.func !== e
+    cancel: function(currentTime) {
+        this.funcs = this.funcs.filter(function(startY) {
+            return startY.func !== currentTime
         })
     },
     getUniqueId: function() {

+ 4 - 4
src/viewer/EDLRenderer.js

@@ -84,7 +84,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
 
 	
 
-	clearTargets(params){
+	clearTargets(params={}){
 		const viewer = this.viewer;
 		const {renderer} = viewer;
 
@@ -123,7 +123,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
 		const doShadows = lights.length > 0 && !(lights[0].disableShadowUpdates);
 		if(doShadows){
 			let light = lights[0];
-
+ 
 			this.shadowMap.setLight(light);
 
 			let originalAttributes = new Map();
@@ -152,7 +152,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
 	}
 
 	render(params={}){
-
+ 
          
         /* 
             渲染顺序:
@@ -222,7 +222,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
         if(!params.magnifier){
             viewer.setCameraLayers(camera, ['skybox'])
             viewer.renderer.render(viewer.scene.scene, camera);
-        }
+        } 
          
         
         //pointcloud

+ 1 - 0
src/viewer/PropertyPanels/CameraAnimationPanel.js

@@ -38,6 +38,7 @@ export class CameraAnimationPanel{
 			step: 0.001,
 			slide: (event, ui) => { 
 				animation.set(ui.value);
+                animation.updateFrustum()
 			}
 		});
 

+ 3 - 4
src/viewer/Scene.js

@@ -3,11 +3,10 @@ import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Annotation} from "../Annotation.js";
 import {CameraMode} from "../defines.js";
 import {View} from "./View.js";
-import {Utils} from "../utils.js";
-import {EventDispatcher} from "../EventDispatcher.js";
+import {Utils} from "../utils.js"; 
 import Axis from '../objects/Axis'
 
-export class Scene extends EventDispatcher{
+export class Scene extends THREE.EventDispatcher{
 
 	constructor(){
 		super();
@@ -250,7 +249,7 @@ export class Scene extends EventDispatcher{
 	};
 
 	removeCameraAnimation(animation){
-		let index = this.cameraAnimations.indexOf(volume);
+		let index = this.cameraAnimations.indexOf(animation);
 		if (index > -1) {
 			this.cameraAnimations.splice(index, 1);
 

+ 45 - 107
src/viewer/View.js

@@ -1,9 +1,13 @@
 import * as THREE from "../../libs/three.js/build/three.module.js";
 import {transitions, easing, lerp} from '../utils/transitions.js'
-import Common from '../utils/Common'
+import math from '../utils/math.js'
+import Common from '../utils/Common' 
 
-export class View{
+let sid = 0
+ 
+export class View extends THREE.EventDispatcher{
 	constructor () {
+        super()
 		this.position = new THREE.Vector3(0, 0, 0);
 
 		this.yaw = 0//Math.PI / 4; // =  4dkk lon + 90  
@@ -13,7 +17,8 @@ export class View{
 		this.maxPitch = Math.PI / 2;
 		this.minPitch = -Math.PI / 2;
          
-        
+        this.sid = sid++
+        this.LookTransition = 'LookTransition'+this.sid
         
 
 	}
@@ -170,74 +175,6 @@ export class View{
         this.restrictPos()
 	}
 
-	/* setView(position, target, duration = 0, callback = null, onUpdate = null, Easing=''){
-
-		let endPosition = null;
-		if(position instanceof Array){
-			endPosition = new THREE.Vector3(...position);
-		}else if(position.x != null){
-			endPosition = position.clone();
-		}
-
-		let endTarget = null;
-		if(target instanceof Array){
-			endTarget = new THREE.Vector3(...target);
-		}else if(target.x != null){
-			endTarget = target.clone();
-		}
-		
-		const startPosition = this.position.clone();
-		const startTarget = this.getPivot();
-
-		//const endPosition = position.clone();
-		//const endTarget = target.clone();
-
-		let easing = Easing ? TWEEN.Easing.Quartic[Easing] : TWEEN.Easing.Quartic.Out ;
-
-		if(duration === 0){
-			this.position.copy(endPosition);
-			this.lookAt(endTarget);
-		}else{
-			let value = {x: 0};
-			let tween = new TWEEN.Tween(value).to({x: 1}, duration);
-			tween.easing(easing);
-			//this.tweens.push(tween);
-
-			tween.onUpdate(() => {
-				let t = value.x;
-
-				//console.log(t);
-
-				const pos = new THREE.Vector3(
-					(1 - t) * startPosition.x + t * endPosition.x,
-					(1 - t) * startPosition.y + t * endPosition.y,
-					(1 - t) * startPosition.z + t * endPosition.z,
-				);
-
-				const target = new THREE.Vector3(
-					(1 - t) * startTarget.x + t * endTarget.x,
-					(1 - t) * startTarget.y + t * endTarget.y,
-					(1 - t) * startTarget.z + t * endTarget.z,
-				);
-
-				this.position.copy(pos);
-				this.lookAt(target);
-                
-                onUpdate && onUpdate(t)//add
-			});
-
-			tween.start();
-
-			tween.onComplete(() => {
-				if(callback){
-					callback();
-				}
-			});
-		}
-
-	} */
-
-
 
     restrictPos(){//add
         if(this.limitBound){
@@ -279,56 +216,57 @@ export class View{
     
     
     
-    
-    setView(position, target, duration = 0, callback = null, onUpdate = null, Easing=''){
-        //待改成quater渐变
-        let endPosition = null;
-		/* if(position instanceof Array){
-			endPosition = new THREE.Vector3(...position);
-		}else  */if(position.x != null){
-			endPosition = new THREE.Vector3().copy(position)
-		}
-
-		let endTarget = null;
-		/* if(target instanceof Array){
-			endTarget = new THREE.Vector3(...target);
-		}else  */if(target && target.x != null){
-			endTarget = new THREE.Vector3().copy(target) 
-		}
-		const startPosition = this.position.clone();
+    setView( info = {}){
+        // position, target, duration = 0, callback = null, onUpdate = null, Easing='', cancelFun
+        transitions.cancelById(this.LookTransition, true ); 
+        
+        let done = ()=>{
+            info.callback && info.callback()
+            endTarget && this.lookAt(endTarget); //compute radius for orbitcontrol
+            this.dispatchEvent('setViewDone')
+        }
+        
+        let endPosition = new THREE.Vector3().copy(info.position)
+	 
+        const startPosition = this.position.clone();
 		const startTarget = this.getPivot();
-
-		if(duration === 0){
+        
+        
+		let endTarget = null, endQuaternion, startQuaternion;
+		if(info.target ){
+			endTarget = new THREE.Vector3().copy(info.target) 
+            
+            endQuaternion = math.getQuaFromPosAim(endPosition,endTarget)
+            startQuaternion = math.getQuaFromPosAim(startPosition,startTarget)
+		}
+         
+         
+		if(!info.duration){
 			this.position.copy(endPosition);
             this.restrictPos()
-			endTarget && this.lookAt(endTarget);
-            onUpdate && onUpdate(1)
-            callback && callback()
+			
+            info.onUpdate && info.onUpdate(1)
+            done()
 		}else{
 
             transitions.start(lerp.vector(this.position, endPosition, (pos, progress)=>{
 				let t = progress 
-
-				//console.log(t);
-                if(endTarget){
-                    const target = new THREE.Vector3(
-                        (1 - t) * startTarget.x + t * endTarget.x,
-                        (1 - t) * startTarget.y + t * endTarget.y,
-                        (1 - t) * startTarget.z + t * endTarget.z,
-                    );
-
-                    this.lookAt(target);
+ 
+                if(endQuaternion){ 
+                    
+                    let quaternion = (new THREE.Quaternion()).copy(startQuaternion) 
+                    lerp.quaternion(quaternion, endQuaternion)(progress),
+                    this.rotation = new THREE.Euler().setFromQuaternion(quaternion)
                 }
                 this.restrictPos()
  
 
-                onUpdate && onUpdate(t)//add
-            }), duration, callback, 0, Easing ? easing[Easing] : easing.easeInOutQuad ); 
+                info.onUpdate && info.onUpdate(t)//add
+            }), info.duration, done, 0, info.Easing ? easing[info.Easing] : easing.easeInOutQuad,null, this.LookTransition, info.cancelFun); 
 
 
         } 
 
     }
-    
-    
+
 };

+ 39 - 22
src/viewer/map/Map.js

@@ -1,5 +1,8 @@
 import * as THREE from "../../../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "../../EventDispatcher.js";
+
+
+
+
 let texLoader = new THREE.TextureLoader() 
     texLoader.crossOrigin = "anonymous" 
 
@@ -39,7 +42,7 @@ const MAX_VERTICAL_DIST_TO_BEST = 1
 //高德坐标拾取工具 : https://lbs.amap.com/tools/picker
 
 
-export class MapLayer extends EventDispatcher{ // 包括了 MapLayerBase SceneLayer
+export class MapLayer extends THREE.EventDispatcher{ // 包括了 MapLayerBase SceneLayer
     constructor(viewer_, viewport){
         super()
         this.sceneGroup = new THREE.Object3D;
@@ -69,26 +72,38 @@ export class MapLayer extends EventDispatcher{ // 包括了 MapLayerBase SceneLa
           */
     }
     
-    addMapEntity(data){
+    addMapEntity(data, datasetId){
          
         if(!data || !data[0]){ 
             Potree.Log('平面图无数据','red')
             return
         }
-        var floorplan = new TiledMapFromEntity(this, this.tileColor, data[0]    )//[0]?
-        this.addMap(floorplan)
-        floorplan.updateProjection()
-        floorplan.updateObjectGroup()
-        
         
-        this.dispatchEvent({type:'floorplanLoaded', floorplan})
-        if(Potree.settings.floorplanEnable){
-            this.needUpdate = true
-        }else{
-            floorplan.setEnable(false)
+        var floorplan = new TiledMapFromEntity(this, this.tileColor, data[0]    )//[0]?
+
+        if(floorplan){
+            floorplan.name += "_"+ datasetId  
+
+            this.addMap(floorplan)
+            floorplan.updateProjection()
+            floorplan.updateObjectGroup()
+             
+            let visible = false
+
+            if(datasetId in Potree.settings.floorplanEnables){
+                visible = Potree.settings.floorplanEnables[datasetId]
+            }else{
+                visible = Potree.settings.floorplanEnable
+            }
+
+            if(visible){
+                this.needUpdate = true
+            }else{
+                floorplan.setEnable(false)
+            }
+            
+            this.dispatchEvent({type:'floorplanLoaded', floorplan})
         }
-        
-        
         return floorplan
     }
     
@@ -137,7 +152,7 @@ export class MapLayer extends EventDispatcher{ // 包括了 MapLayerBase SceneLa
         var n = this;
         
         /* this.isVisibleInViewport(e) && (this.updateTimer || this.loadingInProgress || (this.updateTimer = window.setTimeout((function(){
-            n.update(e).then((function(t){
+            n.update(e).then((function  (t){
                 t && n.loadComplete.emit(!0)
             }
             )).catch(u.handleWarning)
@@ -204,7 +219,7 @@ export class MapLayer extends EventDispatcher{ // 包括了 MapLayerBase SceneLa
    
  
 
-export class TiledMapBase extends EventDispatcher{
+export class TiledMapBase extends THREE.EventDispatcher{
     constructor(/* t,  */name, mapLayer, tileColor, projection){
         super();
         this.name = name
@@ -232,7 +247,7 @@ export class TiledMapBase extends EventDispatcher{
     set zoomLevel(zoomLevel){
         if(this._zoomLevel != zoomLevel){
             this._zoomLevel = zoomLevel
-            //this.emit('zoomLevelChange',zoomLevel)
+            //this.dispatchEvent('zoomLevelChange',zoomLevel)
              
             //if(this.name == 'map')console.log(zoomLevel,viewer.mapViewer.camera.zoom)
         }
@@ -271,7 +286,9 @@ export class TiledMapBase extends EventDispatcher{
     
     setEnable(enable){//add
         if(!this.disabled == enable)return
-        
+        if(enable){
+            console.log('setEnable',true)
+        }
         this.disabled = !enable
      
         viewer.updateVisible(this.objectGroup, 'setEnable', enable)
@@ -452,7 +469,7 @@ export class MapTile{
         var loadDone = ()=>{
             this.map.mapLayer.loadingInProgress--
             if(this.map.mapLayer.loadingInProgress == 0){
-                this.map.mapLayer.emit('loadDone') 
+                this.map.mapLayer.dispatchEvent('loadDone') 
             }
         }
         
@@ -616,9 +633,9 @@ export class TiledMapFromEntity extends TiledMapBase{
         data.globalLocation = Potree.Utils.VectorFactory.fromArray3(e.location),
         data.orientation = Potree.Utils.QuaternionFactory.fromArray(e.orientation) 
         if(Potree.fileServer){
-            data.filePath = `https://${Potree.config.urls.prefix}${e.file_path}`
+            data.filePath = `${Potree.settings.urls.prefix}${e.file_path}`
         }else{
-            data.filePath = `https://${Potree.config.urls.prefix}/data/${Potree.settings.number}/${e.file_path}`
+            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')//暂时

+ 108 - 58
src/viewer/map/MapViewer.js

@@ -11,17 +11,17 @@ import math from "../../utils/math.js";
 import {Images360} from '../../modules/Images360/Images360'
 import Common from '../../utils/Common' 
 import {transitions, easing, lerp} from "../../utils/transitions.js";
-
+import {config } from "../../settings.js";
 /* var centerCross = new THREE.Mesh(new THREE.SphereGeometry(1, 4, 4),new THREE.MeshBasicMaterial({
     transparent:true,  color:"#ff0000", opacity:0.5
 })); */
 
 
-const mapHeight = -1000;//要比点云低。最低
-const cameraHeight = 1000;  //最高
-const panosHeight = mapHeight + 100 //要比点云低  (marker)
+/* const mapHeight = -1000;//要比点云低。最低
+const cameraHeight = 1000;  //最高 */
+const panosHeight = config.map.mapHeight + 100 //要比点云低  (marker)
 const cursorHeight = 0;//比地图高就行  
-const routeLayerHeight = mapHeight + 105
+const routeLayerHeight = config.map.mapHeight + 105
 
 const texLoader = new THREE.TextureLoader()
 const planeGeo = new THREE.PlaneBufferGeometry(1,1)
@@ -45,7 +45,7 @@ export class MapViewer extends ViewerBase{
          
         this.mapLayer = new MapLayer(this, this.viewports[0])
         this.scene.add(this.mapLayer.sceneGroup)
-        this.mapLayer.sceneGroup.position.setZ(mapHeight)
+        this.mapLayer.sceneGroup.position.setZ(Potree.config.map.mapHeight)
         this.mapRatio = 0.5
         this.splitDir = 'leftRight'
         
@@ -77,7 +77,7 @@ export class MapViewer extends ViewerBase{
         
         
         
-        this.on('add',(e)=>{//添加其他mesh
+        this.addEventListener('add',(e)=>{//添加其他mesh
             this.scene.add(e.object)
             if(e.name == 'route'){
                 e.object.position.z = routeLayerHeight 
@@ -121,13 +121,18 @@ export class MapViewer extends ViewerBase{
         if(this.mapLayer.loadingInProgress == 0){
             callback()
         }else{
-            this.mapLayer.once('loadDone',callback)
+            var f = ()=>{
+                callback()
+                this.mapLayer.removeEventListener('loadDone', f)
+            } 
+            this.mapLayer.addEventListener('loadDone', f)  
         }
         
     }
     
     addListener(images360){
-        images360.on('flyToPano',toPano=>{
+        images360.addEventListener('flyToPano',e=>{
+            let toPano = e.toPano
             if(toPano.dontMoveMap) return
             
             /* transitions.start(lerp.vector(this.view.position, toPano.pano.position.clone().setZ(cameraHeight), 
@@ -137,7 +142,7 @@ export class MapViewer extends ViewerBase{
             }), toPano.duration, null, 0, easing[toPano.easeName]  );  */ 
             let boundSize// = new THREE.Vector2(10,10)
             
-            this.moveTo(toPano.pano.position.clone().setZ(cameraHeight),  boundSize, toPano.duration, toPano.easeName) 
+            this.moveTo(toPano.pano.position.clone().setZ(Potree.config.map.cameraHeight),  boundSize, toPano.duration, toPano.easeName) 
         })
         
         
@@ -164,9 +169,9 @@ export class MapViewer extends ViewerBase{
         //this.camera.updateMatrixWorld()
          
         this.view = new View();
-        this.view.position.set(0,0,cameraHeight);
+        this.view.position.set(0,0,Potree.config.map.cameraHeight);
         this.view.lookAt(0,0,0)
-        this.view.limitBound = new THREE.Box3().copy(Potree.config.OrthoCameraLimit.posBound)
+        this.setViewLimit('standard')
             
         let viewport = new Viewport( this.view, this.camera, {
             left:0, bottom:0, width:1, height: 1, name:'mapViewport' 
@@ -207,7 +212,16 @@ export class MapViewer extends ViewerBase{
         viewer.setObjectLayers(this.scene, 'mapObjects' )
     }
     
-     
+    setViewLimit(state){//设置边界,当编辑空间模型等时要解禁
+        let setting = Potree.config.OrthoCameraLimit[state]
+        if(setting){
+            this.view.limitBound = new THREE.Box3().copy(setting.posBound)
+            this.camera.zoomLimit = $.extend({},setting.zoom);
+        }else{  
+            this.view.limitBound = null
+            this.camera.zoomLimit  = null
+        }
+    } 
     
     updateCursor(){
          
@@ -249,7 +263,7 @@ export class MapViewer extends ViewerBase{
             let mouseover = (e)=>{ 
                 if(!e.byMap){
                     pano.mapMarker.material = panoMarkerMats.selected
-                    if(!e.byMainView) pano.emit("hoverOn", {byMap:true})
+                    if(!e.byMainView) pano.dispatchEvent({type: "hoverOn", byMap:true})
                     this.needRender = true    
                 }
             }
@@ -257,7 +271,7 @@ export class MapViewer extends ViewerBase{
             let mouseleave = (e)=>{
                 if(!e.byMap){
                     pano.mapMarker.material = panoMarkerMats.default
-                    if(!e.byMainView) pano.emit("hoverOff", {byMap:true})
+                    if(!e.byMainView) pano.dispatchEvent({type: "hoverOff", byMap:true})
                     this.needRender = true
                 }
             }
@@ -266,8 +280,8 @@ export class MapViewer extends ViewerBase{
             pano.mapMarker.addEventListener('mouseover', mouseover);  
 			pano.mapMarker.addEventListener('mouseleave', mouseleave);
             
-            pano.on('hoverOn', mouseover)
-            pano.on('hoverOff', mouseleave)
+            pano.addEventListener('hoverOn', mouseover)
+            pano.addEventListener('hoverOff', mouseleave)
             
             let onclick = (e)=>{
                 viewer.images360.flyToPano(pano)
@@ -279,7 +293,9 @@ export class MapViewer extends ViewerBase{
                 this.needRender = true
                 
             })
-            
+            pano.addEventListener('rePos',(e)=>{
+                pano.mapMarker.position.copy(pano.position).setZ(0)
+            })
         })
         this.scene.add(panosGroup)
         panosGroup.position.z = panosHeight
@@ -294,7 +310,11 @@ export class MapViewer extends ViewerBase{
         
         //-------
         
-        this.fitPanosToViewport()
+        //this.fitPanosToViewport()
+
+
+        this.initFitView()
+
     }
     
     
@@ -309,14 +329,17 @@ export class MapViewer extends ViewerBase{
         const minDis = 20 //距离鼠标不能太远
 
         
-        var filterFuncs = []; 
-      
-        
-        filterFuncs.push((pano)=>{
-            return pano.position.clone().setZ(0).distanceTo(intersect) < minDis
+        var filterFuncs = [
+            (pano)=>{
+                return pano.position.clone().setZ(0).distanceTo(intersect) < minDis 
+            },
+            Images360.filters.isEnabled(),
             
-        });
+            Images360.filters.isVisible(),//只走显示的点,否则会走到别的层 
+        ]; 
+      
         
+         
     
         
         
@@ -337,44 +360,61 @@ export class MapViewer extends ViewerBase{
         var boundSize = viewer.images360.bound.size.clone().multiplyScalar(1.1);
         boundSize.max(new THREE.Vector3(4,4,4))
         let endPosition = viewer.images360.bound.center.clone()
-        this.moveTo(endPosition, boundSize, 0)
-        
-        
-        
+        this.moveTo(endPosition, boundSize, 0) 
+    }
+
+    fitToPointcloud(pointcloud, duration=400){
+        var boundSize = pointcloud.bound.getSize(new THREE.Vector3)/* .multiplyScalar(1.1); */
+            boundSize.max(new THREE.Vector3(4,4,4))
+        let endPosition = pointcloud.bound.getCenter(new THREE.Vector3)
+        this.moveTo(endPosition, boundSize, duration) //给点duration去变化 否则地图放大后所占的还是很小
+    }
+
+    initFitView(){ 
+        let dis = 5 , px = 70 //地图上px像素长度代表的距离为dis  //px是手动缩放到5m后发现是这个长度
+        let zoom = px / dis;
+        this.camera.zoom = zoom
+        this.moveTo(viewer.images360.bound.center) 
+        this.camera.updateProjectionMatrix()
+
     }
-    
-    
     
     moveTo(endPosition, boundSize, duration=0, easeName){//前两个参数有xy即可
-        endPosition = new THREE.Vector3(endPosition.x,endPosition.y,cameraHeight)
+        endPosition = new THREE.Vector3(endPosition.x,endPosition.y,Potree.config.map.cameraHeight)
         
         let endZoom, startZoom = this.camera.zoom 
         
         //修改相机为bound中心,这样能看到全部(宽度范围内)
         
-        this.view.setView(endPosition, null , duration,  ()=>{//done
-             
-        },(progress)=>{//onUpdate
-            if(boundSize){ 
-                let aspect = boundSize.x / boundSize.y
-                let w, h; 
-                
-                if(this.camera.aspect > aspect){//视野更宽则用bound的纵向来决定
-                    h = boundSize.y
-                    //w = h * this.camera.aspect
-                    endZoom = this.viewports[0].resolution.y / h
-                }else{
-                    w = boundSize.x; 
-                    //h = w / this.camera.aspect
-                    endZoom = this.viewports[0].resolution.x / w
-                }  
-                //onUpdate时更新endzoom是因为画布大小可能更改
-                
-                
-                this.camera.zoom = endZoom * progress + startZoom * (1 - progress)
-                this.camera.updateProjectionMatrix() 
-            } 
-        },easeName)
+        this.view.setView({ position:endPosition,  duration, 
+            callback:()=>{//done
+                 
+            },
+            onUpdate:(progress)=>{ 
+                if(boundSize){ 
+                    let aspect = boundSize.x / boundSize.y
+                    let w, h; 
+                    
+                    if(this.camera.aspect > aspect){//视野更宽则用bound的纵向来决定
+                        h = boundSize.y
+                        //w = h * this.camera.aspect
+                        endZoom = this.viewports[0].resolution.y / h
+                    }else{
+                        w = boundSize.x; 
+                        //h = w / this.camera.aspect
+                        endZoom = this.viewports[0].resolution.x / w
+                    }  
+                    //onUpdate时更新endzoom是因为画布大小可能更改
+                    
+                    
+                    this.camera.zoom = endZoom * progress + startZoom * (1 - progress)
+                    this.camera.updateProjectionMatrix() 
+                } 
+            },
+            
+            Easing:easeName
+        
+        })
           
             
     }
@@ -483,10 +523,10 @@ export class MapViewer extends ViewerBase{
         
         
         //mapRatio != 'dontSet' && !options.dontFit && this.moveTo(...)//要写在updateScreenSize后面,因为要根据视图大小来fit
-        if(options.moveToCurrentPano){
+        if(options.moveToCurrentPos){
             let boundSize = new THREE.Vector2(10,10)
             let duration = 1000
-            this.moveTo(viewer.images360.currentPano.position.clone(), boundSize, duration)
+            this.moveTo(viewer.images360.position.clone(), boundSize, duration)
         }    
         this.needRender = true
     }
@@ -566,7 +606,17 @@ export class MapViewer extends ViewerBase{
     }
     
     
-    
+    /* render(){
+        
+        let camera =  viewer.scene.getActiveCamera();
+
+        viewer.scene.view.position.x += 0.01    
+
+        viewer.setCameraLayers(camera, ['sceneObjects','marker','reticule','skybox' ]) 
+        this.renderer.render(viewer.scene.scene, camera);  
+
+
+    } */
     
     
     

+ 2 - 3
src/viewer/profile.js

@@ -3,8 +3,7 @@ import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Utils} from "../utils.js";
 import {Points} from "../Points.js";
 import {CSVExporter} from "../exporter/CSVExporter.js";
-import {LASExporter} from "../exporter/LASExporter.js";
-import { EventDispatcher } from "../EventDispatcher.js";
+import {LASExporter} from "../exporter/LASExporter.js"; 
 import {PointCloudTree} from "../PointCloudTree.js";
 import {Renderer} from "../PotreeRenderer.js";
 import {PointCloudMaterial} from "../materials/PointCloudMaterial.js";
@@ -229,7 +228,7 @@ class ProfileFakeOctree extends PointCloudTree{
 
 }
 
-export class ProfileWindow extends EventDispatcher {
+export class ProfileWindow extends THREE.EventDispatcher {
 	constructor (viewer) {
 		super();
 

+ 5 - 1
src/viewer/sidebar.html

@@ -310,7 +310,11 @@ Thanks to all the companies and institutions funding Potree:
         </div>
 
 
-
+        <h3 class="accordion-header ui-widget"><span>粒子</span></h3>
+        <div id="particle"  class="accordion-content ui-widget pv-menu-list">
+            <button name='addFire'>添加火烟</button> 
+            <button name='addExplode'>添加爆炸</button> 
+        </div>
 
 
 

+ 13 - 7
src/viewer/sidebar.js

@@ -61,6 +61,7 @@ export class Sidebar{
 		this.initAlignment();
         this.initClipModel();
         this.initSiteModel()
+        this.initParitcle()
 		$('#potree_version_number').html(Potree.version.major + "." + Potree.version.minor + Potree.version.suffix);
 	}
 
@@ -183,15 +184,20 @@ export class Sidebar{
         pannel.find('button[name="removeFirstMarker"] ').on('click', ()=>{
             //SiteModel.removeMarker(SiteModel.selected.markers[0])
             SiteModel.selected.removeMarker(0)
-        })
-        
-        
-        
-        
-        
+        }) 
         
     }
-    
+    initParitcle(){
+        let ParticleEditor = viewer.modules.ParticleEditor
+        var pannel = $('#particle');
+        pannel.find('button[name="addFire"] ').on('click', ()=>{ 
+            ParticleEditor.startInsertion('fire+smoke')
+        })
+        pannel.find('button[name="addExplode"] ').on('click', ()=>{ 
+            ParticleEditor.startInsertion('explode')
+        })
+  
+    }
 
 	initToolbar(){
 

+ 281 - 173
src/viewer/viewer.js

@@ -58,6 +58,7 @@ import math from "../utils/math.js";
 import {UoMService}  from '../utils/UnitConvert'
 import {RouteGuider}  from '../modules/route/RouteGuider'
 import ParticleEditor from '../modules/Particles/ParticleEditor'
+import CamAniEditor from '../modules/CameraAnimation/CamAniEditor'
 import {MeshDraw}  from '../utils/DrawUtil'
 
 
@@ -75,17 +76,18 @@ export class Viewer extends ViewerBase{
 		super(domElement, $.extend(args,{name:'mainViewer'}));
         window.viewer = this
         this.modules = { //add
-            Clip : Clip,
-            Alignment : Alignment,
-            SiteModel : SiteModel,
+            Clip,
+            Alignment,
+            SiteModel,
             RouteGuider : new RouteGuider,
             ParticleEditor,
+            CamAniEditor
         }
         
         this.testingMaxLevel = true
         
          
-        
+        console.log('create viewer')
         
         this.navigateMode = 'free' // 'panorama'; 'free'自由模式是只显示点云或者未进入到漫游点, 
         this.isEdit = true
@@ -117,15 +119,8 @@ export class Viewer extends ViewerBase{
          
         document.addEventListener('visibilitychange',(e)=>{ 
             //console.log('visibilitychange', !document.hidden )
-            this.emit('pageVisible', !document.hidden )
-            /* if(document.hidden){
-                this.paused = true
-            }else{
-                setTimeout(()=>{
-                    if(!document.hidden) this.paused = false
-                },1000)
-            } */
-            
+            this.dispatchEvent({type:'pageVisible', v:!document.hidden} )
+              
         })
         
          
@@ -288,7 +283,7 @@ export class Viewer extends ViewerBase{
                     let gl = this.renderer.getContext();
                     let error = gl.getError();
                     console.log(error);
-                    this.emit('webglError', 'webglcontextlost') 
+                    this.dispatchEvent({type:'webglError', msg:'webglcontextlost'}) 
                 }, false);
             }
 
@@ -514,7 +509,7 @@ export class Viewer extends ViewerBase{
                 }
             })
              
-            this.on('updateNodeMaxLevel',(pointcloud,nodeMaxLevel)=>{
+            this.addEventListener('updateNodeMaxLevel',(e)=>{
                 
                 if(!viewer.testNodeLevelTimer && viewer.testingMaxLevel  ){
                     viewer.testNodeLevelTimer = setTimeout(()=>{//先加载一段时间最高level的点云。但希望不会刚好附近的点云都没有达到最高的level,否则就要走一段才能了。
@@ -525,8 +520,8 @@ export class Viewer extends ViewerBase{
                     },3000) 
                     viewer.beginTestTime = Date.now()
                 }
-                console.log('updateNodeMaxLevel ' +  pointcloud.dataset_id + " : "+ nodeMaxLevel)                
-                if(nodeMaxLevel >= 10 && viewer.testingMaxLevel){//10的时候差不多能加载到11和12了。假设最高只有12的话,就到10就可以。不过大多数场景都到不了10,也不知有没有大于10的,如果没有,这里可以写5.
+                console.log('updateNodeMaxLevel ' +  e.pointcloud.dataset_id + " : "+ e.nodeMaxLevel)                
+                if(e.nodeMaxLevel >= 10 && viewer.testingMaxLevel){//10的时候差不多能加载到11和12了。假设最高只有12的话,就到10就可以。不过大多数场景都到不了10,也不知有没有大于10的,如果没有,这里可以写5. 见过最小是1
                     viewer.testingMaxLevel = false
                     console.log('提前结束testingMaxLevel,用时:'+(Date.now()-viewer.beginTestTime))
                     //我的电脑用时大概1500
@@ -536,7 +531,7 @@ export class Viewer extends ViewerBase{
                 
                 
                 if(!Potree.settings.sizeFitToLevel){
-                    pointcloud.changePointSize()
+                    e.pointcloud.changePointSize()
                     
                 }
                 
@@ -2448,19 +2443,18 @@ export class Viewer extends ViewerBase{
         if(!this.visible   || this.paused  )return
         
 		let pRenderer = this.getPRenderer();
+        let viewports = params_.viewports || this.viewports
         
         let renderSize
         if(params_.target){
-            renderSize =  new THREE.Vector2(params_.target.width, params_.target.height)
-            if(!params_.viewports){
-                console.warn('必须指定target的viewport! 且target大小和viewport.resolution2相同')
-            }
+            renderSize =  new THREE.Vector2(params_.target.width, params_.target.height) //是画布大小
+            //可能需要viewer.setSize
         }else{
-            renderSize = this.renderer.getSize(new THREE.Vector2());
+            renderSize = this.renderer.getSize(new THREE.Vector2()); //是client大小
         }
            
         
-        var viewports = params_.viewports || this.viewports
+        
         let needSResize = viewports.filter(e=>e.active).length > 1 || params_.resize  
         
         
@@ -2477,16 +2471,16 @@ export class Viewer extends ViewerBase{
             if(!view.active)return
             var left,bottom,width,height
             { 
-                left = Math.floor(renderSize.x * view.left)
-                bottom = Math.floor(renderSize.y * view.bottom)
+                left = Math.ceil(renderSize.x * view.left)
+                bottom = Math.ceil(renderSize.y * view.bottom)
                 
-                /* if(params_.target){//有target时最好viewport是专门建出来的
-                    width = Math.floor(renderSize.x * view.width)
-                    height = Math.floor(renderSize.y * view.height)
-                }else{ */
+                if(params_.target){//有target时最好viewport是专门建出来的
+                    width = Math.ceil(renderSize.x * view.width)  //target的大小可能和viewport不同,比如截图,这时会更改viewport大小
+                    height = Math.ceil(renderSize.y * view.height)
+                }else{ 
                     width = view.resolution.x // 用的是client的width和height
                     height = view.resolution.y
-                //}
+                }
                 if(width == 0 || height == 0)return
                 
                 let scissorTest = view.width<1 || view.height<1
@@ -2535,7 +2529,7 @@ export class Viewer extends ViewerBase{
                 
                 
                 
-                view.beforeRender && view.beforeRender(view)
+                view.beforeRender && view.beforeRender()
                 
                 this.updateViewPointcloud(params.camera, view.resolution2, true)
                 
@@ -2555,7 +2549,7 @@ export class Viewer extends ViewerBase{
                 
                 this.renderOverlay(params) 
                 
-                view.afterRender && view.afterRender(view)
+                view.afterRender && view.afterRender()
             } 
             
              
@@ -2568,17 +2562,22 @@ export class Viewer extends ViewerBase{
         
 	}
 	
-    setLimitFar(state){//切换是否limitFar
-        viewer.mainViewport.camera.limitFar = !!state
-        if(state){
-            viewer.mainViewport.camera.near = 0.1;
-            viewer.mainViewport.camera.far = Potree.settings.displayMode == 'showPanos' ? viewer.farWhenShowPano : Potree.settings.cameraFar;
-            viewer.mainViewport.camera.updateProjectionMatrix()
-        }
-    }
-    
+    /* renderDefault(){//测试 ios15.4.1
+        //let pRenderer = this.getPRenderer();
+        //this.clear()
+        this.renderer.autoClear = false
+        this.renderer.setRenderTarget(null)  
+        let camera =  this.scene.getActiveCamera();
+        this.setCameraLayers(camera, [     'sceneObjects',  'marker' ,   'reticule'    ,'skybox'    ]) 
+        this.renderer.render(this.scene.scene, camera);  
+        // pRenderer.clearTargets( );
+        //pRenderer.render( );   
+     
+    }  */
+
+
     renderOverlay(params){
-        
+         
         let camera = params.camera ? params.camera : this.scene.getActiveCamera();
         
         this.reticule.updateAtViewports(params.viewport)
@@ -2618,15 +2617,22 @@ export class Viewer extends ViewerBase{
             }
         } 
         
-         
-        this.setCameraLayers(camera, ['volume','transformationTool'])  
-        this.renderer.render(this.clippingTool.sceneVolume, camera);
-        this.renderer.render(this.transformationTool.scene, camera);
-          
+        if(!params.isMap) {
+            this.setCameraLayers(camera, ['volume','transformationTool'])  
+            this.renderer.render(this.clippingTool.sceneVolume, camera);
+            this.renderer.render(this.transformationTool.scene, camera);
+        }
         
     }
      
-    
+    setLimitFar(state){//切换是否limitFar
+        viewer.mainViewport.camera.limitFar = !!state
+        if(state){
+            viewer.mainViewport.camera.near = 0.1;
+            viewer.mainViewport.camera.far = Potree.settings.displayMode == 'showPanos' ? viewer.farWhenShowPano : Potree.settings.cameraFar;
+            viewer.mainViewport.camera.updateProjectionMatrix()
+        }
+    }
     
     
     setCameraLayers(camera, enableLayers, extraEnableLayers=[]){//add
@@ -2652,16 +2658,28 @@ export class Viewer extends ViewerBase{
     }
     
      
-    updateVisible(object, reason, ifShow){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的
+    updateVisible(object, reason, ifShow, force){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的
         if(!object.unvisibleReasons) object.unvisibleReasons = []; //如果length>0代表不可见
+        if(!object.forceVisibleReasons) object.forceVisibleReasons = []; //只要有一项代表一定可见,优先级比unvisibleReasons高
+        
         /* let mapChange = ()=>{//还是算了,有时候可见性的改变 在mapViewer和mainViewer中交替,如reticule,就会频繁
             var layers = ['measure','map','mapObjects','bothMapAndScene'] 
             if(layers.some(e=> object.layers && (object.layers.mask == Potree.config.renderLayers[e])  )) { 
                 this.mapViewer.dispatchEvent({type:'content_changed'})
             }
         } */
-        
+             
         if(ifShow){
+            if(force){
+                object.forceVisibleReasons.includes(reason) || object.forceVisibleReasons.push(reason)
+                object.visible = true; 
+                object.dispatchEvent({
+                    type: 'isVisible',
+                    visible:true, 
+                    reason
+                })
+            }
+
             var index = object.unvisibleReasons.indexOf(reason) 
             if(index > -1){
                 object.unvisibleReasons.splice(index, 1);
@@ -2676,19 +2694,36 @@ export class Viewer extends ViewerBase{
                 }
             }
             
-        }else{
+        }else{ 
+
+            var index = object.forceVisibleReasons.indexOf(reason) 
+            if(index > -1){//如果是forceVisibleReasons里的,就只是单纯取消之前设置的一定可见
+                object.forceVisibleReasons.splice(index, 1);
+            }else{
+                if(!object.unvisibleReasons.includes(reason)) object.unvisibleReasons.push(reason)  
+            } 
+            if(object.forceVisibleReasons.length) return
+
             var visiBefore = object.visible
-            if(!object.unvisibleReasons.includes(reason)) object.unvisibleReasons.push(reason)
-            object.visible = false 
-            if(visiBefore) { 
+
+            if(object.unvisibleReasons.length && visiBefore){
+                object.visible = false 
                 //mapChange()        
                 object.dispatchEvent({
                     type: 'isVisible',
                     visible:false,
                     reason,
-                }) 
-            }            
-            
+                })
+
+            }else if(object.unvisibleReasons.length ==0 && !visiBefore){
+                object.visible = true;
+                //mapChange()
+                object.dispatchEvent({
+                    type: 'isVisible',
+                    visible:true, 
+                    reason
+                })
+            }  
         }
         
     } 
@@ -2737,10 +2772,17 @@ export class Viewer extends ViewerBase{
 
     startScreenshot(info={},  width=800, height=400, compressRatio){//add
         let deferred = info.deferred || $.Deferred();
+        let viewerMaster = info.map ? this.mapViewer : this; //截图主体
+        let useMap = info.type == 'measure' || info.map
+        
         
         if(this.images360.flying){//如果在飞,飞完再截图
-            info.deferred = deferred
-            this.images360.once('cameraMoveDone', this.startScreenshot.bind(this,  info,  width, height, compressRatio))
+            info.deferred = deferred 
+            let f = ()=>{
+                this.startScreenshot(info,  width, height, compressRatio)
+                this.images360.removeEventListener('cameraMoveDone', f)
+            } 
+            this.images360.addEventListener('cameraMoveDone', f) //once 
             return deferred.promise()
         }
         
@@ -2753,10 +2795,11 @@ export class Viewer extends ViewerBase{
         
         var screenshot = ()=>{
             
-            viewer.mapViewer.needRender = true 
+            useMap && (viewer.mapViewer.needRender = true)
             
             
-            var { dataUrl  } = this.makeScreenshot( new THREE.Vector2(width,height), null, compressRatio    );
+            
+            var { dataUrl  } = viewerMaster.makeScreenshot( new THREE.Vector2(width,height), null, compressRatio    );
         
              
             
@@ -2781,7 +2824,7 @@ export class Viewer extends ViewerBase{
                     viewer.updateVisible(pano, 'screenshot', true)
                 })    
                 viewer.updateVisible(this.reticule, 'screenshot', true)
-                viewer.updateVisible(this.mapViewer.cursor, 'screenshot', true)
+                useMap && viewer.updateVisible(this.mapViewer.cursor, 'screenshot', true)
             
                 if(oldStates.attachedToViewer != this.mapViewer.attachedToViewer){
                     if(info.type == 'measure'){
@@ -2840,11 +2883,11 @@ export class Viewer extends ViewerBase{
          
         
         
-        
-        this.images360.panos.forEach(pano=>{//令漫游点不可见
-            viewer.updateVisible(pano, 'screenshot', false)
-        }) 
-                              
+        if(info.hideMarkers){
+            this.images360.panos.forEach(pano=>{//令漫游点不可见
+                viewer.updateVisible(pano, 'screenshot', false)
+            }) 
+        }                    
         viewer.updateVisible(this.reticule, 'screenshot', false)//令reticule不可见 
                                
         viewer.updateVisible(this.mapViewer.cursor, 'screenshot', false)//令mapCursor不可见
@@ -2859,7 +2902,7 @@ export class Viewer extends ViewerBase{
             viewer.updateScreenSize({forceUpdateSize:true, width, height}) //更新viewports相机透视
             
             //不同角度截图 得到三维的会不一样,因为focusOnObject是根据方向的
-            let promise = this.focusOnObject(info.measurement, 'measure', 0,    /* {basePanoSize:1024} */ )
+            let {promise}= this.focusOnObject(info.measurement, 'measure', 0,     {basePanoSize:1024}  )
             promise.done(()=>{ 
                 console.log('promise.done') 
                 this.viewports.forEach(e=>{
@@ -2903,8 +2946,10 @@ export class Viewer extends ViewerBase{
             screenshot()
         }            
          
-         
-        
+         /*
+            测量线的截图因为要调用分屏的,会改变画面
+            但是普通截图的话,不会改变画面
+         */
         
         return deferred.promise()
         
@@ -2923,77 +2968,33 @@ export class Viewer extends ViewerBase{
             dis;                          //相机距离目标
         duration = duration == void 0 ? 1000 : duration;     
         let camera = viewer.scene.getActiveCamera()
+        let cameraPos = camera.position.clone()
+         
         
         
-        if(this.images360.modeChanging){
-            this.images360.once('endChangeMode',()=>{
-                this.focusOnObject(object, type, duration, $.extend(o,{deferred}))
-            })
-            return deferred.promise();//不能打扰 从点云转向全景图时 的飞行
-        }
-        if (type == 'measure') { 
-
-        
-            
-                
-            target.copy(object.getCenter()) 
-       
-            
+        let getPosWithFullBound = (points, boundingBox, target, cameraPos  )=>{//使boundingBox差不多占满屏幕时的相机到target的距离
+            // points 和 boundingBox 至少有一个
+             
             var cameraTemp = camera.clone()
-            
-             //试试改变位置,直视测量线。能避免倾斜角度造成的非常不居中、以及看不到面的情况 
-            if(object.facePlane/*  && window.focusMeasureFaceToIt */){
-                let normal
-                if(object.facePlane){
-                    normal = object.facePlane.normal.clone()
-                }               
-                let angle = this.scene.view.direction.angleTo(normal)
-                let minDiff = THREE.Math.degToRad(60) 
-                if(angle>minDiff && angle<Math.PI-minDiff){//当几乎正对时就不执行
-                    if(angle<Math.PI/2){ //在背面
-                        normal.negate()
-                    }
-                    let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() 
-                    let newDir = new THREE.Vector3().addVectors(dir,normal)//两个角度的中间
-                    cameraTemp.position.copy(target.clone().add(newDir))
-                }   
-            }else if(object.points.length == 2){ //线段
-                let lineDir = new THREE.Vector3().subVectors(object.points[0],object.points[1]).normalize()
-                let angle = this.scene.view.direction.angleTo(lineDir)
-                let maxDiff = Math.PI*0.25// 45度
-                if(angle<maxDiff || angle>Math.PI-maxDiff){//当几乎正对时就不执行
-                    if(angle>Math.PI/2){  //令dir和lineDir成钝角
-                        lineDir.negate()
-                    } 
-                    let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() 
-                    let mid = new THREE.Vector3().addVectors(lineDir, dir).normalize() //中间法向量(如果刚好dir和lineDir反向,那得到的为零向量,就不移动了,但一般不会酱紫吧)
-                    let newDir = new THREE.Vector3().addVectors(dir, mid)
-                    cameraTemp.position.copy(target.clone().add(newDir))
-                } 
-            }else{
-                console.error('measure 没有facePlane points点数还不为2?')
-            } 
-            
-            
+            cameraTemp.position.copy(cameraPos)  
             cameraTemp.lookAt(target);
             cameraTemp.updateMatrix();
             cameraTemp.updateMatrixWorld();
-            //原始的bound
-            let boundOri = new THREE.Box3() 
-            object.points.forEach(e=>{ 
-                boundOri.expandByPoint(e)
-            })
-            let boundSizeOri = boundOri.getSize(new THREE.Vector3)
-            
             //对镜头的bound
             var inv = cameraTemp.matrixWorldInverse;
-           
-            let bound = new THREE.Box3()  
-            object.points.forEach(e=>{
-                var p = e.clone().applyMatrix4(inv);
-                bound.expandByPoint(p)
-            })
+            var bound = new THREE.Box3()  
+            if(points){//使用points得到的bound更小  //如果points和boundingbox的差别较大,尤其使target和points中心不一致,那么points不一定会刚好在boundingbox内
+                points.forEach(e=>{
+                    var p = e.clone().applyMatrix4(inv);
+                    bound.expandByPoint(p)
+                }) 
+            }else{
+                bound = boundingBox.applyMatrix4(inv);
+            }
             let boundSize = bound.getSize(new THREE.Vector3)
+            
+            
+            
              
             
             if(!this.boundBox){//调试
@@ -3008,7 +3009,7 @@ export class Viewer extends ViewerBase{
            
             this.boundBox.position.copy(target)
             this.boundBox.scale.copy(boundSize)
-            this.boundBox.lookAt(cameraTemp.position)
+            this.boundBox.lookAt(cameraPos)
             
             
             {
@@ -3018,8 +3019,6 @@ export class Viewer extends ViewerBase{
                 
             }
             
-            
-            
             let aspect = boundSize.x / boundSize.y
             if(camera.aspect > aspect){//视野更宽则用bound的纵向来决定
                 dis = boundSize.y/2/ Math.tan(THREE.Math.degToRad(camera.fov / 2)) + boundSize.z/2 
@@ -3027,13 +3026,79 @@ export class Viewer extends ViewerBase{
                 let hfov = cameraLight.getHFOVForCamera(camera, true);
                 dis = boundSize.x/2 / Math.tan(hfov / 2) + boundSize.z/2
             }
+            dis = Math.max(0.1,dis)
             
             //三个顶点以上的由于measure的中心不等于bound的中心,所以点会超出bound外。 且由于视椎近大远小,即使是两个点的,bound居中后线看上去仍旧不居中.
+             
+            //获得相机最佳位置
+            let dir = new THREE.Vector3().subVectors(cameraPos, target).normalize()
+            position.copy(target).add(dir.multiplyScalar(dis)) 
+            return position
+        } 
+        
+        
+        if(this.images360.flying){
+            let f = ()=>{
+                this.focusOnObject(object, type, duration, $.extend(o,{deferred}))
+                this.images360.removeEventListener('cameraMoveDone',f)
+            }
+            this.images360.addEventListener('cameraMoveDone',f) 
+            return {promise: deferred.promise() }();  
+        }
+        if (type == 'measure') {  
+            target.copy(object.getCenter()) 
+       
             
             
+            
+             //试试改变位置,直视测量线。能避免倾斜角度造成的非常不居中、以及看不到面的情况 
+            if(object.facePlane/*  && window.focusMeasureFaceToIt */){
+                let normal
+                if(object.facePlane){
+                    normal = object.facePlane.normal.clone()
+                }               
+                let angle = this.scene.view.direction.angleTo(normal)
+                let minDiff = THREE.Math.degToRad(60) 
+                console.log('angle',angle)
+                if(angle>minDiff && angle<Math.PI-minDiff){//当几乎正对时就不执行
+                    if(angle<Math.PI/2){ //在背面
+                        normal.negate()
+                    }
+                    let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() 
+                    let newDir = new THREE.Vector3().addVectors(dir,normal)//两个角度的中间
+                    cameraPos.copy(target.clone().add(newDir))
+                }   
+            }else if(object.points.length == 2){ //线段
+                let lineDir = new THREE.Vector3().subVectors(object.points[0],object.points[1]).normalize()
+                let angle = this.scene.view.direction.angleTo(lineDir)
+                let maxDiff = Math.PI*0.25// 45度
+                if(angle<maxDiff || angle>Math.PI-maxDiff){//当几乎正对时就不执行
+                    if(angle>Math.PI/2){  //令dir和lineDir成钝角
+                        lineDir.negate()
+                    } 
+                    let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() 
+                    let mid = new THREE.Vector3().addVectors(lineDir, dir).normalize() //中间法向量(如果刚好dir和lineDir反向,那得到的为零向量,就不移动了,但一般不会酱紫吧)
+                    let newDir = new THREE.Vector3().addVectors(dir, mid)
+                    cameraPos.copy(target.clone().add(newDir))
+                } 
+            }else{
+                console.error('measure 没有facePlane points点数还不为2?')
+            }  
+            
+            position = getPosWithFullBound(object.points, null, target, cameraPos  )
+             
+            
             if(this.mapViewer.attachedToViewer){ 
                 //console.log('mapFocusOn: '+target.toArray())
                 const minBound = new THREE.Vector2(1,1)//针对垂直线,在地图上只有一个点
+                //原始的bound
+                let boundOri = new THREE.Box3() 
+                object.points.forEach(e=>{ 
+                    boundOri.expandByPoint(e)
+                })
+                let boundSizeOri = boundOri.getSize(new THREE.Vector3)
+                
+                
                 let boundSizeMap = boundSizeOri.clone().multiplyScalar(2)
                 boundSizeMap.x = Math.max(minBound.x, boundSizeMap.x )
                 boundSizeMap.y = Math.max(minBound.y, boundSizeMap.y )
@@ -3041,9 +3106,7 @@ export class Viewer extends ViewerBase{
             }
             
             
-            //获得相机最佳位置
-            let dir = new THREE.Vector3().subVectors(cameraTemp.position, target).normalize()
-            position.copy(target).add(dir.multiplyScalar(dis))
+            
             
             if(Potree.settings.displayMode == 'showPointCloud'){  //点云 
                 
@@ -3053,17 +3116,23 @@ export class Viewer extends ViewerBase{
                     /*point : target,  //不使用目标点来判断是因为缺少measure角度的信息。比如虽然可以靠近线的中心,但是线朝向屏幕,那几乎就是一个点了。
                     //bestDistance : dis * 0.5, //乘以小数是为了尽量靠近 
                     boundSphere: boundOri.getBoundingSphere(new THREE.Sphere), */
-                    
+                    target, 
                     point : position,
                     bestDistance : 0 , 
                 })
-                
-                pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true  , basePanoSize:o.basePanoSize})//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了
-                
-                if(!pano){
-                    console.error('no pano')
+                if(pano){
+                    viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true  , basePanoSize:o.basePanoSize})//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了
+                }
+                if(viewer.images360.currentPano == pano){ 
+                    let dis1 = viewer.images360.currentPano.position.distanceTo(target)
+                    let dis2 = position.distanceTo(target)
+                    console.log('dis1 / dis2',dis1 / dis2, 'dis1-dis2', dis1-dis2)
+                    return {mag: (dis1 / dis2 > 1.5 && dis1-dis2>10)? 'tooFar' : 'posNoChange',  promise : deferred.promise()  }
+                  
+                }else{
+                    return {promise : deferred.promise()}
                 }
-                return deferred
+                  
                 //出现过到达位置后测量线标签闪烁的情况
             }
             
@@ -3072,7 +3141,7 @@ export class Viewer extends ViewerBase{
             target.copy(object.position)
             let bestDistance = o.distance || 2 
             
-            { 
+            if(!o.dontMoveMap){ 
                 //console.log('mapFocusOn: '+target.toArray()) 
                 this.mapViewer.moveTo(target.clone(), null, duration)
             }
@@ -3088,8 +3157,26 @@ export class Viewer extends ViewerBase{
                     bestDistance  //越近越好,但不要太近,bestDistance左右差不多
                 })
                 pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize })
-                return deferred                
+                return {promise:deferred.promise() }               
             }
+        }else if(object.boundingBox && type == 'boundingBox'){//使屏幕刚好看全boundingBox
+            target = object.boundingBox.getCenter(new THREE.Vector3)
+            position = getPosWithFullBound(object.points, object.boundingBox, target, cameraPos  )
+            if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准)
+                let pano = viewer.images360.fitPanoTowardPoint({ 
+                    point : position,
+                    bestDistance : 0 , 
+                })
+                
+                pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true  , basePanoSize:o.basePanoSize})//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了
+                
+                if(!pano){
+                    console.error('no pano')
+                }
+                return {promise:deferred.promise() }
+                //出现过到达位置后测量线标签闪烁的情况
+            }
+            
         }
 
 
@@ -3105,14 +3192,15 @@ export class Viewer extends ViewerBase{
             
         } */
 
-        viewer.scene.view.setView(position, target, duration, ()=>{
-            console.log('focusOnObjectSuccess: '+object.name,  type)
-            deferred.resolve()
+        viewer.scene.view.setView({position, target, duration, callback:()=>{
+                console.log('focusOnObjectSuccess: '+object.name,  type)
+                deferred.resolve()
+            }
         })
          
         
         
-        return deferred.promise()
+        return {promise:deferred.promise()}
     }
     
 
@@ -3124,19 +3212,39 @@ export class Viewer extends ViewerBase{
         else if(o.pointcloud) pointcloud = o.pointcloud
         else pointcloud = this.scene.pointclouds.find(p => p.dataset_id == o.id);
          
+        let duration = o.duration == void 0 ? 1000 : o.duration
         var center = pointcloud.bound.getCenter(new THREE.Vector3);
+        let position
         
-        let request = []
-        let rank = [
-            Images360.scoreFunctions.distanceSquared({position: center}) 
-        ]
-        let r = Common.sortByScore(pointcloud.panos, request, rank);
         
-        if(r && r.length){
-            this.images360.flyToPano({
-                pano:r[0].item
-            })
-        }
+        if(Potree.settings.displayMode == 'showPanos'){
+            let request = []
+            let rank = [
+                Images360.scoreFunctions.distanceSquared({position: center}) 
+            ]
+            let r = Common.sortByScore(pointcloud.panos, request, rank);
+            
+            if(r && r.length){
+                if(r[0].item == this.images360.currentPano) return 'posNoChange'
+                this.images360.flyToPano({
+                    pano:r[0].item
+                })
+            }else return false
+        }else{ 
+            
+            if(this.controls == this.orbitControls){ 
+                let dis = 2
+                let target = center
+                position = new THREE.Vector3().subVectors(target, this.scene.view.direction)
+                viewer.scene.view.setView({position,  duration,  target})
+            }else{
+                if(math.closeTo(center, this.images360.position)) return 'posNoChange'
+                position = center
+                viewer.scene.view.setView({position,  duration })
+            }  
+        }            
+        
+        return true
         
         
     }
@@ -3343,7 +3451,7 @@ export class Viewer extends ViewerBase{
 
     updateModelBound(){
         this.bound = Utils.computePointcloudsBound(this.scene.pointclouds) 
-        viewer.farWhenShowPano = this.bound.boundSize.length() * 2//全景漫游时要能看到整个skybox
+        viewer.farWhenShowPano = this.bound.boundSize.length() * 10//全景漫游时要能看到整个skybox 原本*2的但对于距离特远的数据集需要乘大一些否则会黑面
         
         
         let boundPlane = new THREE.Box3()
@@ -3365,7 +3473,7 @@ export class Viewer extends ViewerBase{
             object,
             isLoadedCallback,
         }) 
-        1 === this.waitQueue.length && this.emit("loading", true)
+        1 === this.waitQueue.length && this.dispatchEvent({type:"loading", show:true})
     }
     ifAllLoaded(object){
         if(this.waitQueue.length>0){
@@ -3374,7 +3482,7 @@ export class Viewer extends ViewerBase{
             })  
         }
        
-        0 === this.waitQueue.length && this.emit("loading", false) 
+        0 === this.waitQueue.length && this.dispatchEvent({type:"loading", show:false}) 
     } 
     
     
@@ -3389,7 +3497,7 @@ export class Viewer extends ViewerBase{
         if(o.pano != void 0){//pano 权重高于 position
             this.images360.flyToPano(o)
         }else{
-            this.scene.view.setView(o.position, o.target, o.duration, callback)
+            this.scene.view.setView($.extend({},o, {callback}))
         }  
     }
     
@@ -3397,7 +3505,7 @@ export class Viewer extends ViewerBase{
      
     //设置点云为标准模式 
     setPointStandardMat(state, pointDensity){
-        console.log('setPointStandardMat',state)
+        //console.log('setPointStandardMat',state)
         if(state){
             if(this.pointStatesBefore){
                 return console.error('已设置过pointStatesBefore!')
@@ -3604,8 +3712,8 @@ export class Viewer extends ViewerBase{
             viewer.modules.ParticleEditor.addParticle( {
                  type:'fire',
                  positions:[position],
-                 fireRadius:0.42, 
-                 fireHeight:10,
+                 radius:0.42, 
+                 height:10,
             })
              
             viewer.modules.ParticleEditor.addParticle( {
@@ -3620,7 +3728,7 @@ export class Viewer extends ViewerBase{
                  velocitySpread   : new THREE.Vector3( 0.2, 0.2, -0.3), 
                  accelerationBase : 0.2,
                  accelerationSpread : 0.7,	
-                 
+                 radius:0,
                  //particlesPerSecond : 30,
                  particleDeathAge   : 3.0,                         
             })
@@ -3635,11 +3743,11 @@ export class Viewer extends ViewerBase{
                 opacityTween:  [[0, 0.05, 0.3, 0.45], [1, 1, 0.5, 0]] , 
                 speed : 1,            //sphere
                 speedRange : 4,
-                positionRadius: 0.1,
+                radius: 0.1,
                 acceleration : 0.3,         
                 accelerationRange : 1,
-                
-                particlesPerSecond:40,
+                particleSpaceTime:0,
+                strength:4,
             })
         }
     }

+ 105 - 46
src/viewer/viewerBase.js

@@ -1,11 +1,10 @@
 
-import * as THREE from "../../libs/three.js/build/three.module.js";
-import { EventDispatcher } from "../EventDispatcher.js";
-
+import * as THREE from "../../libs/three.js/build/three.module.js"; 
 
+import Common from '../utils/Common'
  
 
-export class ViewerBase extends EventDispatcher{
+export class ViewerBase extends THREE.EventDispatcher{
     constructor(domElement, args = {}){
         super()
         this.name = args.name
@@ -13,7 +12,7 @@ export class ViewerBase extends EventDispatcher{
         this.oldResolution = new THREE.Vector2()
         
         this.screenSizeInfo = {
-            W:0, H:0, pixelRatio:1 
+            W:0, H:0, pixelRatio:1 , windowWidth:0, windowHeight:0
         }
         
         this.initContext(args);
@@ -40,21 +39,10 @@ export class ViewerBase extends EventDispatcher{
             alpha: true,
             depth: true,
             stencil: false,
-            antialias: true,
-            //premultipliedAlpha: _premultipliedAlpha,
+            antialias: true, 
             preserveDrawingBuffer: true,
             powerPreference: "high-performance",
-        };
-
-        // let contextAttributes = {
-        // 	alpha: false,
-        // 	preserveDrawingBuffer: true,
-        // };
-
-        // let contextAttributes = {
-        // 	alpha: false,
-        // 	preserveDrawingBuffer: true,
-        // };
+        }; 
 
         let canvas = document.createElement("canvas");
 
@@ -62,15 +50,17 @@ export class ViewerBase extends EventDispatcher{
 
         this.renderer = new THREE.WebGLRenderer({
             alpha: true, //支持透明
-            premultipliedAlpha: false,
+            premultipliedAlpha: false, 
             canvas: canvas,
-            context: context,
-            
+            context: context, 
         }); 
         
         this.renderer.sortObjects = true; //原先false 打开了renderOrder才奏效
         //this.renderer.setSize(width, height);
-        this.renderer.autoClear = args.autoClear;
+        this.renderer.autoClear = args.autoClear || false;
+
+
+        //args.clearColor = args.clearColor || '#aa0033'
         args.clearColor && this.renderer.setClearColor(args.clearColor)
         this.renderArea.appendChild(this.renderer.domElement);
         this.renderer.domElement.tabIndex = '2222';
@@ -84,12 +74,7 @@ export class ViewerBase extends EventDispatcher{
         // enable frag_depth extension for the interpolation shader, if available
         let gl = this.renderer.getContext();
         
-        
-        
-            
-      
-        
-        
+         
         gl.getExtension('EXT_frag_depth');
         gl.getExtension('WEBGL_depth_texture');
         gl.getExtension('WEBGL_color_buffer_float'); 	// Enable explicitly for more portability, EXT_color_buffer_float is the proper name in WebGL 2
@@ -104,9 +89,13 @@ export class ViewerBase extends EventDispatcher{
             gl.createVertexArray = extVAO.createVertexArrayOES.bind(extVAO);
             gl.bindVertexArray = extVAO.bindVertexArrayOES.bind(extVAO);
         }
-        
-        
-       
+
+ 
+       /*  let oldClear = gl.clear; 
+        gl.clear = (bits)=>{
+             console.error('clear')
+        }   
+ */
         
     }
     
@@ -122,9 +111,12 @@ export class ViewerBase extends EventDispatcher{
             h = o.height
             render = true
             ratio = 1
-        }else {
+        }else { 
+
             w = this.renderArea.clientWidth;
             h = this.renderArea.clientHeight
+            
+
              
             if(w !== this.screenSizeInfo.W || h !== this.screenSizeInfo.H || o.forceUpdateSize || this.screenSizeInfo.pixelRatio != window.devicePixelRatio){
                 this.screenSizeInfo.W = w 
@@ -135,17 +127,80 @@ export class ViewerBase extends EventDispatcher{
                 ratio = window.devicePixelRatio
             }     
         }
-        if (render) {
+        if (render) { 
             this.setSize(w, h, ratio); 
         } 
-    } 
+    }   
+
+
+    /* updateScreenSize(o={}) { //容易出错。。
+           
+        var render = false, ratio, w, h;
+        //记录应当render的大小
+        if (o.width != void 0 && o.height != void 0) {
+            w = o.width
+            h = o.height
+            render = true
+            ratio = 1
+        }else { 
+            w = this.renderArea.clientWidth;
+            h = this.renderArea.clientHeight 
+
+            let refreshWin = ()=>{
+                this.screenSizeInfo.windowWidth = window.innerWidth
+                this.screenSizeInfo.windowHeight = window.innerHeight
+            }
+ 
+            let prepareToRender = ()=>{
+                w = this.renderArea.clientWidth;
+                h = this.renderArea.clientHeight
+                this.screenSizeInfo.W = w 
+                this.screenSizeInfo.H = h 
+                refreshWin() //render后才refreshWin
+                render = true   
+                this.screenSizeInfo.pixelRatio = window.devicePixelRatio  //如果player放在小窗口了,也要监测devicePixelRatio,因为缩放时client宽高不会改变
+                //config.isMobile ? (ratio = Math.min(window.devicePixelRatio, 2)) : (ratio = window.devicePixelRatio)
+                ratio = window.devicePixelRatio
+                //console.log('setSize11', w) 
+                o.forceUpdateSize = false ;//防止再次render
+            }
+
+            let ifNeedUpdate = ()=>{
+                w = this.renderArea.clientWidth;
+                h = this.renderArea.clientHeight
+                return o.forceUpdateSize || w !== this.screenSizeInfo.W || h !== this.screenSizeInfo.H || this.screenSizeInfo.pixelRatio != window.devicePixelRatio
+            }
+            
+            if(ifNeedUpdate()){
+                //当只有一个有效viewport时,且因为改变整个窗口大小而触发的话,延时
+                let canInterval = !o.forceUpdateSize && this.viewports.filter(e=>e.active).length==1 && (this.screenSizeInfo.windowWidth != window.innerWidth || this.screenSizeInfo.windowHeight != window.innerHeight  ) 
+                  
+                if(canInterval){
+                    Common.intervalTool.isWaiting('updateScreenSize', ()=>{ //延时update,防止崩溃 , 未到时间就拦截(第一次直接执行)
+                         if(ifNeedUpdate()){
+                            prepareToRender()
+                            return true
+                         }
+                    }, 500) 
+                }else{  
+                    //console.log('soon', window.innerWidth, this.screenSizeInfo.windowWidth)
+                    prepareToRender() 
+                } 
+                 
+            }    
+            
+        }
+        if (render) { 
+            this.setSize(w, h, ratio); 
+        } 
+    }  */
+
      
-    setSize(width, height, devicePixelRatio){ 
-        //console.log('setSize',width,height, this.name)  
-        //if(width == 0 || height == 0)return;
-        
-        this.renderer.setSize(width, height, null, devicePixelRatio); // resize之后会自动clear(似乎因为setScissor ),所以一定要立刻绘制,所以setSize要在cameraChanged、update之前
-        
+    setSize(width, height, devicePixelRatio, onlyForTarget){ 
+        //console.log('setSize', width) 
+        if(!onlyForTarget){//onlyForTarget表示不更改当前renderer,只是为了rendertarget才要改变viewport
+            this.renderer.setSize(width, height, null, devicePixelRatio); // resize之后会自动clear(似乎因为setScissor ),所以一定要立刻绘制,所以setSize要在cameraChanged、update之前
+        }
        
         //this.composer.setSize(width, height);
         
@@ -158,7 +213,7 @@ export class ViewerBase extends EventDispatcher{
                 
                 if(height_ == 0)return  //avoid NAN
                 
-                view.setResolution(Math.floor(width_), Math.floor(height_), width, height )
+                view.setResolution(Math.ceil(width_), Math.ceil(height_), width, height ) //本来应该是floor,但是这样奇数时会少一个像素,导致向左移一个像素且宽度少1。现在则多绘制1个像素,超出的1个像素应该不会绘制出来(但不知道其他地方是否有偏差,比如pick时)
                 let aspect = width_ / height_;  //camera的参数精确些,不用视口的归整的resolution像素值,否则hasChange无法为true, 导致canvasResize了但map没update从而闪烁
                 view.camera.aspect = aspect;
                 
@@ -184,9 +239,12 @@ export class ViewerBase extends EventDispatcher{
                 view.camera.updateProjectionMatrix();
             })
         }
-        this.emitResizeMsg({viewport:this.viewports[0],  deviceRatio:devicePixelRatio})
-		//this.emitResizeMsg({resolution:this.viewports[0].resolution2, left:this.viewports[0].left, bottom:this.viewports[0].bottom}) //如果多个的话,render时会一直发送的
-       
+        
+        
+        if(!onlyForTarget){//因为onlyForTarget不传递devicePixelRatio所以不发送了
+            this.emitResizeMsg({viewport:this.viewports[0],  deviceRatio:devicePixelRatio})
+        }
+         
     } 
     
     emitResizeMsg(e){//切换viewport渲染时就发送一次, 通知一些材质更新resolution。  
@@ -247,8 +305,9 @@ export class ViewerBase extends EventDispatcher{
 			format: THREE.RGBAFormat,
 		});
  
-		// HACK? removed because of error, was this important?
-		  
+	 
+		this.setSize(width, height,1,true) 
+        
 		this.render({
             target ,
             //camera ,