浏览代码

新加的文件

xzw 3 年之前
父节点
当前提交
d195acbaab
共有 53 个文件被更改,包括 9390 次插入0 次删除
  1. 二进制
      resources/images/rotate-cursor.cur
  2. 二进制
      resources/images/rotate-cursor.png
  3. 二进制
      resources/textures/map_instruction_start_route.png
  4. 二进制
      resources/textures/map_instruction_target_reached.png
  5. 二进制
      resources/textures/map_marker.png
  6. 二进制
      resources/textures/pano_instruction_bottomMarker.png
  7. 二进制
      resources/textures/pano_instruction_start_route.png
  8. 二进制
      resources/textures/pano_instruction_target_reached.png
  9. 二进制
      resources/textures/pic_location128.png
  10. 二进制
      resources/textures/pic_location64.png
  11. 二进制
      resources/textures/pic_point32.png
  12. 二进制
      resources/textures/pic_point64.png
  13. 二进制
      resources/textures/pic_point_s32.png
  14. 二进制
      resources/textures/reticule_cross_hair.png
  15. 二进制
      resources/textures/rotation_circle.png
  16. 二进制
      resources/textures/routePoint_map_activeFloor.png
  17. 二进制
      resources/textures/routePoint_map_fsna.png
  18. 二进制
      resources/textures/routePoint_map_inactiveFloor.png
  19. 二进制
      resources/textures/routePoint_panorama.png
  20. 二进制
      resources/textures/whiteCircle.png
  21. 437 0
      src/extensions/three.shim.js
  22. 113 0
      src/materials/DepthBasicMaterial.js
  23. 9 0
      src/materials/shaders/basicTextured.fs
  24. 6 0
      src/materials/shaders/basicTextured.vs
  25. 10 0
      src/materials/shaders/copyCubeMap.fs
  26. 14 0
      src/materials/shaders/copyCubeMap.vs
  27. 91 0
      src/materials/shaders/depthBasic.fs
  28. 10 0
      src/materials/shaders/depthBasic.vs
  29. 1156 0
      src/modules/Images360/tile/PanoRenderer.js
  30. 171 0
      src/modules/Images360/tile/QualityManager.js
  31. 505 0
      src/modules/Images360/tile/TileDownloader.js
  32. 363 0
      src/modules/Images360/tile/TilePrioritizer.js
  33. 168 0
      src/modules/Images360/tile/TileTree.js
  34. 282 0
      src/modules/Images360/tile/TileUtils.js
  35. 168 0
      src/modules/clipModel/Clip.js
  36. 351 0
      src/modules/siteModel/BuildingBox.js
  37. 571 0
      src/modules/siteModel/SiteModel.js
  38. 263 0
      src/start.js
  39. 70 0
      src/utils/Label.js
  40. 322 0
      src/utils/MathLight.js
  41. 339 0
      src/utils/SplitScreen.js
  42. 347 0
      src/utils/UnitConvert.js
  43. 367 0
      src/utils/browser.js
  44. 21 0
      src/utils/cameraLight.js
  45. 626 0
      src/utils/ctrlPolygon.js
  46. 112 0
      src/utils/file.js
  47. 324 0
      src/utils/mapClipBox.js
  48. 489 0
      src/utils/request.js
  49. 123 0
      src/viewer/Sprite.js
  50. 89 0
      src/viewer/Viewport.js
  51. 735 0
      src/viewer/map/Map.js
  52. 478 0
      src/viewer/map/MapViewer.js
  53. 260 0
      src/viewer/viewerBase.js

二进制
resources/images/rotate-cursor.cur


二进制
resources/images/rotate-cursor.png


二进制
resources/textures/map_instruction_start_route.png


二进制
resources/textures/map_instruction_target_reached.png


二进制
resources/textures/map_marker.png


二进制
resources/textures/pano_instruction_bottomMarker.png


二进制
resources/textures/pano_instruction_start_route.png


二进制
resources/textures/pano_instruction_target_reached.png


二进制
resources/textures/pic_location128.png


二进制
resources/textures/pic_location64.png


二进制
resources/textures/pic_point32.png


二进制
resources/textures/pic_point64.png


二进制
resources/textures/pic_point_s32.png


二进制
resources/textures/reticule_cross_hair.png


二进制
resources/textures/rotation_circle.png


二进制
resources/textures/routePoint_map_activeFloor.png


二进制
resources/textures/routePoint_map_fsna.png


二进制
resources/textures/routePoint_map_inactiveFloor.png


二进制
resources/textures/routePoint_panorama.png


二进制
resources/textures/whiteCircle.png


+ 437 - 0
src/extensions/three.shim.js

@@ -0,0 +1,437 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+
+!function() {
+    if ("performance"in window == 0 && (window.performance = {}),
+    "now"in window.performance == 0) {
+        var e = Date.now();
+        performance.timing && performance.timing.navigationStart && (e = performance.timing.navigationStart),
+        window.performance.now = function() {
+            return Date.now() - e
+        }
+    }
+}(),
+THREE.WebGLRenderer.prototype.paramThreeToGL = function(e) {
+    var t, i = this.extensions, r = this.getContext();//context;
+    if (e === THREE.RepeatWrapping)
+        return r.REPEAT;
+    if (e === THREE.ClampToEdgeWrapping)
+        return r.CLAMP_TO_EDGE;
+    if (e === THREE.MirroredRepeatWrapping)
+        return r.MIRRORED_REPEAT;
+    if (e === THREE.NearestFilter)
+        return r.NEAREST;
+    if (e === THREE.NearestMipMapNearestFilter)
+        return r.NEAREST_MIPMAP_NEAREST;
+    if (e === THREE.NearestMipMapLinearFilter)
+        return r.NEAREST_MIPMAP_LINEAR;
+    if (e === THREE.LinearFilter)
+        return r.LINEAR;
+    if (e === THREE.LinearMipMapNearestFilter)
+        return r.LINEAR_MIPMAP_NEAREST;
+    if (e === THREE.LinearMipMapLinearFilter)
+        return r.LINEAR_MIPMAP_LINEAR;
+    if (e === THREE.UnsignedByteType)
+        return r.UNSIGNED_BYTE;
+    if (e === THREE.UnsignedShort4444Type)
+        return r.UNSIGNED_SHORT_4_4_4_4;
+    if (e === THREE.UnsignedShort5551Type)
+        return r.UNSIGNED_SHORT_5_5_5_1;
+    if (e === THREE.UnsignedShort565Type)
+        return r.UNSIGNED_SHORT_5_6_5;
+    if (e === THREE.ByteType)
+        return r.BYTE;
+    if (e === THREE.ShortType)
+        return r.SHORT;
+    if (e === THREE.UnsignedShortType)
+        return r.UNSIGNED_SHORT;
+    if (e === THREE.IntType)
+        return r.INT;
+    if (e === THREE.UnsignedIntType)
+        return r.UNSIGNED_INT;
+    if (e === THREE.FloatType)
+        return r.FLOAT;
+    if (t = i.get("OES_texture_half_float"),
+    null !== t && e === THREE.HalfFloatType)
+        return t.HALF_FLOAT_OES;
+    if (e === THREE.AlphaFormat)
+        return r.ALPHA;
+    if (e === THREE.RGBFormat)
+        return r.RGB;
+    if (e === THREE.RGBAFormat)
+        return r.RGBA;
+    if (e === THREE.LuminanceFormat)
+        return r.LUMINANCE;
+    if (e === THREE.LuminanceAlphaFormat)
+        return r.LUMINANCE_ALPHA;
+    if (e === THREE.AddEquation)
+        return r.FUNC_ADD;
+    if (e === THREE.SubtractEquation)
+        return r.FUNC_SUBTRACT;
+    if (e === THREE.ReverseSubtractEquation)
+        return r.FUNC_REVERSE_SUBTRACT;
+    if (e === THREE.ZeroFactor)
+        return r.ZERO;
+    if (e === THREE.OneFactor)
+        return r.ONE;
+    if (e === THREE.SrcColorFactor)
+        return r.SRC_COLOR;
+    if (e === THREE.OneMinusSrcColorFactor)
+        return r.ONE_MINUS_SRC_COLOR;
+    if (e === THREE.SrcAlphaFactor)
+        return r.SRC_ALPHA;
+    if (e === THREE.OneMinusSrcAlphaFactor)
+        return r.ONE_MINUS_SRC_ALPHA;
+    if (e === THREE.DstAlphaFactor)
+        return r.DST_ALPHA;
+    if (e === THREE.OneMinusDstAlphaFactor)
+        return r.ONE_MINUS_DST_ALPHA;
+    if (e === THREE.DstColorFactor)
+        return r.DST_COLOR;
+    if (e === THREE.OneMinusDstColorFactor)
+        return r.ONE_MINUS_DST_COLOR;
+    if (e === THREE.SrcAlphaSaturateFactor)
+        return r.SRC_ALPHA_SATURATE;
+    if (t = i.get("WEBGL_compressed_texture_s3tc"),
+    null !== t) {
+        if (e === THREE.RGB_S3TC_DXT1_Format)
+            return t.COMPRESSED_RGB_S3TC_DXT1_EXT;
+        if (e === THREE.RGBA_S3TC_DXT1_Format)
+            return t.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+        if (e === THREE.RGBA_S3TC_DXT3_Format)
+            return t.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+        if (e === THREE.RGBA_S3TC_DXT5_Format)
+            return t.COMPRESSED_RGBA_S3TC_DXT5_EXT
+    }
+    if (t = i.get("WEBGL_compressed_texture_pvrtc"),
+    null !== t) {
+        if (e === THREE.RGB_PVRTC_4BPPV1_Format)
+            return t.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+        if (e === THREE.RGB_PVRTC_2BPPV1_Format)
+            return t.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+        if (e === THREE.RGBA_PVRTC_4BPPV1_Format)
+            return t.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+        if (e === THREE.RGBA_PVRTC_2BPPV1_Format)
+            return t.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
+    }
+    if (t = i.get("WEBGL_compressed_texture_etc1"),
+    null !== t && e === THREE.RGB_ETC1_Format)
+        return t.COMPRESSED_RGB_ETC1_WEBGL;
+    if (t = i.get("EXT_blend_minmax"),
+    null !== t) {
+        if (e === THREE.MinEquation)
+            return t.MIN_EXT;
+        if (e === THREE.MaxEquation)
+            return t.MAX_EXT
+    }
+    return 0
+}
+/* ,
+THREE.WebGLState = function(e, t, i) {
+    var r = this
+      , o = new THREE.Vector4
+      , a = e.getParameter(e.MAX_VERTEX_ATTRIBS)
+      , s = new Uint8Array(a)
+      , l = new Uint8Array(a)
+      , c = new Uint8Array(a)
+      , h = {}
+      , u = null
+      , d = null
+      , p = null
+      , f = null
+      , g = null
+      , m = null
+      , v = null
+      , A = null
+      , y = !1
+      , C = null
+      , I = null
+      , E = null
+      , b = null
+      , w = null
+      , _ = null
+      , T = null
+      , x = null
+      , S = null
+      , M = null
+      , R = null
+      , P = null
+      , O = null
+      , L = null
+      , D = null
+      , N = e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS)
+      , B = void 0
+      , F = {}
+      , V = new THREE.Vector4
+      , U = null
+      , k = null
+      , H = new THREE.Vector4
+      , G = new THREE.Vector4;
+    this.init = function() {
+        this.clearColor(0, 0, 0, 1),
+        this.clearDepth(1),
+        this.clearStencil(0),
+        this.enable(e.DEPTH_TEST),
+        e.depthFunc(e.LEQUAL),
+        e.frontFace(e.CCW),
+        e.cullFace(e.BACK),
+        this.enable(e.CULL_FACE),
+        this.enable(e.BLEND),
+        e.blendEquation(e.FUNC_ADD),
+        e.blendFunc(e.SRC_ALPHA, e.ONE_MINUS_SRC_ALPHA)
+    }
+    ,
+    this.initAttributes = function() {
+        for (var e = 0, t = s.length; e < t; e++)
+            s[e] = 0
+    }
+    ,
+    this.enableAttribute = function(i) {
+        if (s[i] = 1,
+        0 === l[i] && (e.enableVertexAttribArray(i),
+        l[i] = 1),
+        0 !== c[i]) {
+            var n = t.get("ANGLE_instanced_arrays");
+            n.vertexAttribDivisorANGLE(i, 0),
+            c[i] = 0
+        }
+    }
+    ,
+    this.enableAttributeAndDivisor = function(t, i, n) {
+        s[t] = 1,
+        0 === l[t] && (e.enableVertexAttribArray(t),
+        l[t] = 1),
+        c[t] !== i && (n.vertexAttribDivisorANGLE(t, i),
+        c[t] = i)
+    }
+    ,
+    this.disableUnusedAttributes = function() {
+        for (var t = 0, i = l.length; t < i; t++)
+            l[t] !== s[t] && (e.disableVertexAttribArray(t),
+            l[t] = 0)
+    }
+    ,
+    this.enable = function(t) {
+        h[t] !== !0 && (e.enable(t),
+        h[t] = !0)
+    }
+    ,
+    this.disable = function(t) {
+        h[t] !== !1 && (e.disable(t),
+        h[t] = !1)
+    }
+    ,
+    this.getCompressedTextureFormats = function() {
+        if (null === u && (u = [],
+        t.get("WEBGL_compressed_texture_pvrtc") || t.get("WEBGL_compressed_texture_s3tc") || t.get("WEBGL_compressed_texture_etc1")))
+            for (var i = e.getParameter(e.COMPRESSED_TEXTURE_FORMATS), n = 0; n < i.length; n++)
+                u.push(i[n]);
+        return u
+    }
+    ,
+    this.setBlending = function(t, r, o, a, s, l, c, h) {
+        t === THREE.NoBlending ? this.disable(e.BLEND) : this.enable(e.BLEND),
+        t === d && h === y || (t === THREE.AdditiveBlending ? h ? (e.blendEquationSeparate(e.FUNC_ADD, e.FUNC_ADD),
+        e.blendFuncSeparate(e.ONE, e.ONE, e.ONE, e.ONE)) : (e.blendEquation(e.FUNC_ADD),
+        e.blendFunc(e.SRC_ALPHA, e.ONE)) : t === THREE.SubtractiveBlending ? h ? (e.blendEquationSeparate(e.FUNC_ADD, e.FUNC_ADD),
+        e.blendFuncSeparate(e.ZERO, e.ZERO, e.ONE_MINUS_SRC_COLOR, e.ONE_MINUS_SRC_ALPHA)) : (e.blendEquation(e.FUNC_ADD),
+        e.blendFunc(e.ZERO, e.ONE_MINUS_SRC_COLOR)) : t === THREE.MultiplyBlending ? h ? (e.blendEquationSeparate(e.FUNC_ADD, e.FUNC_ADD),
+        e.blendFuncSeparate(e.ZERO, e.ZERO, e.SRC_COLOR, e.SRC_ALPHA)) : (e.blendEquation(e.FUNC_ADD),
+        e.blendFunc(e.ZERO, e.SRC_COLOR)) : h ? (e.blendEquationSeparate(e.FUNC_ADD, e.FUNC_ADD),
+        e.blendFuncSeparate(e.ONE, e.ONE_MINUS_SRC_ALPHA, e.ONE, e.ONE_MINUS_SRC_ALPHA)) : (e.blendEquationSeparate(e.FUNC_ADD, e.FUNC_ADD),
+        e.blendFuncSeparate(e.SRC_ALPHA, e.ONE_MINUS_SRC_ALPHA, e.ONE, e.ONE_MINUS_SRC_ALPHA)),
+        d = t,
+        y = h),
+        t === THREE.CustomBlending ? (s = s || r,
+        l = l || o,
+        c = c || a,
+        r === p && s === m || (e.blendEquationSeparate(i(r), i(s)),
+        p = r,
+        m = s),
+        o === f && a === g && l === v && c === A || (e.blendFuncSeparate(i(o), i(a), i(l), i(c)),
+        f = o,
+        g = a,
+        v = l,
+        A = c)) : (p = null,
+        f = null,
+        g = null,
+        m = null,
+        v = null,
+        A = null)
+    }
+    ,
+    this.setDepthFunc = function(t) {
+        if (C !== t) {
+            if (t)
+                switch (t) {
+                case THREE.NeverDepth:
+                    e.depthFunc(e.NEVER);
+                    break;
+                case THREE.AlwaysDepth:
+                    e.depthFunc(e.ALWAYS);
+                    break;
+                case THREE.LessDepth:
+                    e.depthFunc(e.LESS);
+                    break;
+                case THREE.LessEqualDepth:
+                    e.depthFunc(e.LEQUAL);
+                    break;
+                case THREE.EqualDepth:
+                    e.depthFunc(e.EQUAL);
+                    break;
+                case THREE.GreaterEqualDepth:
+                    e.depthFunc(e.GEQUAL);
+                    break;
+                case THREE.GreaterDepth:
+                    e.depthFunc(e.GREATER);
+                    break;
+                case THREE.NotEqualDepth:
+                    e.depthFunc(e.NOTEQUAL);
+                    break;
+                default:
+                    e.depthFunc(e.LEQUAL)
+                }
+            else
+                e.depthFunc(e.LEQUAL);
+            C = t
+        }
+    }
+    ,
+    this.setDepthTest = function(t) {
+        t ? this.enable(e.DEPTH_TEST) : this.disable(e.DEPTH_TEST)
+    }
+    ,
+    this.setDepthWrite = function(t) {
+        I !== t && (e.depthMask(t),
+        I = t)
+    }
+    ,
+    this.setColorWrite = function(t) {
+        E !== t && (e.colorMask(t, t, t, t),
+        E = t)
+    }
+    ,
+    this.setStencilFunc = function(t, i, n) {
+        w === t && _ === i && T === n || (e.stencilFunc(t, i, n),
+        w = t,
+        _ = i,
+        T = n)
+    }
+    ,
+    this.setStencilOp = function(t, i, n) {
+        x === t && S === i && M === n || (e.stencilOp(t, i, n),
+        x = t,
+        S = i,
+        M = n)
+    }
+    ,
+    this.setStencilTest = function(t) {
+        t ? this.enable(e.STENCIL_TEST) : this.disable(e.STENCIL_TEST)
+    }
+    ,
+    this.setStencilWrite = function(t) {
+        b !== t && (e.stencilMask(t),
+        b = t)
+    }
+    ,
+    this.setFlipSided = function(t) {
+        R !== t && (t ? e.frontFace(e.CW) : e.frontFace(e.CCW),
+        R = t)
+    }
+    ,
+    this.setLineWidth = function(t) {
+        t !== P && (e.lineWidth(t),
+        P = t)
+    }
+    ,
+    this.setPolygonOffset = function(t, i, n) {
+        t ? this.enable(e.POLYGON_OFFSET_FILL) : this.disable(e.POLYGON_OFFSET_FILL),
+        !t || O === i && L === n || (e.polygonOffset(i, n),
+        O = i,
+        L = n)
+    }
+    ,
+    this.getScissorTest = function() {
+        return D
+    }
+    ,
+    this.setScissorTest = function(t) {
+        D = t,
+        t ? this.enable(e.SCISSOR_TEST) : this.disable(e.SCISSOR_TEST)
+    }
+    ,
+    this.activeTexture = function(t) {
+        void 0 === t && (t = e.TEXTURE0 + N - 1),
+        B !== t && (e.activeTexture(t),
+        B = t)
+    }
+    ,
+    this.bindTexture = function(t, i) {
+        void 0 === B && r.activeTexture();
+        var n = F[B];
+        void 0 === n && (n = {
+            type: void 0,
+            texture: void 0
+        },
+        F[B] = n),
+        n.type === t && n.texture === i || (e.bindTexture(t, i),
+        n.type = t,
+        n.texture = i)
+    }
+    ,
+    this.compressedTexImage2D = function() {
+        try {
+            e.compressedTexImage2D.apply(e, arguments)
+        } catch (e) {
+            console.error(e)
+        }
+    }
+    ,
+    this.texImage2D = function() {
+        try {
+            e.texImage2D.apply(e, arguments)
+        } catch (e) {
+            console.error(e)
+        }
+    }
+    ,
+    this.clearColor = function(t, i, n, r) {
+        o.set(t, i, n, r),
+        V.equals(o) === !1 && (e.clearColor(t, i, n, r),
+        V.copy(o))
+    }
+    ,
+    this.clearDepth = function(t) {
+        U !== t && (e.clearDepth(t),
+        U = t)
+    }
+    ,
+    this.clearStencil = function(t) {
+        k !== t && (e.clearStencil(t),
+        k = t)
+    }
+    ,
+    this.scissor = function(t) {
+        H.equals(t) === !1 && (e.scissor(t.x, t.y, t.z, t.w),
+        H.copy(t))
+    }
+    ,
+    this.viewport = function(t) {
+        G.equals(t) === !1 && (e.viewport(t.x, t.y, t.z, t.w),
+        G.copy(t))
+    }
+    ,
+    this.reset = function() {
+        for (var t = 0; t < l.length; t++)
+            1 === l[t] && (e.disableVertexAttribArray(t),
+            l[t] = 0);
+        h = {},
+        u = null,
+        B = void 0,
+        F = {},
+        d = null,
+        E = null,
+        I = null,
+        b = null,
+        R = null
+    }
+} */

+ 113 - 0
src/materials/DepthBasicMaterial.js

@@ -0,0 +1,113 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import {Shaders} from "../../build/shaders/shaders.js";
+
+
+ 
+export default class DepthBasicMaterial extends THREE.ShaderMaterial{
+    constructor(o={}){
+        let {width, height} = viewer.renderer.getSize(new THREE.Vector2());
+        
+        let uniforms = {
+			resolution:    { type: 'v2',  value: new THREE.Vector2(width, height ) },
+            viewportOffset: { type: 'v2',  value: new THREE.Vector2(0, 0 ) }, //left, top    
+			nearPlane:     { type: 'f', 	value: 0.1 },
+			farPlane:      { type: 'f', 	value: 10000 }, 
+			depthTexture:   { type: 't', 	value: null }, 
+			opacity:        { type: 'f',	value: o.opacity == void 0 ? 1 : o.opacity },
+			map:             { type: 't', 	value: o.map }, 
+            baseColor:     {type:'v3',      value: o.color ?  new THREE.Color(o.color) :  new THREE.Color("#ffffff"),
+             
+		}};  
+        
+        let defines = {};
+        if(o.useDepth)defines.useDepth = ''
+        if(o.map)defines.use_map = ''
+        
+        super({ 
+            uniforms,
+            vertexShader: Shaders['depthBasic.vs'],   
+            fragmentShader: Shaders['depthBasic.fs'],
+            depthWrite: !1,
+            depthTest: !1,
+            transparent: o.transparent == void 0 ?  true : o.transparent,
+            side: o.side || 0 /* THREE.DoubleSide */,
+            defines, 
+        } )
+        
+        if(o.useDepth) this.useDepth_ = true
+        
+         
+         
+        let setSize = (e)=>{//如果出现横条状的异常,往往是viewportOffset出错 
+            let viewport = e.viewport
+            let viewportOffset = viewport.offset || new THREE.Vector2() 
+            this.uniforms.resolution.value.copy(viewport.resolution2) 
+            this.uniforms.viewportOffset.value.copy(viewportOffset)
+            
+            //console.log('depth '+viewportOffset.toArray())
+        }
+        
+        let viewport = viewer.mainViewport;
+         
+        setSize( {viewport} )
+        
+        viewer.addEventListener('resize',(e)=>{
+            if(!e.viewport || e.viewport.name != 'mapViewport'){//地图不需要
+                setSize(e)
+                //console.log(this.name +  viewportOffset.toArray())     
+            } 
+        })  
+        
+        
+        viewer.addEventListener('camera_changed', (e)=>{
+            if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) 
+        }) 
+    
+        
+        /* viewer.addEventListener("render.begin", (e)=>{//before render  如果有大于两个viewport的话可能要
+            if(e.viewport.name != 'mapViewport') this.updateDepthParams({camera:e.viewport.camera})
+        }) */
+        
+        this.updateDepthParams()
+        //点云变化时要一直触发updateDepthParams??
+        //viewer.once("render.pass.end",this.updateDepthParams.bind(this))
+    }
+    
+    updateDepthParams(e={}){//主要用于点云遮住mesh
+        if(this.useDepth){ 
+            var camera = e.camera || viewer.scene.getActiveCamera(); 
+            this.uniforms.depthTexture.value = viewer.getPRenderer().rtEDL.depthTexture   //其实只赋值一次就行
+            this.uniforms.nearPlane.value = camera.near;
+            this.uniforms.farPlane.value = camera.far;
+             
+        }            
+    }
+    set map(map){
+        this.uniforms.map.value = map; 
+    }
+    
+    get useDepth(){
+        return this.useDepth_
+    }
+    
+    set useDepth(value){
+        if(this.useDepth_ != value){
+            if(value){
+                this.defines.useDepth = ''
+                this.updateDepthParams()
+            }else{
+                delete this.defines.useDepth
+            }
+            this.useDepth_ = value
+            this.needsUpdate = true
+        }
+    } 
+    
+    
+    /* dispose(){ 
+        super.dispose()
+        viewer.depthBasic
+    } */
+    
+}

+ 9 - 0
src/materials/shaders/basicTextured.fs

@@ -0,0 +1,9 @@
+varying vec2 vUv;
+uniform float alpha;
+uniform sampler2D tDiffuse;
+
+
+void main() {
+  vec4 texColor = texture2D(tDiffuse, vUv);
+  gl_FragColor = vec4(texColor.rgb, texColor.a * alpha);
+}

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

@@ -0,0 +1,6 @@
+varying vec2 vUv;
+void main() {
+  vUv = uv;
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+} 
+  

+ 10 - 0
src/materials/shaders/copyCubeMap.fs

@@ -0,0 +1,10 @@
+varying vec3 vWorldPos;
+uniform float alpha;
+uniform samplerCube tDiffuse;
+
+
+void main() {
+  vec4 texColor = textureCube(tDiffuse, vWorldPos);
+  gl_FragColor = vec4(texColor.rgb, texColor.a * alpha);
+} 
+     

+ 14 - 0
src/materials/shaders/copyCubeMap.vs

@@ -0,0 +1,14 @@
+varying vec3 vWorldPos;
+vec3 transformAxis( vec3 direction ) //navvis->4dkk
+{
+    float y = direction.y;
+    direction.y = direction.z;
+    direction.z = -y;
+    return  direction;
+}
+void main() {
+  vWorldPos = vec3(-position.x, -position.y, position.z);
+  //vWorldPos = transformAxis(vWorldPos);
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+} 
+ 

+ 91 - 0
src/materials/shaders/depthBasic.fs

@@ -0,0 +1,91 @@
+varying vec2 vUv;
+uniform float opacity;
+
+uniform vec3 baseColor;
+
+
+#if defined use_map
+    uniform sampler2D map; 
+#endif
+ 
+#if defined(GL_EXT_frag_depth) && defined(useDepth)  
+    //似乎通过gl.getExtension('EXT_frag_depth')得到的GL_EXT_frag_depth
+     
+    uniform sampler2D depthTexture;
+    uniform float nearPlane;
+    uniform float farPlane; 
+    uniform vec2 resolution;
+    uniform vec2 viewportOffset; //  viewportOffset 范围从0-整个画布的像素
+    
+    float convertToLinear(float zValue)
+    {
+        float z = zValue * 2.0 - 1.0;
+        return (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane));
+    }
+#endif
+  
+void main() {
+  
+    
+    vec4 color = vec4(baseColor, opacity);
+    
+    
+    
+    #if defined(GL_EXT_frag_depth) && defined(useDepth)
+        // mixFactor and clipFactor define the color mixing proportion between the states of
+        // full visibility and occluded visibility
+        // and
+        // full visibility and total invisibility
+        
+        float mixFactor = 0.0;
+        float clipFactor = 0.0;
+        
+        
+        // The linear depth value of the current fragment
+        float fragDepth = convertToLinear(gl_FragCoord.z);
+
+        // The coordinates of the current fragment in the depth texture
+        vec2 depthTxtCoords = vec2(gl_FragCoord.x-viewportOffset.x,  gl_FragCoord.y) / resolution;
+     
+        // The linear depth value of the pixel occupied by this fragment in the depth buffer
+        float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
+
+        // The difference between the two depths
+        float delta = textureDepth - fragDepth;
+
+        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);
+        }
+        
+        // If the fragment is totally transparent, don't bother drawing it
+        if (clipFactor == 1.0)
+        {
+            discard;
+        }else{
+            
+            #if defined use_map
+                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));
+        }
+         
+    #else
+        #if defined use_map
+            color = texture2D(map, vUv) * color;
+        #endif 
+    #endif
+  
+    gl_FragColor = color;
+  
+}

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

@@ -0,0 +1,10 @@
+
+ 
+
+varying vec2 vUv;
+void main() {
+    
+  vUv = uv;
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+} 
+ 

文件差异内容过多而无法显示
+ 1156 - 0
src/modules/Images360/tile/PanoRenderer.js


+ 171 - 0
src/modules/Images360/tile/QualityManager.js

@@ -0,0 +1,171 @@
+
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+ 
+import browser from '../../../utils/browser' 
+import {settings,config} from '../../../settings'
+import {ModelManagerEvents,PanoSizeClass} from '../../../defines'
+
+
+
+export default class QualityManager {
+    constructor(e, t, i) {
+      
+        this.maxNavPanoSize = -1;
+        this.maxZoomPanoSize = -1;
+        this.devicePixelDensity = e;
+        this.deviceScreenSize = t;
+        this.clientBandwidth = i;
+        this.panoSizeClassMap = {};
+        this.useHighResolutionPanos = !0;  //是否能够使用2k及以上图
+        this.useUltraHighResolutionPanos = !1;
+        this.modelHasUltraHighPanos = !1;
+        this.qualityManager = this;
+        
+        this.maxRenderTargetSize = browser.isMobile() ? 2048 : 4096  //add
+        this.init()
+    }
+
+    init(e ) {
+        //var metadata = store.getters['scene/metadata'] ;//有时候请求不到
+        //if(metadata.sceneSource == 11 || metadata.sceneScheme == 12){
+        /* if(config.tileClass == '1k'){
+            this.useHighResolutionPanos = false    //xzw add 只加载1k
+        } */
+         
+        
+        this.buildPanoSizeClassMap(this.devicePixelDensity, this.deviceScreenSize, this.clientBandwidth);
+        this.ultraHighSize = this.getPanoSize(PanoSizeClass.ULTRAHIGH);
+        this.highSize = this.getPanoSize(PanoSizeClass.HIGH);
+        this.standardSize = this.getPanoSize(PanoSizeClass.STANDARD);
+        this.baseSize = this.getPanoSize(PanoSizeClass.BASE);
+        config.tiling.maxZoomPanoQuality && this.ultraHighSize <= config.tiling.maxZoomPanoQuality && (config.tiling.allowUltraHighResolution = !0);
+        this.highQualityThreshold = browser.valueFromHash("threshold2k", config.windowHeightHighQualityThreshold);
+        this.updateMaximums();
+        //e.on(ModelManagerEvents.ActiveModelChanged, this.onModelChanged.bind(this));
+    }
+
+    updateFromModel(e) {
+        //this.updateHighResolutionSettings(e) 
+        this.updateUltraHighResolutionSettings(e)
+    }
+
+    /* updateHighResolutionSettings(e) {
+        this.useHighResolutionPanos = !0 
+        this.updateMaximums()
+    } */
+
+    updateUltraHighResolutionSettings(e) {
+        if (config.tiling.allowUltraHighResolution && this.modelHasUltraHighPanos) {
+            this.useUltraHighResolutionPanos = !0;
+        } else {
+            this.useUltraHighResolutionPanos = !1;
+        }
+        this.updateMaximums();
+    }
+
+    enableUltraHighQualityMode() {
+        this.modelHasUltraHighPanos = !0;
+        this.updateUltraHighResolutionSettings(null);
+    }
+
+    ultraHighQualityModeEnabled() {
+        return this.modelHasUltraHighPanos
+    }
+
+    onModelChanged(e) {
+        this.updateFromModel(e.model),
+            this.updateMaximums()
+    }
+
+    updateMaximums() {
+        this.maxNavPanoSize = config.tiling.maxNavPanoQuality || this.detectMaxNavPanoSize(),
+        this.maxZoomPanoSize = config.tiling.maxZoomPanoQuality || this.detectMaxZoomPanoSize(),
+        this.maxZoomPanoSize < this.maxNavPanoSize && (this.maxNavPanoSize = this.maxZoomPanoSize)
+    }
+
+    buildPanoSizeClassMap() {
+        this.panoSizeClassMap[PanoSizeClass.BASE] = 512,
+            this.panoSizeClassMap[PanoSizeClass.STANDARD] = 1024,
+            this.panoSizeClassMap[PanoSizeClass.HIGH] = 2048,
+            this.panoSizeClassMap[PanoSizeClass.ULTRAHIGH] = 4096
+    }
+
+    getPanoSize(e) {
+        return this.panoSizeClassMap[e]
+    }
+
+    getMaxPossiblePanoSize() {
+        return this.getPanoSize(PanoSizeClass.ULTRAHIGH)
+    }
+
+    getMaxPanoSize() {
+        return this.maxZoomPanoSize
+    }
+
+    getMaxNavPanoSize() {
+        return this.maxNavPanoSize
+    }
+
+    getMaxZoomPanoSize() {
+        return this.maxZoomPanoSize
+    }
+
+    detectMaxNavPanoSizeClass() {
+        //return this.useHighResolutionPanos ? browser.isMobile() ? PanoSizeClass.STANDARD : window.innerHeight < this.highQualityThreshold ? PanoSizeClass.STANDARD : PanoSizeClass.HIGH : PanoSizeClass.STANDARD
+       /*  if(config.name == 'decor'){
+            return PanoSizeClass.STANDARD
+        }
+        return PanoSizeClass.HIGH  */
+        switch(config.navTileClass){  
+            case '1k':
+                return PanoSizeClass.STANDARD;
+                break;
+            case '2k':             
+            default:
+                return PanoSizeClass.HIGH;
+        }
+        
+        
+    }
+
+    detectMaxNavPanoSize() {
+        var e = this.detectMaxNavPanoSizeClass();
+        return this.getPanoSize(e)
+    }
+
+    detectMaxZoomPanoSize() { 
+        if(this.zoomLevelResolution){
+            if(this.zoomLevelResolution == '4k' && this.useUltraHighResolutionPanos){
+                return this.getPanoSize(PanoSizeClass.ULTRAHIGH);
+            }else if(this.zoomLevelResolution == '1k' || !this.useHighResolutionPanos){
+                return this.getPanoSize(PanoSizeClass.STANDARD);
+            }else{
+                return this.getPanoSize(PanoSizeClass.HIGH);
+            }
+        }else{
+            if (this.useHighResolutionPanos) {
+                /* if (browser.isMobile()) {//手机版如果要2k的将这里去掉
+                    if (settings.tiling.mobileHighQualityOverride) {
+                        return this.getPanoSize(PanoSizeClass.HIGH);
+                    } else {
+                        return this.getPanoSize(PanoSizeClass.STANDARD);
+                    }
+                } else  */if (this.useUltraHighResolutionPanos ) {
+                    return this.getPanoSize(PanoSizeClass.ULTRAHIGH);
+                } else {
+                    return this.getPanoSize(PanoSizeClass.HIGH);
+                }
+            } else {
+                return this.getPanoSize(PanoSizeClass.STANDARD);
+            }
+            
+            
+        }
+        
+    }
+    
+    
+    
+    
+    
+}

+ 505 - 0
src/modules/Images360/tile/TileDownloader.js

@@ -0,0 +1,505 @@
+import {TileDownloaderEvents, DownloadStatus} from '../../../defines'
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+ 
+import TilePrioritizer from './TilePrioritizer'
+import TileUtils from './TileUtils'
+ 
+ 
+import {settings, config} from '../../../settings' 
+import {
+    http
+} from '../../../utils/request'
+import { EventDispatcher } from "../../../EventDispatcher.js";
+
+class TileDownloader extends EventDispatcher{
+    constructor( ) {
+        super()
+        this.panos = null;
+        this.retryMinimumTime = 1e4;
+        this.panoLoadCallbacks = {};
+        this.downloadDescriptors = {};
+        this.priorityQueue = [];
+        this.forceQueue = [];
+        this.activeDownloads = [];
+        this.tilePrioritizer = null;
+        this.refreshInterval = null;
+        this.processPriorityQueue = !1;
+        this.concurrentDownloads = 6;//e.concurrentDownloads || 1;
+        this.downloadTestResults = {};
+
+        this.freeze = Object.freeze({
+            Testing: 1,
+            Success: 2,
+            Fail: 3
+        });
+
+         
+    }
+
+    setPanoData(e, t /* , i */) {
+        this.panos = e,
+        this.imagePanos = t 
+          //  this.panoGroupId = i
+    }
+
+    start() {
+        this.started = true //add
+        this.refreshUpdateInterval(0)
+    }
+
+    stop() {
+        this.started = false
+        window.clearTimeout(this.refreshInterval)
+    }
+
+    refreshUpdateInterval(e) {
+        e || (e = 0),
+            this.refreshInterval = window.setTimeout(function() {
+                    var e = this.update();
+                    e ? this.refreshUpdateInterval(TileDownloader.ACTIVE_REFRESH_DELAY) : this.refreshUpdateInterval(TileDownloader.IDLE_REFRESH_DELAY)
+                }
+                .bind(this), e)
+    }
+
+    update() {
+        var e = this.forceQueue.length > 0;
+        this.processQueueForDownloading(this.forceQueue);
+        if (this.processPriorityQueue) {
+            this.queuePrioritizedTilesForPanos(this.panos);
+            this.priorityQueue.length > 0 && (e = !0);
+            this.processQueueForDownloading(this.priorityQueue);
+        }
+        return e
+    }
+
+    
+
+    queuePrioritizedTilesForPanos(e) {
+        this.tilePrioritizer && (this.clearQueue(this.priorityQueue),
+            this.tilePrioritizer.filterAndPrioritize(this.priorityQueue, e, this),
+            this.clearFromQueue(this.priorityQueue, DownloadStatus.None, !0),  //去除state为DownloadStatus.None的(可能是去除已经在下载的)
+            this.setStatusOrRemoveForAllDescriptors(this.priorityQueue, DownloadStatus.Queued))
+    }
+
+    clearQueue(e) {//停止下载并清空
+        this.setStatusForAllDescriptors(e, DownloadStatus.None),
+            e.length = 0
+    }
+    
+    clearForceQueue() {
+        this.clearQueue(this.forceQueue)
+    }
+    
+    clearFromQueue(e, t, i) {
+        for (var n = 0; n < e.length; n++) {
+            var r = e[n];
+            r && (t === r.status && !i || t !== r.status && i) && (e[n] = null)
+        }
+    }
+
+    setStatusForAllDescriptors(e, t) {
+        for (var i = 0; i < e.length; i++) {
+            var n = e[i];
+            n && (n.status = t)
+        }
+    }
+
+    setStatusOrRemoveForAllDescriptors(e, t) {
+        for (var i = 0; i < e.length; i++) {
+            var n = e[i];
+            n && (n.status !== t ? n.status = t : e[i] = null)
+        }
+    }
+
+    getTileDownloadDescriptors(pano, size) {//获取该pano的该size的全部的tile的descriptor
+        var i = this.getAllTileDownloadDescriptorsForPano(pano),
+            n = i[size];
+        return n || (n = this.buildDownloadDescriptorArray(size),//创建的全部是空的
+                i[size] = n,
+                this.initTileDownloadDescriptors(n, pano, size)),//绑定到该pano size
+            n
+    }
+
+    getAllTileDownloadDescriptorsForPano(pano) {//新建空Descriptors
+        var t = this.downloadDescriptors[pano.id];
+        return t || (t = {},
+                this.downloadDescriptors[pano.id] = t),
+            t
+    }
+
+    processQueueForDownloading(e, t) {//执行下载任务
+        this.cleanupActiveDownloads();
+        if (this.activeDownloads.length < this.concurrentDownloads || t) {
+            var i = t ? e.length : this.concurrentDownloads - this.activeDownloads.length;
+
+            for (var n = 0, r = 0; n < i && e.length > 0; r++) {
+                var o = e.shift();
+                o && (this.startDownload(o),
+                    n++)
+            }
+        }
+    }
+ 
+    testDownload(panoSize, tileSize, callback) {
+        var n = this.downloadTestResults[panoSize];
+        if (n)
+            return void(n === this.freeze.Success ? callback(!0) : n === this.freeze.Fail && callback(!1));
+        this.downloadTestResults[panoSize] = this.freeze.Testing;
+        var r = this.panos[0],
+            o = this.getTileUrl({pano:r, panoSize, tileSize, tileIndex:0}   /* r.id, panoSize, tileSize, 0 */),
+            a = function(t) {
+                this.downloadTestResults[panoSize] = this.freeze.Success,
+                    callback(!0)
+            }
+            .bind(this),
+            s = function() {
+                this.downloadTestResults[panoSize] = this.freeze.Fail,
+                    callback(!1)
+            }
+            .bind(this);
+        this.loadImage(o, 0, a, s)
+    }
+
+    startDownload(e) {//开始下载啦
+        e.status = DownloadStatus.Downloading;
+        var t = this.getTileUrl(e/* e.pano.id, e.panoSize, e.tileSize, e.tileIndex, e.pano.alignmentType */);//xzw add alignmentType
+        if(!t)return;
+        this.activeDownloads.push(e);
+        this.loadImage(t, TileDownloader.DOWNLOAD_RETRIES, this.downloadComplete.bind(this, e), this.downloadFailed.bind(this, e))
+    }
+
+    downloadFailed(e, t) {}
+
+    downloadComplete(e, t) {//下载成功时
+        //if (e.panoGroupId === this.panoGroupId) {
+            var i = this.getPanoLoadCallbacks(e.pano, e.panoSize);
+            e.status = DownloadStatus.Downloaded,
+                i && i.onProgress && i.onProgress(e.pano, e.panoSize);
+            var n = {
+                panoId: e.pano.id,
+                image: t,
+                tileSize: e.tileSize,
+                panoSize: e.panoSize,
+                tileIndex: e.tileIndex,
+                faceTileIndex: e.faceTileIndex,
+                totalTiles: e.totalTiles,
+                face: e.face,
+                tileX: e.tileX,
+                tileY: e.tileY,
+                direction: e.direction
+            };
+            e.image = t,
+                this.dispatchEvent({type:TileDownloaderEvents.TileDownloadSuccess, desc:n} ),
+                this.isPanoDownloaded(e.pano, e.panoSize) && (n = {
+                        panoId: e.pano.id,
+                        tileSize: e.tileSize,
+                        panoSize: e.panoSize
+                    },
+                    this.dispatchEvent({type:TileDownloaderEvents.PanoDownloadComplete, desc:n}),
+                    i && i.onLoad && i.onLoad(e.pano, e.panoSize))
+        //}
+    }
+
+
+    isPanoDownloaded(e, t) {
+        var i = this.getTileDownloadDescriptors(e, t);
+        if (i.length <= 0)
+            return !1;
+        for (var n = 0; n < i.length; n++) {
+            var r = i[n];
+            if (r.status !== DownloadStatus.Downloaded)
+                return !1
+        }
+        return !0
+    }
+
+    setPanoLoadCallbacks(e, t, i, n, r) {
+        var o = e.id + ":" + this.qualityManager.getPanoSize(t);
+        this.panoLoadCallbacks[o] = {
+            onLoad: i,
+            onFail: n,
+            onProgress: r
+        }
+    }
+
+    getPanoLoadCallbacks(e, t) {
+        var i = e.id + ":" + t;
+        return this.panoLoadCallbacks[i]
+    }
+
+    buildDownloadDescriptorArray(e) {
+        for (var t = TileUtils.getTileCountForSize(e), i = [], n = 0; n < t; n++) {
+            var r = this.buildDownloadDescriptor();
+            i.push(r)
+        }
+        return i
+    }
+
+    buildDownloadDescriptor() {//Descriptor!
+        var e = {
+            panoGroupId: null,
+            pano: null,
+            panoSize: -1,
+            tileSize: -1,
+            tileIndex: -1,
+            totalTiles: -1,
+            faceTileIndex: -1,
+            status: DownloadStatus.None,
+            url: null,
+            image: null,
+            direction: new THREE.Vector3, //该tile在cube中的方向
+            face: -1,
+            cubeFace: -1,
+            tileX: -1,
+            tileY: -1
+        };
+        return e
+    }
+
+    initTileDownloadDescriptors(e, t, i) {
+        for (var n = 0; n < e.length; n++) {
+            var r = e[n];
+            this.initTileDownloadDescriptor(r, t, i, n)
+        }
+    }
+
+    initTileDownloadDescriptor(desc, pano, size, index) {
+        var r = size >= TileUtils.TILE_SIZE ? TileUtils.TILE_SIZE : size;
+        desc.face = TileUtils.getFaceForTile(size, index);//根据顺序得到的face的index
+        desc.cubeFace = TileUtils.mapFaceToCubemapFace(desc.face);//为了贴图而转化的face index
+        //desc.panoGroupId = this.panoGroupId;//就是场景号
+        desc.pano = pano;
+        desc.panoSize = size;
+        desc.tileSize = r;      //瓦片图size 512
+        desc.tileIndex = index;
+        desc.totalTiles = TileUtils.getTileCountForSize(size);
+        desc.status = DownloadStatus.None;
+        desc.image = null;
+        TileUtils.getTileLocation(desc.panoSize, desc.tileIndex, desc);//得到该tile在这个face中的具体位置(tileX等)
+        TileUtils.getTileVector(desc.panoSize, desc.tileSize, desc.cubeFace, desc.tileX, desc.tileY, TileUtils.LocationOnTile.Center, 0, desc.direction);
+    }
+
+    
+
+    getTiles(d, sceneNum){
+        return `https://4dkk.4dage.com/images/images${sceneNum}/${d}`    
+    }
+
+    loadImage(e, t, i, n) {
+        //自己修改了ajax,把getImage改成了loadImg
+        http.loadImage(e, t).then(function(e) {
+            i(e)
+        }).fail(n)
+    }
+}
+TileDownloader.prototype.forceQueueTilesForPano = function() {//根据条件开始加载tile
+    var e = [],
+        t = [];
+    return function(pano, size, dir, hFov, vFov, download) { 
+        e.length = 0;
+        for (var u = this.getTileDownloadDescriptors(pano, size), d = 0; d < u.length; d++) {
+            var p = u[d];
+            p.status !== DownloadStatus.None && p.status !== DownloadStatus.Queued || e.push(p)
+        }
+        if (dir && e.length > 0) {
+            TilePrioritizer.sortPanoTiles(e, pano, dir) //按最佳方向排序e
+            t.length = 0 
+            TileUtils.matchingTilesInDirection(pano, size, dir, hFov, vFov, t);//得到在符合视野标准的集合t
+            for (var f = 0, g = function(e) {
+                    return e.face === m.face && e.faceTileIndex === m.faceTileIndex
+                }; f < e.length;) {
+                var m = e[f],
+                    v = t.findIndex(g);
+                v < 0 ? e.splice(f, 1) : f++
+            }
+        }
+        for (var A = 0; A < e.length; A++)
+            this.forceQueue.push(e[A]);         //装载
+        this.setStatusForAllDescriptors(this.forceQueue, DownloadStatus.ForceQueued);
+        this.clearFromQueue(this.priorityQueue, DownloadStatus.ForceQueued, !1);
+        download && this.processQueueForDownloading(this.forceQueue, !0);
+    }
+}()
+
+TileDownloader.prototype.cleanupActiveDownloads = function() {
+    var e = [];
+    return function() {
+        e.length = 0;
+        for (var t = 0; t < this.activeDownloads.length; t++) {
+            var i = this.activeDownloads[t];
+            i.status !== DownloadStatus.Downloaded && i.status !== DownloadStatus.Failed && e.push(i)
+        }
+        this.activeDownloads.length = 0,
+        this.activeDownloads.push.apply(this.activeDownloads, e)
+    }
+}()
+
+TileDownloader.prototype.getTileUrl = function() {
+    var e = {
+            256: "256",
+            512: "512",
+            1024: "1k",
+            2048: "2k",
+            4096: "4k"
+        },
+        t = {
+            face: -1,
+            faceTileIndex: -1,
+            tileX: -1,
+            tileY: -1
+        };
+    
+    return function(o={}  ) {  
+        var id = o.pano.originID, ////////
+            panoSize = o.panoSize,
+            tileSize = o.tileSize,
+            tileIndex = o.tileIndex,
+            datasetName = o.pano.pointcloud.name
+        var metadata = {sceneScheme:10}  
+        
+        
+        TileUtils.getTileLocation(panoSize, tileIndex, t);
+        var s = Math.floor(panoSize / tileSize),
+            l = s * s,
+            h = Math.floor(tileIndex / l),
+            u = "",
+            d = '',  g = '';
+        1 === config.tiling.customCompression && (u = "_" + config.tiling["q" + e[panoSize]]);
+         
+        /* if (metadata.sceneScheme == 10)  */{//阿里云oss的规则
+            
+            d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
+            if (e[panoSize] == '512') {
+                d += 'image/resize,h_512';
+            } else {
+                //4k的图,移动端是1k,pc端是2k,放大才是4k
+                if (e[panoSize] == '1k' || e[panoSize] == '2k') {  //https://4dkk.4dage.com/images/imagesx4iqYDG3/tiles/4k/122_skybox0.jpg?x-oss-process=image/resize,m_lfit,w_1024/crop,w_512,h_512,x_511,y_0
+                    d += 'image/resize,m_lfit,w_' + panoSize + '/crop,w_512,h_512,';
+                } else {
+                    d = 'tiles/4k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
+                }
+                //起始位置
+                /* if (t.tileX == 0) {
+                    d += 'x_0,';
+                } else {
+                    d += 'x_' + (512 * t.tileX - 1) + ',';
+                }
+
+                if (t.tileY == 0) {
+                    d += 'y_0';
+                } else {
+                    d += 'y_' + (512 * t.tileY - 1);
+                } */
+                
+                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);
+            g = "&" 
+        } 
+        
+         
+        
+        /* //8目
+        else if (metadata.sceneScheme == 11) {
+            //阿里云oss的规则 
+            d = 'tiles/2k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
+            if (e[panoSize] == '512') {
+                d += 'image/resize,h_512';
+            } else {
+                //移动端是1k,pc端是2k
+                if (e[panoSize] == '1k' || e[panoSize] == '2k') {
+                    //https://4dkk.4dage.com/images/imagesx4iqYDG3/tiles/4k/122_skybox0.jpg?x-oss-process=image/resize,m_lfit,w_1024/crop,w_512,h_512,x_511,y_0
+                    d += 'image/resize,m_lfit,w_' + panoSize + '/crop,w_512,h_512,';
+                } else {
+                    d = 'tiles/2k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
+                }
+
+                if (t.tileX == 0) {
+                    d += 'x_0,';
+                } else {
+                    d += 'x_' + (512 * t.tileX - 1) + ',';
+                }
+
+                if (t.tileY == 0) {
+                    d += 'y_0';
+                } else {
+                    d += 'y_' + (512 * t.tileY - 1);
+                }
+            } 
+            d = this.getTiles(d, datasetName); 
+            g = "&" 
+        }
+        //双目,随心装等
+        else if (metadata.sceneScheme == 12) {
+            //阿里云oss的规则 
+            d = 'tiles/1k/' + id + '_skybox' + h + '.jpg?x-oss-process=';
+            if (e[panoSize] == '512') {
+                d += 'image/resize,h_512';
+            } else {
+                d = 'tiles/1k/' + id + '_skybox' + h + '.jpg?x-oss-process=image/crop,w_512,h_512,';
+                if (t.tileX == 0) {
+                    d += 'x_0,';
+                } else {
+                    d += 'x_' + (512 * t.tileX - 1) + ',';
+                }
+
+                if (t.tileY == 0) {
+                    d += 'y_0';
+                } else {
+                    d += 'y_' + (512 * t.tileY - 1);
+                }
+            } 
+            d = this.getTiles(d, datasetName); 
+            g = "&" 
+        }
+        else {//国际版 
+            //var d = this.getTiles("tiles/" + id + "/" + e[panoSize] + u + "_face" + h + "_" + t.tileX + "_" + t.tileY + ".jpg");
+            d = this.getTiles("tiles/" + id + "/" + e[panoSize]  + "_face" + h + "_" + t.tileX + "_" + t.tileY + ".jpg", datasetName);
+            //return d = ab.changeIfTileGenerating(d)
+            g = "?"  
+            
+        }
+          */
+          
+        /* if(typeof(this.store.getters['scene/metadata'].imagesVersion)!='undefined'){
+            d+= g +'imagesVersion='+this.store.getters['scene/metadata'].imagesVersion
+        }  */
+         
+        return d;
+    }
+}();
+
+TileDownloader.tilegen = true;
+TileDownloader.IDLE_REFRESH_DELAY = 500;
+TileDownloader.ACTIVE_REFRESH_DELAY = 16;
+TileDownloader.DOWNLOAD_RETRIES = 4;
+
+// var tileconc = TileDownloader.tilegen ? 6 : 2;
+// publicObjectSet.tileDownloader = new TileDownloader({
+//     concurrentDownloads: tileconc
+// });
+
+// export default new  TileDownloader({
+//     concurrentDownloads: TileDownloader.tilegen ? 6 : 2
+// })
+
+
+/* export default new TileDownloader({
+    concurrentDownloads: TileDownloader.tilegen ? 6 : 2
+}) */
+
+export default TileDownloader

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

@@ -0,0 +1,363 @@
+ 
+import {DownloadStatus} from '../../../defines'
+import {Images360} from '../Images360'
+import TileUtils from './TileUtils'
+import cameraLight from '../../../utils/cameraLight'
+import math from '../../../utils/math'
+import Common from '../../../utils/Common' 
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+
+
+
+
+var h = Object.freeze({
+    None: 0,
+    DirectionalFOV: 1
+});
+
+var u = function () {
+    var e = function e(t, i) {
+        var n = e._panoSpaceDir,
+            r = e._fovThreshold,
+            o = e._fovThresholdNarrow,
+            a = Math.max(Math.min(n.dot(t.direction), 1), -1),
+            s = Math.max(Math.min(n.dot(i.direction), 1), -1);
+        return t._dot = a,
+            i._dot = s,
+            a >= r && s < r ? -1 : a < r && s >= r ? 1 : a >= o && s < o ? -1 : a < o && s >= o ? 1 : t.panoSize > i.panoSize ? 1 : i.panoSize > t.panoSize ? -1 : -(a - s)
+    };
+    return e._panoSpaceDir = new THREE.Vector3,
+        e._fovThreshold = -1,
+        e._fovThresholdNarrow = -1,
+        e
+}();
+
+export default class TilePrioritizer {//优先级处理序列
+    constructor(e,t, i, o, a) {
+        this.qualityManager = e;
+        this.maxNavQuality = this.qualityManager.getMaxNavPanoSize();
+        this.maxZoomQuality = this.qualityManager.getMaxZoomPanoSize();
+        this.baseSize = t;
+        this.standardSize = i;
+        this.highSize = o;
+        this.ultraHighSize = a;
+        this.priorityCriteria = new TilePrioritizer.PriorityCriteria(null, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 0, -1));
+    }
+
+    updateCriteria(e, t, i, n) {//由player更新
+        this.priorityCriteria.pano = e,
+        this.priorityCriteria.cameraPosition.copy(t),
+        //this.priorityCriteria.cameraDir.copy(i),
+        this.priorityCriteria.cameraDirs = i
+        
+        this.priorityCriteria.upcomingPanos = n,
+        this.maxNavQuality = this.qualityManager.getMaxNavPanoSize(),
+        this.maxZoomQuality = this.qualityManager.getMaxZoomPanoSize()
+          
+            
+    }
+
+    canDownloadSize(e) {
+        return this.maxNavQuality >= e || this.maxZoomQuality >= e && this.zoomingActive
+    }
+
+   
+
+
+
+    /* populateNeighborPanos(e, t, i) {
+        i = i || [],
+            i.length = 0;
+        var n = t.getNeighbours(e);
+        for (var r in n)
+            if (n.hasOwnProperty(r)) {
+                var o = t.get(r);
+                if(!o){
+                    console.log(1)
+                }
+                i.push(o)
+            }
+        return i
+    } */
+
+    populateScoredPanos(e, t, i, dirs, a) {
+        i = i || [],
+            i.length = 0;
+        var s = [Images360.filters.inPanoDirection(e.position, dirs, TilePrioritizer.DIRECTION_SCORE_STRICTNESS), Images360.filters.not(e)],
+            l = [Images360.scoreFunctions.distanceSquared(e), Images360.scoreFunctions.direction(e.position, dirs)],
+            c = Common.sortByScore(t, s, l);  
+        if (c)
+            for (var h = 0; h < c.length && h < a; h++) {
+                var u = c[h].item;
+                i.push(u)
+            }
+        return i
+    }
+
+    queueTilesForPanos(e, t, i, n, r) {
+        for (var o = 0, a = 0; a < t.length; a++) {
+            var s = t[a],
+                l = this.queueTilesForPano(e, i, s, n);
+            if (o += l > 0 ? 1 : 0,
+                r && o >= r)
+                break
+        }
+        return o
+    }
+
+
+    /* queueTilesInDirectionForPanos(e, t, i, n, r, o, a, s) {//没用到
+        for (var l = 0, c = 0; c < i.length; c++) {
+            var h = i[c],
+                u = this.queueTilesInDirectionForPano(e, t, h, n, o, a);
+            if (l += u > 0 ? 1 : 0,
+                s && l >= s)
+                break
+        }
+        return l
+    }
+    */
+
+    canIncludeDescriptor(e) {
+        return e.status !== DownloadStatus.Downloading && e.status !== DownloadStatus.Downloaded
+    }
+
+    canIncludePano(e, t) {
+        return !e.isLoaded(t)
+    }
+
+    getFOVDotThreshold(e) {
+        return Math.cos(THREE.Math.degToRad(e / 2))
+    }
+
+    setZoomingActive(e) {
+        e !== this.zoomingActive && (this.zoomingActive = e)
+    }
+}
+
+TilePrioritizer.PriorityCriteria = function (e, t, i, n, o) {
+    this.pano = e,
+        this.cameraPosition = (new THREE.Vector3).copy(t),
+        
+        //this.cameraDir = (new THREE.Vector3).copy(i), 
+        this.cameraDirs = [], //
+        
+        this.panoSpaceDir = (new THREE.Vector3).copy(n),
+        this.upcomingPanos = o,
+        this.copy = function (e) {
+            this.pano = e.pano,
+            this.cameraPosition.copy(e.cameraPosition),
+            //this.cameraDir.copy(e.cameraDir),
+            this.cameraDirs = e.cameraDirs
+            
+            this.panoSpaceDir.copy(e.panoSpaceDir),
+            this.upcomingPanos = o
+        },
+        this.zoomingActive = !1
+}
+
+TilePrioritizer.DIRECTIONAL_FOV = 180;
+TilePrioritizer.DIRECTIONAL_FOV_NARROW = 120;
+TilePrioritizer.MAX_SCORED_PANOS_TOCONSIDER = 6;
+TilePrioritizer.MAX_SCORED_PANOS_TOADD = 2;
+TilePrioritizer.MAX_UPCOMING_PANOS_TOADD = 3;
+TilePrioritizer.DIRECTION_SCORE_STRICTNESS = .75;
+TilePrioritizer.appendQueue = function (e, t) {
+    if (e && t)
+        for (var i = 0; i < t.length; i++){
+            e.push(t[i])
+            //console.log(t[i])
+        }
+};
+
+TilePrioritizer.sortPanoTiles = function (descriptors, pano, dir) {
+    if(dir instanceof Array)  dir = dir.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
+    u._panoSpaceDir.copy(dir) 
+    TileUtils.getRelativeDirection(pano.quaternion4dkk, u._panoSpaceDir) //应该是将dir根据quaternion转化下
+    u._fovThresholdNarrow = math.getFOVDotThreshold(TilePrioritizer.DIRECTIONAL_FOV_NARROW)
+    u._fovThreshold = math.getFOVDotThreshold(TilePrioritizer.DIRECTIONAL_FOV) 
+    descriptors.sort(u)
+};
+
+TilePrioritizer.insertSortedPanoTile = function (e, t, pano, dir) {
+    if(dir instanceof Array)  dir = dir.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
+    u._panoSpaceDir.copy(dir),
+        TileUtils.getRelativeDirection(pano.quaternion4dkk, u._panoSpaceDir),
+        u._fovThresholdNarrow = math.getFOVDotThreshold(TilePrioritizer.DIRECTIONAL_FOV_NARROW),
+        u._fovThreshold = math.getFOVDotThreshold(TilePrioritizer.DIRECTIONAL_FOV);
+    for (var o = -1, a = 0; a < e.length; a++) {
+        var s = u(t, e[a]);
+        if (s <= 0) {
+            o = a;
+            break
+        }
+    }
+    if (o === -1)
+        e[e.length] = t;
+    else {
+        for (var h = e.length; h > o; h--)
+            e[h] = e[h - 1];
+        e[o] = t
+    }
+};
+
+
+TilePrioritizer.prototype.filterAndPrioritize = function () {//挑选出优先加载的  (有点复杂,没看很懂)
+    var e = [],
+        t = [],
+        i = [];
+    return function (queue, panos, tileDownloader) {
+        //this.populateNeighborPanos(this.priorityCriteria.pano, panos, e);
+        
+         
+        let cameraDirLocals = this.priorityCriteria.cameraDirs.map(e=>{ //add
+            return {
+                datasetId:e.datasetId,
+                direction: math.convertVector.YupToZup(e.direction)
+            }
+        }) 
+        //let cameraDirLocals = math.convertVector.YupToZup(this.priorityCriteria.cameraDirs)
+        
+        this.populateScoredPanos(this.priorityCriteria.pano, panos, t, cameraDirLocals , TilePrioritizer.MAX_SCORED_PANOS_TOCONSIDER);
+        var s = this.baseSize //512
+            ,
+            l = this.standardSize //1024
+            ,
+            c = this.highSize //2048
+            ,
+            h = this.ultraHighSize; //4096
+        this.queueTilesForPano(queue, tileDownloader, this.priorityCriteria.pano, s);
+        if (this.priorityCriteria.upcomingPanos) {//即将走到的,之前用于导览路线
+            this.queueTilesForPanos(queue, this.priorityCriteria.upcomingPanos, tileDownloader, s, TilePrioritizer.MAX_UPCOMING_PANOS_TOADD);
+        }
+        i.length = 0;
+        if (this.canDownloadSize(l)) {//l没超过最大size限制的话
+            this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, l, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
+        }
+
+        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);
+        TilePrioritizer.appendQueue(queue, i);
+        this.queueTilesForPanos(queue, t, tileDownloader, s, TilePrioritizer.MAX_SCORED_PANOS_TOADD);
+        i.length = 0;
+        
+        
+        //NARROW    :
+        if (this.canDownloadSize(c)) {
+            this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, c, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
+        }
+
+        if (this.canDownloadSize(h)) {
+            this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, h, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV_NARROW);
+        }
+
+        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);
+        TilePrioritizer.appendQueue(queue, i);
+        i.length = 0;
+
+        if (this.canDownloadSize(l)) {
+            this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, l, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
+        }
+
+        if (this.canDownloadSize(c)) {
+            this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, c, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
+        }
+
+        if (this.canDownloadSize(h)) {
+            this.queueTilesInDirectionForPano(i, tileDownloader, this.priorityCriteria.pano, h, this.priorityCriteria.cameraPosition, this.priorityCriteria.cameraDirs, TilePrioritizer.DIRECTIONAL_FOV);
+        }
+
+        TilePrioritizer.sortPanoTiles(i, this.priorityCriteria.pano, this.priorityCriteria.cameraDirs);
+        TilePrioritizer.appendQueue(queue, i);
+        this.queueTilesForPanos(queue, e, tileDownloader, s);
+    }
+}()
+TilePrioritizer.prototype.queueTilesInDirectionForPano = function () {
+    var e = {
+            filter: h.DirectionalFOV,
+            direction: new THREE.Vector3,
+            fov: 60
+        },
+        t = new THREE.Vector3;
+    return function (i, n, pano, o, a, dirs, c) {
+        
+        var dir = dirs.find(e=>e.datasetId == pano.pointcloud.dataset_id).direction;//add
+        //var dir = dirs
+        
+        t.copy(dir);
+        
+        TileUtils.getRelativeDirection(pano.quaternion4dkk, t);
+        e.direction.copy(t);
+        e.fov = c;
+        return this.filterAndQueueTileDownloadDescriptors(i, n, pano, o, e)
+    }
+}()
+
+TilePrioritizer.prototype.filterAndQueueTileDownloadDescriptors = function () {
+    var e = [];
+    return function (t, i, n, r, o) {
+        var a = i.getTileDownloadDescriptors(n, r);
+        e.length = 0,
+            this.filterTileDownloadDescriptors(n, a, e, o);
+        for (var s = 0, l = 0; l < e.length; l++) {
+            var c = e[l];
+            if (c) {
+                t.push(c);
+                s++;
+            }
+        }
+        return s
+    }
+}()
+
+TilePrioritizer.prototype.filterTileDownloadDescriptors = function () {
+    new THREE.Vector3;
+    return function (e, t, i, n) {
+        var r, o;
+        switch (n.filter) {
+            case h.DirectionalFOV:
+                for (r = 0; r < t.length; r++)
+                    o = t[r],
+                    TileUtils.isTileWithinFOV(o.panoSize, o.tileSize, o.face, o.tileX, o.tileY, n.direction, n.fov) && i.push(o);
+                break;
+            default:
+                for (r = 0; r < t.length; r++)
+                    o = t[r],
+                    i.push(o)
+        }
+        for (r = 0; r < i.length; r++)
+            o = i[r],
+            this.canIncludeDescriptor(o) || (i[r] = null)
+    }
+}()
+TilePrioritizer.prototype.queueTilesForPano = function () {
+    var e = {
+        filter: h.None
+    };
+    return function (t, i, n, r) {
+        return this.filterAndQueueTileDownloadDescriptors(t, i, n, r, e)
+    }
+}()
+
+
+
+/* TilePrioritizer.prototype.queueTilesForPanosInDirection = function () { //没用到
+    var e = new THREE.Vector3;
+    return function (t, i, n, r, o, a, s, l) {
+        for (var h = 0, u = 0; u < n.length; u++) {
+            var d = n[u];
+            e.copy(d.position),
+                e.sub(o),
+                e.normalize();
+            var p = Math.max(Math.min(a.dot(e), 1), -1),
+                f = c.getFOVDotThreshold(s);
+            if (p >= f) {
+                var g = this.queueTilesInDirectionForPano(t, i, d, r, o, a, s);
+                if (h += g > 0 ? 1 : 0,
+                    l && h >= l)
+                    break
+            }
+        }
+        return h
+    }
+}() */
+

+ 168 - 0
src/modules/Images360/tile/TileTree.js

@@ -0,0 +1,168 @@
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+
+
+function Node(e, t) {
+    this.tree = e,  //所属树(TileTree)
+    this.parent = t,
+    this.children = [],
+    this.id = ++u;
+}
+
+function o(e, t, i, r, a, s, l, h) {
+    if (e) {
+        l = l || TileTree.TraversalType.PreOrder;
+        var u = r * c + i;
+        if (l === TileTree.TraversalType.PreOrder && (a && a(e, t, u, i, r),
+                s && s.push(e)),
+            e.children && 0 !== e.children.length) {
+            for (var d = r * c, p = i * c, f = 0; f < c; f++)
+                for (var g = 0; g < c; g++)
+                    o(e.children[g * c + f], t + 1, p + f, d + g, a, s, l, h);
+            l === TileTree.TraversalType.PostOrder && (a && a(e, t, u, i, r),
+                s && s.push(e))
+        }
+    }
+}
+
+function Plant(seed) {
+    seed.root = Branch(seed, null, 0)
+}
+
+function Branch(seed, parent, level) {
+    if (level > seed.levels)
+        return null;
+    var node = new Node(seed, parent);
+    seed.allNodes.push(node);
+    for (var o = 0; o < h; o++)
+        node.children[o] = Branch(seed, node, level + 1);
+    return node
+}
+
+function l(parent, t, level, n, r) {
+    if (!parent)
+        return null;
+    if (0 === level)
+        return parent;
+    if (!parent.children || 0 === parent.children.length)
+        return null;
+    var o = Math.pow(c, level),
+        a = o / c,
+        s = n % a,
+        h = r % a,
+        u = Math.floor(r / a),
+        d = Math.floor(n / a),
+        p = u * c + d,
+        f = parent.children[p];
+    return l(f, t + 1, level - 1, s, h)
+}
+
+
+/*  cube每个面都有一个分层树  用于代表瓦片图的细分?
+
+    树4096的分为三层,每层有4个子节点。(最后一层的四个子节点都是null)
+
+ */
+ 
+ 
+var c = 2,
+    h = c * c; //4个子节点
+var u = 0;
+
+
+
+
+export default class TileTree {
+    constructor(e, t) {
+        this.levels = t,
+        this.tileSize = e,
+        this.root = null,
+        this.allNodes = [],
+        Plant(this);
+    }
+
+    getSubNode(e, t, i) {
+        (!t || e < this.tileSize) && (t = 0),
+        (!i || e < this.tileSize) && (i = 0),
+        e < this.tileSize && (e = this.tileSize);
+        var level = TileTree.getLevelCountForSize(this.tileSize, e),
+            o = l(this.root, 0, level, t, i);
+        return o
+    }
+
+    breadthFirst(e) {//广度优先搜索
+        e = e || {};
+        var t = !!e.nullLevelEnd,
+            i = e.maxLevel,
+            n = e.minLevel,
+            r = e.callback,
+            o = e.saveVisited,
+            a = [],
+            s = {},
+            l = 0,
+            c = 0;
+        for (a.push(this.root),
+            a.push(s); a.length > 0 && !(i && l > i);) {
+            var h = a.shift();
+            if (h === s)
+                (!n || l >= n) && (r && t && r(null),
+                    o && t && o.push(null)),
+                a.length > 0 && a.push(s),
+                l++,
+                c = 0;
+            else {
+                if (h.children)
+                    for (var u = 0; u < h.children.length; u++) {
+                        var d = h.children[u];
+                        d && a.push(h.children[u])
+                    }
+                var p = this.getFaceIndexFromNode(h);
+                (!n || l >= n) && (r && r(h, l, p),
+                    o && o.push(h)),
+                c++
+            }
+        }
+    }
+
+    getFaceIndexFromNode(e) {
+        if (!e)
+            return -1;
+        for (var t = 1, i = e, n = 0, r = 0;;) {
+            var o = i.parent;
+            if (!o)
+                break;
+            for (var a = -1, s = 0; s < o.children.length; s++)
+                o.children[s] === i && (a = s);
+            var l = a % c,
+                h = Math.floor(a / c);
+            n = l * t + n,
+                r = h * t + r,
+                t *= c,
+                i = o
+        }
+        return r * t + n
+    }
+
+    depthFirst(e, t, i) {
+        o(this.root, 0, 0, 0, e, t, i, this.tileSize)
+    }
+}
+
+TileTree.TraversalType = Object.freeze({
+    PreOrder: 0,
+    PostOrder: 1
+});
+
+TileTree.getLevelCountForSize = function(tileSize, size) {//512->0 2024->1
+    var i = 0;
+    for (size < tileSize && (size = tileSize);;) {
+        if (size /= c,
+            size < tileSize)
+            break;
+        i++
+    }
+    return i
+};
+
+TileTree.getSizeForLevel = function(e, t) {
+    return Math.pow(c, t) * e
+};

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

@@ -0,0 +1,282 @@
+import {GLCubeFaces} from '../../../defines'
+import MathLight from '../../../utils/MathLight'
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
+
+
+
+var TileUtils = {};
+TileUtils.TILE_SIZE = 512,
+TileUtils.FACES_PER_PANO = 6,
+TileUtils.LocationOnTile = {
+    Center: 0,
+    UpperLeft: 1,
+    UpperRight: 2,
+    LowerRight: 3,
+    LowerLeft: 4
+},
+
+
+/*
+ * 获取某tile在cube中的方向 direction (向量起点在cube中心,终点在tile图的指定位置)。spherical通过先求uv,再直接得到dir
+ * @param {*} size 面分辨率
+ * @param {*} cubeFace 所在面
+ * @param {*} Center 在tile上的目标位置,默认为中心,其他位置就是四个顶点
+ * @param {*} c 似乎是在tile的缩进百分比,根据所在面的不同,分别向不同方向缩进,但都是向tile的中心
+ * @param {*} dir 所求方向
+ */
+
+TileUtils.getTileVector = function() {//获取某tile在cube中的方向 direction (向量起点在cube中心,终点在tile图的中心)
+    return function(size, tileSize, cubeFace, tileX, tileY, Center, c, dir) {//c似乎是缩进百分比
+       
+        Center = Center || TileUtils.LocationOnTile.Center;
+        
+        //假设该cube边长为2:
+        var u = size / tileSize
+            , d = tileX / u;
+        tileY = -tileY + (u - 1);
+        var p = tileY / u
+            , f = tileSize / size
+            , g = 2 * f //一个tile的宽度   (乘以2是因为cube边长是2)
+            , m = g / 2
+            , v = 2 * d - 1 + m
+            , A = 2 * p - 1 + m;
+            
+        switch (Center) {//计算在tile中指定位置带来的偏移 
+            case TileUtils.LocationOnTile.UpperLeft:        //1
+                v -= m,
+                A += m,
+                v += c * g; //似乎是向内缩进
+                break;
+            case TileUtils.LocationOnTile.UpperRight:
+                v += m,
+                A += m,
+                A -= c * g;
+                break;
+            case TileUtils.LocationOnTile.LowerRight:
+                v += m,
+                A -= m,
+                v -= c * g;
+                break;
+            case TileUtils.LocationOnTile.LowerLeft:
+                v -= m,
+                A -= m,
+                A += c * g;
+                break;
+            case TileUtils.LocationOnTile.Center:   //0
+        }
+        switch (cubeFace) {
+            case GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+                MathLight.setVector(dir, -1, A, -v);
+                break;
+            case GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+                MathLight.setVector(dir, 1, A, v);
+                break;
+            case GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_Y: //顶面
+                MathLight.setVector(dir, -v, 1, -A);
+                break;
+            case GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+                MathLight.setVector(dir, -v, -1, A);
+                break;
+            case GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+                MathLight.setVector(dir, -v, A, 1);
+                break;
+            case GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+                MathLight.setVector(dir, v, A, -1)
+        }
+        MathLight.normalize(dir)
+    }
+}(),
+
+/*
+ * 获取该tile在第几个面(简易装载法) 
+ */
+TileUtils.getFaceForTile = function(size, index) {//获取该tile在第几个面
+    var tileSize = TileUtils.TILE_SIZE;
+    size < TileUtils.TILE_SIZE && (tileSize = size);
+    var n = Math.floor(size / tileSize)
+        , sum = n * n; //得每个面tile总数
+    return Math.floor(index / sum)
+}
+,
+
+
+TileUtils.getTileLocation = function(size, t, result) {
+    var tileSize = TileUtils.TILE_SIZE;
+    size < TileUtils.TILE_SIZE && (tileSize = size);
+    var r = TileUtils.getFaceForTile(size, t)
+        , a = Math.floor(size / tileSize)
+        , s = a * a
+        , l = t - r * s;
+    result.tileX = l % a;
+    result.tileY = Math.floor(l / a);
+    result.face = r;
+    result.faceTileIndex = l;
+}
+,
+
+/*
+ * 求size分辨率需要多少张tile
+ */
+TileUtils.getTileCountForSize = function(e) {
+    if (e <= TileUtils.TILE_SIZE)
+        return TileUtils.FACES_PER_PANO;
+    var t = Math.floor(e / TileUtils.TILE_SIZE)
+        , i = t * t
+        , n = i * TileUtils.FACES_PER_PANO;
+    return n
+}
+,
+TileUtils.getRelativeDirection = function() {
+    var e = new MathLight.Matrix4
+        , t = new MathLight.Quaternion;
+    return function(i, n) {//i是pano.quaternion,  n是camera的direction  
+        t.copy(i),
+        t.inverse(),
+        e.makeRotationFromQuaternion(t),
+        e.applyToVector3(n),
+        MathLight.normalize(n)
+    }
+}(),
+
+/*
+ * 根据方向寻找合适的tile加载
+ */
+TileUtils.matchingTilesInDirection = function() {
+    var e = new MathLight.Vector3
+        , t = new MathLight.Vector3(0,0,-1)
+        , i = new MathLight.Quaternion
+        , n = function(e, t) {
+            e.push({
+                face: t.face,
+                faceTileIndex: t.faceTileIndex,
+                tileX: t.tileX,
+                tileY: t.tileY
+            })
+        }
+        , a = function() {
+            var e = {
+                face: -1,
+                faceTileIndex: -1,
+                tileX: -1,
+                tileY: -1
+            };
+            return function(size, i, r) {
+                for (var a = TileUtils.getTileCountForSize(size), s = 0, l = 0; l < a; l++)
+                    TileUtils.getTileLocation(size, l, e),
+                    i && !i(e) || (s++,
+                    r && n(r, e));
+                return s
+            }
+    }();
+    return function(pano, size, dir, hFov, vFov, result) {
+        var d = size < TileUtils.TILE_SIZE ? size : TileUtils.TILE_SIZE;
+        //TileUtils.getTileCountForSize(size);
+        if (!hFov && !vFov)
+            return a(size, null, result);
+        var p = !!vFov;
+        vFov = vFov || hFov,
+        vFov = Math.max(0, Math.min(vFov, 360)),
+        hFov = Math.max(0, Math.min(hFov, 360)),
+        MathLight.copyVector(dir, e),
+        TileUtils.getRelativeDirection(pano.quaternion4dkk, e)
+        if(p){//如果有vFov hFov
+            i.setFromUnitVectors(e, t);
+            var f = function(e) {
+                return TileUtils.isTileWithinFrustum(size, d, e.face, e.tileX, e.tileY, i, hFov, vFov)//在视野中的
+            };
+            return a(size, f, result)
+        }
+        var g = function(t) {//如果仅有hFov
+            return TileUtils.isTileWithinFOV(size, d, t.face, t.tileX, t.tileY, e, hFov)
+        };
+        return a(size, g, result)
+    }
+}(),
+
+/*
+ * 是否在屏幕范围内
+ */ 
+TileUtils.isTileWithinFrustum = function() {
+    var e = new MathLight.Vector3
+        , t = 1e-5;
+    return function(i, n, a, s, l, c, h, u) {
+        for (var d = Math.tan(.5 * u * MathLight.RADIANS_PER_DEGREE), p = -d, f = Math.tan(.5 * h * MathLight.RADIANS_PER_DEGREE), g = -f, m = TileUtils.mapFaceToCubemapFace(a), v = 0, A = 0, y = 0, C = 0, I = 0, E = 0, b = TileUtils.LocationOnTile.Center; b <= TileUtils.LocationOnTile.LowerLeft; b++){
+            TileUtils.getTileVector(i, n, m, s, l, b, 0, e),//get e   //  size, tileSize, cubeFace, tileX, tileY, Center, c, dir
+            MathLight.applyQuaternionToVector(c, e)      
+            if (e.z >= -t)//似乎是在相机背面
+                I++;
+            else { 
+                var w = -1 / e.z
+                    , _ = e.x * w
+                    , T = e.y * w;
+                T > d ? v++ : T < p && A++,  //这四种似乎代表在这个画框之外,如在左、在上、在下、在右
+                _ > f ? y++ : _ < g && C++,
+                E++
+            }
+        }
+        return A !== E && v !== E && y !== E && C !== E  //如果有一项和E相等代表要么是在相机背面要么是tile的四个顶点都画在画布的同一边,所以肯定不在画布上
+    }
+}(),
+
+
+/*
+ * 是否在FOV范围内
+ */
+TileUtils.isTileWithinFOV = function() {
+    var e = new MathLight.Vector3
+        , t = new MathLight.Vector3(0,1,0)
+        , i = new MathLight.Vector3(1,0,0);
+    return function(panoSize, tileSize, face, tileX, tileY, direction, fov) {//direction是作用了pano.quaternion的camera.direction
+        var d = TileUtils.mapFaceToCubemapFace(face);
+        MathLight.cross(direction, t, i) //get i  好像没用到
+        TileUtils.getTileVector(panoSize, tileSize, d, tileX, tileY, TileUtils.LocationOnTile.Center, 0, e) 
+        if (TileUtils.isWithinFOV(e, direction, fov, null))//先判断tile中心在不在FOV内
+            return !0;
+        for (var p = fov / 360, f = Math.floor(1 / p), g = 0, m = 0; m < f; m++) {
+            for (var v = TileUtils.LocationOnTile.UpperLeft; v <= TileUtils.LocationOnTile.LowerLeft; v++)
+                if (TileUtils.getTileVector(panoSize, tileSize, d, tileX, tileY, v, g, e),
+                TileUtils.isWithinFOV(e, direction, fov, null))
+                    return !0;
+            g += p   //可能是考虑到有可能tile比fov覆盖了fov(虽然一般不可能,除非fov特别小),所以将tile分成若干段,取tile中的点再检测下
+        }
+        return !1
+    }
+}(),
+
+
+TileUtils.isWithinFOV = function() {
+    var e = new MathLight.Vector3
+        , t = new MathLight.Vector3;
+    return function(dir, cameraDir, fov, a) {
+        if (MathLight.copyVector(dir, t),
+        a) {
+            MathLight.copyVector(a, e),
+            MathLight.normalize(e);
+            var s = MathLight.dot(e, dir);
+            e.x *= s,
+            e.y *= s,
+            e.z *= s,
+            MathLight.subVector(t, e)
+        }
+        var l = fov / 2 * MathLight.RADIANS_PER_DEGREE
+            , c = Math.cos(l)
+            , h = MathLight.dot(t, cameraDir);
+        return h >= c
+    }
+}(),
+
+TileUtils.mapFaceToCubemapFace = function() {
+    var e = {
+        0: GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+        1: GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+        2: GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+        3: GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+        4: GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+        5: GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
+    };
+    return function(t) {
+        return e[t]
+    }
+}();
+
+export default TileUtils

+ 168 - 0
src/modules/clipModel/Clip.js

@@ -0,0 +1,168 @@
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import {BoxVolume} from '../../utils/Volume'
+import {  ClipTask, ClipMethod} from "../../defines.js"
+import {mapClipBox} from '../../utils/mapClipBox'
+import Common from '../../utils/Common'
+import {Images360} from '../Images360/Images360'
+
+
+const defaultBoxWidth = 6;  //navvis:  10
+                           //navvis position: si {x: 0, y: 0, z: 0}
+
+var Clip = {
+   
+    enter:function(){
+        this.previousView = { 
+			position: viewer.images360.position,
+			target: viewer.scene.view.getPivot(),
+            displayMode : Potree.settings.displayMode,
+            //---
+            ifShowMarker : Potree.settings.ifShowMarker,
+            
+		} 
+        let bound = viewer.scene.pointclouds[0].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 position = new THREE.Vector3().addVectors(target, eyeDir)
+        
+        Potree.settings.displayMode = 'showPointCloud'
+        viewer.setView({
+            position ,
+            target,
+            duration:300,
+            callback:function(){ 
+            }
+        })
+        viewer.setControls(viewer.orbitControls);
+        viewer.setLimitFar(false)
+         
+        
+        
+        {
+            this.box = new BoxVolume({
+                clip:true
+                
+            })  
+            this.box.name = "ClipBox"; 
+            this.box.position.copy(target)
+            this.box.scale.copy(scale)
+            //带动mapBox
+            this.box.addEventListener('position_changed',e=>{
+                this.mapBox.center.setX(this.box.position.x)
+                this.mapBox.center.setY(this.box.position.y)
+                this.mapBox.updatePoints() 
+            })
+            this.box.addEventListener('scale_changed',e=>{
+                var scale = this.box.scale 
+                this.mapBox.updatePoints(scale) 
+            })
+            this.box.addEventListener('orientation_changed',e=>{
+                this.mapBox.angle = this.box.rotation.z
+                this.mapBox.rotateBar.rotation.z = this.mapBox.angle
+                this.mapBox.updatePoints()
+            })
+            viewer.scene.addVolume(this.box);
+            
+        }
+        
+        {
+            let boxRotateBack = ()=>{//不知道是不是这么写。 因为可能z的旋转不一定都在z 
+                this.box.rotation.x = 0;
+                this.box.rotation.y = 0;
+            }
+            this.mapBox = new mapClipBox(target, scale)
+            viewer.mapViewer.scene.add(this.mapBox)
+            //带动box
+            this.mapBox.addEventListener('repos',e=>{
+                this.box.position.setX(this.mapBox.center.x)
+                this.box.position.setY(this.mapBox.center.y)
+                boxRotateBack()
+            })
+            this.mapBox.addEventListener('dragChange',e=>{
+                var scale = this.mapBox.getScale() 
+                this.box.scale.setX(scale.x)
+                this.box.scale.setY(scale.y)
+                this.box.position.setX(this.mapBox.center.x)
+                this.box.position.setY(this.mapBox.center.y)
+                boxRotateBack()
+            })
+            this.mapBox.addEventListener('rotate',e=>{ 
+                this.box.rotation.z = this.mapBox.angle 
+                boxRotateBack()
+            })
+        }
+        
+        
+        
+        
+        {
+            viewer.setClipTask(ClipTask["SHOW_INSIDE"])
+            //viewer.setClipMethod(ClipMethod["INSIDE_ANY"])  
+        }
+        
+        Potree.settings.unableNavigate = true
+        Potree.settings.ifShowMarker = false
+        viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', false)  
+        viewer.inputHandler.toggleSelection(this.box);
+        viewer.inputHandler.fixSelection = true
+        viewer.transformationTool.frame.material.color.set(Potree.config.clip.color)//navvis 15899953 
+         
+    },
+    
+    leave:function(){
+        viewer.inputHandler.fixSelection = false
+        viewer.scene.removeVolume(this.box);
+        
+        this.mapBox.dispose()
+        viewer.setControls(viewer.fpControls);
+        
+        Potree.settings.unableNavigate = false
+        Potree.settings.ifShowMarker = this.previousView.ifShowMarker
+        viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', true)  
+        
+        viewer.setView(this.previousView)
+        viewer.setLimitFar(true)
+    },
+    
+    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)
+        return target
+    },
+    /* switchMap:function(state){
+        
+        
+    }, */
+    
+    download:function(){
+        var visiPointclouds = viewer.scene.pointclouds.filter(e=> viewer.getObjVisiByReason(e, 'datasetSelection'))
+        let data = {   
+            transformation_matrix: visiPointclouds.map((cloud)=>{
+                return {
+                    id: cloud.dataset_id,
+                    matrix: this.getTransformationMatrix(cloud).elements,
+                    modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
+                } 
+            }) ,
+            aabb: "b-0.5 -0.5 -0.5 0.5 0.5 0.5" //剪裁空间( 所有点在乘上这个矩阵后, 还能落在 1 * 1 * 1的box内的点就是所裁剪的
+           
+        }
+        console.log(data)
+        return data
+        //https://testlaser.4dkankan.com/indoor/t-ia44BhY/api/pointcloud/crop
+    },
+    
+    getTransformationMatrix:function(pointcloud) {//剪裁矩阵
+        var invMatrix = new THREE.Matrix4().getInverse(this.box.matrixWorld) 
+        return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose()
+    }
+}
+
+
+
+export {Clip} 

+ 351 - 0
src/modules/siteModel/BuildingBox.js

@@ -0,0 +1,351 @@
+
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import {ctrlPolygon} from '../../utils/ctrlPolygon'
+import {LineDraw, MeshDraw } from "../../utils/DrawUtil";
+import Sprite from '../../viewer/Sprite'
+/* import {config} from '../settings' */
+
+
+let texLoader = new THREE.TextureLoader() 
+
+ 
+let markerMats
+let markerSizeInfo = {width2d:30}
+let color = new THREE.Color('#FFF')
+
+
+let faceMats = {
+    building: new THREE.MeshPhongMaterial({
+        color:"#00bbff",
+        side:THREE.DoubleSide,
+        opacity:0.1,
+        transparent:true,
+        depthTest:false
+    }),
+    floor:  new THREE.MeshPhongMaterial({
+        color:"#eeee00",
+        side:THREE.DoubleSide,//BackSide,
+        opacity:0.06,
+        transparent:true,
+        depthTest:false
+    }),
+    floorSelect: new THREE.MeshPhongMaterial({
+        color:"#eeee00",
+        side:THREE.DoubleSide,
+        opacity:0.15,
+        transparent:true,
+        depthTest:false
+    }),
+    room: new THREE.MeshPhongMaterial({
+        color:"#ff44ee",
+        side:THREE.DoubleSide,//BackSide,
+        opacity:0.06,
+        transparent:true,
+        depthTest:false
+    }),
+    roomSelect: new THREE.MeshPhongMaterial({
+        color:"#ff44ee",
+        side:THREE.DoubleSide,//BackSide,
+        opacity:0.15,
+        transparent:true,
+        depthTest:false
+    }),
+    
+}
+
+
+
+export class BuildingBox extends ctrlPolygon{//建筑实体,包括building, floor, room
+    constructor(prop) {
+        prop.dimension = '3d'
+        super('siteModel_'+prop.type_,  prop);
+        this.midMarkers = []
+        
+        if(this.type_=='floor'){//一旦添加了楼层,建筑原本的faceMesh lineMesh就隐藏
+            /* this.buildParent.box.visible = false 
+            this.buildParent.lineMesh.visible = false  */
+            
+            this.points = this.buildParent.points;//完全等于建筑的点
+        } 
+        
+        this.box = this.createBox()
+        this.add(this.box)
+        
+        this.lineMesh = LineDraw.createLine([],{color})
+        this.lineMesh.name = 'buildingLines'        
+        this.add(this.lineMesh) 
+        viewer.setObjectLayers(this.lineMesh, 'sceneObjects' )
+        
+        this.buildChildren = []//子实体
+        
+        this.addEventListener('dragChange',(e)=>{ //修改中点
+            this.isNew || this.updateTwoMidMarker(e.index)
+        })
+   
+        
+    } 
+    
+    
+    
+    createBox(){ 
+        var geometry = new THREE.Geometry();
+        var mesh = new THREE.Mesh(geometry, faceMats[this.type_])
+        mesh.name = 'buildingBox';
+        if(this.type_ == 'floor'){
+            viewer.setObjectLayers(mesh, 'siteModelMapUnvisi' )
+        }else{
+            viewer.setObjectLayers(mesh, 'sceneObjects' )
+        }
+        return mesh
+    }
+    
+    
+    
+    addMarker(o={} ){
+        if(this.type_=='floor')return; //楼层不需要marker
+        
+        let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_marker"} )
+        
+        marker.renderOrder = 3 
+         
+        viewer.setObjectLayers(marker, 'siteModeOnlyMapVisi' ) 
+        
+        o.marker = marker
+        super.addMarker(o)
+        
+         
+        return marker
+    }
+    
+    
+    
+    addMidMarker(index, point){
+        if(this.type_=='floor')return; //楼层不需要marker
+        let marker = new Sprite({mat:this.getMarkerMaterial('midPrepare'), sizeInfo: markerSizeInfo, dontFixOrient: true, name:"building_midMarker"} )
+        this.midMarkers = [...this.midMarkers.slice(0,index), marker, ...this.midMarkers.slice(index,this.midMarkers.length)]
+        
+        marker.renderOrder = 3 
+         
+        viewer.setObjectLayers(marker, 'siteModeOnlyMapVisi' ) 
+        { // Event Listeners  
+            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"
+                })
+            }
+            let drag = (e) => {
+                let index = this.midMarkers.indexOf(marker)
+                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) 
+                viewer.inputHandler.startDragging(newMarker , {/* dragViewport:viewer.mapViewer.viewports[0],  */   } ); //notPressMouse代表不是通过按下鼠标来拖拽.  dragViewport指定了只能在地图上拖拽
+            }
+            marker.addEventListener('drag', drag );
+            //marker.addEventListener('drop', drop);
+            marker.addEventListener('mouseover', mouseover);
+            marker.addEventListener('mouseleave', mouseleave);
+        }
+        this.add(marker)
+        this.updateMarker(marker, point)
+        return marker
+    }
+    
+    
+    
+    addMidMarkers(){//第一次画好所有marker后,一次性为线段增加中点marker
+        let length = this.points.length
+        this.points.forEach((point,index)=>{
+            let nextPoint = this.points[(index+1)%length]
+            let midPoint = new THREE.Vector3().addVectors(point, nextPoint).multiplyScalar(0.5)
+            this.addMidMarker(index, midPoint )
+        })
+    }
+    
+    updateTwoMidMarker(index){//更新第index个marker两边的midMarker
+        let length = this.points.length
+        let last = this.points[(index-1+length)%length] //它之前的marker位置
+        let next = this.points[(index+1)%length];//它之后的marker位置
+        let current = this.points[index]//当前位置 
+        let lastMid = new THREE.Vector3().addVectors(last, current).multiplyScalar(0.5)//上一个中点
+        let nextMid = new THREE.Vector3().addVectors(next, current).multiplyScalar(0.5)//下一个中点
+        let lastMidMarker = this.midMarkers[(index-1+length)%length];
+        let nextMidMarker = this.midMarkers[index]
+        this.updateMarker(lastMidMarker, lastMid) 
+        this.updateMarker(nextMidMarker, nextMid) 
+    }
+    
+    dispose(){
+        super.dispose()
+        this.box.geometry.dispose();
+        this.lineMesh.geometry.dispose();
+        
+        
+        this.buildChildren.forEach(e=>e.dispose())
+    }
+    
+    update(ifUpdateMarkers, dontUpdateChildren){ 
+        super.update(ifUpdateMarkers)
+        
+        
+        this.box.geometry.dispose()
+        let length = this.points.length 
+        if(length >= 3){ 
+            this.box.geometry = MeshDraw.getExtrudeGeo(this.points, this.zMax-this.zMin) 
+            this.box.position.z = this.zMin
+        }
+        
+       /*  if(!this.isNew){
+            this.midMarkers
+        }
+         */
+        
+        {//update lines
+            //let zMin = 0, zMax = 1
+            let positions = [];
+            
+            this.points.forEach((point, index)=>{
+                
+                //竖线:
+                positions.push(point.clone().setZ(this.zMin), point.clone().setZ(this.zMax))
+                
+                //横线
+                let nextPoint = this.points[(index+1)%length]; 
+                if(point == nextPoint)return;//when length==1
+                
+                positions.push(point.clone().setZ(this.zMax), nextPoint.clone().setZ(this.zMax))//上横线
+                positions.push(point.clone().setZ(this.zMin), nextPoint.clone().setZ(this.zMin))//下横线
+            })
+            
+            LineDraw.moveLine(this.lineMesh,positions)
+             
+        }
+        
+        
+        if(this.type_ == 'building' && !dontUpdateChildren){
+            this.buildChildren.forEach(e=>{
+                e.points = this.points
+                e.update()
+            })
+            
+        }
+        
+        
+        
+    }
+    
+    removeMarker(index ){  
+        super.removeMarker(index) 
+        this.update(); 
+        if(this.points.length == 2){//清除原先length>=3时候的
+            this.box.geometry = new THREE.Geometry();
+        }
+        //this.dispatchEvent({type: 'marker_removed', measurement: this});
+    }
+    
+    
+    
+    
+    getMarkerMaterial(type) {
+        if(!markerMats){
+            markerMats = {  
+                default:    new THREE.MeshBasicMaterial({  
+                    transparent: !0,
+                    color,
+                    opacity: 0.8,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ),  
+                    depthTest:false,
+                    
+                }),
+                midPrepare:    new THREE.MeshBasicMaterial({ //线中心的半透明点  
+                    transparent: !0,
+                    color,
+                    opacity: 0.4,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
+                    depthTest:false,
+                }),  
+                select:    new THREE.MeshBasicMaterial({   
+                    transparent: !0,
+                    color,
+                    opacity: 1,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
+                    depthTest:false,
+                     
+                }),   
+            }
+            
+        }
+        return markerMats[type]
+        
+    }
+    
+    
+    setMarkerSelected(marker, state, hoverObject){ 
+        //console.warn(marker.id , state, hoverObject)
+        if(state){
+            marker.material = this.getMarkerMaterial('select')
+        }else{
+            if(marker.name.includes('mid')){
+                marker.material = this.getMarkerMaterial('midPrepare')
+            }else{
+                marker.material = this.getMarkerMaterial('default')
+            } 
+        }
+    }
+    
+    
+    select(){
+        if(this.selected)return
+        
+        
+        if(this.type_ == 'building'){
+            this.buildChildren.forEach(e=>{
+                e.lineMesh.visible = true
+            })
+            
+        }else if(this.type_ == 'floor'){
+            
+            this.box.material = faceMats.floorSelect
+            viewer.setObjectLayers(this.box, 'sceneObjects' )
+             
+        }else if(this.type_ == 'room'){
+            this.box.material = faceMats.roomSelect
+        }
+        this.lineMesh.visible = true
+        this.markers && this.markers.forEach(e=>viewer.updateVisible(e,'select',true) )
+        this.midMarkers && this.midMarkers.forEach(e=>e.visible = true)
+        
+        this.selected = true
+        
+    }
+    
+    
+    unselect(){
+        
+        if(this.type_ == 'building'){ 
+            this.buildChildren.forEach(e=>{
+                e.lineMesh.visible = false
+            })  
+        }else if(this.type_ == 'floor'){ 
+            viewer.setObjectLayers(this.box, 'siteModelMapUnvisi' )
+            this.box.material = faceMats.floor
+        }else if(this.type_ == 'room'){
+            this.box.material = faceMats.room
+        }
+        this.lineMesh.visible = false
+        this.markers && this.markers.forEach(e=>viewer.updateVisible(e,'select',false) )
+        this.midMarkers && this.midMarkers.forEach(e=>e.visible = false)
+        
+        this.selected = false
+    }
+    
+    
+     
+}

+ 571 - 0
src/modules/siteModel/SiteModel.js

@@ -0,0 +1,571 @@
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import SplitScreen from "../../utils/SplitScreen"
+import {BuildingBox} from "./BuildingBox"
+
+const minFloorHeight = 0.5
+
+var SiteModel = {
+    
+    entities:[],  //所有实体
+    buildings:[], //所有建筑父集
+    meshGroup: new THREE.Object3D,
+     
+    
+    init: function(){
+        viewer.scene.scene.add(this.meshGroup) 
+        this.meshGroup.name = 'siteModel' 
+        this.SplitScreen = SplitScreen
+        
+        this.createHeightPull();
+        
+    },
+    
+    enter:function(){
+        let mapViewport = viewer.mapViewer.viewports[0]
+        SplitScreen.splitScreen4Views({siteModel:true/* , viewports:[{name:'Top',viewport : mapViewport  }] */})
+        
+        
+        viewer.viewports.forEach(e=>{
+            if(e.name != 'mapViewport'){
+                e.layersAdd('siteModelMapUnvisi') 
+            }
+            if(e.name == 'Right' || e.name == 'Back'){
+                e.layersAdd('siteModeSideVisi') 
+            }
+        })   
+        
+        
+        
+        mapViewport.layersAdd('siteModeOnlyMapVisi') //只有mapViewport能看到marker
+       
+        
+         
+        
+    },
+    
+    
+    
+    leave:function(){
+        let mapViewport = viewer.mapViewer.viewports[0]
+        SplitScreen.recoverFrom4Views()
+       
+         
+        mapViewport.layersRemove('siteModeOnlyMapVisi') 
+        
+         
+        {//清空
+            /* entities:[],  //所有实体
+            buildings:[], //所有建筑父集
+            meshGroup: new THREE.Object3D, */
+            this.selectEntity(null)
+            
+            let length = this.buildings.length;
+            for(let i=0;i<length;i++){
+                this.buildings[i].dispose()
+            }
+            
+            this.entities = []
+            this.buildings = []
+            
+            
+        }
+    } 
+    
+    ,
+    
+    
+    
+     
+    addFloor:function(parent, dirType){//dirType:'top'|'bottom'在上方建还是下方。如果建筑中没有楼层,默认在基底建一个
+        let type_ = 'floor' 
+        let zMin, zMax
+        if(parent.buildChildren.length == 0){
+            zMin = parent.zMin
+            zMax = zMin + Potree.config.siteModel.floorHeightDefault
+        }else{
+            if(dirType == 'bottom'){
+                //var btm = Common.find(parent.buildChildren,null,[e=>e.zMin]) 
+                var btm = parent.buildChildren[0]
+                zMax = btm.zMin
+                zMin = zMax - Potree.config.siteModel.floorHeightDefault 
+            }else{
+                //var top = Common.find(parent.buildChildren,null,[e=>e.zMax]) 
+                var top = parent.buildChildren[parent.buildChildren.length - 1]
+                zMin = top.zMax 
+                zMax = zMin + Potree.config.siteModel.floorHeightDefault
+            }
+            
+        }
+       
+        
+        let prop = {
+            type_,
+            name : Potree.config.siteModel.names[type_],
+            zMin,
+            zMax,
+            buildParent:parent,
+             
+        }
+        var floor = new BuildingBox(prop);
+        parent.buildChildren.push(floor)
+        this.meshGroup.add(floor);
+        this.entities.push(floor)
+        
+        floor.update()
+        this.updateBuildingZ(parent)
+        
+    },
+     
+   
+    
+    startInsertion:function(type_, parent, callback, cancelFun){
+        
+        let zMin, zMax
+        if(type_ == 'building'){ 
+            zMin = viewer.bound.boundingBox.min.z
+            zMax = viewer.bound.boundingBox.min.z
+        }else if(type_ == 'room'){
+            zMin = parent.zMin
+            zMax = parent.zMax
+        }
+        
+        let prop = { 
+            type_,
+            name : Potree.config.siteModel.names[type_],//'building',
+            zMin, 
+            zMax,
+            buildParent:parent 
+        }
+        let mapViewport = viewer.mapViewer.viewports[0]
+        let minMarkers = 3
+        
+        
+        let entity = new BuildingBox(prop);
+        entity.isNew = true
+        //entity.editStateChange(true) 
+        
+        
+        let timer;
+        
+        let continueDrag = (marker)=>{
+            timer = setTimeout(()=>{//等 drag=null之后 //右键拖拽结束后需要重新得到drag
+                //console.log('continueDrag')
+                viewer.inputHandler.startDragging(marker,
+                    {endDragFun, notPressMouse:true} 
+                ); 
+            },1)  
+        }
+
+		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状态
+                continueDrag(marker)  
+			} else if (e.button === THREE.MOUSE.RIGHT ) {
+				if(e.pressDistance < Potree.config.clickMaxDragDis )end(e);//非拖拽的话
+                else continueDrag(e.drag.object)
+                 
+			}
+		};
+
+		let end = (e={}) => {//确定、结束
+ 
+            if(!e.complete && entity.markers.length<=minMarkers){//右键  当个数不够时取消
+                 
+                //重新开始画
+                entity.reDraw(1)
+                 
+                viewer.updateVisible(entity.markers[0],'unMove',false);
+                var f = ()=>{
+                    viewer.updateVisible(entity.markers[0],'unMove',true); 
+                    entity.removeEventListener('dragChange',f)
+                }
+                entity.addEventListener('dragChange',f) 
+                
+                console.log('waitcontinue')
+                continueDrag(entity.markers[0])
+                return
+                
+                 
+            } 
+             
+            if (!e.complete && entity.markers.length > 3) {
+				entity.removeMarker(entity.points.length - 1); 
+			}
+            entity.isNew = false
+            entity.addMidMarkers()
+            if(type_ == 'room'){
+                this.fitPullBox()
+            }
+            clearTimeout(timer) 
+			viewer.removeEventListener('cancel_insertions', Exit);
+            pressExit && viewer.inputHandler.removeEventListener('keydown', pressExit);
+            callback && callback() 
+            
+            /* this.viewer.dispatchEvent({
+                type: 'finish_inserting_polygonment',
+                entity: entity
+            }); */
+		};
+
+        
+        let Exit = (e)=>{//模拟右键点击 
+            if(viewer.inputHandler.drag){//还未触发drop的话
+                viewer.inputHandler.drag.object.dispatchEvent({
+                    type: 'drop',
+                    drag: viewer.inputHandler.drag, 
+                    viewer: viewer,
+                    pressDistance:0,
+                    button : THREE.MOUSE.RIGHT  
+                });
+                viewer.inputHandler.drag = null 
+            }else{
+                end()  //未结束时添加新的polygon时会触发
+            }
+        }
+        viewer.addEventListener('cancel_insertions', 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);
+         
+             
+        window.marker = marker           
+             
+        this.addEntity(entity, parent)
+		
+		return entity;
+        
+         
+        
+        
+    },
+    
+    
+    
+    addEntity:function(entity, parent){ 
+        this.meshGroup.add(entity);
+        this.entities.push(entity)
+        if(entity.type_ == 'building'){
+            this.buildings.push(entity)
+        }else{
+            if(entity.type_ == 'room'){
+                entity.retrictArea = parent
+            }
+            
+            parent.buildChildren.push(entity)
+        }   
+        
+        if(entity.isNew){
+            this.selectEntity(entity)
+            var f = ()=>{
+                viewer.updateVisible(marker,'unMove',true); 
+                entity.removeEventListener('dragChange',f)
+            }
+            entity.addEventListener('dragChange',f) 
+        }
+         
+    
+        if(entity.type_ == 'room'){
+            entity.addEventListener('marker_dropped',()=>{
+                this.fitPullBox()
+            })  
+            
+        }
+    
+    },
+    removeEntity:function(){
+        
+    },
+    
+    
+    
+    updateBuildingZ:function(building){
+        building.buildChildren = building.buildChildren.sort((e,a)=>e.zMin-a.zMin)//从低到高排序
+        building.zMin = building.zMax = building.buildChildren[0].zMin  //基底高度
+        //building.zMax = building.buildChildren[building.buildChildren.length-1].zMax
+        building.update(false,true)
+    },
+   
+    
+    
+    selectEntity : function(entity){
+        if(this.selected == entity)return
+        //this.buildings.forEach(e=>e.unselect())
+        this.selected && this.selected.unselect()
+        this.height_pull_box.visible = false
+        
+        
+        if(entity){
+            entity.select()
+            if(entity.type_ == 'floor' || entity.type_ == 'room' ){
+                this.height_pull_box.visible = true 
+                this.fitPullBox()
+            } 
+        }
+         
+        
+        this.selected = entity
+        
+        
+        
+        
+        
+    },
+    
+    
+    /* selectFloor:function(floor){ 
+        this.buildings.forEach(e=>e.unselect())
+        floor.select()
+        this.selected = floor
+        this.height_pull_box.visible = true
+        
+        this.fitPullBox()
+    },
+    selectBuilding:function(building){
+        this.buildings.forEach(e=>e.unselect())
+        building.select()
+    }
+    selectRoom:function(room){
+        this.buildings.forEach(e=>e.unselect())
+        room.select()
+    }
+     */
+    
+    
+    
+    
+    fitPullBox: function(){ //自适应拖拽楼层的pullMesh
+        let bound = new THREE.Box3();
+        bound.expandByObject(this.selected.box)
+        let center = bound.getCenter(new THREE.Vector3() )  
+        let size = bound.getSize(new THREE.Vector3() )
+        this.height_pull_box.scale.copy(size)
+        this.height_pull_box.position.copy(center) 
+    },
+    
+    
+    removeEntity : function(e){
+        var index = e.buildParent.buildChildren.indexOf(e);
+        e.buildParent.buildChildren.splice(index,1)
+        e.dispose()
+        
+    },
+    
+    
+    createHeightPull:function(){ //拖拽楼层的bounding box
+        let boxGeo = new THREE.BoxBufferGeometry( 1, 1, 1/4 )
+        let boxMat = new THREE.MeshPhongMaterial({
+            color:"#F00", 
+            opacity:0.3,
+            transparent:true,
+            depthTest:false
+        }) 
+        
+        let height_pull_box_up = new THREE.Mesh(boxGeo,boxMat)
+        let height_pull_box_down = new THREE.Mesh(boxGeo,boxMat)
+        height_pull_box_up.name = 'height_pull_box_up';
+        height_pull_box_down.name = 'height_pull_box_down';
+        this.height_pull_box = new THREE.Object3D(); 
+        this.height_pull_box.add(height_pull_box_up)
+        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)
+        viewer.setObjectLayers(this.height_pull_box, 'siteModeSideVisi' )
+        
+        
+        
+        let mouseover = (e)=>{
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"siteModelFloorDrag"
+            }) 
+        }
+        let mouseleave = (e)=>{
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"siteModelFloorDrag"
+            }) 
+        }
+        
+        
+        let firstZ;
+        let drag = (e)=>{
+            var intersectPoint = e.intersectPoint.orthoIntersect //不要点云的intersect,只要orthocamera算出的平面intersect
+            
+            
+            if(firstZ != void 0){
+                let max, min     
+                let moveZ = intersectPoint.z - firstZ                 
+                if(this.selected.type_ == 'floor'){//楼层  
+                    let index = this.selected.buildParent.buildChildren.indexOf(this.selected)
+                    
+                    //限制高度不能超过上下
+                    if(e.target == height_pull_box_up){ 
+                        let upper = this.selected.buildParent.buildChildren[index+1];
+                        this.selected.zMax = firstZ + moveZ;
+                        min = this.selected.zMin + minFloorHeight
+                        if(this.selected.zMax < min){
+                            this.selected.zMax = min
+                        }else{ 
+                            if(upper){
+                                max = upper.zMax - minFloorHeight;
+                                if(this.selected.zMax > max){
+                                    this.selected.zMax = max
+                                }
+                            } 
+                        }
+                        if(upper){
+                            upper.zMin = this.selected.zMax
+                            
+                            
+                            upper.update()
+                        }
+                    }else{
+                        let lower = this.selected.buildParent.buildChildren[index-1];
+                        this.selected.zMin = firstZ + moveZ;
+                        max = this.selected.zMax - minFloorHeight
+                        if(this.selected.zMin > max){
+                            this.selected.zMin = max
+                        }else{ 
+                            if(lower){
+                                min = lower.zMin + minFloorHeight;
+                                if(this.selected.zMin < min){
+                                    this.selected.zMin = min
+                                }
+                            } 
+                        }
+                        if(lower){
+                            lower.zMax = this.selected.zMin
+                            lower.update()
+                        } 
+                        if(index == 0)this.updateBuildingZ(this.selected.buildParent)   
+                    }
+                    
+                    
+                }else if(this.selected.type_ == 'room'){//房屋
+                    /* max = this.selected.buildParent.zMax
+                    min = this.selected.buildParent.zMin */
+                    //按照navvis的是不一定限制在当前楼层,只要高度不超过当前楼层即可。
+                    let maxHeight = this.selected.buildParent.zMax - this.selected.buildParent.zMin
+                    
+                   
+                    if(e.target == height_pull_box_up){  
+                        min = this.selected.zMin + minFloorHeight
+                        max = this.selected.zMin + maxHeight
+                        this.selected.zMax = THREE.Math.clamp(firstZ + moveZ, min, max); 
+                    }else{
+                        min = this.selected.zMax - maxHeight
+                        max = this.selected.zMax - minFloorHeight
+                        this.selected.zMin = THREE.Math.clamp(firstZ + moveZ, min, max); 
+                    }
+                }
+                
+                
+                this.selected.update()
+                this.fitPullBox() 
+                
+            }
+             
+            
+            firstZ = intersectPoint.z 
+                          
+        }
+        let drop = (e)=>{
+            firstZ = null
+        }
+        
+        
+        
+        height_pull_box_up.addEventListener('mousemove',mouseover)
+        height_pull_box_down.addEventListener('mousemove',mouseover)
+        height_pull_box_up.addEventListener('mouseleave',mouseleave)
+        height_pull_box_down.addEventListener('mouseleave',mouseleave)
+        height_pull_box_up.addEventListener('drag',drag)
+        height_pull_box_down.addEventListener('drag',drag)
+        height_pull_box_up.addEventListener('drop',drop)
+        height_pull_box_down.addEventListener('drop',drop)
+    },
+    
+    
+    
+    
+    
+    test:function(){
+        
+        
+        var extrudeSettings = {
+            steps: 1,
+            bevelEnabled: false,
+            depth: 10,
+        };
+
+
+        var pts = [], numPts = 5;
+
+        for ( var i = 0; i < numPts * 2; i ++ ) {
+
+            var l = i % 2 == 1 ? 10 : 20;
+
+            var a = i / numPts * Math.PI;
+
+            pts.push( new THREE.Vector2( Math.cos( a ) * l, Math.sin( a ) * l ) );
+
+        }
+
+        var shape = new THREE.Shape( pts );
+
+        var geometry = new THREE.ExtrudeBufferGeometry( shape, extrudeSettings );
+
+        
+        var mesh = new THREE.Mesh( geometry,  new THREE.MeshBasicMaterial({color:"#3FF",opacity:0.4,transparent:true, side:2}) );
+        mesh.layers.set(Potree.config.renderLayers.sceneObjects)
+        mesh.scale.set(0.1,0.1,0.1)
+        viewer.scene.scene.add( mesh );
+
+        
+    }
+    
+    
+}
+
+/* 
+
+规则
+
+层级:
+
+type            中文名                  改动范围       其他  
+    
+BUILDING        建筑                      xy          mesh由自己的基底以及所有floor的组成。如果删除所有floor,就剩一个平面
+FLOOR           楼层                      z           点击楼层时房间也会显示,而建筑的其他楼层不显示线框,会显示面. 拖拽高度实际是拖拽楼层间的分界线,楼层之间不会有缝隙
+ROOM            房间                      xyz         可能超出楼层外,因为楼层拖拽时房间没变。所以建筑不一定包容房间。
+CUSTOM          自定义(现作房间)        xyz
+
+
+navvis弊端:
+    空间模型不会随着数据集移动而移动
+    建筑点修改后,房间可能飘出建筑外的。 楼层高度修改后也是。
+
+ 
+问题:
+    磨砂材质
+    没有阴影,可directionallight 加了呀
+    
+
+ */
+
+export {SiteModel} 

+ 263 - 0
src/start.js

@@ -0,0 +1,263 @@
+import * as THREE from "../libs/three.js/build/three.module.js";
+import {settings, config} from './settings' 
+import math from './utils/math' 
+import browser from './utils/browser' 
+import './extensions/three.shim.js' 
+ 
+
+import {Utils} from "../src/utils.js"
+ 
+
+var start = function(dom, mapDom, number, fileServer){ //t-Zvd3w0m
+
+    console.log('start')
+    Potree.settings.number = number || 't-o5YMR13'// 't-iksBApb'// 写在viewer前
+    Potree.fileServer = fileServer
+    
+    let viewer = new Potree.Viewer(dom , mapDom);
+    
+    let Alignment = viewer.modules.Alignment
+    
+    
+    //let pointDensity = config.pointDensity.middle
+	viewer.setEDLEnabled(false);
+    viewer.setFOV(config.view.fov);
+    //viewer.setPointBudget(pointDensity.pointBudget);
+    viewer.loadSettingsFromURL(); 
+    
+    
+    
+    if(!Potree.settings.isOfficial){ 
+        viewer.loadGUI(() => {
+            viewer.setLanguage('en');
+            //$("#menu_appearance").next().show();
+            $("#menu_tools").next().show();
+            $("#menu_scene").next().show();
+            //$("#siteModel").show();
+            $("#alignment").show();
+            viewer.toggleSidebar();
+        });
+        Potree.settings.sizeFitToLevel = true//当type为衰减模式时自动根据level调节大小。每长一级,大小就除以2
+    }
+
+    Potree.loadDatasetsCallback = function(data, ifReload){
+        Potree.datasetData = data
+        viewer.transform = null
+        var datasetLength = data.length 
+        var loaded = 0
+        var loadDone = function(){//点云cloud.js加载完毕后 
+            viewer.updateModelBound()
+            let {boundSize, center} = viewer.bound
+           
+            Potree.Log(`中心点: ${math.toPrecision(center.toArray(),2)}, boundSize: ${math.toPrecision(boundSize.toArray(),2)} ` , null, 12)
+            
+            if(!ifReload){
+                Potree.Images360Loader.load(viewer, {
+                    boundSize: boundSize.clone(),
+                    center: center.clone() 
+                },  images360 => {
+                    viewer.scene.add360Images(images360);
+               
+                    viewer.mapViewer.addListener(images360)
+                    
+                    {//初始位置
+                        
+                        var urlFirstView = false
+                        var panoId = browser.urlHasValue('pano',true);
+                        if(panoId != void 0){
+                            var pos
+                            var pano = viewer.images360.panos.find(e=>e.id==panoId);
+                            if(pano){
+                                viewer.images360.focusPano({
+                                    pano,
+                                    duration:0, 
+                                    callback:()=>{/* Potree.settings.displayMode = 'showPanos' */}
+                                })
+                                  
+                            }
+                        }
+                        
+                    }
+                    viewer.emit('allLoaded')
+                });
+                
+            }
+            Potree.loadMapEntity() //加载floorplan,不一定成功 
+            
+            if(!ifReload){
+                viewer.scene.view.setView(//position, target
+                    center.clone().add(new THREE.Vector3(10,5,10)), 
+                    center
+                )
+                
+                if(!Potree.settings.isOfficial){
+                    setTimeout(//暂时延迟,等focus第一个点之后
+                        ()=>{
+                           // viewer.loadProject(Potree.scriptPath + "/data/t-iksBApb/potree.json5") 
+                        },
+                    500)
+                }   
+                
+                viewer.dispatchEvent({type:'loadPointCloudDone'})
+            
+                if(!Potree.settings.UserPointDensity){
+                    Potree.settings.UserPointDensity = 'middle' 
+                }
+                
+                
+                Potree.Log('loadPointCloudDone  点云加载完毕', null, 10)
+            }    
+             
+            
+            
+            
+        }
+        
+        
+        
+        var transformPointcloud = (pointcloud, dataset)=>{
+            var locationLonLat = dataset.location.slice(0,2)
+            //当只有一个dataset时,无论如何transform 点云和漫游点都能对应上。
+            var location = viewer.transform.lonlatToLocal.forward(locationLonLat)  //transform.inverse()
+            //初始化位置 
+            pointcloud.matrixAutoUpdate = false   //最好禁止updateMatrix  直接使用matrixWorld
+            pointcloud.orientationUser = 0  
+            pointcloud.translateUser = new THREE.Vector3;
+            
+            viewer.sidebar && viewer.sidebar.addAlignmentButton(pointcloud) 
+            Alignment.rotate(pointcloud, null, dataset.orientation)   
+            Alignment.translate(pointcloud, new THREE.Vector3(location[0], location[1], dataset.location[2])) 
+            
+            pointcloud.updateMatrixWorld()
+            
+            
+            Potree.Log(`点云${pointcloud.dataset_id}旋转值:${pointcloud.orientationUser}, 位置${math.toPrecision(pointcloud.translateUser.toArray(),3)}, 经纬度 ${locationLonLat}, spacing ${pointcloud.material.spacing}`, null, 17 )
+            
+            
+            //-------------------
+             
+            //viewer.mapView.showSources(false);  
+        }
+        
+        data.forEach((dataset,index)=>{
+            
+            //dataset.location = [ 113.60182446595765,22.364155116865753,0]
+            //dataset.orientation = -0.9
+             
+            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");
+                //proj4.defs("pointcloud", viewer.getProjection()); //不用从cloud里拿了
+            
+                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  
+                } 
+                // proj4(fromProjection, toProjection, coordinates)
+                //let transform = proj4("WGS84", "pointcloud");
+                viewer.mapViewer && viewer.mapViewer.mapLayer.maps[0].updateProjection()
+                
+            } 
+            
+            if(!ifReload){
+                var cloudPath = `https://${Potree.config.urls.prefix}/data/${number}/data/${dataset.name}/webcloud/cloud.js` 
+                Potree.loadPointCloud(cloudPath, dataset.name , e => {
+                    let scene = viewer.scene;
+                    let pointcloud = e.pointcloud; 
+                    let config = Potree.config.material
+                    let material = pointcloud.material; 
+                    
+                    material.minSize =  config.minSize
+                    material.maxSize =  config.maxSize   
+                    material.pointSizeType = Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED
+                    pointcloud.changePointSize(config.pointSize)  //material.size =  config.pointSize;
+                    pointcloud.changePointOpacity(1)
+                    material.shape = Potree.PointShape.SQUARE; 
+                    pointcloud.color = dataset.color  
+                    pointcloud.dataset_id = dataset.id;//供漫游点找到属于的dataset点云
+                    pointcloud.panos = [] 
+                    transformPointcloud(pointcloud,dataset)
+                    scene.addPointCloud(pointcloud);
+                    loaded ++;
+                    if(loaded == datasetLength)loadDone()
+                })
+                
+            }else{
+                let pointcloud = viewer.scene.pointclouds.find(p => p.dataset_id == dataset.id)
+                if(!pointcloud){
+                    Potree.Log('数据集id变了,自动使用第一个','#500')
+                    pointcloud = viewer.scene.pointclouds[0]
+                }
+                //先归零 
+                Alignment.translate(pointcloud,  pointcloud.translateUser.clone().negate())
+                Alignment.rotate(pointcloud, null,  - pointcloud.orientationUser)
+                
+                transformPointcloud(pointcloud, dataset)
+                 
+            } 
+                
+        })
+        
+        if(ifReload){ 
+            
+            loadDone()
+        }
+        
+        
+    } 
+    
+    
+    
+    Potree.loadDatasets(Potree.loadDatasetsCallback) 
+     
+     
+
+    window.testTransform = function(locationLonLat, location1, location2){
+        proj4.defs("NAVVIS:test", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15));
+        
+        let transform = proj4("WGS84", "NAVVIS:test"); //这个ok  navvis里也是这两种转换 见proj4Factory
+        if(location1){//经纬度
+            return transform.forward(location1) 
+        }else{
+            return transform.inverse(location2) 
+        }            
+        
+    }       
+
+    window.THREE = THREE
+    window.buttonFunction = function(){
+        /* 
+        viewer.startScreenshot({type:'measure', measurement:viewer.scene.measurements[0]})
+         */
+        
+        viewer.modules.RouteGuider.routeStart = new THREE.Vector3(0,0,-1.3)
+        viewer.modules.RouteGuider.routeEnd = new THREE.Vector3(-10,0,-1.3)
+          
+        
+    }
+}
+  //https://testlaser.4dkankan.com/indoor/t-ia44BhY/api/site_model
+  
+  //https://testlaser.4dkankan.com/indoor/t-ia44BhY/api/tiled_maps
+export {start}
+
+
+
+   /* 
+
+
+坐标转换问题:
+
+由于控制点可以随便输入,所以本地和地理位置的转换也是可拉伸的。而navvis的转换是等比由中心展开,
+所以对比两种转化方式时误差较大。
+
+另外地理注册控制点是有参考数据集的,若参考数据集和我放置在0,0,0的数据集一致,就可直接使用,否则要转换。
+
+
+
+   */

+ 70 - 0
src/utils/Label.js

@@ -0,0 +1,70 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import {Utils} from "../utils.js";
+
+class Label{
+    constructor(o={}){
+        this.position = o.pos;
+        this.text = o.text || '';
+        this.elem = $('<div class="hide"><a></a></div>');
+        o.className && this.elem.addClass(o.className)
+        this.elem.find('a').html(this.text); 
+        $("#potree_labels").append(this.elem)
+        this.pos2d = new THREE.Vector3
+        this.dom = o.dom || viewer.renderArea
+        this.camera = o.camera || viewer.scene.getActiveCamera() 
+        
+        viewer.addEventListener('camera_changed', ()=>{
+            this.update()
+        })
+       
+        
+        
+    }
+     
+    update(){
+        if(!this.position || this.elem.hasClass('unvisible'))return
+        var p = Utils.getPos2d(this.position,this.camera,this.dom, viewer.mainViewport);
+        if(!p.trueSide){
+            this.elem.addClass("hide");  return;
+        }
+        this.elem.css({
+            left: p.pos.x +'px',
+            top: p.pos.y +'px'
+        })
+        
+        
+        
+        this.elem.removeClass("hide");
+        this.pos2d = p.vector;
+        
+        
+        
+        
+        
+    }
+    
+    setVisible(visi){
+        if(!visi){
+            this.elem.addClass("unvisible");  
+        }else{
+            this.elem.removeClass("unvisible");
+            this.update()
+        }
+    }
+    
+    setText(text){
+        this.text = text || '';
+        this.elem.find('a').html(this.text); 
+    }
+    setPos(pos){
+        this.position = pos;
+    }
+    
+    remove(){
+        this.elem.remove();
+    }
+}
+
+export default Label;
+
+

+ 322 - 0
src/utils/MathLight.js

@@ -0,0 +1,322 @@
+var MathLight = {};
+MathLight.RADIANS_PER_DEGREE = Math.PI / 180;
+MathLight.DEGREES_PER_RADIAN = 180 / Math.PI;
+MathLight.Vector3 = function(e, t, i) {
+    this.x = e || 0,
+    this.y = t || 0,
+    this.z = i || 0
+};
+
+MathLight.Matrix4 = function() {
+    this.elements = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
+    arguments.length > 0 && console.error("MathLight.Matrix4: the constructor no longer reads arguments. use .set() instead.")
+};
+
+MathLight.Matrix4.prototype = {
+    identity: function() {
+        return this.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1),
+        this
+    },
+    copy: function(e) {
+        return this.elements.set(e.elements),
+        this
+    },
+    applyToVector3: function(e) {
+        var t = e.x
+            , i = e.y
+            , n = e.z
+            , r = this.elements;
+        return e.x = r[0] * t + r[4] * i + r[8] * n + r[12],
+        e.y = r[1] * t + r[5] * i + r[9] * n + r[13],
+        e.z = r[2] * t + r[6] * i + r[10] * n + r[14],
+        this
+    },
+    getInverse: function(e, t) {
+        var i = this.elements
+            , n = e.elements
+            , r = n[0]
+            , o = n[1]
+            , a = n[2]
+            , s = n[3]
+            , l = n[4]
+            , c = n[5]
+            , h = n[6]
+            , u = n[7]
+            , d = n[8]
+            , p = n[9]
+            , f = n[10]
+            , g = n[11]
+            , m = n[12]
+            , v = n[13]
+            , A = n[14]
+            , y = n[15]
+            , C = p * A * u - v * f * u + v * h * g - c * A * g - p * h * y + c * f * y
+            , I = m * f * u - d * A * u - m * h * g + l * A * g + d * h * y - l * f * y
+            , E = d * v * u - m * p * u + m * c * g - l * v * g - d * c * y + l * p * y
+            , b = m * p * h - d * v * h - m * c * f + l * v * f + d * c * A - l * p * A
+            , w = r * C + o * I + a * E + s * b;
+        if (0 === w) {
+            var _ = "MathLight.Matrix4.getInverse(): can't invert matrix, determinant is 0";
+            if (t)
+                throw new Error(_);
+            return console.warn(_),
+            this.identity()
+        }
+        var T = 1 / w;
+        return i[0] = C * T,
+        i[1] = (v * f * s - p * A * s - v * a * g + o * A * g + p * a * y - o * f * y) * T,
+        i[2] = (c * A * s - v * h * s + v * a * u - o * A * u - c * a * y + o * h * y) * T,
+        i[3] = (p * h * s - c * f * s - p * a * u + o * f * u + c * a * g - o * h * g) * T,
+        i[4] = I * T,
+        i[5] = (d * A * s - m * f * s + m * a * g - r * A * g - d * a * y + r * f * y) * T,
+        i[6] = (m * h * s - l * A * s - m * a * u + r * A * u + l * a * y - r * h * y) * T,
+        i[7] = (l * f * s - d * h * s + d * a * u - r * f * u - l * a * g + r * h * g) * T,
+        i[8] = E * T,
+        i[9] = (m * p * s - d * v * s - m * o * g + r * v * g + d * o * y - r * p * y) * T,
+        i[10] = (l * v * s - m * c * s + m * o * u - r * v * u - l * o * y + r * c * y) * T,
+        i[11] = (d * c * s - l * p * s - d * o * u + r * p * u + l * o * g - r * c * g) * T,
+        i[12] = b * T,
+        i[13] = (d * v * a - m * p * a + m * o * f - r * v * f - d * o * A + r * p * A) * T,
+        i[14] = (m * c * a - l * v * a - m * o * h + r * v * h + l * o * A - r * c * A) * T,
+        i[15] = (l * p * a - d * c * a + d * o * h - r * p * h - l * o * f + r * c * f) * T,
+        this
+    },
+    makeRotationFromQuaternion: function(e) {
+        var t = this.elements
+            , i = e.x
+            , n = e.y
+            , r = e.z
+            , o = e.w
+            , a = i + i
+            , s = n + n
+            , l = r + r
+            , c = i * a
+            , h = i * s
+            , u = i * l
+            , d = n * s
+            , p = n * l
+            , f = r * l
+            , g = o * a
+            , m = o * s
+            , v = o * l;
+        return t[0] = 1 - (d + f),
+        t[4] = h - v,
+        t[8] = u + m,
+        t[1] = h + v,
+        t[5] = 1 - (c + f),
+        t[9] = p - g,
+        t[2] = u - m,
+        t[6] = p + g,
+        t[10] = 1 - (c + d),
+        t[3] = 0,
+        t[7] = 0,
+        t[11] = 0,
+        t[12] = 0,
+        t[13] = 0,
+        t[14] = 0,
+        t[15] = 1,
+        this
+    }
+};
+
+MathLight.Quaternion = function(e, t, i, n) {
+    this._x = e || 0,
+    this._y = t || 0,
+    this._z = i || 0,
+    this._w = void 0 !== n ? n : 1
+};
+
+MathLight.Quaternion.prototype = {
+    get x() {
+        return this._x
+    },
+    set x(e) {
+        this._x = e
+    },
+    get y() {
+        return this._y
+    },
+    set y(e) {
+        this._y = e
+    },
+    get z() {
+        return this._z
+    },
+    set z(e) {
+        this._z = e
+    },
+    get w() {
+        return this._w
+    },
+    set w(e) {
+        this._w = e
+    },
+    copy: function(e) {
+        this._x = e.x,
+        this._y = e.y,
+        this._z = e.z,
+        this._w = e.w
+    },
+    inverse: function() {
+        return this.conjugate().normalize()
+    },
+    conjugate: function() {
+        return this._x *= -1,
+        this._y *= -1,
+        this._z *= -1,
+        this
+    },
+    length: function() {
+        return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w)
+    },
+    normalize: function() {
+        var e = this.length();
+        return 0 === e ? (this._x = 0,
+        this._y = 0,
+        this._z = 0,
+        this._w = 1) : (e = 1 / e,
+        this._x = this._x * e,
+        this._y = this._y * e,
+        this._z = this._z * e,
+        this._w = this._w * e),
+        this
+    },
+    setFromAxisAngle: function(e, t) {
+        var i = t / 2
+            , n = Math.sin(i);
+        return this._x = e.x * n,
+        this._y = e.y * n,
+        this._z = e.z * n,
+        this._w = Math.cos(i),
+        this
+    },
+    setFromUnitVectors: function() {
+        var e, t, i = 1e-6;
+        return function(n, o) {
+            return void 0 === e && (e = new MathLight.Vector3),
+            t = MathLight.dot(n, o) + 1,
+            t < i ? (t = 0,
+            Math.abs(n.x) > Math.abs(n.z) ? MathLight.setVector(e, -n.y, n.x, 0) : MathLight.setVector(e, 0, -n.z, n.y)) : MathLight.cross(n, o, e),
+            this._x = e.x,
+            this._y = e.y,
+            this._z = e.z,
+            this._w = t,
+            this.normalize()
+        }
+    }(),
+    multiply: function(e) {
+        return this.multiplyQuaternions(this, e)
+    },
+    premultiply: function(e) {
+        return this.multiplyQuaternions(e, this)
+    },
+    multiplyQuaternions: function(e, t) {
+        var i = e._x
+            , n = e._y
+            , r = e._z
+            , o = e._w
+            , a = t._x
+            , s = t._y
+            , l = t._z
+            , c = t._w;
+        return this._x = i * c + o * a + n * l - r * s,
+        this._y = n * c + o * s + r * a - i * l,
+        this._z = r * c + o * l + i * s - n * a,
+        this._w = o * c - i * a - n * s - r * l,
+        this
+    }
+};
+
+MathLight.convertWorkshopVector = function(e) {
+    return new MathLight.Vector3(-e.x,e.y,e.z)
+};
+
+MathLight.convertWorkshopQuaternion = function(e) {
+    return new MathLight.Quaternion(-e.x,e.y,e.z,-e.w).multiply(new MathLight.Quaternion(Math.sqrt(2) / 2,Math.sqrt(2) / 2,0,0))
+};
+
+MathLight.convertWorkshopOrthoZoom = function(e) {
+	//return e === -1 ? -1 : e / 16 * ($('#player').width() / $('#player').height()) / n.workshopApsect
+	return e === -1 ? -1 : e  * ($("#player").width() / $("#player").height())  ;
+};
+
+MathLight.convertWorkshopPanoramaQuaternion = function(e) {
+    return new MathLight.Quaternion(e.x,-e.y,-e.z,e.w).normalize().multiply((new MathLight.Quaternion).setFromAxisAngle(new MathLight.Vector3(0,1,0), 270 * MathLight.RADIANS_PER_DEGREE))
+};
+
+MathLight.normalize = function(e) {
+    var t = e.x * e.x + e.y * e.y + e.z * e.z
+        , i = Math.sqrt(t);
+    e.x /= i,
+    e.y /= i,
+    e.z /= i
+};
+
+MathLight.dot = function(e, t) {
+    return e.x * t.x + e.y * t.y + e.z * t.z
+};
+
+MathLight.cross = function(e, t, i) {
+    var n = e.x
+        , r = e.y
+        , o = e.z;
+    i.x = r * t.z - o * t.y,
+    i.y = o * t.x - n * t.z,
+    i.z = n * t.y - r * t.x
+};
+
+MathLight.setVector = function(e, t, i, n) {
+    e.x = t,
+    e.y = i,
+    e.z = n
+};
+
+MathLight.copyVector = function(e, t) {
+    t.x = e.x,
+    t.y = e.y,
+    t.z = e.z
+};
+
+MathLight.addVector = function(e, t) {
+    e.x += t.x,
+    e.y += t.y,
+    e.z += t.z
+};
+
+MathLight.subVector = function(e, t) {
+    e.x -= t.x,
+    e.y -= t.y,
+    e.z -= t.z
+};
+
+MathLight.applyQuaternionToVector = function(e, t) {
+    var i = t.x
+        , n = t.y
+        , r = t.z
+        , o = e.x
+        , a = e.y
+        , s = e.z
+        , l = e.w
+        , c = l * i + a * r - s * n
+        , h = l * n + s * i - o * r
+        , u = l * r + o * n - a * i
+        , d = -o * i - a * n - s * r;
+    t.x = c * l + d * -o + h * -s - u * -a,
+    t.y = h * l + d * -a + u * -o - c * -s,
+    t.z = u * l + d * -s + c * -a - h * -o
+};
+
+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

+ 339 - 0
src/utils/SplitScreen.js

@@ -0,0 +1,339 @@
+import {View} from "../viewer/View.js";
+import Viewport from "../viewer/Viewport.js";
+
+const viewportProps = [
+    {
+        left:0.5,
+        bottom:0.5,
+        width: 0.5,height:0.5,
+        name : 'MainView',   
+        //view: viewer.scene.view,
+        active: true,
+    },
+    {
+        left:0,
+        bottom:0.5,
+        width: 0.5,height:0.5,
+        name : 'Top',   
+        name2 : 'mapViewport', 
+        axis:["x","y"],
+        //axisSign:[1,1],
+        active: true,
+    },
+    {
+        left:0.5,
+        bottom:0,
+        width: 0.5,height:0.5,
+        name : 'Right', 
+        axis:["y","z"],
+        //axisSign:[1,1],
+        active: true,
+    },
+    {
+        left:0,
+        bottom:0,
+        width: 0.5,height:0.5, 
+        name : 'Back', 
+        axis:["x","z"],
+        //axisSign:[-1,1],    // 从镜头方向看  x向左 所以取负 
+        active: true,
+    },
+]
+
+
+
+
+var SplitScreen = {
+    
+    //viewer.modules.Alignment.SplitScreen.viewportFitBound(viewer.mapViewer.viewports[0],viewer.bound.boundSize, viewer.bound.center)
+    
+    splitScreen4Views : function(o={}){
+        var defaultCamera = viewer.scene.getActiveCamera()
+       
+        let {boundSize, center} = viewer.bound
+          
+        var getOrthographicCamera = function(widthRatio, heightRatio, axis){
+            var widthPX = viewer.renderArea.clientWidth * widthRatio;
+            var heightPX = viewer.renderArea.clientHeight * heightRatio;
+            var aspect = widthPX/heightPX
+            console.log(viewer.renderArea.clientWidth,viewer.renderArea.clientHeight,aspect)
+            var width = Math.max(boundSize[axis[0]],  boundSize[axis[1]] * aspect)
+            var orthographicCamera = new THREE.OrthographicCamera(-widthPX/2, widthPX/2, heightPX/2, -heightPX/2, 0.01, 10000);
+            var margin = 50 //px 
+            orthographicCamera.zoom = (widthPX - margin) / width//zoom越大视野越小
+            var moveAtAxis = ['x','y','z'].find(e=>!(axis.includes(e)))
+            orthographicCamera.up.set(0,0,1)
+            orthographicCamera.position.copy(center)
+            orthographicCamera.position[moveAtAxis] += 1000;//偏移一些 在模型外即可
+            
+         
+           
+            
+            return orthographicCamera
+        }
+
+        
+        
+        
+        //材质
+        let material = viewer.scene.pointclouds[0].material
+        this.statesBefore = {
+            mat : {
+                colorType : material.activeAttributeName,
+                color : material.color.clone(),
+                opacity : material.opacity,
+            },
+            pointDensity : Potree.settings.pointDensity,
+            displayMode : Potree.settings.displayMode,
+        }
+      
+        
+        let beforeRender = function(viewport){
+            viewer.scene.pointclouds.forEach(e=>{ 
+                if(viewport.name == "MainView"){ 
+                    e.material.activeAttributeName = SplitScreen.statesBefore.mat.colorType 
+                    e.material.color.copy(SplitScreen.statesBefore.mat.color)
+                    e.material.useFilterByNormal = false
+                    e.material.opacity = SplitScreen.statesBefore.mat.opacity 
+                    Potree.settings.pointDensity = 'fourViewports' //本来想比另外三屏高一点质量,结果发现会闪烁,因为点云加载需要时间 (navvis仿版也是一样,以后看看能否优化)
+                }else{ 
+                    e.material.activeAttributeName = "color"
+                    e.material.color.set(e.color)
+                    e.material.useFilterByNormal = true
+                    e.material.opacity =  /* THREE.Math.clamp( */e.material.spacing /* , 0.01,100); */   //0.09 //越稀疏给的opacity应该越高,支持超过1(隧道场景  spacing达9之多) 
+                    Potree.settings.pointDensity = 'fourViewports' //强制降低点云质量
+                }                 
+            })  
+        }
+         
+        
+        
+        let viewports = []
+        
+        //更改mainViewport参数 
+        viewports.push(viewer.mainViewport) 
+         
+        let mapViewport = viewer.mapViewer.viewports[0]
+        viewports.push(mapViewport)
+        
+        
+        
+        
+        
+        
+        for(let i=0;i<4;i++){
+            let prop = viewportProps[i];
+            let viewport;
+            
+             
+            let v = viewports.find(e=>e.name == (prop.name2||prop.name)) 
+            if(v){
+                viewport = v
+                viewport.left = prop.left; viewport.bottom = prop.bottom; viewport.width = prop.width; viewport.height = prop.height;
+            }
+            
+            if(!viewport){
+                viewport = new Viewport( new View(), getOrthographicCamera(prop.width, prop.height, prop.axis), prop )
+                viewports.push(viewport) 
+            }                
+             
+           
+            viewport.beforeRender = beforeRender;
+            
+            if(viewport.name != 'MainView'){
+                viewport.view.setCubeView(viewport.name)
+                viewport.view.position.copy(viewport.camera.position)
+            }
+             
+            
+        }
+        
+        
+        viewer.viewports = viewports;
+        viewer.setLimitFar(false)
+        
+        viewer.mapViewer.attachToMainViewer(true,'split4Screens','dontSet') 
+        //覆盖在map上、点云等其他物体之下的一层背景
+        
+        mapViewport.noPointcloud = false
+        //隐藏地图游标
+        viewer.updateVisible(viewer.mapViewer.cursor, 'split4Screens', false)
+        viewer.images360.panos.forEach(pano=>{
+            viewer.updateVisible(pano.mapMarker, 'split4Screens', false) //希望这时候mapMarker已经建好了吧
+        })
+            
+            
+         
+        
+        this.enableMap(false)
+        this.enableFloorplan(false)
+        
+        //viewer.dispatchEvent({'type': 'beginSplitView' }) 
+        viewer.updateScreenSize({forceUpdateSize:true})   
+        this.viewportFitBound(mapViewport, boundSize, center)
+        
+        Potree.settings.displayMode = 'showPointCloud'
+    },
+    
+    
+     
+      
+    
+    
+    recoverFrom4Views: function(){
+       
+        this.unfocusViewport()
+         
+        const {width, height} = viewer.renderer.getSize(new THREE.Vector2());
+        viewer.renderer.setViewport(0,0,width,height)
+        viewer.renderer.setScissorTest( false );
+        
+        viewer.scene.pointclouds.forEach(e=>{ 
+            e.material.color.set(SplitScreen.statesBefore.mat.color)
+            e.material.activeAttributeName = SplitScreen.statesBefore.mat.colorType 
+            e.material.useFilterByNormal = false
+            e.material.opacity = SplitScreen.statesBefore.mat.opacity  
+        })
+        viewer.viewports = [viewer.mainViewport] 
+        viewer.mainViewport.width = 1;
+        viewer.mainViewport.height = 1;
+        viewer.mainViewport.left = 0
+        viewer.mainViewport.bottom = 0;
+        viewer.mainViewport.beforeRender = null 
+        viewer.setLimitFar(true)
+        
+        let mapViewport = viewer.mapViewer.viewports[0]
+        viewer.mapViewer.attachToMainViewer(false) 
+        viewer.updateVisible(viewer.mapViewer.cursor, 'split4Screens', true)
+        viewer.images360.panos.forEach(pano=>{
+            viewer.updateVisible(pano.mapMarker, 'split4Screens', true)
+        })
+        mapViewport.noPointcloud = true
+         
+        this.enableMap(true)
+        this.enableFloorplan(true)
+        
+        
+        
+        
+        Potree.settings.pointDensity = SplitScreen.statesBefore.pointDensity
+        Potree.settings.displayMode = SplitScreen.statesBefore.displayMode
+        
+        //viewer.dispatchEvent({'type': 'finishSplitView' }) 
+        viewer.updateScreenSize({forceUpdateSize:true}) 
+    },
+    
+    focusOnViewport : function(name){//全屏
+        viewer.viewports.forEach((viewport, i )=>{
+            if(viewport.name == name){
+                this.focusInfo = {
+                    name,
+                    left : viewport.left, bottom : viewport.bottom, height : viewport.height, width : viewport.width
+                }
+                viewport.left = 0;  viewport.bottom = 0;   viewport.height = 1;  viewport.width = 1  
+                
+            }else{
+                viewport.active = false
+            }
+        })
+         
+        viewer.updateScreenSize({forceUpdateSize:true}) 
+    },
+    
+    unfocusViewport:function(){
+        if(!this.focusInfo)return
+        viewer.viewports.forEach((viewport, i )=>{ 
+            if(this.focusInfo.name == viewport.name){//全屏的恢复 
+                viewport.left = this.focusInfo.left;
+                viewport.bottom = this.focusInfo.bottom;
+                viewport.height = this.focusInfo.height;
+                viewport.width = this.focusInfo.width;
+            }
+            viewport.active = true 
+        }) 
+        viewer.updateScreenSize({forceUpdateSize:true}) 
+        this.focusInfo = null
+    },
+    
+    
+    
+    
+    updateMapViewerBG(){
+        let mapViewport = viewer.mapViewer.viewports[0]
+        if(this.floorplanEnabled || this.mapEnabled){
+            mapViewport.background = 'overlayColor'
+            mapViewport.backgroundColor = new THREE.Color(0,0,0)
+            mapViewport.backgroundOpacity = 0.5; 
+        }else{
+            mapViewport.background = null
+            mapViewport.backgroundColor = null
+            mapViewport.backgroundOpacity = null
+        }
+    },
+    
+    setFloorplanDisplay: function(e, show=false){ 
+        viewer.updateVisible(e.floorplan.objectGroup, 'splitScreen', !!show)   
+    },
+    
+     
+    enableMap(enable){ 
+        let map = viewer.mapViewer.mapLayer.maps.find(e=>e.name == 'map')
+        viewer.updateVisible(map.objectGroup, 'splitScreen', !!enable) 
+        
+        //viewer.mapViewer.mapGradientBG = viewer.background == 'gradient' && !enable
+        this.mapEnabled = enable
+        this.updateMapViewerBG()
+        if(enable)viewer.mapViewer.mapLayer.needUpdate = true //加载地图
+        
+        
+    },
+    enableFloorplan(enable){ //是否让自定义的平面图显示
+        let floorplan = viewer.mapViewer.mapLayer.maps.find(e=>e.name == 'floorplan')
+        if(!enable){ 
+            //隐藏平面图
+            
+            if(floorplan){
+                //console.log('已经有floorplan')
+                this.setFloorplanDisplay({floorplan},false)
+            }else{ 
+                viewer.mapViewer.mapLayer.addEventListener( 'floorplanLoaded', this.setFloorplanDisplay ) //万一之后才加载 
+            }
+        }else{
+            if(!floorplan){
+                //???
+            }else{
+                this.setFloorplanDisplay({floorplan},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
+        var width = Math.max(boundSize[axis[0]],  boundSize[axis[1]] * viewport.camera.aspect)//视口宽度(米)
+        var margin = 50 //px
+        viewport.camera.zoom = (viewport.resolution.x - margin) / width  
+        viewport.camera.updateProjectionMatrix()
+    },
+    
+    focusOnPointCloud:function(pointcloud){//三个屏都聚焦在这个点云 
+        var boundSize = pointcloud.bound.getSize(new THREE.Vector3);
+	    var center = pointcloud.bound.getCenter(new THREE.Vector3);
+        viewer.viewports.forEach(e=>{
+            if(e.name == 'MainView')return 
+            this.viewportFitBound(e, boundSize, center)
+        })
+        
+        
+    }
+    
+}
+
+export default SplitScreen

+ 347 - 0
src/utils/UnitConvert.js

@@ -0,0 +1,347 @@
+ 
+const FEET_TO_INCHES_FACTOR = 12
+const EIGHTHS_SYMBOLS = ["", "⅛", "¼", "⅜", "½", "⅝", "¾", "⅞"]//eighths 八分之……
+
+
+class UnitOfMeasurement{//转化单位工具
+    constructor(t, e, n, i){
+        this.name = t,
+        this.symbol = e,
+        this.base = n,
+        this.factor = i 
+    }
+    toBase(t) {//换算到base
+        return t * this.factor
+    }
+   
+    fromBase(t) {//换算到当前
+        return t / this.factor
+    }
+}
+
+    
+    
+    
+
+/*  var o = function t(e) {
+    this.gettext = e,
+    t.METRIC = this.gettext("metric", void 0, "measurement system"),
+    t.IMPERIAL = this.gettext("imperial", void 0, "measurement system") 
+    
+    
+};
+e.UoMSystem = o;
+
+let UoMSystem = {
+    
+} */
+
+
+
+/* var MeasurementDomain = {
+
+    DISTANCE : "DISTANCE",
+    t.AREA = "AREA",
+    t.VOLUME = "VOLUME",
+    t.DATA = "DATA",
+    t
+}
+ 
+
+ */
+    
+    
+    
+    
+    
+     
+    
+var UnitsOfMeasurement = {
+ 
+    MILLIMETER : ["Millimeter", "mm"],
+    CENTIMETER : ["Centimeter", "cm"],
+    METER : ["Meter", "m"],
+    KILOMETER : ["Kilometer", "km"],
+    INCH : ["Inch", "in"],
+    FOOT : ["Foot", "ft"],
+    MILE : ["Mile", "mi"],
+    SQUAREMETER : ["SquareMeter", "m²"],
+    SQUAREFOOT : ["SquareFoot", "ft²"],
+    CUBICMETER : ["CubicMeter", "m³"],
+    CUBICFOOT : ["CubicFoot", "ft³"],
+    BYTE : ["Byte", "B"],
+    KILOBYTE : ["Kilobyte", "kB"],
+    MEGABYTE : ["Megabyte", "MB"],
+    GIGABYTE : ["Gigabyte", "GB"],
+    TERABYTE : ["Terabyte", "TB"],
+    PETABYTE : ["Petabyte", "PB"],
+    
+    
+    init : function() {
+        var e, n, i, a, s, c, l, u, d, p, h, 
+        f = new UnitOfMeasurement(UnitsOfMeasurement.METER[0],UnitsOfMeasurement.METER[1],void 0,1), 
+        g = new UnitOfMeasurement(UnitsOfMeasurement.SQUAREMETER[0],UnitsOfMeasurement.SQUAREMETER[1],void 0,1), 
+        m = new UnitOfMeasurement(UnitsOfMeasurement.CUBICMETER[0],UnitsOfMeasurement.CUBICMETER[1],void 0,1), 
+        v = new UnitOfMeasurement(UnitsOfMeasurement.BYTE[0],UnitsOfMeasurement.BYTE[1],void 0,1);
+        
+        UnitsOfMeasurement.DISTANCE = (
+            (e = {})['metric'] = ((n = {})[UnitsOfMeasurement.MILLIMETER[0]] = new UnitOfMeasurement(UnitsOfMeasurement.MILLIMETER[0],UnitsOfMeasurement.MILLIMETER[1],f,.001),
+            n[UnitsOfMeasurement.CENTIMETER[0]] = new UnitOfMeasurement(UnitsOfMeasurement.CENTIMETER[0],UnitsOfMeasurement.CENTIMETER[1],f,.01),
+            n[UnitsOfMeasurement.METER[0]] = f,
+            n[UnitsOfMeasurement.KILOMETER[0]] = new UnitOfMeasurement(UnitsOfMeasurement.KILOMETER[0],UnitsOfMeasurement.KILOMETER[1],f,1e3),
+            n),
+            e['imperial'] = ((i = {})[UnitsOfMeasurement.INCH[0]] = new UnitOfMeasurement(UnitsOfMeasurement.INCH[0],UnitsOfMeasurement.INCH[1],f,.0254),
+            i[UnitsOfMeasurement.FOOT[0]] = new UnitOfMeasurement(UnitsOfMeasurement.FOOT[0],UnitsOfMeasurement.FOOT[1],f,.3048),
+            i[UnitsOfMeasurement.MILE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.MILE[0],UnitsOfMeasurement.MILE[1],f,1609.344),
+            i),
+            e);
+            
+        UnitsOfMeasurement.AREA = ((a = {})['metric'] = ((s = {})[UnitsOfMeasurement.SQUAREMETER[0]] = g,
+            s),
+            a['imperial'] = ((c = {})[UnitsOfMeasurement.SQUAREFOOT[0]] = new UnitOfMeasurement(UnitsOfMeasurement.SQUAREFOOT[0],UnitsOfMeasurement.SQUAREFOOT[1],g,.092903),
+            c),
+            a);
+            
+        
+        UnitsOfMeasurement.VOLUME = ((l = {})['metric'] = ((u = {})[UnitsOfMeasurement.CUBICMETER[0]] = m,
+            u),
+            l['imperial'] = ((d = {})[UnitsOfMeasurement.CUBICFOOT[0]] = new UnitOfMeasurement(UnitsOfMeasurement.CUBICFOOT[0],UnitsOfMeasurement.CUBICFOOT[1],m,.0283168),
+            d),
+            l);
+        
+        //数据大小
+        var y = ((p = {})[UnitsOfMeasurement.BYTE[0]] = v,
+            p[UnitsOfMeasurement.KILOBYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.KILOBYTE[0],UnitsOfMeasurement.KILOBYTE[1],v,1e3),
+            p[UnitsOfMeasurement.MEGABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.MEGABYTE[0],UnitsOfMeasurement.MEGABYTE[1],v,1e6),
+            p[UnitsOfMeasurement.GIGABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.GIGABYTE[0],UnitsOfMeasurement.GIGABYTE[1],v,1e9),
+            p[UnitsOfMeasurement.TERABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.TERABYTE[0],UnitsOfMeasurement.TERABYTE[1],v,1e12),
+            p[UnitsOfMeasurement.PETABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.PETABYTE[0],UnitsOfMeasurement.PETABYTE[1],v,1e15),
+            p);
+            UnitsOfMeasurement.DATA = ((h = {})['metric'] = y,
+            h['imperial'] = y,
+            h)
+    }
+    ,
+    getUnitsOfMeasurementByDomain : function(e) {
+        
+        return this[e.toUpperCase()]
+        
+        /* switch (e.toUpperCase()) {
+        case a.DISTANCE:
+            return t.DISTANCE;
+        case a.AREA:
+            return t.AREA;
+        case a.VOLUME:
+            return t.VOLUME;
+        case a.DATA:
+            return t.DATA;
+        default:
+            console.error(e + " measurement domain is not supported.")
+        } */
+    }
+    ,
+    getUnitsOfMeasurementByDomainAndSystem : function(domain, system) {
+        var r = this.getUnitsOfMeasurementByDomain(domain);
+        if (r.hasOwnProperty(system.toLowerCase()))
+            return r[system.toLowerCase()];
+        console.error(n + " measurement system is not supported.")
+    }
+    ,
+    getDefaultUnitByDomainAndSystem : function(e, n) {
+        switch (e.toUpperCase()) {
+            case 'DISTANCE':
+                switch (n.toLowerCase()) {
+                case 'metric':
+                    return this.DISTANCE['metric'][this.METER[0]];
+                case 'imperial':
+                    return this.DISTANCE['imperial'][this.FOOT[0]];
+                default:
+                    console.error(n + " measurement system is not supported.")
+                }
+            case 'AREA':
+                switch (n.toLowerCase()) {
+                case 'metric':
+                    return this.AREA['metric'][this.SQUAREMETER[0]];
+                case 'imperial':
+                    return this.AREA['imperial'][this.SQUAREFOOT[0]];
+                default:
+                    console.error(n + " measurement system is not supported.")
+                }
+            case 'VOLUME':
+                switch (n.toLowerCase()) {
+                case 'metric':
+                    return this.VOLUME['metric'][this.CUBICMETER[0]];
+                case 'imperial':
+                    return this.VOLUME['imperial'][this.CUBICFOOT[0]];
+                default:
+                    console.error(n + " measurement system is not supported.")
+                }
+            case 'DATA':
+                switch (n.toLowerCase()) {
+                case 'metric':
+                    return this.DATA['metric'][this.BYTE[0]];
+                case 'imperial':
+                    return this.DATA['imperial'][this.BYTE[0]];
+                default:
+                    console.error(n + " measurement system is not supported.")
+                }
+            default:
+                console.error(e + " measurement domain is not supported.")
+        }
+    }
+    
+   
+}
+    
+    
+    
+
+ 
+
+
+
+
+
+class UnitService{
+    
+    constructor(/* e, n, i */){
+          //this.LanguageService = e,
+        //this.localStorageService = n,
+        //this.gettext = i,
+        //this.unitChanged = new r.Signal,
+        this.LOCAL_STORAGE_KEY = "iv_unit_key"//?
+        UnitsOfMeasurement.init()
+        this.unitSystems = ['metric', 'imperial']//[o.UoMSystem.METRIC, o.UoMSystem.IMPERIAL],
+        this.defaultSystem = 'metric'//'imperial'
+        //var a = this.LanguageService.getBrowserLocaleString().toLowerCase();
+        //this.defaultSystem =  t.isLocaleImperial(a) ? o.UoMSystem.IMPERIAL : o.UoMSystem.METRIC,
+        
+        //this.initUnit()
+        
+    }
+     
+    
+    /* initUnit() {
+        var t = this.localStorageService.get(this.LOCAL_STORAGE_KEY);
+        if (t)
+            for (var e = 0, n = this.unitSystems; e < n.length; e++) {
+                var i = n[e];
+                if (i === t)
+                    return void this.setUnit(i, !0)
+            }
+        this.setUnit(this.defaultSystem, !1)
+    }
+    setUnit(t, e) {
+        this.currentSystem !== t && (this.currentSystem = t,
+        this.unitChanged.emit()),
+        e && this.localStorageService.set(this.LOCAL_STORAGE_KEY, t)
+    } */
+    
+   
+    
+    /*isLocaleImperial(e) {
+        return t.IMPERIAL_LOCALES.indexOf(e.toLowerCase()) >= 0
+    }
+    ,
+     t.IMPERIAL_LOCALES = ["en_us"],
+    t.ɵfac(e) {
+        return new (e || t)(c.ɵɵinject(l.LanguageService),
+        c.ɵɵinject("localStorageService"),c.ɵɵinject("gettext"))
+    }
+     ,
+    t.ɵprov = c.ɵɵdefineInjectable({
+        token: t,
+        factory: t.ɵfac,
+        providedIn: "root"
+    }), */
+    
+} 
+
+
+
+    
+
+class UoMService{
+    constructor(/* UnitService */){
+        this.UnitService = new UnitService()/* UnitService */
+        
+    }
+    scopedConvert (t, n, precision = 2, r, maxFactor) {
+        return  e.convert(t, n, precision, r, maxFactor)
+    }
+    
+    convert(number, domain, precision = 2, system, maxFactor, ifEighths = !1) { 
+        if (!number) return "";
+        var s = this.getMostRelevantMeasurement(domain, system || this.UnitService.currentSystem, number, maxFactor);
+        return this.getFormattedMeasurementString(s[0], s[1], precision, ifEighths)
+    }
+    
+    getFormattedMeasurementString(number, unit, precision, ifEighths) {
+        var result 
+        if(ifEighths && unit.name === UnitsOfMeasurement.FOOT[0]){
+            result = this.formatImperialDistance(number * FEET_TO_INCHES_FACTOR)
+        }else if(ifEighths && unit.name === UnitsOfMeasurement.INCH[0]){
+            result = this.formatImperialDistance(number)
+             
+        }else{
+            result = number.toLocaleString(void 0, {
+                minimumFractionDigits: precision,
+                maximumFractionDigits: precision
+            }) + " " + unit.symbol
+        }
+        
+        return result
+    }
+    
+    formatImperialDistance(e) {
+        var n = Math.round(8 * e)
+          , i = Math.floor(n / 8)
+          , r = Math.floor(i / FEET_TO_INCHES_FACTOR)
+          , o = i - r * FEET_TO_INCHES_FACTOR
+          , a = EIGHTHS_SYMBOLS[n % 8]
+          , s = 0 === o && "" !== a ? "" : o;
+          
+        "" !== s && "" !== a && (a = " " + a)   
+        
+        return  0 !== r ? r + "' " + s + a + '"' : "" + s + a + '"'
+    }
+    
+    getMostRelevantMeasurement(t, e, n, maxFactor=0) {
+        /* var a = r.values(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
+        }); */
+        let a = []
+        let u = UnitsOfMeasurement.getUnitsOfMeasurementByDomainAndSystem(t, e)
+        for(let i in u){a.push(u[i])}
+         
+        let s = a.filter(m=>m.factor >= maxFactor) 
+         
+        //有可能会写错,看不懂原函数
+        
+        let c = s.reduce(function(i, e) {//reduce计算数组元素相加后的总和
+            return e.fromBase(n) < i.fromBase(n) && e.fromBase(n) >= 1 ? e : i
+        })
+        
+        return c ? [c.fromBase(n), c] : void 0
+    }
+   
+     
+    /* ɵfac(e){
+        return new (e || t)(c.ɵɵinject(l.UnitService))
+    }
+    
+    ɵprov = c.ɵɵdefineInjectable({
+        token: t,
+        factory: t.ɵfac,
+        providedIn: "root"
+    }) */
+   
+}
+
+
+
+export {UoMService}

文件差异内容过多而无法显示
+ 367 - 0
src/utils/browser.js


+ 21 - 0
src/utils/cameraLight.js

@@ -0,0 +1,21 @@
+import MathLight from './MathLight'
+
+var cameraLight = {
+    clampVFOV: function(currentFov, maxHFov, width, height) {//限制currentFov, 使之造成的横向fov不大于指定值maxHFov
+        var r = cameraLight.getHFOVFromVFOV(currentFov, width, height);
+        return r > maxHFov ? cameraLight.getVFOVFromHFOV(maxHFov, width, height) : currentFov
+    },
+    getHFOVForCamera: function(camera, width, height) {
+        return cameraLight.getHFOVFromVFOV(camera.fov, width, height)
+    },
+    getHFOVFromVFOV: function(fov, width, height) { 
+        return 2 * Math.atan(Math.tan(fov * MathLight.RADIANS_PER_DEGREE / 2) * (width / height)) * MathLight.DEGREES_PER_RADIAN;
+          
+    },
+    getVFOVFromHFOV: function(fov, width, height) { 
+        return 2 * Math.atan(Math.tan(fov * MathLight.RADIANS_PER_DEGREE / 2) * (height / width)) * MathLight.DEGREES_PER_RADIAN;
+         
+    }
+};
+
+export default cameraLight

+ 626 - 0
src/utils/ctrlPolygon.js

@@ -0,0 +1,626 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import {LineDraw, MeshDraw} from "../utils/DrawUtil";
+import math from "./math.js";
+
+
+
+
+
+
+const verticalLine = new THREE.Line3()
+
+//控制点和边的合集。具有可以拖拽修改的功能,拖拽时能防止线相交。
+export class ctrlPolygon extends THREE.Object3D { 
+    constructor (type, prop) {
+        super()
+        this.type = type
+        
+        this.maxMarkers = Number.MAX_SAFE_INTEGER;
+       
+         
+        this.transformData(prop);
+        for(let i in prop){
+            this[i] = prop[i]
+        }
+        
+        if(this.closed && this.dimension == '2d'){
+            this.areaPlane = this.createAreaPlane(); 
+            this.add(this.areaPlane)
+        }
+        
+        //数据--刚开始一定是空的
+        this.points = []; 
+        //mesh 不一定有
+        this.markers = []; 
+		this.edges = [];
+        
+        this.center
+        
+        
+        
+        
+    }
+    
+    initData(prop){
+        //开始加数据  
+        if(prop.points){ 
+        
+            for(const p of prop.points){
+                const pos = new THREE.Vector3().copy(p) 
+                this.addMarker({point:pos}); 
+            }
+         
+            if(this.datasetId != void 0){//初始化位置
+                if(this.dataset_points){
+                    this.dataset_points = this.dataset_points.map(e=>{
+                        return e && new THREE.Vector3().copy(e) 
+                    })
+                    this.transformByPointcloud() //根据dataset_points生成points  
+                }  
+            }else{
+                if(prop.dataset_points && prop.dataset_points.some(e=>e != void 0)){
+                    console.error('存在测量线的datasetId为空而dataset_points有值,请检查并删除:'+this.sid)//存在过的bug,原因未知,可能是后台处理dataset时替换的错误:http://192.168.0.21/index.php?m=bug&f=view&bugID=23601
+                    console.log(this)
+                }
+            }
+            
+            
+              
+            
+            this.getFacePlane()
+            this.getPoint2dInfo(this.points)
+            
+            this.update(true)
+            //this.dragChange(new THREE.Vector3().copy(prop.points[prop.points.length-1]), prop.points.length-1); 
+            this.setSelected(false )
+            
+            
+        }
+    }
+    
+    addMarker(o={}){
+        var index = o.index == void 0 ? this.points.length : o.index  //要当第几个
+        
+        this.points = [...this.points.slice(0,index), o.point, ...this.points.slice(index,this.points.length)]
+		//this.points.push(o.point);
+         
+        if(o.marker){
+            this.add(o.marker)
+            this.markers = [...this.markers.slice(0,index), o.marker, ...this.markers.slice(index,this.markers.length)]
+            this.updateMarker(o.marker, o.point)
+            { // Event Listeners  
+                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"
+                    })
+                }
+
+                o.marker.addEventListener('drag', this.dragMarker.bind(this));
+                o.marker.addEventListener('drop', this.dropMarker.bind(this));
+                o.marker.addEventListener('mouseover', mouseover);
+                o.marker.addEventListener('mouseleave', mouseleave);
+            }
+            
+        } 
+        
+        if(o.edge){
+            this.add(o.edge)
+            this.edges = [...this.edges.slice(0,index), o.edge, ...this.edges.slice(index,this.edges.length)]
+        } 
+    }
+    
+    
+    
+    
+    dragMarker(e){
+        
+        var I, atMap 
+        
+        if(e.drag.hoverViewport != e.drag.dragViewport)return 
+        atMap = e.drag.dragViewport.name == 'mapViewport' 
+        
+        if(atMap && this.unableDragAtMap){
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"polygon_AtWrongPlace"
+            });
+            e.drag.object = null //取消拖拽
+            return
+        }
+        
+        I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
+        
+        //记录数据集
+        
+        //在三维中脱离点云(在map中拉到周围都没有点云的地方)的顶点,无法拖拽怎么办
+         
+        if (I) { 
+            atMap && (I.z = 0)
+            let i = this.markers.indexOf(e.drag.object);
+            if (i !== -1) { 
+                this.dragChange(I, i, atMap) 
+                
+                if(this.points_datasets){
+                    if(e.intersectPoint.pointcloud) this.points_datasets[i] = e.intersectPoint.pointcloud.dataset_id
+                    else this.points_datasets[i] = null
+                } 
+            }
+            
+            
+        }
+        
+        
+        this.editStateChange(true)
+        
+    };
+    
+    
+    
+    
+    
+    
+    dragChange(intersectPos, i, atMap){
+        var len = this.markers.length
+        var location = intersectPos.clone()
+
+        if(this.faceDirection && this.maxMarkers == 2 && len == 2){//add 固定方向的点不直接拖拽
+            var p1 = this.markers[0].position
+            if(this.faceDirection == 'horizontal'){
+                var projectPos = location.clone().setZ(p1.z)
+            }else{
+                var projectPos = p1.clone().setZ(location.z)
+            }
+            //var p2 = p1.clone().add(this.direction)
+            //var projectPos = math.getFootPoint(location, p1, p2)
+            
+            
+            LineDraw.updateLine(this.guideLine, [location, projectPos])
+            location = projectPos
+            this.guideLine.visible = true
+        }else if( len > 1){ 
+             
+            var points = this.points.map(e=>e.clone())
+            points[i].copy(location) //算normal需要提前确认point
+            
+            //若为定义了面朝向的矩形
+            if(this.faceDirection == 'horizontal'){
+                if(len == 2){
+                    location.setZ(points[0].z) 
+                }
+                if(!this.facePlane){//一个点就能确定面
+                    this.facePlane = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3(0,0,1), this.points[0]  )
+                }
+            }else if(this.faceDirection == 'vertical'){//当有两个点时, 有两个方向的可能
+                if(len == 2){
+                    if(this.isRect){
+                        let vec = points[0].clone().sub(location) 
+                        if(Math.sqrt(vec.x*vec.x+vec.y*vec.y) > Math.abs(vec.z) ){//水平(高度差小于水平距离时)
+                            location.setZ(points[0].z) 
+                            //this.cannotConfirmNormal = false;//能确定面为水平方向
+                        }else{//垂直 (当两点一样时也属于这种)
+                            location.setX(points[0].x);
+                            location.setY(points[0].y);
+                            //this.cannotConfirmNormal = true; //不能确定面,因第三点可绕着纵轴线自由移动
+                        }
+                    }
+                }else{ 
+                    {//判断cannotConfirmNormal. 如果前几段都在竖直线上,就不能固定出面方向。
+                        this.cannotConfirmNormal = true
+                        let max = this.isRect ? 1 : len-2 
+                        for(let i=0;i<max;i++){
+                            let p1 = points[i].clone()
+                            let p2 = points[i+1].clone()
+                            let vec = p1.sub(p2);
+                            if(vec.x != 0 || vec.y != 0){
+                                this.cannotConfirmNormal = false
+                                break;
+                            } 
+                        } 
+                    }
+                    
+                    if(!this.facePlane || this.cannotConfirmNormal){//三个点且为水平方向时,计算面
+                          
+                        var points_ = points.map(e=>new THREE.Vector2(e.x,e.y))
+                        var points2 = getDifferentPoint(points_, 2); 
+                        if(points2){
+                            let normal = math.getNormal2d({p1:points2[0], p2:points2[1]})  
+                            normal = new THREE.Vector3(normal.x, normal.y, 0)
+                            this.facePlane = new THREE.Plane().setFromNormalAndCoplanarPoint( normal, this.points[0]  )
+                        } 
+                    }
+                }
+            } 
+
+            if(len > 2){//area
+            
+                if(!this.faceDirection){ 
+                    if(len == 3 || this.isRect) this.cannotConfirmNormal = true //当第三个点固定后(有四个点时)才能固定面
+                    if(!this.facePlane || this.cannotConfirmNormal){
+                        var points3 = getDifferentPoint(points, 3);//只有找到三个不同的点算拥有面和area
+                        if(points3){
+                            this.facePlane = new THREE.Plane().setFromCoplanarPoints(...points3 )
+                        }
+                    }
+                }
+                
+                if( this.facePlane && !this.cannotConfirmNormal  ){//之后加的点一定要在面上  
+                    if(atMap){ 
+                        //地图上用垂直线,得到和面的交点。
+                        verticalLine.set(location.clone().setZ(100000), location.clone().setZ(-100000))//确保长度范围覆盖所有测量面 
+                        location = this.facePlane.intersectLine(verticalLine, new THREE.Vector3() )
+                        if(!location) return; 
+                    }else{ 
+                        location = this.facePlane.projectPoint(intersectPos, new THREE.Vector3() )
+                    }
+                }  
+                
+                
+                points[i].copy(location)//再copy确认一次
+                
+                if(this.isRect){ //是矩形 (即使没有faceDirection也能执行)
+                    //根据前两个点计算当前和下一个点
+                    var p1 = points[(i-2+len)%len]
+                    var p2 = points[(i-1+len)%len]
+                    if(p1.equals(p2)){//意外情况:重复点两次 ( bug点,改了好多遍)
+                        if(this.faceDirection == 'vertical'){
+                            p2.add(new THREE.Vector3(0,0,0.0001))
+                        }else{
+                            p2.add(new THREE.Vector3(0,0.0001,0))
+                        } 
+                    } 
+                    //p3 : location 
+                    var foot = math.getFootPoint(location, p1, p2)//p2 修改p2到垂足的位置
+                    var vec = foot.clone().sub(location)
+                    var p4 = p1.clone().sub(vec) 
+                     
+                    
+                    points[(i-1+len)%len].copy(foot) 
+                    points[(i+1)%len].copy(p4) 
+                    this.setPosition((i-1+len)%len, foot);//p2
+                    this.setPosition((i+1)%len, p4);
+                    
+                }  
+                
+                /* let points2d;
+                if(this.facePlane){
+                    var originPoint0 = points[0].clone() 
+                    var qua = math.getQuaBetween2Vector(this.facePlane.normal, new THREE.Vector3(0,0,1), new THREE.Vector3(0,0,1));
+                    points2d = points.map(e=>e.clone().applyQuaternion(qua))  
+                } */
+                this.getPoint2dInfo(points)
+                
+                var isIntersectSelf = !this.isRect && len > 3 && this.intersectSelf(this.point2dInfo.points2d)//检测相交
+                if(isIntersectSelf){
+                    //not-allowed
+                    viewer.dispatchEvent({
+                        type : "CursorChange", action : "add",  name:"polygon_isIntersectSelf"
+                    })
+                    this.isIntersectSelf = true
+                    return
+                }else{
+                    this.isIntersectSelf = false
+                    viewer.dispatchEvent({
+                        type : "CursorChange", action : "remove",  name:"polygon_isIntersectSelf"
+                    })
+                    /* this.facePlane && (this.point2dInfo = {
+                        originPoint0 ,
+                        points2d,
+                        quaInverse : qua.clone().invert()
+                    }) */
+                }
+                
+                
+                 
+            }
+            
+            var showGuideLine = len>1 && (this.faceDirection || len > 3)
+            if(showGuideLine && this.guideLine){
+                LineDraw.updateLine(this.guideLine, [intersectPos, location])
+                this.guideLine.visible = true
+            } 
+             
+            //console.log(this.points.map(e=>e.toArray())) 
+
+        } 
+        if(this.retrictArea && !math.isPointInArea(this.retrictArea.points, location)){
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"polygon_AtWrongPlace"
+            })
+            this.isAtWrongPlace = true
+            return
+        }else{
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"
+            })
+            this.isAtWrongPlace = false
+            this.setPosition(i, location); 
+            this.update()
+            
+            this.dispatchEvent({type:'dragChange', index:i})
+        }
+        
+        
+    }
+    
+    dropMarker(e){
+        if (this.isNew && e.pressDistance>Potree.config.clickMaxDragDis //拖拽的话返回 
+            || e.button != THREE.MOUSE.RIGHT && (//右键click的话继续执行,因为会停止
+                this.isIntersectSelf && this.isNew //有线相交了
+                || this.isAtWrongPlace && this.isNew
+                || !e.isAtDomElement && this.isNew//如果是刚添加时在其他dom点击, 不要响应
+                ||  e.viewport != viewer.mainViewport && this.unableDragAtMap //垂直的测量线不允许在地图上放点
+                || !getDifferentPoint(this.points, this.points.length) //不允许和之前的点相同
+            ) 
+        ){
+            return continueDrag(e, this)   
+        } 
+         
+        
+         
+        let i = this.markers.indexOf(e.drag.object); 
+        if (i !== -1) {
+            this.dispatchEvent({
+                'type': 'marker_dropped', 
+                'index': i
+            });
+            if(this.markers.length>2 && this.facePlane)this.cannotConfirmNormal = false
+            this.guideLine &&(this.guideLine.visible = false)
+        }
+        
+       
+        
+        this.setMarkerSelected(e.drag.object, false, 'single') 
+        
+        this.editStateChange(false)
+        
+        
+        
+        
+        e.drag.endDragFun && e.drag.endDragFun(e)//  addmarker
+        if(this.changeCallBack)this.changeCallBack()
+    };
+    
+    getFacePlane(){//最普通一种get方法,根据顶点。且假设所有点已经共面,且不重合
+        if(this.points.length<3) return
+        this.facePlane = new THREE.Plane().setFromCoplanarPoints(...this.points.slice(0,3) )
+         
+    }
+    
+    getPoint2dInfo(points){ //在更新areaplane之前必须更新过point2dInfo
+        if(this.facePlane){ 
+            var originPoint0 = points[0].clone() 
+            var qua = math.getQuaBetween2Vector(this.facePlane.normal, new THREE.Vector3(0,0,1), new THREE.Vector3(0,0,1));
+            let points2d = points.map(e=>e.clone().applyQuaternion(qua))  
+        
+            this.point2dInfo = {
+                originPoint0 ,
+                points2d,
+                quaInverse : qua.clone().invert()
+            } 
+        }    
+    }
+    
+    
+    
+    
+    setPosition (index, position) {//拖拽后设置位置
+		let point = this.points[index];
+		point.copy(position);
+        if(this.datasetId){
+            this.dataset_points[index] = Potree.Utils.datasetPosTransform.toDataset({datasetId:this.datasetId, position:point.clone()})
+        
+        }
+        let marker = this.markers[index];  
+        this.updateMarker(marker, point)
+ 
+	 
+	} 
+    
+    updateMarker(marker, pos){
+        marker.position.copy(pos);
+        marker.update();
+    }
+    
+    
+    intersectSelf(points2d){//add
+        var len =  points2d.length
+        for(var i=0;i<len;i++){
+            for(var j=i+2;j<len;j++){
+                if(Math.abs(j-len-i)<2)continue;//不和邻边比
+                
+                var p1 =  points2d[i]
+                var p2 =  points2d[i+1]
+                var p3 =  points2d[j]
+                var p4 =  points2d[(j+1)%len] 
+                if(p1.equals(p2) || p3.equals(p4) || p1.equals(p3) || p2.equals(p3) || p1.equals(p4) || p2.equals(p4))continue 
+             
+                
+                var line1 = [p1,p2]
+                var line2 = [p3,p4]
+                var intersect = math.isLineIntersect(line1, line2, false, 0.001)
+                if(intersect){
+                    return true
+                    break
+                } 
+            }
+        }
+          
+    }
+	
+    removeMarker (index) {
+        
+		this.points.splice(index, 1); 
+       
+		this.remove(this.markers[index]); 
+        this.markers.splice(index, 1);   
+ 
+		let edgeIndex = index//(index === 0) ? 0 : (index - 1);
+		this.remove(this.edges[edgeIndex]);
+		this.edges.splice(edgeIndex, 1);
+  
+        this.point2dInfo && this.point2dInfo.points2d.splice(index, 1); //add
+
+
+		//this.update(); 
+	} 
+    
+    createAreaPlane(mat){ 
+        var geometry = new THREE.Geometry();
+        var mesh = new THREE.Mesh(geometry, mat)
+        
+        return mesh
+    }
+    
+    updateAreaPlane(){
+        if(!this.point2dInfo)return
+        this.areaPlane.geometry.dispose()
+        if(this.points.length>2){
+            this.areaPlane.geometry = MeshDraw.getShapeGeo(this.point2dInfo.points2d)
+            var center = math.getCenterOfGravityPoint(this.point2dInfo.points2d) //重心 
+                  
+            var firstPos =  this.point2dInfo.points2d[0].clone()
+            firstPos.z = 0                  //因为shape只读取了xy,所以位移下, 再算出最终位置,得到差距
+            firstPos.applyQuaternion(this.point2dInfo.quaInverse)
+            var vec = this.point2dInfo.originPoint0.clone().sub(firstPos)
+            center = new THREE.Vector3(center.x, center.y, 0)
+            center.applyQuaternion(this.point2dInfo.quaInverse)
+            this.areaPlane.quaternion.copy(this.point2dInfo.quaInverse) 
+            this.areaPlane.position.copy(vec)       
+            center.add(vec)
+            this.center = center 
+        }else{
+            this.areaPlane.geometry = new THREE.Geometry()
+            
+        } 
+        
+        
+    }
+
+
+    
+    
+    
+     
+
+    update(ifUpdateMarkers){
+        if(this.points.length === 0){
+			return;
+		} 
+        
+        
+        
+        
+        
+        let lastIndex = this.points.length - 1;
+            
+       
+        for (let index = 0; index <= lastIndex; index++) {
+            
+            let nextIndex = (index + 1 > lastIndex) ? 0 : index + 1;
+            let previousIndex = (index === 0) ? lastIndex : index - 1;
+
+            let point = this.points[index];
+            let nextPoint = this.points[nextIndex];
+            let previousPoint = this.points[previousIndex];
+
+            if(ifUpdateMarkers){
+                this.updateMarker(this.markers[index], point)
+            }   
+
+            { // edges
+                let edge = this.edges[index]; 
+                if(edge){
+                    LineDraw.updateLine(edge, [point, nextPoint]) 
+                    edge.visible = index < lastIndex || this.closed;
+                }
+                
+                
+            }
+
+            
+             
+        }
+        
+        if(this.areaPlane){
+            this.updateAreaPlane() 
+        }
+        
+             
+        viewer.mapViewer.emit('content_changed')
+        
+    } 
+    
+    dispose(){//add 
+        this.parent.remove(this)
+        /* viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"polygon_isIntersectSelf"
+        })
+        viewer.dispatchEvent({
+            type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"
+        }) */
+    }
+    
+    
+    reDraw(restMarkerCount=0){//重新开始画
+        let pointCount = this.points.length - restMarkerCount // restMarkerCount为需要留下的marker数量
+        while(pointCount > 0){
+            this.removeMarker(--pointCount)
+        }
+        this.point2dInfo = null
+        this.facePlane = null   
+         
+        
+                    
+    }
+    
+    
+    setMarkerSelected(){}
+    editStateChange(state){
+        if(!state){
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"polygon_isIntersectSelf"
+            })
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"polygon_AtWrongPlace"
+            })
+        }
+    }
+    
+    transformData(){}
+    setSelected(){}
+}
+
+
+
+function getDifferentPoint(points, count){//for facePlane
+    var result = [];
+    for(let i=0;i<points.length;i++){
+        var p = points[i];
+        if(result.find(e=>e.equals(p)))continue;
+        else result.push(p)
+        if(result.length == count)break
+    }
+    if(result.length == count)return result
+}
+
+
+ 
+
+
+
+function continueDrag(e, this_){
+    setTimeout(()=>{//等 drag=null之后 //右键拖拽结束后需要重新得到drag
+        if(this_.parent){//没被删的话
+            viewer.inputHandler.startDragging(e.drag.object,
+                {endDragFun: e.drag.endDragFun, notPressMouse:e.drag.notPressMouse, dragViewport:e.drag.dragViewport} 
+            )
+        } 
+    },1)
+}
+

+ 112 - 0
src/utils/file.js

@@ -0,0 +1,112 @@
+//import { i18n } from "@/lang/index"
+// 媒体名称
+/* export const mediaTypes = {
+    image: i18n.t("common.photo"),
+    video: i18n.t("common.video"),
+    audio: i18n.t("common.voice"),
+} */
+
+// 媒体扩展类型
+export const mediaMimes = {
+    image: ["jpg", "png", "jpeg", "bmp", "gif"],
+    audio: ["mp3", "aac", "ogg", "wav" /* , "m4a" */],
+    video: ["mp4", "mov", "quicktime", "webm" /* "rmvb", "wmv" */], //ios:mov
+}
+
+// 媒体大小显示(MB)
+export const mediaMaxSize = {
+    image: 10,
+    video: 20,
+    audio: 5,
+}
+
+/**
+ * 获取媒体扩展类型
+ * @param {Stirng} filename 文件名称
+ */
+export const getMime = filename => {
+    if (!filename || filename.indexOf(".") === -1) {
+        return ""
+    }
+
+    return filename
+        .split(".")
+        .pop()
+        .toLowerCase()
+}
+
+/**
+ * 在路径中获取文件名
+ * @param {*} path
+ */
+export const getFilename = path => {
+    const segment = (path || "").split("/")
+    return segment[segment.length - 1]
+}
+
+/**
+ * 检测媒体文件是否超过预设限制
+ * @param {String} type 媒体类型
+ * @param {Number} size 文件大小
+ */
+export const checkSizeLimit = (type, size) => {
+    size = size / 1024 / 1024
+
+    return size <= mediaMaxSize[type]
+}
+
+export const checkSizeLimitFree = (size, limit) => {
+    size = size / 1024 / 1024
+
+    return size <= limit
+}
+
+/**
+ * 检测媒体类型
+ * @param {String} type 媒体类型
+ * @param {String} filename 文件名称
+ */
+export const checkMediaMime = (type, filename) => {
+    const mime = getMime(filename)
+    const find = mediaMimes[type]
+    if (!find) {
+        return false
+    }
+
+    return find.indexOf(mime) !== -1
+}
+
+export const checkMediaMimeByAccept = (accept, filename) => {
+    let mime = getMime(filename)
+    let type = accept
+    if (type && type.indexOf("jpg") == -1 && type.indexOf("jpeg") != -1) {
+        type += ",image/jpg"
+    }
+    return (type || "").indexOf(mime) != -1
+}
+
+export const base64ToBlob = base64 => {
+    let arr = base64.split(","),
+        mime = arr[0].match(/:(.*?);/)[1],
+        bstr = atob(arr[1]),
+        n = bstr.length,
+        u8arr = new Uint8Array(n)
+    while (n--) {
+        u8arr[n] = bstr.charCodeAt(n)
+    }
+    return new Blob([u8arr], { type: mime })
+}
+
+export const base64ToDataURL = base64 => {
+    return window.URL.createObjectURL(base64ToBlob(base64))
+}
+
+export const blobToBase64 = function(blob) {
+    return new Promise(resolve => {
+        var reader = new FileReader()
+        reader.onload = function() {
+            resolve(reader.result)
+        }
+        reader.readAsDataURL(blob)
+    })
+}

+ 324 - 0
src/utils/mapClipBox.js

@@ -0,0 +1,324 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import {ctrlPolygon} from './ctrlPolygon'
+import {LineDraw } from "../utils/DrawUtil";
+import Sprite from '../viewer/Sprite'
+import {config} from '../settings'
+
+import math from "../utils/math";
+let texLoader = new THREE.TextureLoader() 
+
+let color = new THREE.Color(config.clip.color)
+
+let markerMats
+let markerSizeInfo = {width2d:30}
+
+
+
+
+export class mapClipBox extends ctrlPolygon {
+    constructor (center, scale) { 
+        center = center.clone().setZ(0);//所有Z都为0
+        
+        
+        let prop = {
+            points : getPoints(center, scale, 0),
+            closed : true,
+            isRect : true,
+            dimension : '2d'
+        }
+         
+        
+        super('mapClipBox', prop)
+        
+        this.angle = 0
+        this.createRotateBar();
+        this.edgeMarkers = [];
+        
+        //addMarkers:
+        this.initData(prop)
+        
+        
+        
+        
+        {//areaPlane event 能拖动 
+            this.areaPlane.addEventListener('mouseover',()=>{ 
+                viewer.dispatchEvent({
+                    type : "CursorChange", action : "add",  name:"mapClipMove"
+                })  
+            })
+            this.areaPlane.addEventListener('mouseleave',()=>{ 
+                viewer.dispatchEvent({
+                    type : "CursorChange", action : "remove",  name:"mapClipMove"
+                })
+            })
+            
+            let lastPos;
+            let drag = (e)=>{
+                let intersectPoint = e.intersectPoint.orthoIntersect 
+                if(lastPos){ 
+                    let moveVec = new THREE.Vector3().subVectors(intersectPoint, lastPos).setZ(0)
+                    this.center.add(moveVec)
+                    this.updatePoints()
+                    this.dispatchEvent({type:'repos'})
+                     
+                } 
+                lastPos = intersectPoint.clone();                
+            }
+            let drop = (e)=>{
+                lastPos = null
+            }
+            this.areaPlane.addEventListener('drag', drag)
+            this.areaPlane.addEventListener('drop', drop)
+              
+        }
+        
+        
+        /* this.addEventListener('dragChange',()=>{
+            this.updateTwoMidMarker(index+1)
+        }) */
+        
+        
+        
+        viewer.setObjectLayers(this, 'mapObjects' )
+    }
+   
+   
+    
+    
+    
+    getScale(){
+        return new THREE.Vector3(this.points[0].distanceTo(this.points[1]), this.points[1].distanceTo(this.points[2]) ,1)
+    }
+ 
+   
+    
+    addMarker(o={} ){
+        let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, dontFixOrient: true, viewports:viewer.mapViewer.viewports, name:"mapClipBox_marker",   } )
+        
+        marker.renderOrder = 3 
+        //marker.markerSelectStates = {}
+        
+        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')  */}
+        edgeMarker.addEventListener('mouseover', mouseover);
+        edgeMarker.addEventListener('mouseleave', mouseleave);
+        let dragInfo = {lastPos:null};
+        edgeMarker.addEventListener('drag', this.dragEdge.bind(this,dragInfo));
+        edgeMarker.addEventListener('drop', this.dropEdge.bind(this,dragInfo));   
+        this.edgeMarkers.push(edgeMarker)
+        this.add(edgeMarker)
+        
+        
+        super.addMarker({point:o.point, marker:marker,  edge})
+        
+    }
+    
+    
+    
+    dragEdge(dragInfo, e){//拖拽一个边(或一个边类型的marker),带动它的两个端点。 可以转化为拖拽marker往法线方向
+        var I, atMap 
+         
+        atMap = e.drag.dragViewport.name == 'mapViewport'
+        I = e.intersectPoint.orthoIntersect 
+        
+        if (I && dragInfo.lastPos) {
+            let i = this.edgeMarkers.indexOf(e.drag.object);
+            if (i !== -1) {  
+                let lineNormal = math.getNormal2d({p1:this.points[i], p2:this.points[(i+1)%4]}); 
+                let moveVec = new THREE.Vector3().subVectors(I, dragInfo.lastPos).setZ(0)//移动的向量
+                let dragVec = moveVec.projectOnVector(lineNormal).setZ(0)//在法线上移动的向量
+                let newPos = new THREE.Vector3().addVectors(this.points[i], dragVec) //marker应到的位置
+                this.dragChange(newPos, i, atMap) //转化为这个marker的拖拽
+            }
+        }
+        dragInfo.lastPos = I.clone();  
+    }
+    dropEdge(dragInfo, e){
+        dragInfo.lastPos = null 
+        this.setMarkerSelected(e.drag.object, false, 'single') //拖拽时似乎没有触发mouseout
+    }
+    
+    
+    
+    createAreaPlane(){
+        var planeMat =  new THREE.MeshBasicMaterial({
+            color,//"#00eeff",
+            side:THREE.DoubleSide,
+            opacity:0.3,
+            transparent:true,
+            depthTest:false
+        })
+        
+        return super.createAreaPlane(planeMat)
+    }
+    
+    
+    
+    getMarkerMaterial(type) {
+        if(!markerMats){
+            markerMats = {  
+                default:    new THREE.MeshBasicMaterial({  
+                    transparent: !0,
+                    color,
+                    opacity: 0.8,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ),  
+                }),
+                select:    new THREE.MeshBasicMaterial({   
+                    transparent: !0,
+                    color,
+                    opacity: 1,
+                    map: texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png' ), 
+                     
+                }),   
+            }
+            mapClipBox.markerMats = markerMats
+        }
+        return markerMats[type]
+        
+    }
+    
+    setMarkerSelected(marker, state, hoverObject){ 
+        //console.warn(marker.id , state, hoverObject)
+        if(state){
+            marker.material = this.getMarkerMaterial('select')
+        }else{
+            marker.material = this.getMarkerMaterial('default')
+        }
+        
+        /* marker.markerSelectStates[hoverObject] = state
+        let absoluteState = false
+        for(var i in marker.markerSelectStates){
+            if(marker.markerSelectStates[i]){
+                absoluteState = true; break;
+            }
+        }
+        if(absoluteState){
+            marker.material = this.getMarkerMaterial('select')
+        }else{
+            marker.material = this.getMarkerMaterial('default')
+        }
+        
+        marker.selected = absoluteState */
+        
+        viewer.mapViewer.emit('content_changed') 
+    }
+    
+    createRotateBar(){
+        //中心点在线的一端,整体初始在box上方
+        const lineLen = 1.5, circleWidth = 2, barOpacity = 0.7;
+       
+        let object = new THREE.Object3D;
+        
+        let bar = new Sprite({mat:new THREE.MeshBasicMaterial({ 
+                side:THREE.DoubleSide,
+                opacity: barOpacity,
+                transparent:true,
+                depthTest:false, 
+                map: texLoader.load(Potree.resourcePath+'/textures/rotation_circle.png' ), 
+            }) ,
+            root:object,
+            sizeInfo: markerSizeInfo, 
+            dontFixOrient: true,
+            viewports:viewer.mapViewer.viewports,
+            name:"mapClipRotateBar"
+        })
+        bar.position.set(0,lineLen+circleWidth/2,0)
+        bar.scale.set(circleWidth,circleWidth,circleWidth)
+        bar.addEventListener('mouseover',()=>{
+            bar.material.opacity = 1
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "add",  name:"mapClipRotate"
+            })
+            viewer.mapViewer.emit('content_changed') 
+        })
+        bar.addEventListener('mouseleave',()=>{
+            bar.material.opacity = barOpacity
+            viewer.dispatchEvent({
+                type : "CursorChange", action : "remove",  name:"mapClipRotate"
+            })
+            viewer.mapViewer.emit('content_changed') 
+        })
+        let lastPos;
+        bar.addEventListener('drag',(e)=>{
+            var intersectPoint = e.intersectPoint.orthoIntersect   
+            if(lastPos){ 
+                let vec1 = new THREE.Vector3().subVectors(lastPos, this.center).setZ(0)
+                let vec2 = new THREE.Vector3().subVectors(intersectPoint, this.center).setZ(0)
+                let angle = math.getAngle(vec1,vec2,'z')  
+                this.angle += angle 
+                this.rotateBar.rotation.z = this.angle
+                this.updatePoints() 
+                this.dispatchEvent({type:'rotate', angle: this.angle})
+            }
+            lastPos = intersectPoint.clone();       
+        })
+        bar.addEventListener('drop',()=>{
+            lastPos = null 
+        })
+        
+        let line = LineDraw.createLine([new THREE.Vector3, new THREE.Vector3(0,lineLen,0)],{color})
+        
+        
+        object.add(bar)
+        object.add(line) 
+        this.add(object)
+        this.rotateBar = object
+        this.rotateBar.bar = bar
+    }
+    
+    updatePoints(scale){
+        this.points = getPoints(this.center, scale || this.getScale(), this.angle)
+        this.getPoint2dInfo(this.points)
+        this.update(true) 
+    }
+    
+    update(ifUpdateMarkers){ 
+        super.update(ifUpdateMarkers)
+        
+        {//update rotateBar
+            let center = new THREE.Vector3().addVectors(this.points[0], this.points[1]).multiplyScalar(0.5); 
+            this.rotateBar.position.copy(center) 
+            this.rotateBar.bar.update()//更新sprite matrix
+        }
+        {
+            for(let i=0;i<4;i++){
+                let current = this.points[i];
+                let next = this.points[(i+1)%4];
+                let mid = new THREE.Vector3().addVectors(current, next).multiplyScalar(0.5)
+                
+                this.updateMarker(this.edgeMarkers[i], mid)
+            }
+            
+            
+        }
+        
+        
+    }
+    
+    
+     
+}
+
+
+
+
+
+
+function getPoints(center, scale, angle=0){  
+    let points = [
+        new THREE.Vector3(-scale.x/2, +scale.y/2, 0),  
+        new THREE.Vector3(+scale.x/2, +scale.y/2, 0), 
+        new THREE.Vector3(+scale.x/2, -scale.y/2, 0),  
+        new THREE.Vector3(-scale.x/2, -scale.y/2, 0),     
+    ] 
+    var rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle)//再旋转 
+    points.forEach(e=>{
+        e.applyMatrix4(rotMatrix) 
+        e.add(center) 
+    })
+    return points
+}
+ 

+ 489 - 0
src/utils/request.js

@@ -0,0 +1,489 @@
+/*
+ * @Author: Rindy
+ * @Date: 2019-08-06 16:25:08
+ * @LastEditors: Rindy
+ * @LastEditTime: 2021-08-27 12:33:49
+ * @Description: Request
+ */
+
+/** http请求模块
+ * @category utils
+ * @module utils/request
+ */
+
+ 
+ 
+import browser from "./browser"
+import { base64ToBlob } from "./file"
+//import { $alert, $confirm, $loginTips } from "@/components/shared/message"
+//import { $waiting } from "@/components/shared/loading"
+//import { checkLogin } from "@/api"
+//import { LoginDetector } from "@/core/starter"
+//import { i18n } from "@/lang"
+//import { password } from "@/utils/string"
+//import store from "../Store"
+
+// 空函数
+const noop = function() {}
+// 请求回调队列
+let postQueue = []
+
+/**
+ * @property {number} NEXT              - 继续执行
+ * @property {number} SUCCESS           - 成功
+ * @property {number} EXCEPTION         - 异常错误
+ * @property {number} FAILURE_CODE_3001 - 缺少必要参数
+ * @property {number} FAILURE_CODE_3002 - 访问异常
+ * @property {number} FAILURE_CODE_3003 - 非法访问
+ * @property {number} FAILURE_CODE_3004 - 用户未登录
+ * @property {number} FAILURE_CODE_3005 - 验证码已过期
+ * @property {number} FAILURE_CODE_3006 - 验证码错误
+ * @property {number} FAILURE_CODE_3007 - 昵称已存在
+ * @property {number} FAILURE_CODE_3008 - 该手机已被注册
+ * @property {number} FAILURE_CODE_3009 - 两次输入的密码不一致
+ * @property {number} FAILURE_CODE_3010 - 昵称长度错误
+ * @property {number} FAILURE_CODE_3011 - 密码长度错误
+ * @property {number} FAILURE_CODE_3012 - 昵称包含敏感词
+ * @property {number} FAILURE_CODE_3013 - 手机号码格式错误
+ * @property {number} FAILURE_CODE_3014 - 账号或密码不正确
+ * @property {number} FAILURE_CODE_3015 - 用户不存在
+ * @property {number} FAILURE_CODE_3016 - 您没有权限,请联系管理员
+ * @property {number} FAILURE_CODE_3017 - 空文件
+ * @property {number} FAILURE_CODE_3018 - 需要上传或使用的文件不存在
+ * @property {number} FAILURE_CODE_5010 - 找不到该场景对应的相机
+ * @property {number} FAILURE_CODE_5012 - 数据不正常
+ * @property {number} FAILURE_CODE_5014 - 无权操作该场景
+ * @property {number} FAILURE_CODE_5005 - 场景不存在
+ */
+export const statusCode = {
+    NEXT: -999,
+    SUCCESS: 0,
+    EXCEPTION: -1,
+    FAILURE_CODE_3001: 3001,
+    FAILURE_CODE_3002: 3002,
+    FAILURE_CODE_3003: 3003,
+    FAILURE_CODE_3004: 3004,
+    FAILURE_CODE_3005: 3005,
+    FAILURE_CODE_3006: 3006,
+    FAILURE_CODE_3007: 3007,
+    FAILURE_CODE_3008: 3008,
+    FAILURE_CODE_3009: 3009,
+    FAILURE_CODE_3010: 3010,
+    FAILURE_CODE_3011: 3011,
+    FAILURE_CODE_3012: 3012,
+    FAILURE_CODE_3013: 3013,
+    FAILURE_CODE_3014: 3014,
+    FAILURE_CODE_3015: 3015,
+    FAILURE_CODE_3016: 3016,
+    FAILURE_CODE_3017: 3017,
+    FAILURE_CODE_3018: 3018,
+    FAILURE_CODE_5010: 5010,
+    FAILURE_CODE_5012: 5012,
+    FAILURE_CODE_5014: 5014,
+    FAILURE_CODE_5005: 5005,
+}
+
+ 
+/**
+ * 获取Token
+ *  @function
+ */
+export function getToken() {
+    var urlToken = browser.urlHasValue("token", true)
+    if (urlToken) {
+        // 设置token共享给用户中心
+        localStorage.setItem("token", urlToken)
+    }
+    return urlToken || localStorage.getItem("token") || ""
+}
+
+/**
+ * 根据状态码的结果处理后续操作
+ * @function
+ * @param {number} code - 状态码
+ * @param {function} callback - 回调
+ */
+export function statusCodesHandler(code, callback) {
+    if (code == statusCode.EXCEPTION) {
+        return $alert({ content: i18n.t("tips.exception") })
+    }
+
+    if (
+        code == statusCode.FAILURE_CODE_3002 ||
+        code == statusCode.FAILURE_CODE_3003 ||
+        code == statusCode.FAILURE_CODE_3004
+    ) {
+        callback(code)
+        return showLoginTips()
+    }
+
+    if (code == statusCode.FAILURE_CODE_3001) {
+        callback(code)
+        return $alert({ content: i18n.t("tips.params_notfound") })
+    }
+
+    if (code == statusCode.FAILURE_CODE_3017) {
+        callback(code)
+        return $alert({ content: i18n.t("tips.file_notfound") })
+    }
+
+    if (code == statusCode.FAILURE_CODE_5005) {
+        /* if (!config.isEdit) {
+            return (location.href = config.pages.NotFound)
+        } */
+        callback(code)
+        return $alert({ content: i18n.t("tips.scene_notfound") })
+    }
+
+    if (code == statusCode.FAILURE_CODE_5010) {
+        callback(code)
+        return $alert({ content: i18n.t("tips.camera_notfound") })
+    }
+
+    if (code == statusCode.FAILURE_CODE_5012) {
+        callback(code)
+        return $alert({ content: i18n.t("tips.data_error") })
+    }
+
+    if (code == statusCode.FAILURE_CODE_5014) {
+        callback(code)
+        return $alert({ content: i18n.t("tips.auth_deny") })
+    }
+
+    return statusCode.NEXT
+}
+
+$.ajaxSetup({
+    headers: {},
+    beforeSend: function(xhr) {
+        const token = getToken()
+        if (token) {
+            xhr.setRequestHeader("token", token)
+        } else if (!token && this.url.indexOf("isLogin") != -1) {
+            showLoginTips()
+        }
+
+        /* if (config.oem == "localshow") {
+            // 本地版本兼容当前目录
+            if (this.url.indexOf("http") == -1 && this.url.indexOf("/") == 0) {
+                this.url = this.url.substr(1)
+            }
+        } */
+
+        // if(this.url.indexOf('http')==-1 && this.url.indexOf('/') !=0){
+        //     this.url = '/'+this.url
+        // }
+    },
+    error: function(xhr, status, error) {
+        // 出错时默认的处理函数
+        if (this.url.indexOf("/scene.json") != -1 && xhr.status == 404) {
+            return $alert({ content: i18n.t("tips.scene_notfound") })
+        } else if (this.type === "POST") {
+            return $alert({ content: i18n.t("tips.network_error") })
+        }
+    },
+    success: function(result) {},
+    complete: function() {
+        // Post类型请求无论成功或失败都关闭等待提示
+        if (this.type === "POST") {
+            http.__loading && $waiting.hide()
+        }
+
+        http.__loading = true
+    },
+})
+
+/**
+ * @namespace http
+ * @type {Object}
+ */
+export const http = {
+    statusCode,
+    __loading: true,
+    __request(xhr, method, url, data, done, fail) {
+        if (typeof done != "function") {
+            done = noop
+        }
+        if (typeof fail != "function") {
+            fail = noop
+        }
+
+        xhr.done(result => {
+            if (typeof result.code !== "undefined") {
+                const flag = statusCodesHandler(result.code, function(code) {
+                    // 需要登录的状态
+                    if (
+                        code == statusCode.FAILURE_CODE_3001 ||
+                        code == statusCode.FAILURE_CODE_3002 ||
+                        code == statusCode.FAILURE_CODE_3003 ||
+                        code == statusCode.FAILURE_CODE_3004
+                    ) {
+                        if (url.indexOf("isLogin") == -1 && url.indexOf("openSceneBykey") == -1) {
+                            postQueue.push(function() {
+                                http[method](url, data, done, fail)
+                            })
+                        }
+                    }
+                    fail()
+                })
+
+                if (flag === statusCode.NEXT) {
+                    done(result, result.code == 0)
+                }
+            } else {
+                done(result)
+            }
+        })
+
+        xhr.fail(fail)
+
+        xhr.always(() => (xhr = null))
+
+        return xhr
+    },
+    /**
+     * Get请求
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    get(url, data = {}, done, fail) {
+        if (/\.json/.test(url)) {
+            // json文件格式自动调用getJson方法
+            return this.getJson(url, data, done, fail)
+        }
+        return this.__request($.get(url, data), "get", url, data, done, fail)
+    },
+    /**
+     * Get Blob请求
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    getText(url, data = {}, done, fail) {
+        return this.__request(
+            $.ajax({
+                url: url,
+                dataType: "text",
+            }),
+            "getText",
+            url,
+            data,
+            done,
+            fail
+        )
+    },
+    /**
+     * GetJson请求 读取json文件数据
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    getJson(url, data = {}, done, fail) {
+        return this.__request($.getJSON(url, data), "get", url, data, done, fail)
+    },
+    /**
+     * Get Blob请求
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    getBlob(url, data = {}, done, fail) {
+        return this.__request(
+            $.ajax({
+                url: url,
+                dataType: "blob",
+            }),
+            "getBlob",
+            url,
+            data,
+            done,
+            fail
+        )
+    },
+    /**
+     * Get Arraybuffer请求
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    getArraybuffer(url, data = {}, done, fail) {
+        return this.__request(
+            $.ajax({
+                url: url,
+                dataType: "arraybuffer",
+            }),
+            "getArraybuffer",
+            url,
+            data,
+            done,
+            fail
+        )
+    },
+    /**
+     * Post 请求
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    post(url, data = {}, done, fail) {
+        if (url.indexOf("isLogin") == -1) {
+            http.__loading && $waiting.show()
+        }
+
+        return this.__request($.post(url, data), "post", url, data, done, fail)
+    },
+    /**
+     * PostJson 请求
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    postJson(url, data = {}, done, fail) {
+        http.__loading && $waiting.show()
+        return this.__request(
+            $.ajax({
+                type: "POST",
+                url: url,
+                contentType: "application/json",
+                data: JSON.stringify(data),
+            }),
+            "postJson",
+            url,
+            data,
+            done,
+            fail
+        )
+    },
+    /**
+     * Post 表单 支持文件上传
+     * @param {String} url 请求地址
+     * @param {FormData?} formData 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    postForm(url, formData, done, fail, onProgress) {
+        if (typeof onProgress === "function") {
+            return this.__request(
+                $.ajax({
+                    type: "POST",
+                    url: url,
+                    processData: false,
+                    contentType: false,
+                    data: formData,
+                    xhr: function() {
+                        const xhr = new XMLHttpRequest()
+                        xhr.upload.addEventListener("progress", function(e) {
+                            onProgress((e.loaded / e.total) * 100 + "%")
+                        })
+                        return xhr
+                    },
+                }),
+                "postForm",
+                url,
+                formData,
+                done,
+                fail
+            )
+        } else {
+            http.__loading && $waiting.show()
+            return this.__request(
+                $.ajax({
+                    type: "POST",
+                    url: url,
+                    processData: false,
+                    contentType: false,
+                    data: formData,
+                }),
+                "postForm",
+                url,
+                formData,
+                done,
+                fail
+            )
+        }
+    },
+    /**
+     * 加载图片
+     * @param {String} url 请求地址
+     * @param {Number?} retry 重试次数,默认为3
+     */
+    loadImage(url, retry = 3) {
+        const def = $.Deferred()
+        const img = new Image()
+
+        /* if (process.env.VUE_APP_REGION == "AWS" && url.indexOf("x-oss-process=image") != -1) {
+            var arr = url.split("?")
+            url = arr[0] + encodeURIComponent("?" + arr[1].replace(/\//g, "@"))
+        } */
+
+        const load = () => {
+            console.warn("Retrying load image: " + url)
+            this.loadImage(url, retry - 1)
+                .done(def.resolve.bind(def))
+                .progress(def.notify.bind(def))
+                .fail(def.reject.bind(def))
+        }
+
+        img.onerror = function() {
+            retry > 0 ? setTimeout(() => load(), 1e3) : def.reject(`[${url}]加载失败`)
+        }
+
+        img.onload = function() {
+            def.resolve(img)
+        }
+
+        img.crossOrigin = "anonymous"
+        img.src = url
+
+        return def
+    },
+    /**
+     * 上传文件
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    uploadFile(url, data = {}, done, fail, onProgress) {
+        const form = new FormData()
+        // if (file.needTransfer) { //ie和苹果都不支持dataURLtoFile得传送,所以只能用blob
+        //     form.append("file", common.dataURLtoBlob(file.file), file.name || file.file.name);
+        // } else {
+        //     form.append("file", file.file, file.name || file.file.name);
+        // }
+        for (let key in data) {
+            if (key == "file") {
+                form.append("file", data[key], data.filename || data[key].name)
+            } else if (key != "filename") {
+                form.append(key, data[key])
+            }
+        }
+        return this.postForm(url, form, done, fail, onProgress)
+    },
+    /**
+     * 上传文件
+     * @param {String} url 请求地址
+     * @param {Object?} data 请求参数 {file:'base64 string',filename:'image.jpg',...}
+     * @param {Function?} done 成功回调
+     * @param {Function?} fail 失败回调
+     */
+    uploadBlobFile(url, data = {}, done, fail) {
+        const form = new FormData()
+        for (let key in data) {
+            if (key === "file") {
+                form.append("file", base64ToBlob(data.file), data.filename)
+            } else if (key != "filename") {
+                form.append(key, data[key])
+            }
+        }
+        return this.postForm(url, form, done, fail)
+    },
+}

+ 123 - 0
src/viewer/Sprite.js

@@ -0,0 +1,123 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import DepthBasicMaterial from "../materials/DepthBasicMaterial.js";
+import math from "../utils/math.js";
+
+const geo = new THREE.PlaneBufferGeometry(1,1)
+export default class Sprite extends THREE.Mesh{
+    
+    constructor(options){  
+        super(geo, options.mat || new DepthBasicMaterial({map:options.map, useDepth:options.useDepth}))
+         
+        this.root = options.root || this;
+        this.renderOrder = options.renderOrder != void 0 ? options.renderOrder : 4;
+        this.sizeInfo = options.sizeInfo
+        this.dontFixOrient = options.dontFixOrient
+        
+        this.root.matrixAutoUpdate = false;
+        this.matrixMap = new Map()
+        this.name = options.name || 'sprite'
+        this.useViewport = null
+        this.viewports = options.viewports//指定更新的viewports
+        
+        viewer.addEventListener("camera_changed", (e)=>{
+            /* if(viewer.viewports.length == 1){//直接更新。如果有多个不在这更新,在"render.begin"
+                this.update(e)
+            } */
+            this.update(e)            
+        });
+        
+        
+        viewer.mapViewer.addEventListener("camera_changed", (e)=>{
+            this.update(e)     
+        });
+        
+        viewer.addEventListener("render.begin", (e)=>{//before render
+            //if(viewer.viewports.length > 1) this.update(e)  //magnifier时要禁止吗
+            
+            this.applyMatrix(e) 
+        });
+        
+        viewer.on("raycaster", (e)=>{//before render
+            this.applyMatrix(e) 
+        })
+    }
+    
+    
+    
+    update(e){
+        if(!e){
+            (this.viewports || viewer.viewports).forEach(view=>{
+                this.update({viewport:view}) 
+            })
+            return;
+        }
+        if(this.viewports && !this.viewports.includes(e.viewport) )return
+        if(e.viewport.name == 'magnifier')return
+        
+        let camera = e.viewport.camera
+        //rotation
+        this.dontFixOrient || this.root.quaternion.copy(camera.quaternion)
+        
+        //scale
+        var info = this.sizeInfo
+        if(info){
+            this.root.updateMatrix();//先更新,getWorldPosition才能得到正确的
+            this.root.updateMatrixWorld(true)
+            
+            
+            var scale
+            if(info.restricMeshScale){//仅限制最大或最小的话,不判断像素大小,直接限制mesh的scale
+                var dis = camera.position.distanceTo(this.getWorldPosition(new THREE.Vector3()))
+                if(dis < info.nearBound){
+                    scale = info.scale * dis / info.nearBound
+                }else{
+                    scale = info.scale
+                }
+            }else{
+                
+                scale = math.getScaleForConstantSize($.extend(info,{//规定下最小最大像素 
+                    camera , position:this.getWorldPosition(new THREE.Vector3()) ,
+                    resolution: e.viewport.resolution2
+                }))
+                
+            }
+            
+            if(!isNaN(scale)){
+                this.root.scale.set(scale, scale, scale); 
+            } 
+        }
+        this.root.updateMatrix();
+        this.root.updateMatrixWorld(true)
+        this.matrixMap.set(e.viewport, this.root.matrix.clone())
+        
+        this.useViewport = e.viewport
+    }
+    
+    applyMatrix(e){
+        if(!e)e = {viewport:viewer.mainViewport}//随便写一个viewport
+        if(e.viewport.name == 'magnifier')return
+        if(this.viewports && !this.viewports.includes(e.viewport) )return
+        
+        
+        var matrix = this.matrixMap.get(e.viewport);
+          
+        if(!matrix){
+            this.update(e)
+            matrix = this.matrixMap.get(e.viewport);
+        }
+        
+        if(e.viewport == this.useViewport){
+            return
+        }       
+        this.useViewport = e.viewport   
+        this.root.matrix.copy(matrix) 
+        this.root.updateMatrixWorld(true)
+        //console.log(this.root.name + e.viewport.name + " : "+this.root.matrixWorld.elements)
+    }
+    
+    setUniforms(name,value){
+        this.material.setUniforms(name,value) 
+    }
+    
+     
+}

+ 89 - 0
src/viewer/Viewport.js

@@ -0,0 +1,89 @@
+
+
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import Common from '../utils/Common'
+
+export default class Viewport{
+    
+    constructor( view, camera, prop={}){//目前不支持换camera
+        
+        this.left = prop.left;
+        this.bottom = prop.bottom;
+        this.width = prop.width;
+        this.height = prop.height;
+        this.name = prop.name 
+        this.view = view
+        this.camera = camera 
+        this.active = true 
+        this.unableChangePos = false
+        this.noPointcloud;
+        //this.keys = [...] firstPersonCtl....
+        this.resolution = new THREE.Vector2;
+        this.resolution2 = new THREE.Vector2;
+        this.offset = new THREE.Vector2; //viewportOffset 范围从0-整个画布的像素
+        this.extraEnableLayers = prop.extraEnableLayers || [];//额外可展示的层
+        this.cameraLayers = prop.cameraLayers 
+        
+    }
+     
+    
+    clone(){ 
+        return Common.CloneClassObject(this)
+          
+    }
+    
+    getMoveSpeed(){
+        return this.moveSpeed
+    }
+    setMoveSpeed(e){
+        this.moveSpeed = e
+    }
+    
+    layersAdd(name){
+        this.extraEnableLayers.includes(name) || this.extraEnableLayers.push(name)
+        
+    }
+    layersRemove(name){
+        let index = this.extraEnableLayers.indexOf(name)
+        if(index > -1){
+            this.extraEnableLayers.splice(index, 1)
+        }
+    }
+
+
+    
+	cameraChanged() {
+		var copy = ()=>{
+            this.previousState = {
+                projectionMatrix: this.camera.projectionMatrix.clone(),//worldMatrix在this.control时归零了所以不用了吧,用position和qua也一样
+                position: this.camera.position.clone(),
+                quaternion: this.camera.quaternion.clone()
+                
+            }; 
+        }
+        
+        
+        if (!this.previousState){ 
+            copy()
+            return true//{cameraChanged:!0, changed:!0};
+        }
+        var cameraChanged =  
+            !this.camera.projectionMatrix.equals(this.previousState.projectionMatrix) ||
+            !this.camera.position.equals(this.previousState.position) ||
+            !this.camera.quaternion.equals(this.previousState.quaternion)
+            
+             
+        copy() 
+        
+        return cameraChanged
+	}
+    
+    setResolution(w,h, wholeW=0, wholeH=0){
+        this.resolution.set(w,h);//是client的width height
+        
+        this.resolution2.copy(this.resolution).multiplyScalar(window.devicePixelRatio)
+         
+        this.offset.set(wholeW,wholeH).multiply(new THREE.Vector2(this.left,this.bottom)).multiplyScalar(window.devicePixelRatio) 
+    }
+}

+ 735 - 0
src/viewer/map/Map.js

@@ -0,0 +1,735 @@
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import { EventDispatcher } from "../../EventDispatcher.js";
+let texLoader = new THREE.TextureLoader() 
+    texLoader.crossOrigin = "anonymous" 
+
+
+let createErrorMaterial = function() {
+   var t = new THREE.MeshBasicMaterial({
+       transparent: !0,
+       depthWrite: !1,
+       depthTest: !0,
+       opacity: 1,
+       side: THREE.DoubleSide
+   });
+   return t.color = new THREE.Color(3355443),
+   t
+}
+let tempVector = new THREE.Vector3,  //sharedata
+    face1 = new THREE.Face3(0,1,2),
+    face2 = new THREE.Face3(2,3,0),
+    errorMaterial = createErrorMaterial(),
+    uv00 = new THREE.Vector2(0,0),
+    uv01 = new THREE.Vector2(0,1),
+    uv10 = new THREE.Vector2(1,0),
+    uv11 = new THREE.Vector2(1,1),
+    face1UV = [uv00, uv10, uv11],
+    face2UV = [uv11, uv01, uv00]
+
+
+
+const HALF_WORLD_SIZE = 21e6 
+const MAX_VERTICAL_DIST = 2 
+const MAX_VERTICAL_DIST_TO_BEST = 1  
+ 
+
+
+
+
+
+
+
+export class MapLayer extends EventDispatcher{ // 包括了 MapLayerBase SceneLayer
+    constructor(viewer_, viewport){
+        super()
+        this.sceneGroup = new THREE.Object3D;
+        this.sceneGroup.name = "MapLayer" 
+        
+        
+        this.loadingInProgress = 0
+        this.maps = [] 
+        this.frustum = new THREE.Frustum 
+        this.frustumMatrix = new THREE.Matrix4 
+        this.tileColor = /* i && i.tileColor ? i.tileColor :  */new THREE.Color(16777215)
+        
+        
+        //添加地图
+        var map = new TiledMapOpenStreetMap(this, this.tileColor )
+        this.addMap(map)
+         
+        
+        this.viewport = viewport
+        this.changeViewer(viewer_) 
+        
+        /* this.on('needUpdate',()=>{ 
+            this.mapLayer.update()
+        }) 
+          */
+    }
+    
+    addMapEntity(data){
+        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.needUpdate = true
+        
+        this.dispatchEvent({type:'floorplanLoaded', floorplan})
+    }
+    
+    addMap(t){ 
+        this.maps.push(t)
+        //this.view.invalidateScene()
+        this.needUpdate = true
+    }
+     
+    removeMap(t){
+        var e = this.maps.indexOf(t);
+        if(e >= 0){
+            t.removeFromSceneGroup(this.sceneGroup) 
+            this.maps.splice(e, 1) 
+        }
+         
+        /* this.view.invalidateScene() */ 
+        this.needUpdate = true
+        this.viewer.dispatchEvent({
+            type:'content_changed'
+        }) 
+    }
+    
+     
+    
+    
+    changeViewer(viewer_){//add 
+        this.viewer = viewer_ 
+    }
+    
+    initProjection(){
+        this.maps.forEach(map=>{
+            map.updateProjection()
+            map.updateObjectGroup() 
+        })
+    }
+    
+    visibilityChanged(){
+        if (!this.visible)
+            for (var t = 0, e = this.maps; t < e.length; t++){
+                e[t].removeFromSceneGroup(this.sceneGroup)
+            }
+    }
+    
+    onAfterRenderViewport(e){
+        var n = this;
+        
+        /* this.isVisibleInViewport(e) && (this.updateTimer || this.loadingInProgress || (this.updateTimer = window.setTimeout((function(){
+            n.update(e).then((function(t){
+                t && n.loadComplete.emit(!0)
+            }
+            )).catch(u.handleWarning)
+        }
+        ), 100))) */
+    }
+     
+     
+     
+    update(){
+        this.needUpdate = false
+        if(this.disabled || !this.maps.find(e=>!e.disabled) || !this.maps.find(e=>e.objectGroup.visible)   )return  //add
+        
+         
+        var e, n, i, r, o;
+        
+        this.updateTimer = void 0,
+        e = this.viewport.camera,
+        n = e.projectionMatrix.clone(),
+        
+        n.elements[0] /= 1.5,
+        n.elements[5] /= 1.5,
+        this.frustumMatrix.multiplyMatrices(n, e.matrixWorldInverse),
+        this.frustum.setFromProjectionMatrix(this.frustumMatrix),
+        this.frustum.planes[4].setComponents(0, 0, 0, 0),
+        this.frustum.planes[5].setComponents(0, 0, 0, 0),
+        i = !0 
+        
+       
+        for (r = 0; r < this.maps.length; r++){
+            var map = this.maps[r] 
+            i = map.update(this.frustum, this.sceneGroup) && i;
+        }
+            
+        return [2, i]
+        
+                 
+    }
+     
+    getAttributions(){
+        for (var t = {}, e = 0, n = this.maps; e < n.length; e++){
+            n[e].fillAttributions(t)
+        }
+        return t
+    }
+     
+    updateProjection(){
+        for (var t = 0, e = this.maps; t < e.length; t++){
+            var n = e[t];
+            n.clearProjection(),
+            n.updateObjectGroup()
+        }
+    }
+}
+ 
+    
+    
+    
+    
+    
+    
+    
+    
+   
+ 
+
+export class TiledMapBase extends EventDispatcher{
+    constructor(/* t,  */name, mapLayer, tileColor, projection){
+        super();
+        this.name = name
+        //this.TransformService = t,
+        this.mapLayer = mapLayer,
+        this.tileColor = tileColor,
+        this.bias = 0 
+        this.zIndex = -1  
+        this.objectGroup = new THREE.Object3D;
+        this.objectGroup.name = name
+        this.objectGroupAdded = !1,
+        this.baseTile = new MapTile(this,/*   this.mapLayer, */this.objectGroup,this.tileColor),
+        this.isTileVisibleBox = new THREE.Box3,
+        this.isTileVisibleVec = new THREE.Vector3
+        
+        
+        this.projection = projection
+        this._zoomLevel = 0;//1-20
+        
+    }
+    
+    get zoomLevel(){
+        return this._zoomLevel
+    }
+    set zoomLevel(zoomLevel){
+        if(this._zoomLevel != zoomLevel){
+            this._zoomLevel = zoomLevel
+            this.emit('zoomLevelChange',zoomLevel)
+        }
+    }
+    
+    
+    updateObjectGroup(){
+        this.position && this.objectGroup.position.copy(this.position),
+        this.quaternion && this.objectGroup.quaternion.copy(this.quaternion),
+        this.objectGroup.updateMatrixWorld(!0)
+    }
+    
+    updateProjection(){
+        //this.transformMapToLocal || (this.transformMapToLocal = this.TransformService.getTransform(this.projection, this.TransformService.crsLocal))
+        if(!this.transformMapToLocal){
+            if(proj4.defs("NAVVIS:TMERC")){
+                if(this.projection == "EPSG:4550"){
+                    this.transformMapToLocal = {
+                            forward:(e)=>{
+                                var a = viewer.transform.lonlatTo4550.inverse(e)
+                                return viewer.transform.lonlatToLocal.forward(a)
+                            },
+                        }
+                        
+                   
+                }else{
+                    this.transformMapToLocal = proj4(this.projection, "NAVVIS:TMERC") 
+                }
+                    //this.transformMapToLocal = proj4(this.projection, "NAVVIS:TMERC") 
+             
+               
+            }
+        } 
+    
+    } 
+    
+    setEnable(enable){//add
+        if(!this.disabled == enable)return
+        
+        this.disabled = !enable
+     
+        viewer.updateVisible(this.objectGroup, 'setEnable', enable)
+        
+        if(!enable){
+            this.baseTile.remove()
+        }else{
+            this.mapLayer.needUpdate = true
+        }
+         
+        this.mapLayer.viewer.dispatchEvent({
+            type:'content_changed'
+        })
+        
+        
+    }
+    
+    
+    
+    /* clearProjection(){
+        this.transformMapToLocal = void 0,
+        this.projection.name !== this.TransformService.NAVVIS_LOCAL && this.baseTile.remove()
+    } */
+    
+    update(e, n){
+        
+        
+        if(this.disabled || !this.objectGroup.visible)return
+        
+        this.updateProjection()
+        
+        if(!this.transformMapToLocal)return
+        
+        if ( !this.isTileVisible(new THREE.Vector3(0,0,0), this.mapSizeM, e))
+            return this.removeFromSceneGroup(n), !0;
+        
+        let viewport = this.mapLayer.viewport
+        
+        
+        var i = new THREE.Vector3(-.5 * this.mapSizeM,0,0);
+        i.applyMatrix4(this.objectGroup.matrixWorld),
+        i.project(viewport.camera);
+        var o = new THREE.Vector3(.5 * this.mapSizeM,0,0);
+        o.applyMatrix4(this.objectGroup.matrixWorld),
+        o.project(viewport.camera);
+        var a = viewport.resolution.x 
+          , s = viewport.resolution.y 
+        if (a <= 0 || s <= 0 || isNaN(i.x) || isNaN(o.x))  return !1;
+        i.sub(o),
+        i.x *= a / 2,
+        i.y *= s / 2;
+        var c = this.tileSizePx / i.length()
+          , level = Math.ceil(-Math.log(c) / Math.log(2) - this.bias);
+        level = Math.max(level, 0) 
+        level = Math.min(level, void 0 === this.maxDepth ? 1 / 0 : this.maxDepth) 
+        this.zoomLevel = level//add
+        //console.log(level)
+        
+        this.addToSceneGroup(n) 
+        return this.baseTile.update(this, e, level, this.mapSizeM, 0, 0, "")
+    }
+     
+    isTileVisible(e, n, i){
+        if (n > HALF_WORLD_SIZE) return !0;
+        var r = .5 * n;
+        this.transformMapToLocal.forward(e) 
+        this.isTileVisibleBox.makeEmpty() 
+        this.isTileVisibleVec.set(e.x - r, e.y - r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
+        this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
+        this.isTileVisibleVec.set(e.x - r, e.y + r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
+        this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
+        this.isTileVisibleVec.set(e.x + r, e.y - r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
+        this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
+        this.isTileVisibleVec.set(e.x + r, e.y + r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
+        this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
+        return i.intersectsBox(this.isTileVisibleBox)
+    }
+     
+    addToSceneGroup(t){
+        this.objectGroupAdded || (t.add(this.objectGroup),
+        this.objectGroupAdded = !0)
+    }
+     
+    removeFromSceneGroup(t){
+        this.baseTile.remove(),
+        this.objectGroupAdded && (t.remove(this.objectGroup),
+        this.objectGroupAdded = !1)
+    }
+    
+    
+}
+
+export class MapTile{
+    constructor(map,/*  t, */ e, n){
+        this.map = map;
+        //this.mapLayer = t,
+        this.objectGroup = e,
+        this.tileColor = n,
+        this.meshAdded = !1,
+        this.textureLoaded = !1,
+        this.children = []
+    }
+    update(e, n, i, r, o, a, s){
+        return !!this.doesNotContainTilesToBeDisplayed(e) || (0 === i ? this.updateTile(e, r, o, a) : this.updateSubTiles(e, n, i, r, o, a, s))
+    }
+    
+    doesNotContainTilesToBeDisplayed(t){
+        return t.tilePresenceMap && t.tilePresenceMap.empty
+    }
+    
+    updateTile(t, e, n, i){ 
+        if(!this.mesh){
+            this.createTileObject(t, e, n, i)
+        }
+        if(!this.meshAdded){
+            this.objectGroup.add(this.mesh) 
+            this.meshAdded = !0
+        }
+        if(this.textureLoaded){
+            this.removeChildren() 
+        }
+        
+        return this.textureLoaded
+    }
+    
+    updateSubTiles(entity, n, level, o, a, s, c){
+        for (var l = !0, u = [-.25 * o, .25 * o, -.25 * o, .25 * o], d = [.25 * o, .25 * o, -.25 * o, -.25 * o], p = 0; p < 4; ++p){
+            var h = c + p.toString(10);
+            //一级(512):0 1 2 3分别为左上、右上、左下、右下。二级(1024)就是把一级的每一块分裂,如00 01 02 03分别是0的左上、右上、左下、右下……
+            /* if(entity.name == 'floorplan'){
+                console.log(1)
+            } */
+            
+            
+            if (!entity.tilePresenceMap || entity.tilePresenceMap[h]){
+                //去掉判断,直接显示
+                var f = a + u[p]
+                  , m = s + d[p];
+                tempVector.set(f, m, 0);
+                if (entity.isTileVisible(tempVector, .5 * o, n)){
+                     
+                    this.children[p] || (this.children[p] = new MapTile(this.map, this.objectGroup,this.tileColor))
+                    l = this.children[p].update(entity, n, level - 1, .5 * o, f, m, h) && l
+                } else {
+                    if (this.children[p]){
+                        this.children[p].remove()
+                        delete this.children[p]
+                    }
+
+                }
+                 
+            }
+        }
+        return l && this.removeObject3D(),
+        l
+
+    }
+    
+    createTileObject(t, e, n, a){
+        var s = this;
+        this.mesh = this.createMesh(t.transformMapToLocal, e, n, a),
+        this.textureLoaded = !1;
+        var c = t.mapSizeM / e
+          , l = Math.log(c) / Math.log(2)
+          , u = n / e + .5 * (c - 1)
+          , d = -a / e + .5 * (c - 1)
+          , p = t.getTileUrl(Math.round(l), Math.round(u), Math.round(d));
+        viewer.setObjectLayers(this.mesh, 'map' )
+        this.mesh.renderOrder = -(1e6 - l - 100 * (t.zIndex || 0));
+        var h = this.mesh.material;
+        
+        
+        var loadDone = ()=>{
+            this.map.mapLayer.loadingInProgress--
+            if(this.map.mapLayer.loadingInProgress == 0){
+                this.map.mapLayer.emit('loadDone') 
+            }
+        }
+        
+        h.map = texLoader.load(p, (tex)=>{
+            if(this.mesh){//如果还要显示的话
+                this.textureLoaded = true
+                this.mesh.material.opacity = 1
+                //this.mapLayer.view.invalidateScene()
+                
+                this.map.mapLayer.viewer.dispatchEvent({
+                    type:'content_changed'
+                })
+                this.map.mapLayer.needUpdate = true //表示还要继续update(以removeChildren)
+            }else{
+                tex.dispose()   
+            } 
+            loadDone()
+        } , void 0, (()=>{//error
+            this.textureLoaded = !0
+            if(this.mesh){ 
+                this.mesh.material.dispose() //o.disposeMeshMaterial(this.mesh) 
+                this.mesh.material =  errorMaterial
+                //this.map.mapLayer.view.invalidateScene())
+                this.map.mapLayer.viewer.dispatchEvent({
+                    type:'content_changed'
+                }) 
+            } 
+            loadDone()
+        })) 
+        
+        h.map.anisotropy = 0,
+        h.map.generateMipmaps = !1,
+        h.map.minFilter = THREE.LinearFilter,
+        h.map.magFilter = THREE.LinearFilter,
+        this.map.mapLayer.loadingInProgress++
+    }
+    
+    createMesh(t, e, n, o){
+        var a = new THREE.Geometry;
+        return tempVector.set(n - e / 2, o - e / 2, 0),
+        a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
+        tempVector.set(n + e / 2, o - e / 2, 0),
+        a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
+        tempVector.set(n + e / 2, o + e / 2, 0),
+        a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
+        tempVector.set(n - e / 2, o + e / 2, 0),
+        a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
+        a.faces.push(face1),
+        a.faces.push(face2),
+        a.faceVertexUvs[0].push(face1UV),
+        a.faceVertexUvs[0].push(face2UV),
+        new THREE.Mesh(a,this.createMaterial())
+    }
+    
+    createMaterial(){
+        var t = new THREE.MeshBasicMaterial({
+            transparent: !0,
+            depthWrite: !1,
+            depthTest: !0,
+            opacity: 0,
+            side: THREE.DoubleSide
+        });
+        return t.color = this.tileColor ? this.tileColor : new THREE.Color(16777215),
+        t
+    }
+    
+    remove(){
+        this.removeObject3D(),
+        this.removeChildren()
+    }
+    
+    removeObject3D(){
+        if (this.mesh){
+            if (this.objectGroup.remove(this.mesh),
+            this.textureLoaded){
+                var t = this.mesh.material.map;
+                t && t.dispose()
+            } 
+            this.mesh.material.dispose() //o.disposeMeshMaterial(this.mesh),
+            this.mesh.geometry.dispose() 
+            this.mesh = void 0
+        }
+        this.meshAdded = !1,
+        this.textureLoaded = !1
+        
+    }
+    
+    removeChildren(){
+        for (var t = 0, e = this.children; t < e.length; t++){
+            var n = e[t];
+            n && (n.removeObject3D(),
+            n.removeChildren())
+        }
+        this.children.length = 0
+    }
+  
+}
+
+
+
+
+proj4.defs("EPSG:3857", "+title=WGS 84 / Pseudo-Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs")
+//这里地图世界的中心是不是lon:0,lat:0     
+
+export class TiledMapOpenStreetMap extends TiledMapBase{
+    constructor(mapLayer, tileColor){
+        super('map', mapLayer, tileColor, /* "EPSG:4550" */  "EPSG:3857"  ) //EPSG projection
+        this.baseUrl = "https://wprd03.is.autonavi.com/appmaptile?style=7&x=${x}&y=${y}&z=${z}",
+        //this.baseUrl = "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x=${x}&y=${y}&z=${z}" //最高只到18 level
+        this.attribution = "© PopSmart,  © 高德地图",
+        this.tileSizePx = 256
+        this.mapSizeM = 40075017
+        this.maxDepth = 20
+        this.bias = 0.5 
+         
+      
+            
+    }
+    
+    getTileUrl(t, e, n){
+        return this.baseUrl.replace(/\${z}/, t.toString(10)).replace(/\${x}/, e.toString(10)).replace(/\${y}/, n.toString(10))
+    }
+     
+    fillAttributions(t){
+        t[this.attribution] = {
+            score: 50
+        }
+    }
+    
+    
+    
+}
+
+
+
+
+export class TiledMapFromEntity extends TiledMapBase{
+    constructor(mapLayer, tileColor, data){ 
+        super('floorplan', mapLayer, tileColor,  "NAVVIS:TMERC"   /* "EPSG:3857"  *//* "WGS84" */)  //直接就是本地坐标,没有projec 
+        
+         
+        var entity = this.fillFromData(data) 
+         
+        this.tiledMapEntity = entity
+        //c.RestService = s,
+        this.tileSizePx = entity.tileSizePx,
+        this.mapSizeM = entity.mapSizeM,
+        this.maxDepth = entity.maxDepth,
+        //this.projection = n.crsLocal,
+        this.zIndex = 0, 
+        this.tilePresenceMap = this.decodeBitStream(this.tiledMapEntity.quadtree) //包含tile分裂信息,如果写错了会造成tile显示不全
+         
+    }
+  
+    fillFromData(e){ 
+        let data = {}
+        
+        data.id = e.id
+        data.globalLocation = Potree.Utils.VectorFactory.fromArray3(e.location),
+        data.orientation = Potree.Utils.QuaternionFactory.fromArray(e.orientation) 
+        if(Potree.fileServer){
+            data.filePath = `https://${Potree.config.urls.prefix}${e.file_path}`
+        }else{
+            data.filePath = `https://${Potree.config.urls.prefix}/data/${Potree.settings.number}/${e.file_path}`
+        }
+         
+        //if(!data.filePath.includes('building_1'))data.filePath = data.filePath.replace('building','building_1')//暂时
+        data.fileName = '$DEPTH/$X/$Y.png',//e.file_name,
+        data.type = e.type,
+        data.mapSizeM = e.map_size_m,
+        data.tileSizePx = e.tile_size_px,
+        data.maxDepth = e.max_depth,
+        data.quadtree = e.quadtree,
+        data.floorId = e.floor_id,
+        data.bundleId = e.bundle_id  
+        //this.computeLocalCoordinates()
+        return data
+        
+    }
+    
+    
+    
+    computeLocalCoordinates(){
+        if(proj4.defs("NAVVIS:TMERC")){
+            this.tiledMapEntity.location = new THREE.Vector3().copy(viewer.transform.lonlatToLocal.forward(this.tiledMapEntity.globalLocation))
+        } 
+    }
+     
+    updateProjection() {
+        super.updateProjection()
+        if(!this.position){
+            this.computeLocalCoordinates()
+        } 
+        /* this.projection = this.TransformService.crsLocal,
+        t.prototype.updateProjection.call(this) */
+    } 
+    
+    
+    get position(){
+        return this.tiledMapEntity.location
+       /* enumerable: !0,
+       configurable: !0 */
+    }
+    get quaternion(){
+        return this.tiledMapEntity.orientation
+        /* enumerable: !0,
+        configurable: !0 */
+    }
+   
+    getTileUrl(t, e, n) {
+        var i = (this.tiledMapEntity.filePath + "/" + this.tiledMapEntity.fileName).replace(/\$DEPTH/g, t.toString(10)).replace(/\$X/g, e.toString(10)).replace(/\$Y/g, n.toString(10));
+        return i += "?t=" + (new Date).getTime() 
+        //this.RestService.addAuthorizationQueryParameter(i) //????
+    }
+    
+    fillAttributions(t) {
+        t.NavVis = {
+            score: 100
+        }
+    }
+   
+    
+    
+    
+    decodeBitStream(t) {
+        if (!t)
+            return {
+                empty: !0
+            };
+        for (var e = {}, n = [e], i = 0; i < t.length; i++) {
+            var r = n.shift()
+              , o = parseInt(t.substr(i, 1), 16);
+            if (1 & o) {
+                var a = {};
+                r[0] = a,
+                n.push(a)
+            }
+            2 & o && (a = {},
+            r[1] = a,
+            n.push(a)),
+            4 & o && (a = {},
+            r[2] = a,
+            n.push(a)),
+            8 & o && (a = {},
+            r[3] = a,
+            n.push(a))
+        }
+        var s = {
+            empty: !0
+        };
+        return this.computeHashes(s, e, ""),
+        s
+    }
+   
+    computeHashes(t, e, n) {
+        for (var i = 0; i < 4; i++)
+            e[i] && (t[n + i.toString(10)] = !0,
+            t.empty = !1,
+            this.computeHashes(t, e[i], n + i.toString(10)))
+    }
+    
+ 
+
+}
+
+
+/* { 
+    "bundle_id": 1,                         //t-CwfhfqJ
+    "file_name": "$DEPTH/$X/$Y.png",
+    "file_path": "data/bundle_t-CwfhfqJ/building_1/map_tiles/11",
+    "floor_id": 11,
+    "id": 1,
+    "location": [
+        113.5957510575092,
+        22.366605927999239,
+        0.0
+    ],
+    "map_size_m": 61.44,
+    "max_depth": 3,
+    "orientation": [
+        0.7071067811865476,
+        0.0,
+        0.0,
+        0.7071067811865475
+    ],
+    "quadtree": "fe5f7c7fcffff7f53",
+    "sceneCode": "t-CwfhfqJ",
+    "tile_size_px": 256,
+    "type": "TILED_PYRAMID"
+
+
+}
+ */
+
+
+
+ 
+

+ 478 - 0
src/viewer/map/MapViewer.js

@@ -0,0 +1,478 @@
+
+import * as THREE from "../../../libs/three.js/build/three.module.js";
+import {MapLayer} from './Map'
+import {FirstPersonControls} from '../../navigation/FirstPersonControls'
+import {View} from "../View.js";
+import Viewport from "../Viewport.js"; 
+import {InputHandler} from "../../navigation/InputHandler.js";
+import {ViewerBase} from "../viewerBase.js"
+import math from "../../utils/math.js";
+//import CursorDeal from '../utils/CursorDeal' 
+ 
+ 
+import {transitions, easing, lerp} from "../../utils/transitions.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 cursorHeight = 0;//比地图高就行  
+const routeLayerHeight = mapHeight + 105
+
+const texLoader = new THREE.TextureLoader()
+const planeGeo = new THREE.PlaneBufferGeometry(1,1)
+
+const markerSize = 1;
+
+
+var initCameraFeildWidth = 50
+var panoMarkerMats  
+
+
+
+export class MapViewer extends ViewerBase{
+    constructor(dom){
+        super(dom, {
+            clearColor: Potree.config.mapBG
+        })
+       
+        this.initScene()
+         
+         
+        this.mapLayer = new MapLayer(this, this.viewports[0])
+        this.scene.add(this.mapLayer.sceneGroup)
+        this.mapLayer.sceneGroup.position.setZ(mapHeight)
+        
+        
+        //this.scene.add(centerCross)
+        
+        //CursorDeal.attachToViewer(this)
+        viewer.addEventListener("camera_changed", this.updateWhenAtViewer.bind(this)); 
+        viewer.addEventListener("global_mousemove", this.updateWhenAtViewer.bind(this))  //鼠标移动时reticule也动,所以直接就needRender
+        
+         
+        
+        viewer.scene.addEventListener("360_images_added", this.addPanos.bind(this))
+        
+        
+        viewer.addEventListener("loadPointCloudDone",  this.initProjection.bind(this))
+        
+        this.on('add',(e)=>{//添加其他mesh
+            this.scene.add(e.object)
+            if(e.name == 'route'){
+                e.object.position.z = routeLayerHeight 
+            } 
+            viewer.setObjectLayers(e.object, 'mapObjects' )
+        })
+        
+        
+        if(!Potree.settings.isOfficial){
+            let domRoot = viewer.renderer.domElement.parentElement; 
+            let elAttach = $("<input type='button' value='map绑定'></input>");
+            elAttach.css({
+                position : "absolute",
+                right : '80%',
+                bottom: '20px',
+                zIndex: "10000",
+                fontSize:'1em', color:"black",
+                background:'rgba(255,255,255,0.8)',
+            })
+            let state = false
+            elAttach.on("click", () => {
+                state = !state
+                this.attachToMainViewer(state, 'measure')
+                elAttach.val(state ? 'map分离':'map绑定')
+            }); 
+            domRoot.appendChild(elAttach[0]);
+            
+            
+        }
+        
+    }
+    
+    waitLoadDone(callback){//确保加载完后执行
+        if(this.mapLayer.loadingInProgress == 0){
+            callback()
+        }else{
+            this.mapLayer.once('loadDone',callback)
+        }
+        
+    }
+    
+    addListener(images360){
+        images360.on('flyToPano',toPano=>{
+            if(toPano.dontMoveMap) return
+            
+            /* transitions.start(lerp.vector(this.view.position, toPano.pano.position.clone().setZ(cameraHeight), 
+            
+            (pos, progress)=>{
+                  
+            }), 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) 
+        })
+        
+        
+    }
+    
+    
+    
+    initProjection(){
+        this.started = true
+        this.mapLayer.initProjection()
+        
+    }
+     
+    initScene(){
+        let w = initCameraFeildWidth
+        let width = this.renderArea.clientWidth;
+		let height = this.renderArea.clientHeight; 
+        //let aspect = width / height
+        this.camera = new THREE.OrthographicCamera(-width/2,width/2,height/2,-height/2/* -w/2, w/2, w/2/aspect, -w/2/aspect */, 0.01, 10000);
+        this.camera.zoom = width / w  //zoom越大视野越小
+        //this.camera.position.set(0,0,10);
+        this.camera.up.set(0,0,1)
+        //this.camera.lookAt(new THREE.Vector3())
+        //this.camera.updateMatrixWorld()
+         
+        this.view = new View();
+        this.view.position.set(0,0,cameraHeight);
+        this.view.lookAt(0,0,0)
+        this.view.limitBound = new THREE.Box3().copy(Potree.config.OrthoCameraLimit.posBound)
+            
+        let viewport = new Viewport( this.view, this.camera, {
+            left:0, bottom:0, width:1, height: 1, name:'mapViewport' 
+        })
+        viewport.axis = ["x","y"]
+        viewport.axisSign = [1,1]
+        
+        viewport.noPointcloud = true; //不要渲染点云
+        viewport.render = this.render.bind(this)//标志给mainView渲染
+        
+        this.viewports = [viewport]
+        
+               
+                
+                
+        this.controls = new FirstPersonControls(this, this.viewports[0]);
+        this.controls.setEnable(true)
+        this.scene = new THREE.Scene();  
+  
+        
+        
+        this.inputHandler = new InputHandler(this, this.scene);
+        this.inputHandler.name = 'mapInputHandler'
+        //this.inputHandler.addInputListener(this.controls);
+        this.inputHandler.registerInteractiveScene(this.scene);//interactiveScenes
+       
+        this.viewports[0].interactiveScenes = this.inputHandler.interactiveScenes;//供viewer的inputHandler使用
+         
+        var cursor = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
+            transparent:true,
+            opacity:0.9,
+            map: texLoader.load(Potree.resourcePath+'/textures/pic_location128.png' )
+        }))
+            cursor.position.set(0,0,cursorHeight);
+        this.cursor = cursor
+       
+        this.scene.add(cursor)
+        viewer.setObjectLayers(this.scene, 'mapObjects' )
+    }
+    
+     
+    
+    updateCursor(){
+        var scale = math.getScaleForConstantSize( {//规定下最小最大像素
+            minSize : 80,  maxSize : 180,   nearBound : initCameraFeildWidth*0.1 , farBound : initCameraFeildWidth*2,
+            camera:this.camera , position: this.cursor.getWorldPosition(new THREE.Vector3()),
+            resolution: this.viewports[0].resolution2
+        })
+        this.cursor.scale.set(scale, scale, scale);
+        this.cursor.position.copy(viewer.mainViewport.camera.position).setZ(cursorHeight)
+        this.cursor.rotation.z = viewer.mainViewport.view.yaw
+        
+    }
+    
+    addPanos(e){
+        var panosGroup = new THREE.Object3D;
+        panoMarkerMats = {
+            default: new THREE.MeshBasicMaterial({
+                transparent:true,
+                opacity: 0.5,
+                map: texLoader.load(Potree.resourcePath+'/textures/map_marker.png' ),
+            }),
+            selected: new THREE.MeshBasicMaterial({
+                transparent:true,
+                opacity: 1,
+                map: texLoader.load(Potree.resourcePath+'/textures/map_marker.png' ),
+            })
+        }
+        
+         
+        e.images.panos.forEach(pano=>{
+            pano.mapMarker = new THREE.Mesh(planeGeo, panoMarkerMats.default);
+            pano.mapMarker.position.copy(pano.position).setZ(0)   
+            pano.mapMarker.scale.set(markerSize,markerSize,markerSize)
+            
+            panosGroup.add(pano.mapMarker); 
+            
+            
+            let mouseover = (e)=>{ 
+                if(!e.byMap){
+                    pano.mapMarker.material = panoMarkerMats.selected
+                    if(!e.byMainView) pano.emit("hoverOn", {byMap:true})
+                    this.needRender = true    
+                }
+            }
+            
+            let mouseleave = (e)=>{
+                if(!e.byMap){
+                    pano.mapMarker.material = panoMarkerMats.default
+                    if(!e.byMainView) pano.emit("hoverOff", {byMap:true})
+                    this.needRender = true
+                }
+            }
+             
+            
+            pano.mapMarker.addEventListener('mouseover', mouseover);  
+			pano.mapMarker.addEventListener('mouseleave', mouseleave);
+            
+            pano.on('hoverOn', mouseover)
+            pano.on('hoverOff', mouseleave)
+            
+            let onclick = (e)=>{
+                viewer.images360.flyToPano(pano)
+            }
+            pano.mapMarker.addEventListener('click', onclick);
+            
+            pano.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走) 
+                viewer.updateVisible(pano.mapMarker, 'panoVisible', e.visible )
+                this.needRender = true
+                
+            })
+            
+        })
+        this.scene.add(panosGroup)
+        panosGroup.position.z = panosHeight
+        this.panosGroup = panosGroup
+        viewer.setObjectLayers(panosGroup, 'mapObjects' )
+        
+        
+        /* e.images.on('markersDisplayChange', (show)=>{
+            panosGroup.visible = show
+            this.needRender = true
+        }) */
+        
+        //-------
+        
+        this.fitPanosToViewport()
+    }
+    
+    
+    
+    fitPanosToViewport(){//使所有漫游点占满viewport 
+        
+        //var w = viewer.bound.boundSize.x;
+         
+        
+        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)
+        
+        
+        
+    }
+    
+    
+    
+    moveTo(endPosition, boundSize, duration=0, easeName){//前两个参数有xy即可
+        endPosition = new THREE.Vector3(endPosition.x,endPosition.y,cameraHeight)
+        
+        let endZoom, startZoom = this.camera.zoom
+        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
+            }  
+            //initCameraFeildWidth = w;
+        }
+        
+        
+        //修改相机为bound中心,这样能看到全部(宽度范围内)
+        
+        this.view.setView(endPosition, null , duration,  ()=>{//done
+             
+        },(progress)=>{//onUpdate
+            if(boundSize){
+                this.camera.zoom = endZoom * progress + startZoom * (1 - progress)
+                this.camera.updateProjectionMatrix() 
+            } 
+        },easeName)
+          
+            
+    }
+    
+    
+    
+    
+    updateWhenAtViewer(e){
+        if(this.attachedToViewer){
+            if(!e || e.viewport == this.viewports[0]){ 
+                if(this.started) this.mapLayer.update() 
+                
+            }
+        } 
+        this.needRender = true
+        
+        this.updateCursor()//更改大小 
+    }
+    
+    update(delta, areaSize ){
+        if(this.attachedToViewer){
+            if(this.mapLayer.needUpdate){//必须更新
+                this.updateWhenAtViewer()
+            }
+            return
+        }            
+         
+         
+        this.updateScreenSize()
+         
+        this.controls.update(delta);
+        this.view.applyToCamera(this.camera) 
+        
+            
+        let changed = this.cameraChanged() 
+        
+        
+        if(this.started && (changed || this.mapLayer.needUpdate))this.mapLayer.update()
+        
+        if(changed /*||   || this.needRender */){ 
+            this.dispatchEvent({
+                type: "camera_changed", 
+                camera: this.camera,
+                viewport : this.viewports[0]
+            })
+                 
+            this.needRender = true
+            this.updateCursor()//更改大小
+        }
+        this.render()
+        
+    }
+    
+    attachToMainViewer(state, desc, mapWidthRatio=0.5, options={}){//转移到viewer中。测量时展示or截图需要
+        
+        if(!Potree.settings.isOfficial)this.renderArea.style.display = state ? 'none':'block'
+         
+        if(state){
+            if(mapWidthRatio != 'dontSet'){
+                viewer.mainViewport.left = mapWidthRatio
+                viewer.mainViewport.width = 1-mapWidthRatio
+                this.viewports[0].width = mapWidthRatio; 
+                if(this.attachedToViewer){ 
+                    //this.fitPanosToViewport() 
+                    viewer.updateScreenSize({forceUpdateSize:true})        
+                    return
+                }                
+                viewer.viewports = [viewer.mainViewport, viewer.mapViewer.viewports[0] ];//因为mainViewer的相机变化会触发map的变化,所以先渲染mainViewer
+            }
+              
+            
+            if(desc == 'measure') this.inputHandler.registerInteractiveScene(viewer.measuringTool.scene);//虽然用的是viewer的inputHandler,但借用了this.inputHandler的interactiveScenes
+            else if(desc == 'split4Screens') {
+                this.inputHandler.registerInteractiveScene(viewer.scene.scene);
+            }
+            
+        }else{
+            if(!this.attachedToViewer)return
+             
+            viewer.mainViewport.left = 0;
+            viewer.mainViewport.width = 1;
+             
+             
+            this.viewports[0].width = 1;
+            this.viewports[0].bottom = 0;
+            this.viewports[0].height = 1;
+            this.viewports[0].left = 0;
+            
+            this.inputHandler.unregisterInteractiveScene(viewer.measuringTool.scene);
+            this.inputHandler.unregisterInteractiveScene(viewer.scene.scene);
+            viewer.viewports = [viewer.mainViewport]
+            this.updateScreenSize({forceUpdateSize:true}) //更新相机projectionMatrix
+        }
+        
+        //if(desc == 'measure')this.renderMeasure = state 
+        this.attachedToViewer = state
+        
+        
+        
+        
+         
+        viewer.updateScreenSize({forceUpdateSize:true})
+        
+        
+        //mapWidthRatio != 'dontSet' && !options.dontFit && this.moveTo(...)//要写在updateScreenSize后面,因为要根据视图大小来fit
+        if(options.moveToCurrentPano){
+            let boundSize = new THREE.Vector2(10,10)
+            let duration = 1000
+            this.moveTo(viewer.images360.currentPano.position.clone(), boundSize, duration)
+        }            
+    }
+  
+    
+    
+    render(params={}){
+        if(!this.needRender && !params.force)return
+        
+        let renderer = params.renderer || this.renderer
+        
+        if(params.target){
+            renderer.setRenderTarget(params.target)
+        } 
+        if(params.resize){
+            this.emitResizeMsg(new THREE.Vector2(params.width,params.height))
+        } 
+        params.clear ? params.clear() : renderer.clear(); 
+        
+        viewer.setCameraLayers(this.camera, ['map','mapObjects'  , 'bothMapAndScene'  ])
+        
+        if(this.mapGradientBG){//渲染背景
+            viewer.scene.cameraBG.layers.set(Potree.config.renderLayers.bg);
+            renderer.render(viewer.scene.scene, viewer.scene.cameraBG);
+        }
+        
+        renderer.render(this.scene, this.camera);
+        renderer.render(viewer.scene.scene, this.camera);
+        //测量线
+        //if(this.renderMeasure)viewer.dispatchEvent({type: "render.pass.perspective_overlay", viewer: viewer, camera:this.camera});
+        params.renderOverlay && params.renderOverlay({camera:this.camera, isMap:true })//其余如reticule
+        
+        
+        
+        renderer.setRenderTarget(null)
+        
+        this.needRender = false
+        
+    }
+    
+}
+
+
+
+//本地调试地图白屏是因为代码自动更新了 但没刷新
+

+ 260 - 0
src/viewer/viewerBase.js

@@ -0,0 +1,260 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import { EventDispatcher } from "../EventDispatcher.js";
+
+
+ 
+
+export class ViewerBase extends EventDispatcher{
+    constructor(domElement, args = {}){
+        super()
+        
+        this.renderArea = domElement 
+        this.oldResolution = new THREE.Vector2()
+        
+        this.screenSizeInfo = {
+            W:0, H:0, pixelRatio:1 
+        }
+        
+        this.initContext(args);
+        
+         
+        this.addEventListener('content_changed', ()=>{//画面改变,需要渲染
+            this.needRender = true
+        })
+        
+        
+    }
+    
+    
+    
+    
+    
+    initContext(args){
+        //console.log(`initializing three.js ${THREE.REVISION}`);
+
+        let width = this.renderArea.clientWidth;
+        let height = this.renderArea.clientHeight;
+
+        let contextAttributes = {
+            alpha: true,
+            depth: true,
+            stencil: false,
+            antialias: true,
+            //premultipliedAlpha: _premultipliedAlpha,
+            preserveDrawingBuffer: true,
+            powerPreference: "high-performance",
+        };
+
+        // let contextAttributes = {
+        // 	alpha: false,
+        // 	preserveDrawingBuffer: true,
+        // };
+
+        // let contextAttributes = {
+        // 	alpha: false,
+        // 	preserveDrawingBuffer: true,
+        // };
+
+        let canvas = document.createElement("canvas");
+
+        let context = canvas.getContext('webgl', contextAttributes );
+
+        this.renderer = new THREE.WebGLRenderer({
+            alpha: true, //支持透明
+            premultipliedAlpha: false,
+            canvas: canvas,
+            context: context,
+            
+        });     
+        this.renderer.sortObjects = true; //原先false 打开了renderOrder才奏效
+        //this.renderer.setSize(width, height);
+        this.renderer.autoClear = args.autoClear;
+        args.clearColor && this.renderer.setClearColor(args.clearColor)
+        this.renderArea.appendChild(this.renderer.domElement);
+        this.renderer.domElement.tabIndex = '2222';
+        this.renderer.domElement.style.position = 'absolute';
+        this.renderer.domElement.addEventListener('mousedown', () => {
+            this.renderer.domElement.focus();
+        });
+        //this.renderer.domElement.focus();
+
+        // NOTE: If extension errors occur, pass the string into this.renderer.extensions.get(x) before enabling
+        // 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
+        
+        if(gl.createVertexArray == null){
+            let extVAO = gl.getExtension('OES_vertex_array_object');
+
+            if(!extVAO){
+                throw new Error("OES_vertex_array_object extension not supported");
+            }
+
+            gl.createVertexArray = extVAO.createVertexArrayOES.bind(extVAO);
+            gl.bindVertexArray = extVAO.bindVertexArrayOES.bind(extVAO);
+        }
+    }
+    
+    
+    
+    
+    updateScreenSize(o={}) { //有可能需要让viewport来判断,当窗口大小不变但viewport大小变时 
+          
+        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
+             
+            if(w !== this.screenSizeInfo.W || h !== this.screenSizeInfo.H || o.forceUpdateSize || this.screenSizeInfo.pixelRatio != window.devicePixelRatio){
+                this.screenSizeInfo.W = w 
+                this.screenSizeInfo.H = h 
+                render = true 
+                this.screenSizeInfo.pixelRatio = window.devicePixelRatio  //如果player放在小窗口了,也要监测devicePixelRatio,因为缩放时client宽高不会改变
+                //config.isMobile ? (ratio = Math.min(window.devicePixelRatio, 2)) : (ratio = window.devicePixelRatio)
+                ratio = window.devicePixelRatio
+            }    
+        }
+        if (render) {
+            this.setSize(w, h, ratio); 
+        } 
+    } 
+    
+    setSize(width, height, devicePixelRatio){
+        this.renderer.setSize(width, height, null, devicePixelRatio); // resize之后会自动clear(似乎因为setScissor ),所以一定要立刻绘制,所以setSize要在cameraChanged、update之前
+        //this.composer.setSize(width, height);
+        
+        if(this.viewports){
+            this.viewports.forEach((view,i)=>{
+                if(!view.active)return
+                
+                var width_ = width * view.width
+                var height_ = height * view.height
+                
+                view.setResolution(Math.floor(width_), Math.floor(height_), width, height )
+                let aspect = width_ / height_;  //camera的参数精确些,不用视口的归整的resolution像素值,否则hasChange无法为true, 导致canvasResize了但map没update从而闪烁
+                view.camera.aspect = aspect;
+                
+                if(view.camera.type == "OrthographicCamera"){
+                     
+                    /* //不改宽度 同4dkk
+                    var heightHalf = view.camera.right / aspect 
+                    view.camera.top = heightHalf 
+                    view.camera.bottom = -heightHalf  */
+                    //高宽都改 使大小不随视口大小改变  navvis (直接和视口大小一致即可,通过zoom来定大小)
+                     
+                    view.camera.left = -width_/2 
+                    view.camera.right = width_/2
+                    view.camera.bottom = -height_/2;
+                    view.camera.top = height_/2
+                    
+                    
+                }else{   
+                
+                
+                }
+                
+                view.camera.updateProjectionMatrix();
+            })
+        }
+        this.emitResizeMsg({viewport:this.viewports[0]})
+		//this.emitResizeMsg({resolution:this.viewports[0].resolution2, left:this.viewports[0].left, bottom:this.viewports[0].bottom}) //如果多个的话,render时会一直发送的
+       
+    } 
+    
+    emitResizeMsg(e){//切换viewport渲染时就发送一次, 通知一些材质更新resolution。  
+        if(!e.viewport.resolution.equals(this.oldResolution)){ 
+            this.dispatchEvent({viewport:e.viewport, type:'resize'})
+        }
+        this.oldResolution.copy(e.viewport.resolution)
+        
+    }
+    
+    
+    
+    cameraChanged() {//判断相机是否改变
+        var changed = false;
+        /* if(this.needRender){
+            this.needRender = false
+            return true
+        } */
+        for(let i=0,j=this.viewports.length;i<j;i++){
+            let changed_ = this.viewports[i].cameraChanged()
+            if(changed_){
+                changed = true
+                this.dispatchEvent({
+                    type: "camera_changed", 
+                    camera: this.viewports[i].camera,
+                    viewport : this.viewports[i]
+                })
+            }                
+        }
+        return changed
+    }
+    
+    
+    
+    
+    
+    makeScreenshot( size,  viewports, callback){//暂时不要指定viewports渲染,但也可以
+        
+       
+        let {width, height} = size;
+        
+        
+        
+        
+        /* let oldBudget = Potree.pointBudget;
+        Potree.pointBudget = Math.max(10 * 1000 * 1000, 2 * oldBudget);
+        let result = Potree.updatePointClouds(this.scene.pointclouds, camera, size );
+        Potree.pointBudget = oldBudget;
+         
+         
+		this.dispatchEvent({ //resize everything  such as lines  targets
+            type: 'resize', 
+            resolution: new THREE.Vector2(width,height), 
+        });*/
+       
+		let target = new THREE.WebGLRenderTarget(width, height, {
+			format: THREE.RGBAFormat,
+		});
+ 
+		// HACK? removed because of error, was this important?
+		  
+		this.render({
+            target ,
+            //camera ,
+            viewports: viewports || this.viewports,
+            screenshot : true, 
+            width ,
+            height, 
+            resize :true //需要resize
+        });
+
+		let pixelCount = width * height;
+		let buffer = new Uint8Array(4 * pixelCount);
+
+		this.renderer.readRenderTargetPixels(target, 0, 0, width, height, buffer);
+
+        callback && callback(buffer) 
+		target.dispose();
+        
+        //resize back 
+        //this.updateScreenSize({forceUpdateSize:true})   
+        
+		return {
+			width: width,
+			height: height,
+			buffer: buffer
+		};
+	}
+    
+}