xzw hai 1 mes
pai
achega
9755dac9af

+ 288 - 23
public/static/data/data.json

@@ -19,16 +19,82 @@
       {
         "id": 1,
         "description": "上节为神人,上下弦纹之间以折角部位的螺旋小尖喙向两侧展开,填刻螺旋纹小尖喙和线束纹样,神人重圈管钻眼,刻眼角。"
+        ,"pos":{
+            "x": 14.879483956326686,
+            "y": 10.502966023247717,
+            "z": -38.97321840946214
+        },
+        "normal":{
+            "x": 0.17476547479044635,
+            "y": 0.021964034045356295,
+            "z": -0.98436507964765
+        },
+        "action":[
+            {
+                "view":{"yaw":-3.7814036723820315,"pitch":-0.24352933240594524,"radius":563.3983588099231,"target":{"x":-0.0000013200264561419317,"y":9.824366884458868e-7,"z":-2.1799451133119874e-11},"endPos":{"x":326.4481485844841,"y":438.62693632989976,"z":135.85185946951077}}
+                
+                
+                ,"tex":{"show":[0], "dur":3500, "delay":0.3, "shine":6000}  
+            }
+        
+        ] 
       },
       {
         "id": 2,
         "description": "下节为兽面纹,重圈大眼填刻二等分线束,重圈斜上下刻小尖喙。"
+        ,"pos":{
+            "x": 34.07681696101927,
+            "y": -7.850301930531856,
+            "z": 31.20216569500978
+        }
       },
       {
         "id": 3,
         "description": "玉琮内圆外方,中部贯穿,四角对称施刻四组相同图案,四壁有纵向直槽。"
+        ,"pos":{
+            "x": -30.754114698881384,
+            "y": 23.533868605790076,
+            "z": -19.859742583241538
+        },
+        "normal":{
+            "x": -0.08150592205220979,
+            "y": 0.9905431372174927,
+            "z": -0.11036792098135562
+        },
+        "action":[ 
+            {
+            "view":{
+                "yaw": -3.1574036523194025,
+                "pitch": -1.5707963267948966,
+                "radius": 728.182961270621,
+                "target": {
+                    "x": 0,
+                    "y": 1.1368683772161605e-13,
+                    "z": 0
+                },
+                "endPos": {
+                    "x": 2.5563596094135064e-15,
+                    "y": 2.753557259498052e-13,
+                    "z": 728.182961270621
+                }
+            } 
+            ,"structure":{"state":1, "dur":4000, "delay":0.1, "dir":[0,0,1], "rimPower":0.3, "opacity":0.07}  
+            ,"planeTex":{"dur": 4000, "delay":0.6, "file":"top.png", "matrix":[-151.9737285624897, 2.825920525193396, 0, 0, -2.825920525193396, -151.9737285624897, 0, 0, 0, 0, 152, 0, -8, -1, 31, 1]}
+          }   
+        ]
       }
-    ]
+    ],
+    "model":{
+        "file":"lz080" ,
+        "transform":{
+            "rotation" : [1.5707963267948966, 0, 0],
+            "position" : [0, 0, 0]
+        },
+        "view":{"position":{"x":373.20474595157543,"y":550.4456527525658,"z":296.61123722791194},"target":{"x":0,"y":1.1368683772161603e-13,"z":0}},
+        "bright":3,
+        "texNum":1,
+        "structureOpacity":0.6
+    }
   },
   {
     "id": 2,
@@ -48,20 +114,97 @@
       {
         "id": 4,
         "description": "整体为介字形冠形,正面凹弧,背面弧凸,两侧内弧收,下角内凹弧。"
+        ,"pos":{
+            "x": 0.1641761054499954,
+            "y": 7.410403467242432,
+            "z": 0.7172174000907339
+        },
+        "normal":{
+            "x": 0.9817277292544953,
+            "y": 0.1590981184790662,
+            "z": 0.10439566231043096
+        },
+        "action":[
+            {
+                "view":{"dur":1000, "position":{"x":-19.223484367951656,"y":394.1640911638143,"z":20.256669370853146},"target":{"x":-4.813451550776005,"y":-3.6378675309149457,"z":-1.6918808507048055}}
+                ,"tex":{"show":[0], "dur":1200, "delay":0.3 }, "备注":"轮廓线"
+            }, 
+            { 
+                "tex":{"hide":[0], "dur":1200},
+                "structure":{"state":1, "dur":4000, "dir":[0,1,0]}  , "备注":"通透高亮分层轮廓"
+            },{ 
+                "structure":{"state":0, "dur":7000, "delay":0.4}  
+            }
+        ]
+        
+         
       },
       {
         "id": 5,
         "description": "正面居中阴线刻划神人兽面像,神人上肢和兽面下肢省略。"
+        ,"pos":{
+            "x": -4.7241792698715805,
+            "y": 0.014058153880719577,
+            "z": 0.44782054613479255
+        },
+        "normal":{
+            "x": 0.04376137159851507,
+            "y": -0.023124680150885535,
+            "z": -0.9987743446463451
+        },
+        "action":[
+            {
+                "view":{"dur":1000, "position":{"x":-19.223484367951656,"y":394.1640911638143,"z":20.256669370853146},"target":{"x":-4.813451550776005,"y":-3.6378675309149457,"z":-1.6918808507048055}}
+                ,"tex":{"show":[1], "dur":3500, "delay":0.3, "shine":6000}  
+            }
+        ]
       },
       {
         "id": 6,
         "description": "神人兽面像两侧为鸟形象,鸟身刻纹与兽面大眼完全一致。"
+       ,"pos":{
+            "x": -12.129693646492871,
+            "y": 3.6376986772848245,
+            "z": 0.21731222161912453
+        },
+        "normal":{
+            "x": 0.017903050691639968,
+            "y": -0.05013025261852744,
+            "z": -0.9985822142159027
+        },
+        "action":[
+            {
+                "view":{"dur":1000, "position":{"x":-19.223484367951656,"y":394.1640911638143,"z":20.256669370853146},"target":{"x":-4.813451550776005,"y":-3.6378675309149457,"z":-1.6918808507048055}}
+                ,"tex":{"show":[2], "dur":3500, "delay":0.3}  
+            }
+        ]
       },
       {
         "id": 7,
+        "type": "animate",
         "description": "器物底边近榫头部位阴线刻划由螺旋线和小尖喙组合而成的纹样,椭圆形镂孔被移至神人兽面像下方。"
+        ,"pos":{ 
+            "x": -8.19546918674551,
+            "y": -8.506136575687606,
+            "z": 0.9280186467049883
+        },
+        "normal":{
+            "x": -0.5612790292806588,
+            "y": -0.8267091018852216,
+            "z": -0.038960392065421234
+        }  
+         
       }
-    ]
+    ],
+    "model":{
+        "file":"lz048", 
+        "transform": {
+            "rotation": [1.5707963267948966, 0, 0],
+            "position": [0, 0, 0]
+        },
+        "view":{"position":{"x":-24.928156219531626,"y":551.6467051911975,"z":28.945704171847563},"target":{"x":-4.8134515507760085,"y":-3.6378675309149457,"z":-1.6918808507048055}},
+        "texNum":3 
+    }
   },
   {
     "id": 3,
@@ -78,7 +221,7 @@
         "material": "玉",
         "location": "1987年瑶山遗址发掘 M7:26",
         "level": "一级文物",
-        "size": "高4.8、宽8.5、厚0.8"
+        "size": "高4.8、宽8.5、厚0.8" 
       },
       {
         "material": "玉",
@@ -91,16 +234,82 @@
       {
         "id": 8,
         "description": "三叉形器正、背面均平整,正面阴线刻划神人兽面像,神人分布于左、右两叉,中叉部位刻划兽面纹,最上方为五组冠帽内羽状纹,可与两叉神人冠帽羽状线拼合。"
+        ,"pos":{
+            "x": 8.78718837258275,
+            "y": 3.982262348281098,
+            "z": -4.29577445909655
+        },
+        "normal":{
+            "x": 0.021974119411351835,
+            "y": 0.06858178293634976,
+            "z": -0.9974034675723592
+        },
+        "follow":"Group31490",
+        "action":[
+            {
+                "view":{ "dur":1000, "position":{"x":19.25028124930443,"y":328.0313197426815,"z":6},"target":{"x":0,"y":0,"z":6}}
+                ,"tex":{"show":[0]}
+            }  
+        ]
       },
       {
         "id": 9,
         "description": "神人一分为二分别位于左、右两叉,倒梯形脸和冠冕可以拼合。"
+        ,"pos":{
+            "x": -41.139349273127756,
+            "y": 22.52139907923668,
+            "z": -3.436244211343763
+        },
+        "normal":{
+            "x": -0.02637369225205579,
+            "y": 0.05081855564751736,
+            "z": -0.9983596059330994
+        },
+        "follow":"Group31490",
+        "action":[
+            {
+                "view":{ "dur":1200, "position":{"x": 19.25028124930443,"y":328.0313197426815,"z":10},"target":{"x":0,"y":0,"z":10}}
+                ,"tex":{"show":[1]}
+            }  
+        ]
       },
       {
         "id": 10,
         "description": "M7:25长玉管位于M7:26三叉形器中叉上方,两者配伍关系清晰可见。"
-      }
-    ]
+         ,"pos":{
+            "x": -5.662604131743555,
+            "y": 65.87916644906076,
+            "z": -2.9428345811860486
+        },
+        "normal":{
+            "x": -0.8640284222340182,
+            "y": -0.012510554729108891,
+            "z": -0.5032875635182761
+        },
+        "follow":"Group33026",
+        "action":[
+            {
+                "combineModel": {"dur":1000},
+                "view":{ "dur":1000, "position":{"x":-3.0859169996269227,"y":498.6574278516968,"z":15},"target":{"x":0,"y":5.684341886080802e-14,"z":15}}
+            }
+            
+        ]
+    }
+    ],
+    "model":{
+        "file":"lz015-016",
+        "view":{"position":{"x":-3.0859169996269227,"y":498.6574278516968,"z":26.865927514200735},"target":{"x":0,"y":5.684341886080802e-14,"z":27.929000000000016}},
+        "transform":{
+            "rotation" : [1.5707963267948966, 0, 0],
+            "position" : [0, 0, 0]
+        },
+        "splitParts":{ 
+            "Group33026": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 40.762538549544836, 0, 1]
+        },
+        "texParts":{"Group31490":[0,1]},
+        "texNum": 2,
+        "bright": 5
+    }
   },
   {
     "id": 4,
@@ -169,17 +378,64 @@
       {
         "type": "animate",
         "id": 15,
-        "description": "玉璜为半璧形,两侧对钻有小挂孔。"
+        "description": "玉璜为半璧形,两侧对钻有小挂孔。",
+        "pos":{
+            "x": 36.93542620269393,
+            "y": 14.452569130289406,
+            "z": 4.176318416321238
+        },
+        "normal":{
+            "x": -0.002821012261721027,
+            "y": 0.0025915436758300325,
+            "z": 0.9999926628686812
+        }
       },
       {
         "id": 16,
-        "description": "正面弧凸,中间和两侧分别减地浅浮雕雕琢兽面纹和鸟形象。"
+        "description": "正面弧凸,中间和两侧分别减地浅浮雕雕琢兽面纹和鸟形象。",
+        "pos":{
+            "x": 7.7448180280847145,
+            "y": -4.854258162897523,
+            "z": 4.62191437512172
+        },
+        "normal":{
+            "x": 0.013102510707256263,
+            "y": -0.007405991296326242,
+            "z": 0.9998867313381475
+        },
+        "action":[
+            {"tex":{"show":[0] } }
+        ]
       },
       {
         "id": 17,
         "description": "兽面纹椭圆形眼、介字形冠凸起、曲尺形鼻子;鸟形象为鸟头朝外、单圈管钻小眼、尖喙细长颈。"
+        ,"pos":{
+            "x": -53.38825889139656,
+            "y": 4.674382981445153,
+            "z": 3.940531663738397
+        },
+        "normal":{
+            "x": -0.06040220344025735,
+            "y": -0.03270081115190193,
+            "z": 0.9976383266342412
+        },
+        "action":[
+            {"tex":{"show":[1] } }
+        ]
+      
       }
-    ]
+    ],
+    "model":{
+        "file": "lz081",
+        "transform": {
+            "rotation": [1.5707963267948966, 0, 0],
+            "position": [0, 0, 0]
+        },
+        "texNum":2,
+        "bright":4
+    }
+    
   },
   {
     "id": 6,
@@ -256,22 +512,22 @@
             }
           },
           {
-            "tex": [3]
+            "tex":{"show":[3]}
           },
           {
-            "tex": [5]
+            "tex":{"show":[5]}
           },
           {
-            "tex": [4]
+            "tex":{"show":[4]}
           },
           {
-            "tex": [0]
+            "tex":{"show":[0]}
           },
           {
-            "tex": [1]
+            "tex":{"show":[1]}
           },
           {
-            "tex": [2]
+            "tex":{"show":[2]}
           }
         ]
       }
@@ -342,16 +598,21 @@
             "x": -0.02001136216362797,
             "y": 0.0033439391438464527,
             "z": 0.9997941605427383
-        }
-      
+        },
+        "action":[
+            { 
+                "structure":{"state":1, "dur":1200, "dir":[0,1,0], "rimPower":0.36 }  , "备注":"展示整体和中央孔"
+            },{ 
+                "structure":{"state":0, "dur":2200, "delay":0.1}   , "备注":"缺少纹样"
+            }
+        ]
       } 
     ],
     "model":{
         "file":"lz076",
-        "view":  {"position":{"x":-23.787889645571425,"y":-520.9109493180772,"z":61.62872366823703},"target":{"x":-1.9245945531984816,"y":0.02714036927829966,"z":0.9794479420044979}},  
+        "view":  {"position":{"x":-25.241058726497503,"y":-555.5356992385246,"z":64.53295709760242},"target":{"x":-1.9245945531984638,"y":0.02714036927829966,"z":0.979447942004434}},  
         "transform":{
-            "rotation" : [1.5707963267948966, 0, 3.141592653589793],
-            "position" : [0, 0, 0]
+            "rotation" : [1.5707963267948966, 0, 3.141592653589793]
         },
         "texNum":0,
         "bright": 4
@@ -430,16 +691,20 @@
             "x": 0.4130365985375614,
             "y": -0.9092593150727389,
             "z": 0.0514613080087819
-        }
-      
+        },
+        "action":[
+            { 
+                "structure":{"state":1, "dur":1000, "dir":[0,0,1], "rimPower":10, "opacity":0.05 }  , "备注":"分段高亮"
+            },{ 
+                "structure":{"state":0, "dur":1300, "delay":0}   , "备注":"缺少纹样"
+            }
+        ]
       }
     ],
     "model":{
         "file":"lz063",
         "view":  {"position":{"x":-427.55457070867675,"y":-408.9037957630794,"z":112.7020641920662},"target":{"x":-0.5,"y":0.5000000000000568,"z":-0.7071067811865532}},
         "transform":{
-            "rotation" : [0, 0, 0],
-            "position" : [0, 0, 0]
         },
         "texNum":0
     } 

+ 7 - 4
src/modelViewer/InputHandler.js

@@ -777,8 +777,7 @@ export class InputHandler extends THREE.EventDispatcher {
                     }
                 ))
                 if(!refused)dragConsumed = true
-                viewer.dispatchEvent('content_changed') 
-                
+                 
             }  
              
             if(!dragConsumed){        
@@ -829,7 +828,7 @@ export class InputHandler extends THREE.EventDispatcher {
                     }
                     
                     this.lastMouseoverElement = curr
-                    viewer.dispatchEvent('content_changed') 
+                     
                 }
                 hoveredElements.forEach(ele=>{ //2026.4 add 必须写在mousemove前
                     if(ele.object._listeners?.mousemove_mul){
@@ -970,6 +969,10 @@ export class InputHandler extends THREE.EventDispatcher {
 
 		if (index === -1) {
 			this.selection.push(object);
+            if (!object.boundingBox) {
+                object.boundingBox = new THREE.Box3() //任意大小 只是为了显示黄色外框 
+            }
+            
 			object.dispatchEvent({
 				type: 'select'
 			});
@@ -1130,7 +1133,7 @@ export class InputHandler extends THREE.EventDispatcher {
             }while(object)
        
    
-            if(settings.isTest){
+            if(settings.addTag){
                 
                 if(e?.normal){ // 原先的normal不知为何不对,当有子集时
                     let localNormal = e.face && e.face.normal 

+ 131 - 28
src/modelViewer/ModelManager.js

@@ -553,21 +553,58 @@ export default class ModelManager extends THREE.EventDispatcher{
         
     } */
      
-    changeMaterial(model, texNum){
+    initMaterial(model, data ){
         
         const mapNames = ['map','aoMap', 'normalMap','metalnessMap','roughnessMap']
-        let texs = [], emissiveText =  ''
+        let texs = []  
+        let materials = [], splitParts = [], allTexIndex = []
+         
+        if(data.texNum){
+            let i = -1
+            while(++i < data.texNum){//全部
+                allTexIndex.push(i) 
+            }  
+        } 
+        model.structureOpacity = data.structureOpacity || 1
+        model.traverse(e=>{
+            //console.log('traverseName', e.name)
+            if(e.material){ 
+                materials.push(e.material)
+                e.material.initialOpacity = e.material.opacity
+                if(data.texParts){//有多个材质(很可能就是拆分组合的)每个材质有各自的高亮贴图
+                    e.material.emiss_texs = data.texParts[e.name] || []     //该材质能贴的所有高亮贴图的索引
+                }else{
+                    e.material.emiss_texs = allTexIndex
+                } 
+            }
+            if(data.splitParts){
+                if(e.name in data.splitParts){
+                    let matrix = data.splitParts[e.name]
+                    if(matrix){  
+                        e.initialMatrix = e.matrix.clone()
+                        e.combineMatrix = new THREE.Matrix4().fromArray(matrix)
+                        //e.combileMatrix.decompose(e.position, e.quaternion, e.scale) 
+                        splitParts.push(e)
+                    }else if(settings.moveMesh){ 
+                        e.addEventListener('select',()=>{}) //调试,为了得到位置 
+                    }
+                }
+                
+            }
+        })
+        model.materials_ = materials 
+        model.splitParts = splitParts   //拆分组合的部分
+        model.allTexIndex = allTexIndex
         
         
-        if(texNum>0){ 
+        if(data.texNum>0){ 
             let i = -1
-            while(++i<texNum){ 
+            while(++i<data.texNum){ 
                 let url = Common.joinUrl(model.props.prefixUrl, i+'.png')
                 let tex = texLoader.load(url)
                 texs.push(tex)
                 tex.premultiplyAlpha = true  //因透明部分不一定是黑色导致颜色不对,因emissive不读a通道所以预乘下 emissivemap_fragment
                 //tex.needsUpdate = true
-                emissiveText += 'if(emiIntensitys['+i+'] > 0.0){ gl_FragColor += texture2D(emiMaps['+i+'], vUv) * emiIntensitys['+i+'];} \n'
             }
             
         }
@@ -581,31 +618,73 @@ export default class ModelManager extends THREE.EventDispatcher{
             material.aoMapIntensity = 1 //调高了有的又太黑
             material.normalScale?.set(1.5,1.5)
             
-            
-            if(texNum>0){ 
-                material.defines.emissiveCount = texNum 
+            let emissiveText =  ''  
+            if(material.emiss_texs.length > 0){ 
+                material.defines.emissiveCount = material.emiss_texs.length
                 material.defines.USE_UV = 1
+                material.emiss_texs.forEach(i=>{
+                    emissiveText += 'if(emiIntensitys['+i+'] > 0.0){ gl_FragColor += texture2D(emiMaps['+i+'], vUv) * emiIntensitys['+i+'];} \n'
+                })
             }
-            
+            let firstCompile = true 
             
             material.onBeforeCompile = function ( shader ) {
-                //console.log(shader.vertexShader)
-                //console.log(shader.fragmentShader)
+                // console.log(shader.vertexShader)
+               // console.log(shader.fragmentShader)
                //console.log(shader.uniforms)
-                if(texNum>0){
-                    material.emiIntensitys = new Array(texNum).fill(0)
-                    shader.uniforms.emiMaps = {value: texs} 
-                    shader.uniforms.emiIntensitys = {value: material.emiIntensitys }
-                }
-                 
+                if(!material.uniforms_){
+                    //修改define后 needsUpdate后 shader又被还原 会重新执行,所以要把旧的存起来直接引用
+                    material.uniforms_ = {
+                        fresnelRatio : {value:0},
+                        fresnelDir : {value: new THREE.Vector3(0,1,0)},
+                        rimPower: {value:1}, //值越高对比度越高
+                    }  
+                    if(material.emiss_texs.length >0){ 
+                        let texs_ = texs.map((t,i)=> material.emiss_texs.includes(i) ? t : null) //没有的贴图为null
+                        material.uniforms_.emiMaps = {value: texs_} 
+                        material.uniforms_.emiIntensitys = {value: new Array(data.texNum).fill(0) }//不同材质的数组长度都是总数,但只使用各自的部分
+                    }
+                } 
+                
                 
+                Object.assign(shader.uniforms, material.uniforms_)
                 
+                
+                
+                
+                material.uniforms_ = shader.uniforms
+                
+                //原本的vNormal是视图空间下的
+                shader.vertexShader = shader.vertexShader.replace(
+                    '#include <common>',
+                    `
+                    #include <common>
+                    varying vec3 vWorldNormal;
+                    `
+                );
+    
+                
+                 shader.vertexShader = shader.vertexShader.replace(
+                    '#include <beginnormal_vertex>',
+                    `
+                    #include <beginnormal_vertex>
+                    // 保存模型空间法线 
+                    //vObjectNormal =  * normalize(normal);
+                    
+                    vWorldNormal = mat3(modelMatrix) * normal ;
+                    `
+                );
+              
                 shader.fragmentShader = shader.fragmentShader.replace( '#define STANDARD',
-                `   #ifdef emissiveCount
+                `   #define STANDARD 
+                    #ifdef emissiveCount
                         uniform sampler2D emiMaps[emissiveCount];
                         uniform float emiIntensitys[emissiveCount];
                     #endif
-                     
+                    varying vec3 vWorldNormal;
+                    uniform float fresnelRatio;
+                    uniform vec3 fresnelDir;
+                    uniform float rimPower;                    
                 `)
                 
                 
@@ -615,11 +694,25 @@ export default class ModelManager extends THREE.EventDispatcher{
                     #ifdef Fresnel
                          // ===== 1. 标准漫反射光照(保留玉的立体感) =====
                         
-                        float rimPower = 6.; 
-                        vec3 viewDirN = normalize(vViewPosition);
-                        //normal is from normal_fragment_maps, 算上贴图后的法线
-                         // ===== 3. 菲涅尔边缘光 =====
-                        float fresnel = 1.0 - max(dot(normal, viewDirN), 0.0);
+                       
+                        // ===== 3. 菲涅尔边缘光 =====
+                         
+                        //朝这个方向看是通透的
+                        
+                        vec3 worldNormal = normalize(vWorldNormal);
+                        float fresnel = 1.0 - max(abs(dot(worldNormal, fresnelDir)), 0.0 );
+                        
+                        /* vec3 myDir1 = vec3(0.0,0.0,1.0); 
+                        vec3 myDir2 = vec3(1.0,0.0,0.0);
+                        vec3 myDir3 = vec3(0.0,1.0,0.0);
+                        
+                        float fresnel1 = 1.0 - max(abs(dot(worldNormal, myDir1)), 0.0 );
+                        float fresnel2 = 1.0 - max(abs(dot(worldNormal, myDir2)), 0.0 );
+                        float fresnel3 = 1.0 - max(abs(dot(worldNormal, myDir3)), 0.0 );
+                        float fresnel = fresnel1 * fresnel2 * fresnel3; */
+                        
+                         
+                        
                         // 高次幂压缩:值越大,边缘光带越窄、越锐利
                         float rim = pow(fresnel, rimPower);
                         // 可选:阈值截断,去除中心微弱杂光,让轮廓更干净
@@ -628,10 +721,17 @@ export default class ModelManager extends THREE.EventDispatcher{
 
                     
                         // ===== 4. 合成最终颜色 =====
+                        //float c = gl_FragColor.r + gl_FragColor.g + gl_FragColor.b;
+                        //c /= 3.0;
+                        //gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(c), 0.2);
+                        
+                        
                         
-                        gl_FragColor.a *= (0.4 + rim * 10.);//rim;
-                        //gl_FragColor.rgb += vec3(1.,1.,1.) * rim;
-                        //gl_FragColor.rgb = mix( gl_FragColor.rgb, diffuse, 0.4);
+                        vec4 fresnelCol = gl_FragColor;
+                        fresnelCol.a *= (0.03 + rim * 10.);//rim;
+                        fresnelCol.rgb += vec3(1.,1.,1.) * rim * 0.7  ;
+                        gl_FragColor = mix(gl_FragColor, fresnelCol,  fresnelRatio );
+                         
                     #endif  
 
                     #ifdef emissiveCount
@@ -641,8 +741,11 @@ export default class ModelManager extends THREE.EventDispatcher{
                     ` + '\n}'
                 );  
                 
-                
+                //console.log(shader.vertexShader)
+                //console.log(shader.fragmentShader)
                 //console.log(shader.uniforms)
+                
+                
             }  
         })
     }

+ 39 - 12
src/modelViewer/View.js

@@ -264,7 +264,7 @@ export class View extends THREE.EventDispatcher{//base
         let done = ()=>{ //一定要旋转和位移都结束了才能执行
             
             let f = ()=>{ 
-                this.position.copy(endPosition)  //因为延时 后control的update会导致位置改变
+                endPosition && this.position.copy(endPosition)  //因为延时 后control的update会导致位置改变
                 info.callback && info.callback()   
                 this.dispatchEvent('flyingDone')  
             }
@@ -276,13 +276,13 @@ export class View extends THREE.EventDispatcher{//base
             
         }
         
-        let endPosition = new THREE.Vector3().copy(info.position)
+        let endPosition = info.position && new THREE.Vector3().copy(info.position)
         let startPosition = this.position.clone();
 		let startQuaternion, endQuaternion, endTarget = info.target && new THREE.Vector3().copy(info.target)  ,  
             endYaw, startYaw, endPitch, startPitch, endRadius ;
         
         
-        this.restrictPos(endPosition)
+        endPosition && this.restrictPos(endPosition)
          
          
          
@@ -290,7 +290,7 @@ export class View extends THREE.EventDispatcher{//base
         if(info.endYaw == void 0){
             if(info.target ){ 
                 //endQuaternion = math.getQuaFromPosAim(endPosition,endTarget) //若为垂直,会自动偏向x负的方向
-                endRadius = endPosition.distanceTo(info.target)
+                //endRadius = endPosition.distanceTo(info.target)
                 
                 
             }else if(info.quaternion){
@@ -329,7 +329,7 @@ export class View extends THREE.EventDispatcher{//base
         }
         
 		if(!info.duration){
-			this.position.copy(endPosition);
+			endPosition && this.position.copy(endPosition);
             this.restrictPos()
 			posWaitDone = true, rotWaitDone = true 
             info.onUpdate && info.onUpdate(1)
@@ -340,13 +340,14 @@ export class View extends THREE.EventDispatcher{//base
             let ease = info.Easing ? easing[info.Easing] : easing.easeInOutSine
             if(endRadius && this.radius !== endRadius){
                 transitions.start(lerp.property(this, "radius", endRadius), info.duration, null, 0, ease, null, this.FlyTransition, null, info.ignoreFirstFrame )
-                    
+                  
             }
-            let posChange = !this.position.equals(endPosition)
+            
+            let posChange = endPosition && !this.position.equals(endPosition)
             if(posChange){
                 posWaitDone = true 
                 transitions.start(lerp.vector(this.position, endPosition, (pos, progress, delta)=>{
-                    
+                    //console.log(this.position, this.getPivot() )
                     info.onUpdate && info.onUpdate(progress, delta)  
                     
                 }), info.duration, posDone , 0, ease ,null, this.FlyTransition, ()=>{
@@ -367,9 +368,14 @@ export class View extends THREE.EventDispatcher{//base
             
             if(endQuaternion || endYaw != void 0 || info.target ){
                 rotWaitDone = true 
+                let startTarget = this.getPivot(),  target = new THREE.Vector3, endTarget = info.target && new THREE.Vector3().copy(info.target)
                 transitions.start( (progress, delta )=>{
-                    if(info.target ){//加
-                        this.lookAt(info.target)
+                    if(info.target){//加
+                        target.copy(startTarget)
+                        lerp.vector(target, endTarget)(progress) 
+                    }
+                    if(info.target && endPosition){
+                        this.lookAt(target)
                     }else if(endYaw != void 0){
                         this.yaw = startYaw * (1-progress) + endYaw * progress
                         this.pitch = startPitch * (1-progress) + endPitch * progress
@@ -380,6 +386,10 @@ export class View extends THREE.EventDispatcher{//base
                         this.quaternion = quaternion
                         //console.log(quaternion,this.yaw)
                     }
+                    if(info.target && !endPosition){//只给定target和方向,自己计算position,适用于orbit顶部底部角度
+                        this.position.copy(target).addScaledVector(this.direction, -this.radius)
+                    }
+                    
                     posChange || info.onUpdate && info.onUpdate(progress, delta)  
                     
                 }, info.duration, rotDone , 0, ease ,null, this.LookTransition, ()=>{
@@ -535,11 +545,28 @@ export class View extends THREE.EventDispatcher{//base
         }
         return json  //JSON.stringify(json)
     } 
+    getJson2(){//适用于orbit顶部底部角度
+        let json = {
+            yaw: this.yaw,  pitch: this.pitch, 
+            radius: this.radius,
+            target: this.getPivot(),
+            endPos: this.position.clone()  //不使用,但需要用这个计算距离
+        }
+        return json 
+    }
     
     applyJson(json){
         typeof json == 'string' &&  (json = JSON.parse(json))
-        this.position.copy(json.position)
-        this.lookAt(json.target)
+        
+        this.position.copy(json.position || json.endPos)
+        if(json.yaw != void 0){//角度更准
+            json.yaw != void 0 && (this.yaw = json.yaw)
+            json.pitch != void 0 && (this.pitch = json.pitch)
+        }else{
+            this.lookAt(json.target) 
+        }
+        
+        
         //this.yaw = json.yaw,  this.pitch = json.pitch, this.radius = json.radius ?? this.radius
     }
     

+ 231 - 39
src/modelViewer/Viewer.js

@@ -19,6 +19,10 @@ import Sprite from './objects/Sprite.js'
 let texLoader = new THREE.TextureLoader()
 
 const raycaster = new THREE.Raycaster() 
+const planeGeo = new THREE.PlaneGeometry(1,1)
+  
+
+
 
 export class Viewer extends ViewerBase {
     constructor( args = {}) {
@@ -58,7 +62,7 @@ export class Viewer extends ViewerBase {
         })
         this.viewports.push(this.mainViewport)
         
-        /* let plane = new THREE.Mesh(new THREE.PlaneGeometry(10,10), new THREE.MeshBasicMaterial({transparent:false,depthTest:false,color:"#F00"}))
+        /* let plane = new THREE.Mesh(  planeGeo, new THREE.MeshBasicMaterial({transparent:false,depthTest:false,color:"#F00"}))
         this.scene.add(plane)    
         plane.renderOrder = 1 */
         
@@ -87,7 +91,7 @@ export class Viewer extends ViewerBase {
                 try {
                     if (e.event.ctrlKey) {
                         if (e.event.key.toLowerCase() == 'c') {
-                            let info = this.mainViewport.view.getJson() 
+                            let info = this.mainViewport.view.getJson2() 
                              
                             info = JSON.stringify(info)
                             ;(console.log(`Copy view params: ${info}`),
@@ -123,7 +127,7 @@ export class Viewer extends ViewerBase {
 		light3.lookAt( new THREE.Vector3(0, 0, 0));
         lights.add(light3) 
         
-        if(1){ 
+        if(settings.showLights){ 
             let g = new THREE.SphereGeometry(1, 4, bright),
                 m = new THREE.MeshBasicMaterial({color:'#f02'})
              
@@ -598,56 +602,107 @@ export class Viewer extends ViewerBase {
         }) 
     } */
     
-    closeTag(dur){
-        let allIndex = [], i = -1
-        while(++i < this.modelData.model.texNum){
-            allIndex.push(i)
-        } 
-        this.highlightTex(allIndex, 0, dur )  //hide emissive tex
+    closeTag(duration, defaultTarget, split){
+          
+        this.highlightTex('all', 0, duration )  //hide emissive tex
         this.mainViewport.view.cancelFlying('all', false)  //stop action
+        
+        if(defaultTarget){
+            let target = this.defaultView.target
+            let vec = new THREE.Vector3().subVectors(target, this.mainViewport.view.getPivot())
+            let position = this.mainViewport.view.position.clone().add(vec)
+            this.mainViewport.view.setView({  
+                target ,
+                position,
+                duration: 1000 
+            }) 
+        }
+        
+        if(split){
+            this.changeCombineState(true)
+        }
+        
+        this.texPlaneState && this.showPlane(0)
+        this.structureState && this.showStructure(0)
+        
     }
     
-    highlightTex(indexes, toPercent=0.9, duration=2000, done){
+    highlightTex(indexes='all', toPercent=0.8, duration=2000, done, delay=0, loopDur){
          
         let model = viewer.objs.children[0]
         if(!model)return console.error('模型未加载完')
         //const duration = 2000, minAlpha = 0, maxAlpha = 0.8
         transitions.cancelById('highlight')
          
-        let startPercents = indexes.map(index=>model.materials_[0].emiIntensitys[index])
+        if(indexes == 'all') indexes = model.allTexIndex 
+         
+        let startPercents = indexes.map(index => 
+            model.materials_.find(mat=>mat.emiss_texs.includes(index)).uniforms_.emiIntensitys.value[index]  
+        )
         
+        let realDur = duration * (1-delay)
+            duration = loopDur ? -duration : duration 
+        let loopCount = 0
         
         transitions.start((percent)=>{
              
-            let currents = indexes.map((index, i)=>math.linearClamp(percent,[0,1], [startPercents[i], toPercent] ) )
-            
+            let currents 
             
-            model.materials_.forEach(mat=>{
+            if(!loopCount){
+                currents = indexes.map((index, i)=>math.linearClamp(percent,[0,1], [startPercents[i], toPercent] ) )
+            }else{
+                //快-慢-快
+                percent = percent * 2
+                percent > 1 && (percent = 2 - percent )
+                percent = easing.easeOutCubic(percent, 0, 1, 1)   
+                let opacity = math.linearClamp(percent,[0,1],[toPercent, 0])
+                currents = indexes.map(index=>opacity)
+                //currents = indexes.map((index, i)=>math.linearClamp(percent,[0, 0.2, 0.5, 0.8,1], [toPercent, 0.2, 0, 0.2,toPercent] ) )
                 
-                model.materials_.forEach(mat=>{ 
-                    indexes.forEach((index,i)=>{
-                         
-                        mat.emiIntensitys[index] = currents[i]
-                    }) 
-                })
+            }
+             
+            model.materials_.forEach(mat=>{ 
+                indexes.forEach((index,i)=>{
+                    if(mat.emiss_texs.includes(index)){
+                          mat.uniforms_.emiIntensitys.value[index] = currents[i]
+                    } 
+                }) 
+            })
             
-            })  
+              
             viewer.dispatchEvent('content_changed') 
         },  duration, ()=>{
-            done && done()
-        }, 0, easing.easeInQuad, null,'highlight'  )  
+          
+            loopCount ++;
+            
+            if(loopCount == 1){ 
+                if(loopDur){
+                    transitions.adjustSpeed("highlight", realDur / loopDur)  
+                    transitions.getById("highlight")[0].easing = easing.linearTween 
+                }
+                done && done()
+            }
+            
+            
+        }, delay, easing.easeInQuad, null,'highlight'  )  
      
     }
     
     dealTagAction(id){
-        if(id !== this.lastOpenTag) this.closeTag(0)
          
-        this.lastOpenTag = id
-        
         this.mainViewport.view.cancelFlying('all', false)
         
         let hotspot = this.modelData.hotspots.find(e=>e.id == id)
         let targetStart = this.mainViewport.view.getPivot() 
+        if(!hotspot)return
+        
+        
+        if(hotspot !== this.lastOpenTag){
+            let defaultTarget = true   //如果等下没指定view,需要恢复target
+            let split = this.combineState && !hotspot.action?.find(e=>e.combineModel)
+            this.closeTag(0, defaultTarget, split, )
+        }
+        this.lastOpenTag = hotspot
         
         if(hotspot.action){
             let actionIndex = 0
@@ -670,23 +725,46 @@ export class Viewer extends ViewerBase {
                 if(action.view){ 
                     
                     taskCount++
-                    
-                    let dis = this.mainViewport.view.position.distanceTo( action.view.position )
+                    let endPos = action.view.position || action.view.endPos
+                    let dis = this.mainViewport.view.position.distanceTo( endPos )
+                    let duration = action.view.duration || math.linearClamp(dis,[0, 4, 400], [0, 500, 1500])
+                    console.log('duration', duration);
                     this.mainViewport.view.setView({
-                        //endYaw:  action.view.yaw,
-                        //endPitch: action.view.pitch,
+                        endYaw:  action.view.yaw,
+                        endPitch: action.view.pitch,
                         position: action.view.position,
-                        //endRadius: action.view.radius,
+                        endRadius: action.view.radius,
                         target: action.view.target,
-                        duration: math.linearClamp(dis,[0, 200], [0,1200]),
+                        duration,
                         callback,
-                        cancelFun: callback             
+                        cancelFun: callback
                     }) 
                 } 
                 
                 if(action.tex){
                     taskCount++
-                    this.highlightTex(action.tex, 0.9, 2000, callback )  
+                    let t = action.tex
+                    t.show && this.highlightTex(t.show, 0.9, t.dur || 2000, callback,  t.delay, t.shine)  
+                    t.hide && this.highlightTex(t.hide, 0,   t.dur || 1200, callback,  t.delay, t.shine )  
+                }
+                
+                if(action.combineModel){
+                    taskCount++
+                    this.changeCombineState(false, action.combineModel.dur || 1000, callback)
+                    
+                } 
+                
+                if(action.structure){
+                    taskCount++
+                    let s = action.structure
+                    this.showStructure(s.state, s.dir, s.rimPower, s.opacity, s.dur, callback, s.delay, s.lowOpacity)
+                }
+                
+                if(action.planeTex){
+                    taskCount++
+                    let t = action.planeTex
+                    this.showPlane(t.state, t.file, t.matrix, t.dur, t.delay, callback)
+                    
                 }
             }
             
@@ -696,22 +774,49 @@ export class Viewer extends ViewerBase {
     }
     
     
+    changeCombineState(split, duration=1000, done){ //默认是拆分的
+        let model = this.objs.children[0]
+         
+        transitions.cancelById('splitModel') 
+          
+        let startMatrixs = model.splitParts.map(e=>e.matrix.clone())
+        let endMatrixs =  model.splitParts.map(e=> split ? e.initialMatrix.clone() : e.combineMatrix.clone())   
+        let matrix = new THREE.Matrix4
+        this.combineState = !split
+        
+        transitions.start((percent)=>{
+             
+            model.splitParts.forEach((object, i)=>{
+            
+                matrix.copy(startMatrixs[i])
+                lerp.matrix4(matrix, endMatrixs[i])(percent) 
+                matrix.decompose(object.position, object.quaternion, object.scale) 
+                object.dispatchEvent('moved')
+            })
+                     
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+            done && done()
+        }, 0, easing.easeInOutSine, null,'splitModel'  ) 
+    }
+
     
-    showStructure(state){
+    showStructure0(state, fresnelDir = [0,1,0]){
         let meterials = viewer.objs.children[0].materials_
         if(state){
             meterials.forEach(mat=>{
-                mat.defines.Fresnel = 1 
+                mat.defines.Fresnel = 1
+                mat.uniforms.fresnelDir.value.fromArray(fresnelDir)  
                 mat.transparent = true
-                //mat.side = 2
-                mat.needsUpdate = true 
+                mat.side = 2
+                mat.needsUpdate = true  //会触发onBeforeCompile
             })
             
         }else{
             meterials.forEach(mat=>{
                 delete mat.defines.Fresnel
                 mat.transparent = false
-                //mat.side = 0
+                mat.side = 0
                 mat.needsUpdate = true 
             })  
         } 
@@ -720,7 +825,94 @@ export class Viewer extends ViewerBase {
     }
     
     
+    showStructure(state, fresnelDir = [0,1,0], rimPower=1, opacity=1, duration=500, done, delay=0  ){
+        
+        //if(this.structureState == state)return //直接return不好,要执行done,但要等结束才能执行
+        this.structureState = state
+        transitions.cancelById('modelStructure')
+        let model = viewer.objs.children[0]
+        let meterials = model.materials_
+        let startFreRatios = meterials.map(e=>e.uniforms_.fresnelRatio.value)
+        let startOpacitys = meterials.map(e=>e.opacity / model.structureOpacity)  
+        let toFreRatio = state ? 1 : 0
+        let init
+        
+        transitions.start((percent)=>{ 
+            meterials.forEach((mat,i)=>{ 
+                if(!init && percent >0 && state){ 
+                    Common.addOrRemoveDefine(mat, "Fresnel", "add")  //state ? "add" : "remove" 
+                    mat.uniforms_.fresnelDir.value.set(...fresnelDir );  
+                    mat.uniforms_.rimPower.value = rimPower 
+                    mat.transparent = true
+                    //mat.opacity = model.structureOpacity || 1
+                    mat.depthWrite = false //减轻环璧条纹, 透明度越低越不明显 用structureOpacity调节透明度
+                    mat.side = 2
+                    init = true   
+                }
+                 
+                //mat.uniforms_.fresnelRatio.value = state ? percent : 1-percent
+                //mat.uniforms_.fresnelParam.value[3] = state ? percent : 1-percent
+                mat.uniforms_.fresnelRatio.value = math.linearClamp(percent,[0,1], [startFreRatios[i], toFreRatio] )  
+                mat.opacity = model.structureOpacity * math.linearClamp(percent,state?[0,0.1]:[0.7,1], [startOpacitys[i], opacity * mat.initialOpacity] )   //这得大部分时间很小则过曝
+                //console.log('opa',mat.opacity)
+            })
+                  
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+            if(!state){
+                meterials.forEach((mat,i)=>{
+                    Common.addOrRemoveDefine(mat, "Fresnel", "remove")
+                    mat.side = 0
+                    mat.needsUpdate = true 
+                    mat.depthWrite = true  
+                    mat.transparent = false //
+                }) 
+            }
+            done && done()
+        }, delay, easing.easeInOutSine, null,'modelStructure'  ) 
+        
+         
+        
+    }
+     
+    showPlane(state=1, file, matrix, duration=1000, delay=0, done){
+        let model = viewer.objs.children[0]
+        console.log('showPlane', state)   
+        transitions.cancelById('texPlane')
+        this.texPlaneState = state
+        if(!model.texPlane){
+            let url = Common.joinUrl(model.props.prefixUrl,  file)
+            let plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({transparent:true, map: texLoader.load(url)}))
+            model.texPlane = plane
+            plane.opacity = 0
+            matrix = new THREE.Matrix4().fromArray(matrix)
+            matrix.decompose(plane.position, plane.quaternion, plane.scale )
+            this.scene.add(plane)
+        }
+        
+        transitions.start((percent)=>{
+             
+            model.texPlane.material.opacity = state ? percent : 1-percent
+                     
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+            done && done()
+        }, delay, easing.easeInSine, null,'texPlane'  ) 
+        
+        
+        
+    }
     
+    /* addPlane(){
+        
+        let map = texLoader.load(url)
+        let plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({transparent:true, map}))
+        window.plane = plane
+        plane.scale.set(152,152,152)
+        plane.position.set(-8,-1,31)
+        plane.rotation.z = 3.123
+        this.scene.add(plane)  
+    } */
 }
 
  

+ 18 - 25
src/modelViewer/app.js

@@ -8,7 +8,9 @@ import Tag from './objects/Tag.js'
 Object.assign(settings, {
     resourcePrefix: '/static/',    //'../../public/static/',
     pauseIntersect:true,
-    isTest: 0, //加热点时开启。开启的时候热点intersect会不太正常没关系,因为和model的深度冲突了
+    addTag: 0, //加热点时开启。开启的时候热点intersect会不太正常没关系,因为和model的深度冲突了
+    moveMesh: 0, //可以拖动在splitParts中的mesh , 和上一个不能同时打开
+    showLights: 0,
     fov:  15 , //八猴是这个值
     aniso: browser.isMobile() ? 4 : 8 //八猴是4
 })
@@ -30,7 +32,9 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
         viewer.backgroundOpacity = 0
         viewer.sdk = sdk
         this.load(id)  
-        
+        if(settings.moveMesh){
+            viewer.getTranTool()
+        }
     },
     
     
@@ -47,14 +51,7 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
         let done = (model)=>{
             //document.querySelector('.progressContainer').style.display = 'none'
             sdk.dispatchEvent({type:'modelProgress', value: 100})
-            
-            let materials = []
-            model.traverse(e=>{
-                if(e.material){ 
-                    materials.push(e.material)         
-                }
-            })
-            model.materials_ = materials
+             
             {
                 let url = model.props.url
                 let a = url.split('/') 
@@ -62,32 +59,28 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
                 let head = a.join('/')
                 model.props.prefixUrl = head  //其他资源可能用到
             }
-           
-            viewer.modelManager.changeMaterial(model, modelData.model.texNum)
-             
             
-             
-            
-             
-            
-            let defaultView = modelData.model.view || {"yaw":-12.55357706351913,"pitch":-0.04359457986378778,"position":{"x":5.183429158209428,"y":-555.5368304352811,"z":25.216363971031676},"radius":556.1378226265584}   
+            viewer.modelManager.initMaterial(model, modelData.model)
+               
+            let defaultView = modelData.model.view || {"position":{"x":-25.241058726497503,"y":-555.5356992385246,"z":64.53295709760242},"target":{"x":0,"y":0,"z":0}} 
             viewer.mainViewport.view.applyJson(defaultView)
+            viewer.defaultView = defaultView
             viewer.dispatchEvent('content_changed')
              
             model.matrixWorldInverse = model.matrixWorld.clone().invert()
             
-            if(settings.isTest){
+            if(settings.addTag && !settings.moveMesh){
                 const depth =  0.1
                 model.addEventListener('click',(e)=>{
                     let point = e.hoveredElement.point.applyMatrix4(model.matrixWorldInverse ) 
                     
                     //point.add(e.hoveredElement.normal.multiplyScalar(depth))
-                    
-                    console.log(point, e.hoveredElement.localNormal)
+                    console.log(e.hoveredElement.oriObject.name)
+                    console.log(point, e.hoveredElement.localNormal   )
                 }) 
             }
             
-            this.loadTags(modelData, model.matrixWorld)
+            this.loadTags(modelData, model  )
             
         }
         
@@ -108,7 +101,7 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
             url : `/static/models/${id}/${modelData.model.file}.glb`, 
             metalness: 1, 
             roughness: 0.95,
-            transform: modelData.model.transform 
+            transform: settings.moveMesh || modelData.model.transform //如果在移动模型,部件是子集的话移动会有问题
             //unlit:true
         }, done, onProgress_, onError)
         
@@ -117,7 +110,7 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
     
     
     
-    loadTags(data, modelMatrix){
+    loadTags(data,  model){
         
         let tags = new THREE.Object3D 
             tags.name = 'tags';
@@ -125,7 +118,7 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
         viewer.scene.add(tags);
         
         data.hotspots.forEach(e=>{
-            let tag = new Tag(e, modelMatrix, viewer.objs.children[0].scale.x)
+            let tag = new Tag(e, model)
             tags.add(tag) 
         })
         viewer.dispatchEvent('content_changed')

+ 29 - 6
src/modelViewer/objects/Tag.js

@@ -20,7 +20,7 @@ const sphereMat = new THREE.MeshBasicMaterial({color:'#f33', depthTest:false, tr
 
 export default class Tag extends THREE.Object3D { 
 
-    constructor(data, modelMatrix, scale){
+    constructor(data, model){
         super()
         
         this.sid = data.id 
@@ -32,12 +32,13 @@ export default class Tag extends THREE.Object3D {
             sizeInfo: {maxSize:65, minSize:30, nearBound: 100, farBound:800}
         })
         
-        if(data.pos){
+        let modelMatrix = model.matrixWorld
+        if(data.pos){//排除整个模型变换的坐标
             this.position.copy(data.pos).applyMatrix4(modelMatrix) 
         }
         
-        if(data.normal){//避免在两边容易被遮
-            let nor = new THREE.Vector3().copy(data.normal).multiplyScalar(depth / scale)
+        if(data.normal){//避免在两边容易被遮     //排除整个模型变换的法线
+            let nor = new THREE.Vector3().copy(data.normal).multiplyScalar(depth / model.scale.x )
             this.checkBlockPoint = new THREE.Vector3().addVectors(data.pos, nor)
             this.checkBlockPoint.applyMatrix4(modelMatrix)
             //console.log(this.sid, 'length', this.position.distanceTo(this.checkBlockPoint))
@@ -56,7 +57,29 @@ export default class Tag extends THREE.Object3D {
         }
          
        
-        
+        if(data.follow){
+            model.traverse(e=>{
+                if(e.name == data.follow){
+                    this.follow = e 
+                    return {stopContinue:true}
+                }
+            })
+            if(this.follow){
+                this.follow.initialMatrixWInv = this.follow.matrixWorld.clone().invert()
+                this.initialPose = {
+                    position: this.position.clone(),
+                    checkBlockPoint:  this.checkBlockPoint.clone()
+                }
+                let tranMatrix = new THREE.Matrix4
+                this.follow.addEventListener('moved',()=>{ //热点跟随移动。。。后来才发现要拆分,但热点没加在模型里,只好这么写了
+                    this.follow.updateMatrixWorld() //不加这句会抖动
+                    tranMatrix.multiplyMatrices( this.follow.matrixWorld,  this.follow.initialMatrixWInv)
+                    this.position.copy(this.initialPose.position).applyMatrix4(tranMatrix)
+                    this.checkBlockPoint.copy(this.initialPose.checkBlockPoint).applyMatrix4(tranMatrix)
+                    this.spot.waitUpdate()
+                })  
+            }
+        }
         
         this.add(this.spot)
         this.addEventListener('mouseover',(e)=>{
@@ -72,7 +95,7 @@ export default class Tag extends THREE.Object3D {
         
         
         
-        const delay = 400 + (time++)
+        const delay = 400 + (time++ *16)
         viewer.addEventListener('camera_changed',(e)=>{
              
             Common.intervalTool.isWaiting('tagBlocked_'+this.sid, () => { 

+ 11 - 1
src/modelViewer/utils/Common.js

@@ -320,7 +320,17 @@ var Common = {
         },
     }
     ,
-    
+    addOrRemoveDefine(material, defineName, type, value=''){ 
+        let defines = material.defines
+        if(type == 'add'){
+            if(defines[defineName] != void 0 && defines[defineName] == value)return
+            defines[defineName] = value
+        }else{
+            if(defines[defineName] == void 0)return;
+            delete defines[defineName] 
+        }
+        material.needsUpdate = true;
+    } ,
 }      
 
 

+ 1 - 1
src/modelViewer/utils/CursorDeal.js

@@ -12,7 +12,7 @@ var CursorDeal = {
         {'hoverGrab':'grab'},//通用
         {'move':'move'},//通用 
         {'pointer':'pointer'},//通用
-      
+        {'hoverTranHandle':'grab'},
      
     ], 
     list:[], //当前存在的cursor状态

+ 12 - 1
src/modelViewer/utils/transitions.js

@@ -347,6 +347,16 @@ var transitions = {
     counter: 0,
     uniqueID: 0,
     start: function(func, duration, done, delay, ease, name, id, cancelFun, ignoreFirstFrame=true) {
+        
+        if(duration == 0){
+            func(1)
+            done && done()
+            return //add
+        
+        } 
+        
+        
+        
         return delay = delay || 0,
         this.funcs.push({
             func: func,
@@ -397,13 +407,14 @@ var transitions = {
     update: function(e) {
         this.funcs.forEach(function(t) {
             if(t.updateCount++ == 0 && t.ignoreFirstFrame) return //add start可能发生在一帧中任意时刻,而每次update的是在一帧中的固定时刻,所以从start到第一次update的时间并不是所传入的delta,该delta 是上一帧的update到这一帧的update的耗时。 故去掉了第一次的update,相当于延迟一帧再update.   
-            if (!(t.paused || (t.current += 1e3 * e, t.current < 0))){
+            if (!(t.paused || (t.current += 1e3 * e, t.current < 0))){ 
                 if (t.current >= t.duration && !t.cycling) {
                     var u = t.easing(1, 0, 1, 1);
                     t.func(u, 1e3 * e),
                     t.done && t.done(),
                     t.running = !1
                 } else {
+                    if (t.current >= t.duration) t.done && t.done() //xzw add 需要识别到第一次循环结束
                     var duration = t.easing(t.current % t.duration / t.duration, 0, 1, 1)
                         , r = t.func(duration, 1e3 * e) || !1;
                     r && (t.done && t.done(),