xzw 1 年間 前
コミット
33c7186dd7
12 ファイル変更1360 行追加198 行削除
  1. 11 9
      css/lzb.css
  2. 6 5
      css/main.css
  3. 61 2
      edit.html
  4. BIN
      images/arrow.png
  5. BIN
      images/marker.png
  6. BIN
      images/reticule.png
  7. 1 4
      index.html
  8. 14 11
      js/Hot.js
  9. 396 73
      js/edit.js
  10. 93 46
      js/main_2020_edit.js
  11. 153 32
      js/main_2020_show.js
  12. 625 16
      js/manage.js

+ 11 - 9
css/lzb.css

@@ -107,7 +107,7 @@ ul li {
 }
 
 .toolBottom .toolLeft li.active {
-    background: #00a2d4;
+    background: #409eff;
     opacity: 1;
 }
 
@@ -262,7 +262,7 @@ ul.MenuOptions li:last-child{
     border-right: none;
 }
 ul.MenuOptions li.chosen {
-    background-color: #00a2d4;
+    background-color: #409eff;
 }
 
 
@@ -367,10 +367,12 @@ ul.MenuOptions li.chosen {
     line-height: 32px;
     height: 100%;
 }
-
+.toolRight div.content ul>li label{
+    margin: 6px 6px 13px 0 !important; 
+}
 .buttons button {
     width: 100%;
-    background-color: #00b4ed;
+    background-color: #409eff;
     line-height: 1 !important;
     color: #fff;
     border: none;
@@ -462,7 +464,7 @@ ul.MenuOptions li.chosen {
 }
 
 #hotpointDetail .editCheckbox{
-    margin-left: 65px;
+    margin-left:  37px;
 } 
 .editCheckbox input:checked+label {
     border: 0;
@@ -472,7 +474,7 @@ ul.MenuOptions li.chosen {
 
 .editCheckbox input:checked+label:after {
     content: "\2714";
-    color: #00b4ed;
+    color: #409eff;
     position:relative;
     bottom: 7px;
     /* vertical-align: middle; */
@@ -562,7 +564,7 @@ ul.MenuOptions li.chosen {
 
 .switch .mui-switch.mui-switch-animbg:checked {
     box-shadow: #dfdfdf 0 0 0 0 inset;
-    background-color: #00b4ed !important;
+    background-color: #409eff !important;
     transition: border-color 0.4s, background-color ease 0.4s;
 }
 
@@ -1729,8 +1731,8 @@ ul.MenuOptions li.chosen {
 .slider .scrollBar .scroll_Track {
     width: 0px;
     height: 4px;
-    background-color: #00b4ed;
-    border: 1px solid #00b4ed;
+    background-color: #409eff;
+    border: 1px solid #409eff;
     margin: -1px 0 0 0;
 }
 

+ 6 - 5
css/main.css

@@ -7268,13 +7268,14 @@ a.hasHover.tag-link:hover {
 .widgets-doll-labels .room-label a:hover {
     color:#fff;
 }
-
 .widgets-doll-labels .room-label.selected a{
-    background: rgb(0 0 0 / 88%);
-    border: 2px solid rgb(255 255 255);
+    color: #111;
+    background: rgb(255 255 255 / 88%);
+    border: 1px solid rgb(0 0 0);
     border-radius: 3px;
-    text-shadow: 0px 1px 3px #ffffff;  
-}    
+    text-shadow:unset;  
+    box-shadow: 0px 0px 3px 2px #2b2b2b2e;
+}     
 .widgets-doll-labels.noLine .room-label a{
     top: 0; 
     transform: translate(-50%, -50%);

+ 61 - 2
edit.html

@@ -100,13 +100,17 @@
                         <label>漫游可行</label>
                     </li>
                     <li data-name="cadMap">
-                        <span class="panoVisible"></span>
+                        <span class="snapTour"></span>
                         <label>小地图</label>
                     </li>
                     <li data-name="panoLog">
                         <span class="panoVisible"></span>
                         <label>漫游点位</label>
                     </li>
+                    <li data-name="routeArrow">
+                        <span class="panoVisible"></span>
+                        <label>路线指引</label>
+                    </li>
                     <li data-name="roomLabels">
                         <span class="hotpoint"></span>
                         <label>房间标签</label>
@@ -1074,7 +1078,7 @@
                                         <span>位置大小</span>
                                     </div>
                                     <ul>
-                                        <li class="editCheckbox">
+                                        <li class="editCheckbox" style='margin-left:55px'>
                                             <input name="isSprite"  type="checkbox" value="isSprite"
                                                 id="isSprite">
                                             <label for="isSprite"></label>
@@ -1284,6 +1288,14 @@
                                             <div id="boxDepth"></div>
                                         </li>
                                         <li name='uploadObj'> 
+                                             
+                                                <li class="editCheckbox" style='margin-left:22px'>
+                                                    <input name="remainModelScale"  type="checkbox" value="remainModelScale"
+                                                        id="remainModelScale">
+                                                    <label for="remainModelScale"></label>
+                                                    <label for="remainModelScale">替换模型时使用旧模型大小</label>
+                                                </li>
+                                           
                                             <div name='objPannel'>
                                                 <div id='ObjSelect' class='selectList texList text'> 
                                                     <a class="selection"> 
@@ -1819,6 +1831,53 @@
                     </ul>
                 </div>
                 
+                
+                
+                <div class="routeArrow content hide">
+                    <ul >
+                        <li>
+                            <ul id="routeShowSwitch" class="switch clearfix ">
+                                <label><input class="mui-switch mui-switch-animbg" type="checkbox">
+                                    默认展示 
+                                </label> 
+                            </ul>
+                            <ul>
+                                <li class="editCheckbox">
+                                    <input name="routeGradual" type="checkbox" value="routeGradual" id="routeGradual">
+                                    <label for="routeGradual"></label>
+                                    <label for="routeGradual">根随位置展示前方部分箭头</label>
+                                </li>
+                            </ul>
+                            <ul>
+                                <li class="editCheckbox">
+                                    <input name="routeOpaShine" type="checkbox" value="routeOpaShine" id="routeOpaShine">
+                                    <label for="routeOpaShine"></label>
+                                    <label for="routeOpaShine">渐变闪烁</label>
+                                </li>
+                            </ul>
+                            <label class="remark">点击保存后在展示页查看效果。</label>
+                        </li>
+                        <li>
+                            <div class="itemTitle"><span>箭头工具</span></div>
+                            <ul class="MenuOptions innerBtn" name="routeLinkTool">
+                                <li class="chosen" index="link">连接</li>
+                                <li class="" index="unlink">断开</li> 
+                            </ul>
+                            <label class="remark" name='ctrl'> </label>
+                            
+                        </li> 
+                        <li>
+                            <div class="buttons"> 
+                                <button class="innerBtn delete">清空</button>
+                            </div>
+                        </li>
+                         
+                    </ul>
+                </div>
+                
+                
+                
+                
                 <div class="roomLabels content hide">
                     <ul > 
                         <li class="addBtn"> 

BIN
images/arrow.png


BIN
images/marker.png


BIN
images/reticule.png


+ 1 - 4
index.html

@@ -783,10 +783,7 @@
             </div>
         </div>
         <div id="tag-billboards" style="display: none;"></div>
-
-        <div class="cad">
-            <div id="cad"></div>
-        </div>
+ 
         
     </div>
    

+ 14 - 11
js/Hot.js

@@ -297,7 +297,7 @@ window.initHot = function(model){
                         //是否开启多边形偏移		//ie不开启时blank也不会闪烁
                         polygonOffsetFactor: -0.9,
                         //多边形偏移因子
-                        polygonOffsetUnits: -4.0,
+                        polygonOffsetUnits: -4.0, 
                         //多边形偏移单位  
                     }))
                     
@@ -755,13 +755,18 @@ window.initHot = function(model){
             
         updateVisible(panos, visibility, type) { 
             if(window.isEdit && editTool.hotpoint.editSpot == this){
-                return this.visible = true
+                return convertTool.updateVisible(this,'visi',true) 
             }
             
-            this.visible = visibility != void 0 ? visibility : (!this.info.visiblePanos || 
+            let visible = visibility != void 0 ? visibility : (!this.info.visiblePanos || 
                (type == 'every' ? panos.every(pano=>this.info.visiblePanos.includes(pano.id)) : panos.some(pano=>this.info.visiblePanos.includes(pano.id))))//type为every时,需要panos中每个都可见才显示
+            
+            convertTool.updateVisible(this,'visi',visible) 
+            
             this.titleElem && this.titleElem.setVisible(this.visible, 'hotVisible', 1)
             
+            
+            
             if (this.texType == 'video'){
                 //this.switchPlay(this.visible,  this.visible ? null : 'stop' );//可见时不操作;不可见时停止
                 this.update(player)
@@ -1408,7 +1413,7 @@ window.initHot = function(model){
                 this.mesh.modelBound = this.info.modelBound 
  
             }
-            this.material_.side = THREE.FrontSide
+            this.material_.side = THREE.DoubleSide  //模型单面的话不好校准位置
             this.changeBoxHelperDisplay(false)
             //this.mesh.boxHelper.visible = true
             
@@ -1425,7 +1430,7 @@ window.initHot = function(model){
             delete this.info.modelBound     
 
             this.setMesh(this.plane)
-            //this.material_.side = THREE.DoubleSide  //双面的话飞出来会看到悬空的
+            this.material_.side = THREE.FrontSide  //双面的话飞出来会看到热点悬空的
             
         }
         
@@ -1458,14 +1463,12 @@ window.initHot = function(model){
         } 
         
         changeBoxHelperDisplay(show){
-            if(show){
-                this.visible_ = this.visible
-                this.visible = true
+            if(show){//暂时先把模型强制显示,以展示 boxHelper 
+                convertTool.updateVisible(this, 'showBoxHelper', true, 2, 'add')
+                
                 this.mesh.boxHelper.visible = true
             }else{
-                if(this.visible_ != void 0){
-                    this.visible = this.visible_
-                } 
+                convertTool.updateVisible(this, 'showBoxHelper', false, 2, 'cancel')
                 this.mesh.boxHelper.visible = false
             }
             

+ 396 - 73
js/edit.js

@@ -66,7 +66,12 @@ EditTools.prototype.loadDone = function(name, data){
     }
 }
 
+
 EditTools.prototype.init = function() {
+    
+    
+    this.ifLogin();
+    
     this.hotpoint.init(/* this.n */);
     var that = this;
     this.initSaveAll();
@@ -74,8 +79,10 @@ EditTools.prototype.init = function() {
     
     if(player.mode != 'panorama'){
         $(".toolLeft li[data-name=panoVisible]").addClass('unable')
-        player.once("pano.chosen",()=>{
+        $(".toolLeft li[data-name=routeArrow]").addClass('unable')
+        player.once("pano.chosen",()=>{//playerAndModelReady
             $(".toolLeft li[data-name=panoVisible]").removeClass('unable')
+            $(".toolLeft li[data-name=routeArrow]").removeClass('unable')
         })
     }
     
@@ -184,7 +191,7 @@ EditTools.prototype.init = function() {
 EditTools.prototype.active = function() {
     var that = this;
      
-    $('.toolBottom .toolLeft li').click(function() {
+    $('.toolBottom .toolLeft li').click(async function() {
         var name = $(this).data("name");
         $('.toolLeft li[data-name=' + name + ']').addClass("active").siblings().removeClass("active");
         $('.toolRight .' + name).removeClass("hide").siblings().addClass("hide");
@@ -195,11 +202,16 @@ EditTools.prototype.active = function() {
         switch (that.atPanel) {
             //退出
             case "panoVisible":
+                await VisiSet.unsaveWarn()
+             
                 VisiSet.finishSetPanoVisible()
                 break;
             case "panoLog":
                 VisiSet.finishSetPanoLog()
                 break;    
+            case "routeArrow":
+                VisiSet.finishSetRoute()
+                break;        
             case "screen":
                 confirmSnap.addClass("hide").removeClass("unable");
                 snapshotGui.hide();
@@ -207,7 +219,8 @@ EditTools.prototype.active = function() {
             /* case "overlay":
                 EditOverlay.leave()
                 break; */
-            case "hotpoint":
+            case "hotpoint": 
+                await that.hotpoint.unsaveWarn()
                 $("#hotpointDetail").hasClass("atRight") || $("#hotpointDetail a.close").click();
                 VisiSet.finishSetTagVisible()
                 that.hotpoint.wireframeModel.visible = false
@@ -234,7 +247,10 @@ EditTools.prototype.active = function() {
                 $(".toolLeft").addClass("unable")
                 VisiSet.enterSet(VisiSet.beginSetPanoLog.bind(VisiSet))
 
-                break;    
+                break;  
+            case 'routeArrow':
+                VisiSet.enterSet(VisiSet.beginSetRoute.bind(VisiSet))
+                break;
             case "screen":
                 confirmSnap.removeClass("hide");
                 $('#camera-start').text("点此设置为初始画面")
@@ -357,7 +373,7 @@ EditTools.prototype.initSaveAll = function() {
                 cadBorderWidth: $('#cad-size').val(),
                 showCad: $('input[name="show-cad"]').is(':checked'),
                 /**********/
-                
+                route: VisiSet.saveRoute(),
                 floorPlanAngle: _settings.floorPlanAngle || 0 //俯视图旋转角
             }
  
@@ -402,7 +418,36 @@ EditTools.prototype.initSaveAll = function() {
 }
 
 
+EditTools.prototype.ifLogin = function() {//先登录
+    if(window.location.href.includes('&localTest'))return //本地 不登录
 
+    let url = cmp ? ('/api/scene/roamViable/' + cmp) : '/manage/scene/roamViable'
+    //借用漫游可行保存来判断
+    $.ajax({
+        method: 'POST',
+        url: ceshi + url,
+        headers: {
+            'Content-Type': 'application/json',
+            token: token
+        },
+        contentType: 'application/json',
+        data: JSON.stringify({
+            data: '[]',
+            sceneCode: window.number
+        }),
+        success: (data)=>{ 
+            if (data.code === 5001) {
+                alert('请重新登录')
+                localStorage.dcj_token = ''
+                location.reload()
+            }
+        }
+        ,
+        fail: function() {
+            console.log('ifLogin failed')
+        }
+    })
+}
 
 
 class EditLabel{
@@ -516,7 +561,11 @@ class EditLabel{
          
         if(state){  
             if(this.editingLabel){
+                let old = this.editingLabel
                 this.setEditLabel(false, this.editingLabel)
+                if(old == label){
+                    return  //点击已打开的就关闭
+                }
             }
             label.elem.addClass('selected')
             pannel.removeClass('hide')
@@ -1552,10 +1601,10 @@ Hotpoint.prototype.init = function() {
                 if(this._scale[this.editSpot.texType]){//恢复大小
                     this.editSpot.scale.copy(this._scale[this.editSpot.texType]);
                     this.editSpot.info.scale.copy(this._scale[this.editSpot.texType]);
-                }
+                } 
             }else{
                 this.modelList.options.selectFun()
-                this._scale[this.editSpot.texType] = this.editSpot.scale.clone()
+                //this._scale[this.editSpot.texType] = this.editSpot.scale.clone()
             }
             
         }
@@ -1672,9 +1721,8 @@ Hotpoint.prototype.editHot = function(hot, $li) {
     
     transformControls.transformMenuOptions.updateChoseAtUI({name:'translate'})    //$(".MenuOptions[name='transform'] li[index='translate']").click()//transformControls.transCtlChangeMode("translate")
 
-    this.getTempInfo(hot)
-    hot.visible = true
-       
+    this.getTempInfo(hot) 
+    convertTool.updateVisible(hot,'visi',true)   
          
      
     this.updatePano() 
@@ -1870,9 +1918,24 @@ Hotpoint.prototype.updatePano = function(){
     this.panoTransformCheckBox.updateChoseAtUI(!!this.editSpot.info.transformAtPanos[getTransformSid()])
 }
 
+Hotpoint.prototype.unsaveWarn = async function(done){//如果在编辑某个热点时触发了结束编辑,询问是否保存
+    if(this.editSpot){
+        var sure = confirm("是否保存当前热点?")
+        if(sure){
+            await this.saveHot()   
+        } 
+        done && done()
+    }else if(VisiSet.setTagVisible && (VisiSet.tagVsetting && VisiSet.saveLastTagVi({getIfChange:true}) || Object.keys(VisiSet.tagVTemp).length)){//修改过
+        var sure = confirm("是否保存已编辑的热点可视?")
+        if(sure){
+            VisiSet.saveTagVisibles()
+        } 
+        done && done() 
+    } 
+}
+ 
+ 
  
-
-
  
 
 var ifSameTex = function(imgs0, imgs1){//是否是相同的图   (两个都是空的不算相同)
@@ -2746,15 +2809,20 @@ Hotpoint.prototype.initListSelect = function(){//热点样式图列表
 
 Hotpoint.prototype.switchModel = function(o={}){
     if(o.add){
+        
+        if(!$("#remainModelScale")[0].checked || o.spot.plane){//恢复成等大比例,方便用户看清原始比例,否则可能拉伸严重. 但有时换obj后不想重新设置大小可以勾选保持大小。 
+            var s = 1;                          //从热点转过来一定恢复比例
+            o.spot.scale.set(s,s,s)
+            o.spot.info.scale.set(s,s,s)
+        }
+        
+        
         var model = o.add.clone();
         o.spot.info.objSrc = o.add.src
         o.spot.info.objName = o.add.name  
         o.spot.info.modelBound = o.add.modelBound;
         o.spot.addModel(model)
-        /* var s = 1;//暂时
-        o.spot.scale.set(s,s,s)//恢复成等大比例,方便用户看清原始比例. 但是换obj后要重新设置大小所以取消
-        o.spot.info.scale.set(s,s,s) */
-        //this.updateTransform('scale')
+          
         if(o.spot == this.editSpot){
             transformControls.enableScaleZ()
         }  
@@ -3427,7 +3495,7 @@ Hotpoint.prototype.editDone = function(){
 
 
 // 保存热点信息
-Hotpoint.prototype.saveHot = function() {
+Hotpoint.prototype.saveHot = async function() {
     var $layout = $(".edit-loading");
     var hotpointDetail = this.hotpointDetail;
    
@@ -3478,8 +3546,7 @@ Hotpoint.prototype.saveHot = function() {
         //获取视频路径
         return new Promise(function(resolve, reject) {
             upload($videos, 'videos', resolve)
-        }
-        )
+        } )
     }).then(function(videoUrls) {
         $layout.removeClass('hide');
         args.video = videoUrls
@@ -3622,8 +3689,6 @@ Hotpoint.prototype.saveHot = function() {
         args.position = hot.position.clone()
         //hot.position.copy(hot.mesh.position) */
         
-        return args
-    }).then(function(args) {
           
         hot.info.texType = hot.texType
         
@@ -3657,6 +3722,10 @@ Hotpoint.prototype.saveHot = function() {
         that.editDone()
         
         that._scale[hot.texType] = hot.scale.clone()
+        
+        return new Promise(function(resolve, reject) {
+            resolve() 
+        })
     })
 
     return promise
@@ -5361,13 +5430,13 @@ var VisiSet = {
         })
         
         
-        player.on("mode.changing",(from,to)=>{
+        /* player.on("mode.changing",(from,to)=>{
             if(to == "panorama" && (VisiSet.setPanoVisible || VisiSet.setTagVisible || VisiSet.setPanoLog)){
                 VisiSet.finishSetPanoVisible()
                 VisiSet.finishSetTagVisible()
                 VisiSet.finishSetPanoLog()
             }
-        })
+        }) */
         
         player.model.on("floor.changed",(toFloor)=>{
             if (VisiSet.setPanoVisible || VisiSet.setTagVisible || VisiSet.setPanoLog ){
@@ -5376,7 +5445,7 @@ var VisiSet = {
         }) 
         
         player.on("view.changed",(e)=>{
-            if(e.cameraChanged && (VisiSet.setPanoVisible || VisiSet.setTagVisible || VisiSet.setPanoLog)){
+            if(e.cameraChanged && (VisiSet.setPanoVisible || VisiSet.setTagVisible || VisiSet.setPanoLog || VisiSet.setRoute)){
                 VisiSet.updateFootIconSize();
             }
         })
@@ -5442,7 +5511,39 @@ var VisiSet = {
             } 
         }) 
         
-    
+        //----route-------
+        
+        if(!window.DATA.route) window.DATA.route = {}
+        $('#routeShowSwitch input')[0].checked = !window.DATA.route.hide   //默认是否展示。 若隐藏也可以通过函数展示
+        $("#routeGradual")[0].checked = !!DATA.route.gradualShow   
+        $("#routeOpaShine")[0].checked = !!DATA.route.opacityShine   
+         
+        
+        this.routeOptions = new MenuOptions({
+            dom:  $(".toolRight .routeArrow .MenuOptions[name=routeLinkTool] "), 
+            uiCallBack : (o)=>{  
+                $(".toolRight .routeArrow  .remark[name=ctrl]").text(o.$li.attr("index") == 'link' ? 
+                  '按顺序依次点击点位绘制箭头路线,右键结束绘制。' : 
+                  '点击蓝线以删除路线')  
+            },
+            callbackWhenChose:(o)=>{
+                this.routeInfo.operation = o.$li.attr("index") == 'link' ? 'addLink' : 'removeLink' 
+            }
+        })
+        this.routeOptions.uiCallBack({$li:this.routeOptions.dom.find('.chosen')})
+       
+       
+        $(".toolRight .routeArrow button.delete").on("click", ()=>{//清空 
+            var choice = confirm(`确定删除全部路线吗`); 
+            if(choice){
+                for(let id1 in this.routeInfo.lines){
+                    for(let id2 in this.routeInfo.lines[id1]){ 
+                        this.routeInfo.removeLink(id1,id2)
+                    }
+                }
+            }
+        })
+         
         
        
     },
@@ -5455,7 +5556,9 @@ var VisiSet = {
             player.flyToMode("floorplan", fun);
         }
 
-        permitTranMode(['dollhouse','floorplan'])
+        permitTranMode(['dollhouse','floorplan','inside'])
+        player.cameraControls.controls.panorama.insideLookLimitDown = -60 
+        
         if ( player.modeTran == void 0) {
             player.afterCModeFuc = ()=>{
                 enter()
@@ -5471,9 +5574,9 @@ var VisiSet = {
             return; 
         this.setPanoLog = true
         this.panosSelect = []
-        this.updateFootIconSize()
-        this.showFootIcons(null, true);
         
+        this.showFootIcons(null, true);
+        this.updateFootIconSize()
         
         for(let i in player.model.hots){
             player.model.hots[i].visi_ = player.model.hots[i].mesh.visible;
@@ -5482,7 +5585,214 @@ var VisiSet = {
         
         this.changePanoVisi(true)
     },
+    
+    beginSetRoute: function(){
+        if(this.setRoute)return
+        this.setRoute = true
+        this.showFootIcons(null, true); 
+        this.updateFootIconSize()
+        
+        for(let i in player.model.hots){
+            player.model.hots[i].visi_ = player.model.hots[i].mesh.visible;
+            player.model.hots[i].mesh.visible = false
+        }  
+        this.changePanoVisi(true)
+        //permitTranMode(['inside','dollhouse','floorplan'])
+        if(!this.routeInfo){
+        
+            let color = '#409eff'  
+            let lineMat = new THREE.LineBasicMaterial({color,depthTest:false})
+            /* let arrow = new THREE.Mesh(new THREE.PlaneGeometry(0.3,0.3,1,1), new THREE.MeshBasicMaterial({
+                map: Texture.load("images/introduce_open.png"),//style_jump
+                transparent:true,
+                depthTest:false,
+                color,
+                side: 2
+            }))
+            arrow.rotation.x = Math.PI / 2  //朝上 */
+            
+            
+            
+            let moveLine = (line,p1,p2)=>{
+                LineDraw.moveLine(line, [p1,p2] )  
+                //arrow:
+                line.children[0].position.copy(new THREE.Vector3().addVectors(p1,p2).multiplyScalar(.5))
+                //this.lineAdding.children[0].rotateOnAxis()
+                //line.children[0].rotation.y = -new THREE.Vector2(p1.x - p2.x, p1.z - p2.z).angle() + Math.PI/2 
+                line.children[0].lookAt(p1)
+                line.children[1].position.copy(line.children[0].position)
+                line.children[1].rotation.copy(line.children[0].rotation)
+            }
+            
+            this.routeInfo = {
+                lines : {},
+                panoStart : null,
+                plane : new THREE.Plane(),
+                lineAdding : null,//用于连接的线
+                lineMat, 
+                operation : 'addLink',//or removeLink
+                events : {
+                    viewChange: (e)=>{ 
+                         if(e.cameraChanged && this.routeInfo.panoStart){
+                             this.routeInfo.updatePlane()
+                         } 
+                    },
+                    pointerMove: ()=>{
+                        if(!this.routeInfo.panoStart)return
+                        let endPos = player.intersect ? player.intersect.point :  player.raycaster.ray.intersectPlane(this.routeInfo.plane)
+                        moveLine(this.lineAdding, this.routeInfo.panoStart.floorPosition, endPos)
+                        
+                    },
+                    click: (e)=>{//右键取消 (但拖拽后也会取消,因为右键mousedown不会触发事件,不好写算了)
+                        e.button == 2 && /*player.mouseCouldBeClickToMove && */ this.routeInfo.cancel()
+                    }
+                    
+                },
+                updatePlane: ()=>{
+                     let nor = player.getDirection()
+                     this.routeInfo.plane.setFromNormalAndCoplanarPoint(nor, this.routeInfo.panoStart.floorPosition)
+                },
+                cancel: ()=>{
+                    if(this.lineAdding){ 
+                        this.lineAdding.parent.remove(this.lineAdding)
+                        this.lineAdding = null
+                        this.routeInfo.panoStart = null;
+                    }
+                },
+                addLink: (id1,id2,line)=>{
+                    this.routeInfo.lines[id1] || (this.routeInfo.lines[id1] = {})   
+                    this.routeInfo.lines[id1][id2] = line
+                    line.name = 'routeLine:'+ id1 + '->' +id2 
+                    moveLine(line, player.model.panos.index[id1].floorPosition, player.model.panos.index[id2].floorPosition)
+                },
+                addLine: (startPos=new THREE.Vector3)=>{ 
+                    let line = LineDraw.createLine([startPos,startPos], {mat: this.routeInfo.lineMat})  
+                    //let arrow = this.routeInfo.arrow.clone()  
+                    let s = 0.15
+                    let arrowLeft = LineDraw.createLine([new THREE.Vector3, new THREE.Vector3(-s,0,s)], {mat: lineMat})  
+                    let arrowRight = LineDraw.createLine([new THREE.Vector3, new THREE.Vector3(s,0,s)], {mat: lineMat})  
+                    line.add(arrowLeft), line.add(arrowRight)
+                    arrowLeft.position.copy(startPos), arrowRight.position.copy(startPos)
+                    arrowLeft.renderOrder = arrowRight.renderOrder =  7
+                    this.meshGroup.add(line) 
+                    return line
+                },
+                removeLink: (id1,id2)=>{
+                    this.routeInfo.lines[id1][id2].parent.remove(this.routeInfo.lines[id1][id2])
+                    delete this.routeInfo.lines[id1][id2]
+                }
+                
+            }
+             
+             
+            
+            
+            //初始化绘制
+            /* for(let id1 in player.model.panos.routeNextMap){
+                for(let pano2 of player.model.panos.routeNextMap[id1]){
+                    let line = this.routeInfo.addLine()
+                    this.routeInfo.addLink(id1,pano2.id,line)
+                }
+            } */
+            
+            if(window.DATA.route){
+                for(let id1 in window.DATA.route.data){
+                    for(let id2 of window.DATA.route.data[id1]){
+                        let line = this.routeInfo.addLine()
+                        this.routeInfo.addLink(id1,id2,line)
+                    }
+                    
+                }  
+            }
+             
+        }else{
+            for(let id1 in this.routeInfo.lines){
+                for(let id2 in this.routeInfo.lines[id1]){
+                    let line = this.routeInfo.lines[id1][id2] 
+                    line.visible = true
+                }
+            }
+        }
+        
+        player.on("view.changed",this.routeInfo.events.viewChange)
+        player.domElement.addEventListener("pointermove",this.routeInfo.events.pointerMove)
+        player.domElement.addEventListener("pointerup",this.routeInfo.events.click)
+    },
+    
+    dealRouteClick: function(object){ 
+        
+        if(this.routeInfo.operation == 'addLink'){ 
+            var id = object.name == '' ? object.parent.name : object.name  
+            var panos = player.model.panos; 
+            if(panos.index[id]){ 
+                if(this.routeInfo.panoStart){
+                    if(this.routeInfo.panoStart == panos.index[id])return
+                     
+                    this.routeInfo.addLink(this.routeInfo.panoStart.id, id, this.lineAdding)
+                    //this.lineAdding = null
+                    //this.routeInfo.panoStart = null;
+                }
+                //}else{
+                    this.routeInfo.panoStart = panos.index[id]
+                    this.lineAdding = this.routeInfo.addLine(this.routeInfo.panoStart.floorPosition)
+                    this.routeInfo.updatePlane()
+                       
+                //} 
+            }
+        }else if(this.routeInfo.operation == 'removeLink'){
+            let name = object.name || object.parent.name 
+            if(name.slice(0,10) == 'routeLine:'){
+                let id = name.slice(10).split('->');  
+                this.routeInfo.removeLink(id[0], id[1])
+            }
+            
+        }
+    },
+    
+    finishSetRoute: function(){
+        
+        this.setRoute = false
+        player.removeListener("view.changed",this.routeInfo.events.viewChange)
+        player.domElement.removeEventListener("pointermove",this.routeInfo.events.pointerMove)
+        this.changePanoVisi(false)
+        permitTranMode(true)
+        player.cameraControls.controls.panorama.insideLookLimitDown = null
+        for(let i in player.model.hots){ 
+            player.model.hots[i].mesh.visible = player.model.hots[i].visi_
+        } 
+         
+        for(let id1 in this.routeInfo.lines){
+            for(let id2 in this.routeInfo.lines[id1]){
+                let line = this.routeInfo.lines[id1][id2]
+                //line.parent.removeChild(line)
+                line.visible = false
+            }
+        }
+        
+        this.routeInfo.cancel()
+    },
+    saveRoute : function(){
+        
+        let data = {}
+        if(this.routeInfo){
+            for(let id1 in this.routeInfo.lines){
+                let ids = Object.keys(this.routeInfo.lines[id1])
+                if(ids.length > 0)  data[id1] = Object.keys(this.routeInfo.lines[id1])
+            }
+        }else{
+            return window.DATA.route  
+        }
+        
+        return {
+            data, 
+            hide: $('#routeShowSwitch input').prop('checked') ? 0:1,
+            gradualShow: $("#routeGradual").is(':checked') ? 1:0,
+            opacityShine: $("#routeOpaShine").is(':checked') ? 1:0, 
+        }
+        //点击总保存按钮后才会保存,然后直接刷新
+    },
      
+    
     changePanoVisi:function(state, toFloor){
         var floor = player.model.allFloorsVisible ? 'all' : (toFloor || player.model.currentFloor)
         if(this.setPanoVisible){
@@ -5518,7 +5828,7 @@ var VisiSet = {
         player.flyoutType = null
         
         permitTranMode(true)
-        
+        player.cameraControls.controls.panorama.insideLookLimitDown = null
         
         for(let i in player.model.hots){ 
             player.model.hots[i].mesh.visible = player.model.hots[i].visi_
@@ -5571,7 +5881,7 @@ var VisiSet = {
         //objects.tagManager.hideAllTags();
 
         this.setDisplay(true)
-
+        this.changePanoVisi(true)
 
         this.updateFootIconSize()
         //更新一下大小,尤其是上次换了中心点然后退出又进入但是镜头没有变化的话 
@@ -5627,7 +5937,9 @@ var VisiSet = {
             }
         /* } */
         
-        
+        if(o.onlyGetData){
+            return change
+        }
 
         if (change.length) {
             //添加双向的neighbour:
@@ -5720,8 +6032,9 @@ var VisiSet = {
         player.flyoutType = null
         this.$confirmSnap.addClass("hide")
         permitTranMode(true)
+        player.cameraControls.controls.panorama.insideLookLimitDown = null
         this.setDisplay(false)
-        
+        this.changePanoVisi(false)
         for(let i in player.model.hots){ 
             player.model.hots[i].mesh.visible = player.model.hots[i].visi_
         }        
@@ -5780,7 +6093,7 @@ var VisiSet = {
     },
 
     //最佳推荐操作顺序: 先设置pano可见性 再创建热点   这样热点的visible正确些,否则之后再设置热点可见性会改更多
-    savePanoVisibles: function() {
+    savePanoVisibles: async function() {
         //保存
 
         if (this.panoVsetting)
@@ -5808,37 +6121,39 @@ var VisiSet = {
         console.log(JSON.stringify(PanoData)) */
 
         let url = cmp ? ('/api/scene/roamViable/' + cmp) : '/manage/scene/roamViable'
-
-        $.ajax({
-            method: 'POST',
-            url: ceshi + url,
-            headers: {
-                'Content-Type': 'application/json',
-                token: token
-            },
-            contentType: 'application/json',
-            data: JSON.stringify({
-                data: JSON.stringify(PanoData),
-                sceneCode: window.number
-            }),
-            success: (data)=>{
-                if (data.code === 0) {
-                    this.afterSavePanoVisibles()
-                    alert("保存漫游可行成功")
-                } else
+        return new Promise((resolve,reject)=>{
+            $.ajax({
+                method: 'POST',
+                url: ceshi + url,
+                headers: {
+                    'Content-Type': 'application/json',
+                    token: token
+                },
+                contentType: 'application/json',
+                data: JSON.stringify({
+                    data: JSON.stringify(PanoData),
+                    sceneCode: window.number
+                }),
+                success: (data)=>{
+                    if (data.code === 0) {
+                        this.afterSavePanoVisibles()
+                        alert("保存漫游可行成功")
+                    } else
+                        alert("保存漫游可行失败")
+                    if (data.code === 5001) {
+                        alert('请重新登录')
+                        localStorage.dcj_token = ''
+                        location.reload()
+                    }
+                    resolve()
+                }
+                ,
+                fail: function() {
                     alert("保存漫游可行失败")
-                if (data.code === 5001) {
-                    alert('请重新登录')
-                    localStorage.dcj_token = ''
-                    location.reload()
+                    reject()
                 }
-            }
-            ,
-            fail: function() {
-                alert("保存漫游可行失败")
-            }
+            })
         })
-  
     },
 
     searchNeib: function(panoId) {
@@ -5881,7 +6196,7 @@ var VisiSet = {
  
         player.path.currentPanoMarker.mesh.visible = !state; 
         player.reticule.visible = !state;
-        this.changePanoVisi(state)
+        
     },
 
     delVisibleLines: function() {
@@ -6236,19 +6551,15 @@ var VisiSet = {
             //this.tagVsetting.setElemType(this.tagVsetting.style, this.tagVsetting.styleImageURL); 
         }
 
-        this.tagVsetting = tag;
-        //记录正在修改的
-        this.delVisibleLines();
-        //删除线
-        this.showFootIcons(player.currentPano);
+        this.tagVsetting = tag; //记录正在修改的
+        this.delVisibleLines(); //删除线
+        this.showFootIcons(player.currentPano);//显示、修改pano颜色
         this.changePanoVisi(true)
-        this.createTagVisiLines(tag);
-        //创线
+        this.createTagVisiLines(tag); //创线
 
-        this.updateFootIconSize()
-        //更新一下大小,尤其是上次换了中心点然后退出又进入但是镜头没有变化的话
+        this.updateFootIconSize() //更新一下大小,尤其是上次换了中心点然后退出又进入但是镜头没有变化的话
     },
-    saveLastTagVi: function() {
+    saveLastTagVi: function(o={}) {
         //保存刚设置过的tag
         var change = false;
         var newVPs = this.tagVTemp[this.tagVsetting.sid] || this.tagVsetting.info.visiblePanos.slice(0);
@@ -6271,6 +6582,9 @@ var VisiSet = {
                 //console.log("sub: "+r)
             }
         }
+        if(o.getIfChange){
+            return change
+        }
         if (change) {
             this.tagVTemp[this.tagVsetting.sid] = newVPs;
         }
@@ -6298,6 +6612,7 @@ var VisiSet = {
         this.setDisplay(false)
         this.$confirmSnap.addClass("hide")
         permitTranMode(true)
+        player.cameraControls.controls.panorama.insideLookLimitDown = null
         /* for (var r in objects.tagManager.tags) {
             if(objects.tagManager.tags[r].state == "videoPanoFlag")continue;            
             objects.tagManager.tags[r].disc.visible = false;
@@ -6370,7 +6685,15 @@ var VisiSet = {
         }
     },
     //------------
-    
+    unsaveWarn:async function(done){
+        if(VisiSet.setPanoVisible && (VisiSet.panoVsetting && VisiSet.saveLastPanoVi({onlyGetData:true}).length || Object.keys(VisiSet.panoVTemp).length)){//修改过
+            var sure = confirm("是否保存已编辑的漫游可视?")
+            if(sure){
+                await VisiSet.savePanoVisibles()
+            } 
+            done && done() 
+        }
+    }
     
     
     

+ 93 - 46
js/main_2020_edit.js

@@ -4155,8 +4155,12 @@ function o(a, s, l) {
                 this.lon += this.rotationSpeed.x * e,
                 this.lat += this.rotationSpeed.y * e
                 
-                var insideLookLimitDown = n.insideLookLimitDown - n.insideFOV/2 + this.camera.fov / 2 
-                var insideLookLimitUp = n.insideLookLimitUp + n.insideFOV/2 - this.camera.fov / 2
+                
+                
+                let insideLookLimitDown = this.insideLookLimitDown != void 0 ? this.insideLookLimitDown : n.insideLookLimitDown
+                let insideLookLimitUp = this.insideLookLimitUp != void 0 ? this.insideLookLimitUp : n.insideLookLimitUp
+                insideLookLimitDown = insideLookLimitDown - n.insideFOV/2 + this.camera.fov / 2 
+                insideLookLimitUp = insideLookLimitUp + n.insideFOV/2 - this.camera.fov / 2
                 this.lat = Math.max(insideLookLimitDown, Math.min(insideLookLimitUp, this.lat)); //通过预定义的俯仰角最大最小范围(insideLookLimitUp、insideLookLimitDown)来限制俯仰角。 注:这种数学计算很常见,因此API也很常见(clamp),等价于 this.lat = THREE.Math.clamp( this.lat, settings.insideLookLimitDown, settings.insideLookLimitUp );   
                 //this.lat = Math.max(n.insideLookLimitDown, Math.min(n.insideLookLimitUp, this.lat)),
                 
@@ -5033,7 +5037,7 @@ function o(a, s, l) {
             hblurPass: s,
             vblurPass: l,
             
-            transitionMaskPass : new n.TransitionPass(),
+            fastTranMaskPass : new n.TransitionPass(),
             
             bindEvents: function(e) {
                 e.on(a.ModeChanged, function(e, t) {
@@ -16837,6 +16841,11 @@ function o(a, s, l) {
                     window.data2 = e;
                     this.roomLabels = []
                     
+                    
+                    
+                    
+                    
+                    
                     editTool.loadDone('data2', e)
                     
                      
@@ -16885,7 +16894,7 @@ function o(a, s, l) {
                         }
                         
                         
-                        
+                         
                         
                         /* if(e.multiOverlays){
                             e.multiOverlays.forEach((info)=>{ 
@@ -17078,16 +17087,12 @@ function o(a, s, l) {
             ,
             t.prototype.build = function() {
                 this.heroLocations = this.images.getHeroLocations(this.panos);
-                
-                
-                
-                
-                
+                 
                 var e = function() {
                     return !!(this.heroImage && this.heroImage.metadata && this.heroImage.metadata.scan_id && this.panos.get(this.heroImage.metadata.scan_id)) && !this.panos.get(this.heroImage.metadata.scan_id).isAligned()
                 }
                 .bind(this)();
-                for (var t in this.has360Views = u.show360Views.enabled && this.heroLocations.some(function(e) {
+                this.has360Views = u.show360Views.enabled && this.heroLocations.some(function(e) {
                     return e.cameraMode === s.PANORAMA && this.panos.get(e.panoId) && !this.panos.get(e.panoId).isAligned()
                 }
                 .bind(this)) || e,
@@ -17104,7 +17109,24 @@ function o(a, s, l) {
                     }
                     .bind(this))
                 }
-                .bind(this)),
+                .bind(this))
+                
+                let downVec = new THREE.Vector3(0,-1,0)
+                this.panos.forEach((pano)=>{ 
+                    player.raycaster.set(pano.position,  downVec);
+                    let collider = pano.floor.collider.children
+                    let n = player.raycaster.intersectObjects(collider , true );//add true
+                    if(n && n[0]){
+                        if(n[0].point.y - pano.floorPosition.y > 0.03){
+                            //console.error(`${pano.id}, 探测高度${n[0].point.y.toPrecision(5)} 原高度 ${pano.floorPosition.y.toPrecision(5)}, 相差 ${(n[0].point.y - pano.floorPosition.y).toPrecision(5)}`)
+                            pano.originFloorY = pano.floorPosition.y
+                            pano.floorPosition.y = n[0].point.y
+                        }
+                    }
+                    
+                    //是否需要排除一下相差过高的?因为可能探测到的是长椅这样的
+                })
+                
                 this.panos.forEach(function(e) {
                     e.build2()
                 }),
@@ -17121,15 +17143,14 @@ function o(a, s, l) {
                 this.panos.populate_path_graph(),
                 this.skybox = new n(this.boundingBox),
                 this.skybox.matrixWorldNeedsUpdate = !0,
-                this.add(this.skybox),
+                this.add(this.skybox) 
                 /* setTimeout(function() {
                     //this.hotsCount && this.shineHots() 
                     Hot.beginShineHot()
                     
                 }
                 .bind(this), 1300), */
-                this.tags)
-                    this.tags[t].build();
+                
                 
                 //add-----
                 console.log('计算visiblePanos开始,可能需要一段时间')
@@ -17984,7 +18005,8 @@ function o(a, s, l) {
             S("../util/transitions")), h = S("../util/lerp"), u = S("../util/ajax"), d = S("../util/texture"), p = S("../util/panorama"), f = (S("../enum/TileDownloaderEvents"),
             S("../enum/PanoramaEvents")), g = S("../enum/PanoRendererEvents"), m = S("../enum/PanoSizeClass"), v = S("../enum/RenderLayers"), y = (S("../scene/SceneRenderer"),
             S("../enum/GLCubeFaces"),
-            S("../exception/BasicException")), A = S("../tile/TileUtils"), C = S("events").EventEmitter, I = new n(e), E = new s.SphereBufferGeometry(.1), _ = new s.PlaneBufferGeometry(.4,.4,1,1), b = d.load("images/marker-256x256.png");
+            S("../exception/BasicException")), A = S("../tile/TileUtils"), C = S("events").EventEmitter, I = new n(e), E = new s.SphereBufferGeometry(.1), _ = new s.PlaneBufferGeometry(.35,.35,1,1), b = d.load("images/marker.png");
+            b.anisotropy = 4; //add
             (r.prototype = Object.create(C.prototype)).enter = (t = null,
             function() {
                 this.setZoomed(!1),
@@ -18548,7 +18570,13 @@ function o(a, s, l) {
           , c = e("../shaders")
           , h = e("../enum/RenderLayers")
           , u = e("../enum/RenderOrder")
-          , d = s.load("images/reticule-256x256.png");
+          , d = s.load("images/reticule.png");
+          
+          
+        d.anisotropy =  4; //各向异性  
+         
+          
+          
         (n.prototype = Object.create(r.Mesh.prototype)).move = function(e, t, i) {
             this.hidden = i,
             this.mouseLastMoveTime = Date.now()
@@ -19797,14 +19825,14 @@ function o(a, s, l) {
                    
                 }
                 if(window.DATA.momentTourBlackNewType){
-                    player.sceneRenderer.effects.transitionMaskPass.start(player.sceneRenderer)
+                    player.sceneRenderer.effects.fastTranMaskPass.start(player.sceneRenderer)
                    
                     
                     var camera = this.playerControls.cameras['panorama'];
                     var end =  this.warpDestHeroLoc.position
                     camera.position.copy(end)
                     setTimeout(()=>{
-                        player.sceneRenderer.effects.transitionMaskPass.stop()
+                        player.sceneRenderer.effects.fastTranMaskPass.stop()
                         
                         this.wrapupWarpShadingOnly(n, i);
                         done()
@@ -20364,8 +20392,8 @@ function o(a, s, l) {
                 this.tileDownloader.tilePrioritizer = new g(this.qualityManager,this.basePanoSize,this.standardPanoSize,this.highPanoSize,this.ultraHighPanoSize),
                 this.bindEvents(e.container),
                 this.updateModel()
-                
-                
+                //add
+                this.raycaster.linePrecision = 0.1
                 
             }
             ,
@@ -20922,7 +20950,10 @@ function o(a, s, l) {
                         this.intersect && this.intersect.object.visible && VisiSet.dealPanoLogClick( this.intersect.object);
                         return;
                     }
-                    
+                    if (window.VisiSet && VisiSet.setRoute) {
+                        this.intersect && this.intersect.object.visible && VisiSet.dealRouteClick(this.intersect.object);
+                        return;
+                    }
                     if(this.hoveringPlane){
                         this.clickOverlay(this.hoveringPlane)
                         return;
@@ -20960,7 +20991,7 @@ function o(a, s, l) {
                         }) : this.bump(this.getMouseDirection())
                     }
                 }
-                this.intersect && this.closestPano && this.closestPano.hoverOff(this.mode)
+                //this.intersect && this.closestPano && this.closestPano.hoverOff(this.mode)
             }
             ,
             t.prototype.handleLongTap = function() {
@@ -21430,7 +21461,8 @@ function o(a, s, l) {
                     
                     this.intersectHot = null
                     //$("#player").css("cursor", "")
-                    CursorDeal.remove('hoverFootIcon' ) 
+                    CursorDeal.remove('hoverFootIcon' )
+                    CursorDeal.remove('hoverRouteLine' )                    
                     CursorDeal.remove('hoverHot' )
                     
                     $(".toolRight .spotList ul li").removeClass("active")
@@ -21449,12 +21481,25 @@ function o(a, s, l) {
                     } 
                 }
                 
-                if (window.VisiSet && (VisiSet.setPanoVisible || VisiSet.setTagVisible || VisiSet.setPanoLog)) {
-                    this.intersect = this.getMouseIntersect(null, this.model.hotGroup.children.concat(VisiSet.footIcons||[]) );
+                if (window.VisiSet && (VisiSet.setPanoVisible || VisiSet.setTagVisible || VisiSet.setPanoLog || VisiSet.setRoute )) {
+                    let meshes = this.model.hotGroup.children.slice()
+                    if(VisiSet.routeInfo?.operation == 'removeLink'){
+                        meshes.push(...VisiSet.meshGroup.children.filter(e=>e.name.includes('routeLine:')))
+                    }else{
+                        meshes.push(...VisiSet.footIcons)
+                    }
+                    
+                    
+                    
+                    this.intersect = this.getMouseIntersect(null, meshes );
                     
                     deal()
-                    if(this.intersect){
-                        CursorDeal.add('hoverFootIcon' )   //$("#player").css("cursor", "pointer");
+                    if(this.intersect){ 
+                        if(VisiSet.routeInfo?.operation == 'removeLink'){
+                            CursorDeal.add('hoverRouteLine' ) 
+                        }else{
+                            CursorDeal.add('hoverFootIcon' )   //$("#player").css("cursor", "pointer");
+                        }
                     } 
                     if(this.intersectHot){
                         this.intersect = null
@@ -21478,7 +21523,12 @@ function o(a, s, l) {
                             var hots = this.model.hotGroup.children.filter(e=> e.info.actionType.examine || e.info.actionType.fastTran || e.info.actionType.openHot || e.info.actionType.playAndPause)
                         }
                          
-                        this.intersect = this.getMouseIntersect(null, hots.concat(this.model.colliders));
+                        let colliders = this.model.floors.reduce(function(e, t) {
+                            return t.hidden ? e : e.concat(t.collider.children)
+                        },   this.mode === 'panorama' ? this.panoMarkers : []); 
+                        
+                        
+                        this.intersect = this.getMouseIntersect(null, hots.concat(colliders));
                         if(this.intersect){
                             deal()
                             this.intersectHot && CursorDeal.add('hoverHot' )  //$("#player").css("cursor", "pointer");
@@ -21513,27 +21563,18 @@ function o(a, s, l) {
                         VisiSet.SetOneTagVisible(this.intersectHot); //设置这个热点的可见性
                         return;
                     }else if(editTool.atPanel == 'hotpoint'){
+                        let f = ()=>{editTool.hotpoint.editHot(this.intersectHot)}
                         if(editTool.hotpoint.editSpot && this.intersectHot != editTool.hotpoint.editSpot){
-                            var sure = confirm("是否保存当前热点?")
-                            if(sure){
-                                editTool.hotpoint.saveHot().then(()=>{
-                                    editTool.hotpoint.editHot(this.intersectHot) 
-                                })
-                            }else{
-                                editTool.hotpoint.editHot(this.intersectHot)
-                            }
+                            editTool.hotpoint.unsaveWarn(f)
                         }else{
-                            editTool.hotpoint.editHot(this.intersectHot)
-                        }  
+                            f()
+                        } 
                     }else{
                         if(this.intersectHot.texType == "video" && this.intersectHot.info.actionType.playAndPause){
                             this.intersectHot.switchPlay(!!this.intersectHot.pausedByUser) 
                         }
                         this.intersectHot.examine()
-                    }
-                    
-                    
-                    
+                    } 
                     
                     return !0
                     
@@ -21628,7 +21669,13 @@ function o(a, s, l) {
                 var i = this.model.panos.find(t, [a.sortFunctions.floorDistanceToPoint(e.point)]);
                 i !== this.closestPano ? (i && (this.isPanoHover = !0),
                 this.emit(W.ClosestPanoChanging, this.closestPano, i, this.mode),
-                this.closestPano = i) : this.isPanoHover = !1
+                this.closestPano = i,
+                
+                this.closestPano && this.mode == 'panorama'? (CursorDeal.add('hoverPano' )/*,  this.reticule.visible = false  */)
+                : (CursorDeal.remove('hoverPano' )/*, this.reticule.visible = true */)
+                
+                
+                ) : this.isPanoHover = !1
             }
             ,
             t.prototype.dollhouseMode = function(e, t) {
@@ -23477,7 +23524,7 @@ function o(a, s, l) {
             this.composer.addPass(new M.RenderPass(this.scene,this.camera)),
             /* this.composer.addPass(this.effects.hblurPass),
             this.composer.addPass(this.effects.vblurPass) */
-            this.composer.addPass(this.effects.transitionMaskPass)//add
+            this.composer.addPass(this.effects.fastTranMaskPass)//add
         }
         ,
         h.prototype.setSize = function(e, t, i) {
@@ -23491,7 +23538,7 @@ function o(a, s, l) {
         }
         ,
         h.prototype.render = function() {
-            this.effects.transitionMaskPass.enabled ? this.composer.render() : this.renderer.render(this.scene, this.camera)
+            this.effects.fastTranMaskPass.enabled ? this.composer.render() : this.renderer.render(this.scene, this.camera)
         }
         ,
         h.prototype.boluoVrInit = function() {
@@ -29793,7 +29840,7 @@ function o(a, s, l) {
         }
         
         window.math = t.exports
-        
+        expandMath(math)
         
         
         

+ 153 - 32
js/main_2020_show.js

@@ -4283,8 +4283,12 @@ window.Modernizr = function(n, e, t) {
                 
                 
                 //this.lat = Math.max(a.insideLookLimitDown, Math.min(a.insideLookLimitUp, this.lat)),
-                var insideLookLimitDown = a.insideLookLimitDown - a.insideFOV/2 + this.camera.fov / 2 
-                var insideLookLimitUp = a.insideLookLimitUp + a.insideFOV/2 - this.camera.fov / 2
+                
+                
+                let insideLookLimitDown = this.insideLookLimitDown != void 0 ? this.insideLookLimitDown : a.insideLookLimitDown
+                let insideLookLimitUp = this.insideLookLimitUp != void 0 ? this.insideLookLimitUp : a.insideLookLimitUp
+                insideLookLimitDown = insideLookLimitDown - a.insideFOV/2 + this.camera.fov / 2 
+                insideLookLimitUp = insideLookLimitUp + a.insideFOV/2 - this.camera.fov / 2
                 this.lat = Math.max(insideLookLimitDown, Math.min(insideLookLimitUp, this.lat)); //通过预定义的俯仰角最大最小范围(insideLookLimitUp、insideLookLimitDown)来限制俯仰角。 注:这种数学计算很常见,因此API也很常见(clamp),等价于 this.lat = THREE.Math.clamp( this.lat, settings.insideLookLimitDown, settings.insideLookLimitUp );   
         
                 
@@ -5320,8 +5324,7 @@ window.Modernizr = function(n, e, t) {
         var c = $("#player").width() / $("#player").height();
         isNaN(c) && (c = 1);
         
-        
-        //initTransitionPass(n)
+         
         
         var h = {
             currentBlur: 0,
@@ -5330,7 +5333,7 @@ window.Modernizr = function(n, e, t) {
             hblurPass: s,
             vblurPass: l,
             
-            transitionMaskPass : new n.TransitionPass()
+            fastTranMaskPass : new n.TransitionPass()
             
             ,
             bindEvents: function(e) {
@@ -17504,6 +17507,7 @@ window.Modernizr = function(n, e, t) {
                 this.changingFloor = !1,
                 this.chunks = [],
                 this.panos = new A,
+                
                 this.colliders = [],
                 this.loadPanosPromise = null,
                 this.loadMeshTexturesPromise = null,
@@ -17784,6 +17788,10 @@ window.Modernizr = function(n, e, t) {
                 }).done(function(e) { 
                     
                     window.data2 = e;
+                    
+                    
+                     
+                    
                     this.roomLabels = []
                     if(!e){
                         console.error("data2 is null") 
@@ -18050,7 +18058,33 @@ window.Modernizr = function(n, e, t) {
                     }
                     .bind(this))
                 }
-                .bind(this)),
+                .bind(this)) 
+                
+                
+                //有很多场景的floorPosition在地面之下,导致marker看不到,所以探测一下 
+                let time1 = performance.now()
+                let downVec = new THREE.Vector3(0,-1,0)
+                this.panos.forEach((pano)=>{ 
+                    player.raycaster.set(pano.position,  downVec);
+                    let collider = pano.floor.collider.children
+                    let n = player.raycaster.intersectObjects(collider , true );//add true
+                    if(n && n[0]){
+                        if(n[0].point.y - pano.floorPosition.y > 0.03){
+                            //console.error(`${pano.id}, 探测高度${n[0].point.y.toPrecision(5)} 原高度 ${pano.floorPosition.y.toPrecision(5)}, 相差 ${(n[0].point.y - pano.floorPosition.y).toPrecision(5)}`)
+                            pano.originFloorY = pano.floorPosition.y
+                            pano.floorPosition.y = n[0].point.y
+                        }
+                    }
+                    
+                    //是否需要排除一下相差过高的?因为可能探测到的是长椅这样的
+                })
+                let time2 = performance.now()
+                console.log('探测时间',time2-time1)
+                
+                
+                
+                
+                
                 this.panos.forEach(function(e) {
                     e.build2()
                 }),
@@ -19179,7 +19213,7 @@ window.Modernizr = function(n, e, t) {
             }), 5),
             this.conservativeBoundingBox.max.y = a.lowerMedian(this.collider.children.map(function(e) {
                 return e.geometry.boundingBox.max.y
-            }), 5),
+            }), 5) 
             this.colliderBuilder = null
         }
         ,
@@ -19191,6 +19225,9 @@ window.Modernizr = function(n, e, t) {
             return this.name
         }
         ,
+        
+        n.prototype.constructor = n, //add  
+        
         t.exports = n
     }
     , {
@@ -19434,8 +19471,8 @@ window.Modernizr = function(n, e, t) {
               , C = e("events").EventEmitter
               , I = new o(i)
               , E = new r.SphereBufferGeometry(.1)
-              , b = new r.PlaneBufferGeometry(.4,.4,1,1)
-              , w = d.load("images/marker-256x256.png");
+              , b = new r.PlaneBufferGeometry(.35,.35,1,1)
+              , w = d.load("images/marker.png");
               
               w.anisotropy = browser.isMobile() ? 1 : 3 //add
               
@@ -19466,11 +19503,13 @@ window.Modernizr = function(n, e, t) {
             }
             ,
             n.prototype.hoverOn = function(e) {
+             
                 this.marker && (c.start(h.property(this.marker.material, "opacity", a[e].markerOpacityOnHover), 250),
                 a.navigation.panoScores & a.navigation.mouseDirection && this.addTextSprite("HIT", 12525854))
             }
             ,
             n.prototype.hoverOff = function(e) {
+                
                 this.marker && c.start(h.property(this.marker.material, "opacity", a[e].markerOpacity), 250)
             }
             ,
@@ -19480,6 +19519,7 @@ window.Modernizr = function(n, e, t) {
                 this.floorPosition = this.floorPosition || this.raycastFloorPosition(),
                 this.neighbourPanos = this.neighbourPanos || this.findNeighourPanos(),
                 a.colorMarkerByFloor && this.marker && this.marker.material.color.set(this.floor.debugColor)
+                 
             }
             ,
             n.prototype.build2 = function() {
@@ -19520,10 +19560,15 @@ window.Modernizr = function(n, e, t) {
                     return null
                 }
             }(),
-            n.prototype.placeMarker = function() {
-                this.marker && (this.marker.position.copy(this.floorPosition),
-                this.marker.position.y += .1,   //经常有marker比地面低,所以多往上调5cm
+            n.prototype.placeMarker = function() { 
+                 
+                this.marker && (this.marker.position.copy(this.floorPosition), 
+                //this.fixedFloorY != void 0 &&  this.marker.position.setY(this.fixedFloorY),//add
+                this.marker.position.y += settings.markerHeight,    
                 this.marker.lookAt(new r.Vector3(0,1,0).add(this.marker.position)))
+                
+                
+                
             }
             ,
             n.prototype.translate = function(e) {
@@ -19785,7 +19830,35 @@ window.Modernizr = function(n, e, t) {
                 return this.position
             }
             ,
-            n.prototype.addTextSprite = function(e, t) {
+            n.prototype.addLabel = function() {
+                this.removeLabel()
+                this.label = new TextSprite({
+                    sizeInfo: {
+                        minSize: 100,
+                        maxSize: 200,
+                        nearBound: 0.7,
+                        farBound: 10, //Math.max(20, player.model.size.length() / 3),
+                        farBoundPlan: 100,
+                    }, 
+                    backgroundColor: { r: 255, g: 255, b: 255, a: 0.4 },
+                    textColor: { r: 0, g: 0, b: 0, a: 1 },
+                    borderRadius: 15,
+                    renderOrder: 5,
+                    text: this.id     //`${this.id}-f${this.floorIndex}`     
+                })
+                 
+                let position = this.floorPosition.clone()
+                position.y += 0.2
+                this.label.position.copy(position)
+                this.floor.add(this.label)
+            },
+            
+            n.prototype.removeLabel = function() {
+                this.label && (this.floor.remove(this.label), this.label.material.dispose(), (this.label = null))
+            },
+            
+            
+            /* n.prototype.addTextSprite = function(e, t) {
                 this.removeTextSprite();
                 var i = document.createElement("canvas")
                   , n = i.getContext("2d");
@@ -19806,13 +19879,13 @@ window.Modernizr = function(n, e, t) {
                 this.text3d = new r.Sprite(c),
                 this.text3d.position.copy(this.skyboxMesh.position),
                 this.floor.add(this.text3d)
-            }
+            } 
             ,
             n.prototype.removeTextSprite = function() {
                 this.text3d && (this.floor.remove(this.text3d),
                 this.text3d = null)
-            }
-            ,
+            }*/
+             
             n.prototype.isAligned = function() {
                 return this.alignmentType === l.ALIGNED
             }
@@ -20035,7 +20108,7 @@ window.Modernizr = function(n, e, t) {
           , c = e("../shaders")
           , h = e("../enum/RenderLayers")
           , u = e("../enum/RenderOrder")
-          , d = s.load("images/reticule-256x256.png");
+          , d = s.load("images/reticule.png");
           
         d.anisotropy = browser.isMobile() ? 1 : 4; //各向异性  
           
@@ -21354,14 +21427,14 @@ window.Modernizr = function(n, e, t) {
                 }
                 
                 if(window.DATA.momentTourBlackNewType){
-                    player.sceneRenderer.effects.transitionMaskPass.start(player.sceneRenderer)
+                    player.sceneRenderer.effects.fastTranMaskPass.start(player.sceneRenderer)
                    
                     
                     var camera = this.playerControls.cameras[a.PANORAMA];
                     var end =  this.warpDestHeroLoc.position
                     camera.position.copy(end)
                     setTimeout(()=>{
-                        player.sceneRenderer.effects.transitionMaskPass.stop()
+                        player.sceneRenderer.effects.fastTranMaskPass.stop()
                         
                         this.wrapupWarpShadingOnly(n, i);
                         done()
@@ -22426,7 +22499,7 @@ window.Modernizr = function(n, e, t) {
             n.prototype.handelPadding = function(x, y) {
                 //去除player左边和上面的宽高,因为pc的player左上有其他element  许钟文
                 if (!this.pad)
-                    this.pad = {
+                    this.pad = {//可以直接用$("#player")[0].getBoundingClientRect() , 因为body没有offset
                         x: dom.getOffset("left", $("#player")[0]),
                         y: dom.getOffset("top", $("#player")[0])
                     }
@@ -22492,7 +22565,7 @@ window.Modernizr = function(n, e, t) {
                         }) : this.bump(this.getMouseDirection())
                     }
                 }
-                this.intersect && this.closestPano && this.closestPano.hoverOff(this.mode)
+                //this.intersect && this.closestPano && this.closestPano.hoverOff(this.mode) //xzw 删,否则拖拽后就不高亮了
             }
             ,
             n.prototype.handleLongTap = function() {
@@ -22758,7 +22831,7 @@ window.Modernizr = function(n, e, t) {
                         this.lastChangeTime = Date.now(),
                         !this.mouseDown && this.containsMouse && this.updateIntersect(),
                         this.emit(w.ViewChanged, e),
-                        
+                        // 注意: player.cameraControls.activeControl.camera 的 matrix是错的
                          
                         this.intersect && this.magnifier && this.magnifier.update(this.intersect.point)
 
@@ -23014,8 +23087,9 @@ window.Modernizr = function(n, e, t) {
                 
                 {//clear:
                     if(this.intersectHot) this.intersectHot.setHoverState(false)
-                    this.intersectHot = null
-                    $("#player").css("cursor", "")
+                    this.intersectHot = null 
+                    
+                    CursorDeal.remove('hoverHot')
                 }
                 
                 var deal = ()=>{
@@ -23036,14 +23110,18 @@ window.Modernizr = function(n, e, t) {
                     
                     var hots = this.model.hotGroup.children.filter(e=> e.info.actionType.examine || e.info.actionType.fastTran || e.info.actionType.openHot || e.info.actionType.playAndPause)
                    
+                    let colliders = this.model.floors.reduce(function(e, t) {
+                        return t.hidden ? e : e.concat(t.collider.children)
+                    },  this.mode === 'panorama' ? this.panoMarkers : []) 
                      
-                    this.intersect = this.getMouseIntersect(null, hots.concat(this.model.colliders));
+                    this.intersect = this.getMouseIntersect(null, hots.concat(colliders/* this.model.colliders */));
                     if(this.intersect){
                         deal()
                         let intersect = this.intersect
                         if(this.intersectHot){
-                            this.intersect = null
-                            $("#player").css("cursor", "pointer");
+                            this.intersect = null 
+                            CursorDeal.add('hoverHot')
+                            
                         }else{  
                             if(intersect.object.parent.parent.hidden ){//楼层已隐藏
                                 this.intersect = null
@@ -23164,8 +23242,13 @@ window.Modernizr = function(n, e, t) {
                 }
                 var i = this.model.panos.find(t, [s.sortFunctions.floorDistanceToPoint(e.point)]);
                 i !== this.closestPano ? (i && (this.isPanoHover = !0),
-                this.emit(w.ClosestPanoChanging, this.closestPano, i, this.mode),
-                this.closestPano = i) : this.isPanoHover = !1
+                this.emit(w.ClosestPanoChanging, this.closestPano, i, this.mode),  
+                this.closestPano = i,
+                
+                this.closestPano && this.mode == 'panorama' ? CursorDeal.add('hoverPano' ) : CursorDeal.remove('hoverPano' ) //add
+                
+                ) : this.isPanoHover = !1
+                 
             }
             ,
             n.prototype.dollhouseMode = function(e, t) {
@@ -23973,6 +24056,9 @@ window.Modernizr = function(n, e, t) {
                     t.reject("Already in " + i + " mode").promise());
                 var h = a || !1;
                 F.debug("Switching mode to " + i);
+                if (window.vrEnabled && i != 'panorama'){
+                    return s && s(!1), t.reject("vr不允许飞出").promise();
+                }
                 var d = function() {
                     N.delayOneFrame(function() {
                         this.flyToNewMode(e, t)
@@ -25071,7 +25157,7 @@ window.Modernizr = function(n, e, t) {
             this.composer.addPass(new r.RenderPass(this.scene,this.camera)),
             //this.composer.addPass(this.effects.hblurPass),
             //this.composer.addPass(this.effects.vblurPass)
-            this.composer.addPass(this.effects.transitionMaskPass)//add
+            this.composer.addPass(this.effects.fastTranMaskPass)//add
            
         }
         ,
@@ -25089,7 +25175,7 @@ window.Modernizr = function(n, e, t) {
         n.prototype.render = function() {
             //this.effects.currentBlur > 0 ? this.composer.render() : this.renderer.render(this.scene, this.camera)
             
-            this.effects.transitionMaskPass.enabled ? this.composer.render() : this.renderer.render(this.scene, this.camera)
+            this.effects.fastTranMaskPass.enabled ? this.composer.render() : this.renderer.render(this.scene, this.camera)
         
         }
          
@@ -25138,6 +25224,9 @@ window.Modernizr = function(n, e, t) {
                     a.setSize(window.innerWidth, window.innerHeight),
                     window._vrEnabled = b
 					
+                    
+                    $('.cad').css('visibility',b?'hidden':'')
+                    
 					if(b){
                         setTimeout(function(){
                             console.log("orientEnable"+window.orientEnable) 
@@ -25355,7 +25444,7 @@ window.Modernizr = function(n, e, t) {
             }),
             this.update = function(a) {
                 TWEEN.update();
-                if(window.ifTest && window.vrEnabled) this.triggerTargetEvent() //测试时不根据陀螺仪来转向
+                if(window.vrEnabled) this.triggerTargetEvent() //测试时不根据陀螺仪来转向
                 else window.vrEnabled && (this.setObjectQuaternion(this.target.quaternion, this.alpha, this.beta, this.gamma, this.orient ),
                 this.triggerTargetEvent())   
             }
@@ -32237,6 +32326,7 @@ window.Modernizr = function(n, e, t) {
             
         }
         window.math = t.exports
+        expandMath(math)
     }
     , {
         "../constants": 8,
@@ -60465,5 +60555,36 @@ var addMagnifier = function(){
     两种选择,1 按照正常流程,因为nav为4k所以直接使用4k的tiledPanoRenderTarget, 但useUltraHighResolutionPanos需要开启才能达到4k,否则是2k。这样的话第一个点位需要手动重新替换tiledPanoRenderTarget,所以废弃
     2 使zoomed一直为true,直接使用zoomRenderTarget。  参考之前写过的三屏的本地4k。 且创建的tiledPanoRenderTarget不能超过2048否则过渡卡顿;且直到到达该点后才能下载4k,否则不会渲染到zoomRenderTarget上 
  
+ 
+ 
+    如果body使用transform旋转90度,需要改的地方:
+    convertScreenPositionToNDC: function(e, t, i) {//rotate 90       body:  transform: rotate(90deg);
+        return i = i || new n.Vector2,
+        i.y = e / $("#player").height() * 2 - 1,  //注意,当body旋转90度后,虽然clientX和clinetY是按照正常垂直视角,但player的长宽和原先一样,不会因为倒转而交换
+        i.x = t / $("#player").width() * 2 - 1,
+        i
+    },
+    outsideControl的   n.prototype.onTouchMove = function(e) {中的 
+        this.pan(this.panDelta.x, this.panDelta.y)       改为 this.pan(this.panDelta.y, -this.panDelta.x), //rotate 90
+        还有
+        this.rotateLeft(2 * Math.PI * this.rotateDelta.y / $("#player").width() * this.rotateSpeed), //rotate 90
+        this.rotateUp(2 * Math.PI * -this.rotateDelta.x / $("#player").height() * this.rotateSpeed),
+                
+    ===================
+    
+    
+    
+    
+    一些事件 
+    
+    player.model.on("floor.changed",(currentFloor, mode, oldFloor)=>{//楼层变化
+    
+    player.on("pano.chosen", 旧的pano,  新的pano) //点位变化前 
+    player.on("flying.ended", 新位置, 旧位置, 新的pano, 旧的pano) //点位变化后
+ 
+ 
+    一些后缀
+    &panoLabel 加pano标签
+ 
  */
  

+ 625 - 16
js/manage.js

@@ -119,7 +119,7 @@ var settings = {
     },
      
     hotClickActions:['playAndPause','examine','openHot','fastTran'],
-   
+    markerHeight: 0.05,//距离地板高出多少,
     
     //默认的:
     teleportTime:  1500,//瞬间过渡的时间 
@@ -524,9 +524,9 @@ var convertTool = {
         l.set(w / 2, 0, 0).add(o),  //加上tag宽度的一半
         c.set(2 / op.resolution.x, 2 / op.resolution.y, 1).multiply(l), //再转回  -1 到 1的范围
         h.copy(c).unproject(op.camera);//再转成三维坐标,求得tag边缘的位置
-        var g = h.distanceTo(op.position)//就能得到tag的三维半径
+        let halfMeter = h.distanceTo(op.position)//就能得到tag的三维半径
     
-        return g  //可能NAN  当相机和position重叠时
+        return halfMeter  //可能NAN  当相机和position重叠时
          
     } ,
     
@@ -677,17 +677,59 @@ window.expandCommon = function(common){
             })  	  
          
             return visiblePanos
+        } ,
+        realVisible : function (object) {
+            var v = true;
+            var parent = object;
+            var lastParent;
+            while (parent) {
+                if (parent.visible === false) {
+                    v = false;
+                    break
+                }
+                lastParent = parent;
+                parent = parent.parent;
+            }
+
+            if (v && !(lastParent instanceof THREE.Scene)) {
+                //已被删除
+                v = false;
+            }
+
+            return v
         } 
-        
     })
     
 }
 
 
+window.expandMath = function(math){
+    Object.assign(math,{ 
+        linearClamp(value, xArr, yArr) {
+            if (arguments.length == 5) {
+                xArr = [arguments[1], arguments[2]]
+                yArr = [arguments[3], arguments[4]]
+            }
 
-
-
-
+            let len = xArr.length
+            if (value <= xArr[0]) return yArr[0]
+            if (value >= xArr[len - 1]) return yArr[len - 1]
+            let i = 0
+
+            while (++i < len) {
+                if (value < xArr[i]) {
+                    let x1 = xArr[i - 1],
+                        x2 = xArr[i],
+                        y1 = yArr[i - 1],
+                        y2 = yArr[i]
+                    value = y1 + ((y2 - y1) * (value - x1)) / (x2 - x1)
+                    break
+                }
+            }
+            return value
+        }
+    })
+}
 
 //--------------------------------------
 //管理js文件 获取modeldata.js 判断是否有特殊的字段,如果有就先加载SpecialScene.js 里面有对特殊场景处理的代码 否则就直接加载main
@@ -1444,7 +1486,7 @@ function initByTHREE(THREE){
                      
                 
                 this.addEventListener('dispose',  (e)=>{
-                    player.off("view.changed",update)
+                    player.removeListener("view.changed",update)
                 })
             } 
             this.visible = true
@@ -1564,8 +1606,7 @@ function initByTHREE(THREE){
         }
         
         dispose(){
-            this.elem.remove();
-            this._listeners = {} 
+            this.elem.remove(); 
             this.dispatchEvent({type:'dispose'})
             let index = labels.indexOf(this)
             index > -1 && labels.splice(index,1) 
@@ -1638,8 +1679,7 @@ function initByTHREE(THREE){
     }
     window.RoomLabel = RoomLabel
 
-    
-    
+     
 
     class HistoryRecord extends THREE.EventDispatcher{
         
@@ -1725,6 +1765,259 @@ function initByTHREE(THREE){
     window.HistoryRecord = HistoryRecord
 
     
+    
+     
+   
+    let planeGeo = new THREE.PlaneBufferGeometry(1, 1, 1, 1)
+
+    class TextSprite extends THREE.Object3D {
+        constructor(options = {}) {
+            super()
+            let map = new THREE.Texture()
+            this.root = options.root || this
+            //if (options.fixOrient) {
+            this.sprite = new THREE.Mesh(
+                planeGeo,
+                new THREE.MeshBasicMaterial({
+                    map,
+                    color: 0xffffff,
+                    transparent: true,
+                    depthTest: false,
+                    depthWrite: false,
+                })
+            )
+            /*}  else {
+                this.sprite = new THREE.Sprite(
+                    new THREE.SpriteMaterial({
+                        map,
+                        color: 0xffffff,
+                        transparent: true,
+                        depthTest: false,
+                        depthWrite: false,
+                    })
+                )
+            } */
+
+            this.add(this.sprite)
+            this.sprite.renderOrder = options.renderOrder != void 0 ? options.renderOrder : 2
+            this.fontWeight = options.fontWeight == void 0 ? /* 'Bold' */ '' : options.fontWeight
+            this.rectBorderThick = options.rectBorderThick || 0
+            this.textBorderThick = options.textBorderThick || 0
+            this.fontface = 'Arial'
+            this.fontsize = options.fontsize || 16
+            this.textBorderColor = options.textBorderColor || { r: 0, g: 0, b: 0, a: 0.0 }
+            this.backgroundColor = options.backgroundColor || { r: 255, g: 255, b: 255, a: 1.0 }
+            this.textColor = options.textColor || { r: 0, g: 0, b: 0, a: 1.0 }
+            this.borderColor = options.borderColor || { r: 0, g: 0, b: 0, a: 0.0 }
+            this.borderRadius = options.borderRadius == void 0 ? 6 : options.borderRadius
+            this.margin = options.margin
+            this.textshadowColor = options.textshadowColor
+            if (options.text != void 0) this.setText(options.text)
+            this.name = options.name
+            this.sizeInfo = options.sizeInfo
+            //this.setText(text);
+            
+            this.addEventListener('dispose', this.dispose.bind(this))
+            this.fixOrient = options.fixOrient
+            this.events = {
+                updatePose: (e)=>{
+                    e.cameraChanged && this.updatePose()
+                },
+            }
+
+            player.on("view.changed", this.events.updatePose)
+            this.addEventListener('isVisible', e => {
+                if (e.visible) {
+                    this.updatePose()
+                }
+            })
+
+            this.updatePose()
+        }
+ 
+                 
+        updatePose() {
+         
+            
+            
+            //if (lastFrameChanged) this.needsUpdate = true
+            if (!common.realVisible(this) /* || !this.needsUpdate */) return
+
+            //this.needsUpdate = false
+
+            let camera = player.mode == 'floorplan' ? player.cameraControls.activeControl.camera : player.camera //floorplan 时要用到OrthographicCamera
+
+            if (!this.fixOrient) {
+                let parentQua = this.root.parent.getWorldQuaternion(new THREE.Quaternion())
+                this.root.quaternion.multiplyQuaternions(parentQua.inverse(), camera.quaternion) //乘上parentQua.invert()是为了中和掉父结点的qua,使只剩下camera.quaternion
+            }
+
+            if (this.sizeInfo) {
+                var s = convertTool.getScaleForConstantSize(
+                    Object.assign(
+                        {     
+                        },
+                        this.sizeInfo,
+                        {
+                            farBound: player.mode == 'floorplan' ? this.sizeInfo.farBoundPlan || this.sizeInfo.farBound : this.sizeInfo.farBound,
+                            position: this.root.getWorldPosition(new THREE.Vector3()),
+                        }
+                    )
+                )
+                this.scale.set(s, s, s)
+            }
+        }
+
+        setText(text) {
+            if (this.text !== text) {
+                if (!(text instanceof Array)) {
+                    this.text = [text + '']
+                } else this.text = text
+                this.updateTexture()
+                this.needsUpdate = true
+            }
+        }
+        setPos(pos) {
+            this.position.copy(pos)
+            this.needsUpdate = true //updatePose
+        }
+        setTextColor(color) {
+            this.textColor = color
+            this.updateTexture()
+        }
+
+        setBorderColor(color) {
+            this.borderColor = color
+
+            this.updateTexture()
+        }
+
+        setBackgroundColor(color) {
+            this.backgroundColor = color
+
+            this.updateTexture()
+        }
+
+        setVisible(v) {
+            this.visible = v
+        }
+        setUniforms(name, value) {
+            this.sprite.setUniforms(name, value)
+        }
+        updateTexture() {
+            let canvas = document.createElement('canvas')
+            let context = canvas.getContext('2d')
+            context.font = this.fontWeight + ' ' + this.fontsize + 'px ' + this.fontface
+
+            //context["font-weight"] = 100; //语法与 CSS font 属性相同。
+            // get size data (height depends only on font size)
+
+            //this.text = 'f 啊啊啊 jg'
+
+            let textMaxWidth = 0,
+                infos = []
+            for (let text of this.text) {
+                let metrics = context.measureText(text)
+                let textWidth = metrics.width
+                infos.push(metrics)
+                textMaxWidth = Math.max(textMaxWidth, textWidth)
+            }
+
+            let margin = this.margin || new THREE.Vector2(this.fontsize, Math.max(this.fontsize * 0.4, 10))
+            const lineSpace = (this.fontsize + margin.y) * 0.5
+
+            let spriteWidth = 2 * margin.x + textMaxWidth + 2 * this.rectBorderThick
+            let spriteHeight = 2 * margin.y + this.fontsize * this.text.length + 2 * this.rectBorderThick + lineSpace * (this.text.length - 1)
+            context.canvas.width = spriteWidth
+            context.canvas.height = spriteHeight
+            context.font = this.fontWeight + ' ' + this.fontsize + 'px ' + this.fontface
+            let expand = Math.max(1, Math.pow(this.fontsize / 12, 1.4)) // 针对英文大部分在baseLine之上所以降低一点,或者可以识别当不包含jgqp时才加这个值
+            //canvas原点在左上角
+            context.textBaseline = 'alphabetic' //  "middle"  //设置文字基线。当起点y设置为0时,只有该线以下的部分被绘制出来。middle时文字显示一半(但是对该字体所有字的一半,有的字是不一定显示一半的,尤其汉字),alphabetic时是英文字母的那条基线。
+            // border color
+            context.strokeStyle = 'rgba(' + this.borderColor.r + ',' + this.borderColor.g + ',' + this.borderColor.b + ',' + this.borderColor.a + ')'
+
+            context.lineWidth = this.rectBorderThick
+            // background color
+            context.fillStyle = 'rgba(' + this.backgroundColor.r + ',' + this.backgroundColor.g + ',' + this.backgroundColor.b + ',' + this.backgroundColor.a + ')'
+            this.roundRect(context, this.rectBorderThick / 2, this.rectBorderThick / 2, spriteWidth - this.rectBorderThick, spriteHeight - this.rectBorderThick, this.borderRadius)
+
+            context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' + this.textColor.b + ',' + this.textColor.a + ')'
+
+            let y = margin.y
+            for (let i = 0; i < this.text.length; i++) {
+                //let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent // 当前文本字符串在这个字体下用的实际高度
+
+                //文字y向距离从textBaseline向上算
+                let actualBoundingBoxAscent = infos[i].actualBoundingBoxAscent == void 0 ? this.fontsize * 0.8 : infos[i].actualBoundingBoxAscent //有的流览器没有。只能大概给一个
+                y += actualBoundingBoxAscent + expand
+                //console.log(actualBoundingBoxAscent)
+
+                //console.log(this.text, 'y' , y, 'actualBoundingBoxAscent', metrics.actualBoundingBoxAscent,'expand',expand )
+                let textLeftSpace = (textMaxWidth - infos[i].width) / 2
+                let x = this.rectBorderThick + margin.x + textLeftSpace
+                // text color
+                if (this.textBorderThick) {
+                    context.strokeStyle = 'rgba(' + this.textBorderColor.r + ',' + this.textBorderColor.g + ',' + this.textBorderColor.b + ',' + this.textBorderColor.a + ')'
+                    context.lineWidth = this.textBorderThick
+                    context.strokeText(this.text[i], x, y)
+                }
+                if (this.textshadowColor) {
+                    context.shadowOffsetX = 0
+                    context.shadowOffsetY = 0
+                    context.shadowColor = this.textshadowColor
+                    context.shadowBlur = 12
+                }
+                context.fillText(this.text[i], x, y)
+
+                y += lineSpace
+            }
+
+            let texture = new THREE.Texture(canvas)
+            texture.minFilter = THREE.LinearFilter
+            texture.magFilter = THREE.LinearFilter
+            texture.anisotropy = 4
+            texture.needsUpdate = true
+            //this.material.needsUpdate = true;
+
+            if (this.sprite.material.map) {
+                this.sprite.material.map.dispose()
+            }
+            this.sprite.material.map = texture
+
+            this.sprite.scale.set(spriteWidth * 0.01, spriteHeight * 0.01, 1.0)
+        }
+
+        roundRect(ctx, x, y, w, h, r) {
+            ctx.beginPath()
+            ctx.moveTo(x + r, y)
+            ctx.lineTo(x + w - r, y)
+            ctx.arcTo(x + w, y, x + w, y + r, r) //圆弧。前四个参数同quadraticCurveTo
+            //ctx.quadraticCurveTo(x + w, y, x + w, y + r); //二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。
+            ctx.lineTo(x + w, y + h - r)
+            ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
+            ctx.lineTo(x + r, y + h)
+            ctx.arcTo(x, y + h, x, y + h - r, r)
+            ctx.lineTo(x, y + r)
+            ctx.arcTo(x, y, x + r, y, r)
+            ctx.closePath()
+            ctx.fill()
+            ctx.stroke()
+        }
+
+        dispose() {
+            this.sprite.material.map.dispose()
+            this.sprite.material.dispose()
+            this.parent && this.parent.remove(this)
+            this.sprite.dispatchEvent({ type: 'dispose' })
+            this._listeners = []
+
+            this.events.updatePos && player.removeEventListener('view.changed', this.events.updatePose)
+        }
+    }
+
+    window.TextSprite = TextSprite
+    
     /* class VideoPlayer extends THREE.EventDispatcher{
         constructor(player) {
             super() 
@@ -1802,7 +2095,290 @@ function initByTHREE(THREE){
         
     } */
     
-     
+    
+    
+    
+    
+    window.initRouteArrow = ()=>{
+        if(window.isEdit)return
+        
+       
+        
+        
+        
+        if(!window.DATA.route?.data || Object.keys(window.DATA.route.data).length == 0)return
+        
+        let {hide, gradualShow, opacityShine, data={}} = window.DATA.route || {}      //hide 默认是否隐藏,若隐藏也可以通过函数展示
+        
+        let panos = player.model.panos 
+        panos.routeNextMap = {} //下一个
+        panos.routePreMap = {} //上一个
+        panos.list.forEach((pano,i)=>{
+            panos.routePreMap[pano.id] = []  
+            panos.routeNextMap[pano.id] = data[pano.id] ? data[pano.id].map(id=>panos.get(id)) : []   
+        })
+        for(let panoId in data){
+            data[panoId].forEach(id=>{
+                panos.routePreMap[id].push(panos.get(panoId)) 
+            }) 
+        }
+        
+        var arrowInfo = { 
+            animateDur : 5000,
+            showDur: 1000,
+            minOpa: 0.2,
+            maxOpa: 0.5 
+             
+        }
+        
+ 
+        let arrowTex = Texture.load('images/arrow.png') 
+            arrowTex.anisotropy = 4
+            
+            
+        let arrowMat = new THREE.MeshBasicMaterial({
+            name: 'arrow',
+            transparent:true,
+            map:arrowTex,
+            side:2,
+            opacity: arrowInfo.maxOpa
+            //depthTest:false
+        }) 
+        let mats = {
+            default: arrowMat,
+            fadeIn: arrowMat.clone()
+        }
+        mats.fadeIn.name = 'fadeInArrow'
+        let plane = new THREE.PlaneBufferGeometry(1,1)
+       
+        
+        let arrows = new THREE.Object3D; arrows.name = 'groundArrows'
+          
+        player.model.add(arrows)
+
+        
+        var createArrow = function(mat){
+          
+            var arrow = new THREE.Mesh(plane, mat)
+            arrow.name = 'arrow'
+            
+            let s = 0.15
+            arrow.scale.set(s,s,s)
+             
+            arrows.add(arrow)
+            return arrow
+        }
+
+
+
+        
+        
+         
+        
+        var updateArrowPose = function(from, to, mat ){
+             
+            var vec = to.floorPosition.clone().clone().sub(from.floorPosition).setY(0);
+            let spaceDis = 0.4//箭头之间的间距
+            let margin = 0.3 //marker端需要留一点距离
+            let sliceCount = Math.max(2, Math.round((vec.length()-margin) / spaceDis)) //分段  
+            let arrowCount = sliceCount - 1
+            
+            let dir = vec.clone().normalize()
+            let dir2d = new THREE.Vector2(dir.x, dir.z)
+            let angle = dir2d.angle() - Math.PI/2
+            let sliceLen = (vec.length()-margin) / sliceCount 
+            let i = arrowCount
+            
+            while(i>0){
+                
+                let pos = from.floorPosition.clone().add(dir.clone().multiplyScalar(margin/2 + i*sliceLen))
+                    pos.y+=settings.markerHeight
+                let arrow = createArrow(mat)
+                    arrow.name = 'arrow:'+from.id+"-"+to.id+"|"+i
+                arrow.rotation.set(Math.PI/2, 0, angle);
+                arrow.position.copy(pos)
+                i--
+            } 
+            
+        }
+         
+        var updateArrowOpacity = function(e){//不停更新所有arrow的透明度
+            var transition = function(a){
+                if(!arrows.visible)return
+                
+                var opa = a > 0.5 ? 2-a*2 : 2*a ;
+                opa = arrowInfo.maxOpa*opa+ arrowInfo.minOpa*(1-opa)
+                
+                mats.default.opacity = opa
+                mats.fadeIn.opacity = opa * mats.fadeIn.opacity2
+                    
+            }
+            transitions.start(transition, arrowInfo.animateDur, updateArrowOpacity, 0, easing.easeInOutCubic, "updateArrowOpacity")
+        }
+
+        var fadeInArrow = function(){
+            
+            transitions.cancelById('updateArrowOpacity2')
+            var arrows_ = arrows.children.filter(e=> e.material == mats.fadeIn)
+            if(arrows_.length == 0)return
+            
+            
+            mats.fadeIn.opacity = 0 
+            
+            var transition = function(a){ 
+                if(!opacityShine) mats.fadeIn.opacity = a * arrowInfo.maxOpa
+                else mats.fadeIn.opacity2 = a
+            } 
+            transitions.start(transition, arrowInfo.showDur, function done(){
+                arrows_.forEach(e=>e.material = mats.default)
+            }, 0, easing.easeInOutCubic, "updateArrowOpacity", "updateArrowOpacity2")
+        }
+
+        var lastArrowPanos = []
+        var updateArrow = function(){//根据当前pano更新
+            //console.log(currentPano)
+            if(player.mode != 'panorama' || hide){ //飞出 
+                arrows.visible = false           
+                lastArrowPanos = []
+                return;
+            }
+            arrows.visible = true
+            currentPano = player.currentPano
+            
+            
+            //先获取所有需要箭头的pano
+            var maxDistance = 6;//该距离内pano可见箭头
+            var maxPathCount = 8;
+            var dis = 0
+            var curPano = currentPano;
+            var panos_ = [];
+            
+            var getAngle = function(pano1,pano2){
+                let dir = new THREE.Vector3().subVectors(pano1.position,pano2.position)
+                dir = new THREE.Vector2(dir.x, dir.z)
+                return dir.angle()
+            }
+            var search = function(pano, path=[pano], angles=[]){//多分支搜寻
+                
+                var neighbor = panos.routeNextMap[pano.id];
+                if(!neighbor || !neighbor.length)return path.length>1 && console.log('branchPath',path);
+                
+                
+                neighbor.forEach(e=>{
+                    let branchPath = path.slice(), angles_ = angles.slice() 
+                    if(panos_.find(arr=>arr[0]==e))return console.log('不回头branchPath',branchPath); //不回头
+                    dis = e.floorPosition.distanceTo(currentPano.floorPosition)
+                    
+                    branchPath.push(e)
+                    if(branchPath.length>2){//不折回,否则感觉在面前饶了一圈回来很难看
+                        let i=0
+                        while(i<branchPath.length-1){ //补全angles
+                            if(angles_[i] == void 0){
+                                angles_[i] = getAngle(branchPath[i], branchPath[i+1]) 
+                            }
+                            i++
+                        }
+                        let lastAngle = angles_[branchPath.length-2]//getAngle(branchPath[i],  e)
+                        let reverse = angles_.find((angle, n)=> {
+                            let angleDiff = Math.abs(( lastAngle - angle) % (Math.PI*2) ) //越远限制越大
+                            let minDiff = math.linearClamp(branchPath.length,[3,6],[0.2,0.5])
+                            if(Math.abs(angleDiff - Math.PI ) < minDiff){
+                                console.log('因为折回而提前结束', n, branchPath)
+                                return true
+                            } 
+                        })
+                        if(reverse != void 0){
+                            return
+                        } 
+                    }
+                    
+                    
+                    if(branchPath.length<3 || dis < maxDistance){
+                        panos_.push([pano, e]) 
+                        search(e, branchPath, angles_)
+                    }else console.log('branchPath',branchPath)
+                })
+                
+            }
+            
+            //search(currentPano)
+            let disMap = new Map, cosMap = new Map
+            let camDir = player.getDirection()
+            let neighbors = currentPano.neighbourUUIDs.map(e=>panos.get(e))
+                            .filter(p=>{
+                                let dir = new THREE.Vector3().subVectors(p.position, currentPano.position)
+                                let d = dir.lengthSq()
+                                if(d < 15){//最大距离
+                                    disMap.set(p, d);
+                                    cosMap.set(p, dir.normalize().dot(camDir)) 
+                                    return true
+                                }
+                            }).sort((a,b)=>{ 
+                                let score = disMap.get(a) - disMap.get(b) 
+                                score += (cosMap.get(b) - cosMap.get(a)) * 5
+                                return score
+                            })//从近到远,尽量在前方
+                            
+            let seedPanos = [currentPano, ...neighbors]//如果没有从当前点出发的箭头,就展示隔壁点的(缺点是隔壁点可能是指向当前点的,原规则是不展示来的路径的,所以会有点奇怪。虽然也可以在search后剔除啦)
+            for(let seed of seedPanos){
+                search(seed)
+                if(panos_.length != 0){
+                    break;
+                }
+            }
+             
+            
+            arrows.children.slice().forEach(child=>arrows.remove(child))
+            
+            
+            panos_.forEach((panoArr,i)=>{
+                var isNew = !lastArrowPanos.find(e=>e[0]==panoArr[0] && e[1]==panoArr[1])//新出现的点  渐变出现
+                updateArrowPose(panoArr[0], panoArr[1], isNew ? mats.fadeIn : mats.default )//更新位置
+                
+            })
+            
+            fadeInArrow()
+            
+             
+            
+            lastArrowPanos = panos_
+        }
+
+        let inited
+        let init = ()=>{
+            if(inited)return
+            if(gradualShow){
+                player.on("flying.ended", updateArrow )  
+            }else{ 
+                //展示全部
+                for(let id1 in panos.routeNextMap){
+                    for(let pano2 of panos.routeNextMap[id1]){ 
+                        updateArrowPose(panos.get(id1), pano2, mats.default) 
+                    }  
+                } 
+                
+                player.on("mode.changed", ()=>{
+                    if(hide)return
+                    arrows.visible = player.mode == 'panorama'
+                })
+            }
+            opacityShine && updateArrowOpacity()
+            inited = true
+        }
+         
+        
+        {//ui控制显示
+            let changeShow = (e)=>{ 
+                hide = !e.show 
+                e.show && init()
+                gradualShow ? updateArrow() : (arrows.visible = e.show)
+            }
+            
+            player.on('changeArrowShow',changeShow) 
+            changeShow({show:!hide})
+        }
+        
+    }
  
 
     {
@@ -1849,8 +2425,36 @@ function initByTHREE(THREE){
                 }    
                 
             } */
-     
+            initRouteArrow()
             
+            browser.urlHasValue('panoLabel') && player.model.panos.forEach(p=>p.addLabel())
+          
+            
+            /* if(number == 'SG-LF0SeEdxWjv'){ //---如果要将其中一块chunk提取出来成为单独的floor(或说在该楼层隐藏)的话
+                let roof = new player.model.floors.list[0].constructor(player.model, 2);//放在第三层 
+                roof.build()
+                roof.addChunk(player.model.floors.list[0].chunks[1])
+                player.model.floors.add(roof)
+                player.model.floors.list[0].chunks.splice(1,1) 
+            }  */
+            
+            
+            
+            /* if(number == 'SG-LF0SeEdxWjv' || number == 'SG-mBoheb4MAIb' || number == 'SG-H5A4vvPXqsE'){//在展示第一层时需要隐藏屋顶
+                
+                let [roofFloorIndex,roofChunkIndex] = ({
+                    'SG-LF0SeEdxWjv' : [0,1], //第0层的第1个chunk是屋顶
+                    'SG-mBoheb4MAIb' : [0,3],
+                    'SG-H5A4vvPXqsE' : [0,3],
+                })[number]
+                  
+                let roof = new player.model.floors.list[0].constructor(player.model, 2);//放在第三层 隐藏起来
+                roof.build()
+                roof.addChunk(player.model.floors.list[roofFloorIndex].chunks[roofChunkIndex])
+                player.model.floors.add(roof)
+                player.model.floors.list[roofFloorIndex].chunks.splice(roofChunkIndex,1)
+                
+            } */
         } 
         
         
@@ -2165,6 +2769,8 @@ var CursorDeal = {
     priorityEvent : [//在前面的优先级高
         {"noIntersect":'not-allowed'},
         {"addHot":'cell'},  
+        {'hoverRouteLine':'url(images/coordinateClose.png),auto'},
+        {'hoverPano':'pointer'},
         {"hoverFootIcon":'pointer'},
         {"hoverHot":'pointer'},
         {"addLabel":'cell'},
@@ -2221,7 +2827,7 @@ var CursorDeal = {
             this.judge()
         }
         
-        
+         
         
     },
     
@@ -2282,7 +2888,10 @@ function logSth(){
     漫游点数 ${panoCount} 个)`, '#FF4399', 14)
 }
 
-window.sceneFrom = number.slice(0,3) == 'KJ-' ? 'kankan' : '' //看看or看见转来的
+
+/* let kankanNames = ['SG-','KJ-'] */
+
+window.sceneFrom = (number.length > 6 && number.slice(0,3).includes('-')) ? 'kankan' : '' //看看or看见转来的
 
 //兼容一代的場景
 //請求地址統一管理