xzw 1 ماه پیش
والد
کامیت
7b54dc7324

+ 321 - 45
public/static/data/data.json

@@ -20,21 +20,19 @@
         "id": 1,
         "description": "上节为神人,上下弦纹之间以折角部位的螺旋小尖喙向两侧展开,填刻螺旋纹小尖喙和线束纹样,神人重圈管钻眼,刻眼角。"
         ,"pos":{
-            "x": 14.879483956326686,
-            "y": 10.502966023247717,
-            "z": -38.97321840946214
+            "x": -15.092242444588193,
+            "y": 9.548474405295686,
+            "z": 38.7324807516758
         },
         "normal":{
-            "x": 0.17476547479044635,
-            "y": 0.021964034045356295,
-            "z": -0.98436507964765
-        },
+            "x": -0.16204240769463674,
+            "y": 0.00943875946105266,
+            "z": 0.9867386522926735
+        }, 
         "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}  
+                "view":{"yaw":-6.977387338522023,"pitch":-0.19155633969326386,"radius":452.9850769051254,"target":{"x":0,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":-284.5064966545119,"y":-341.7802946751267,"z":86.24246884164461}},
+                "tex":{"show":[1], "dur":3500, "delay":0.3, "shine":6000}  
             }
         
         ] 
@@ -43,10 +41,21 @@
         "id": 2,
         "description": "下节为兽面纹,重圈大眼填刻二等分线束,重圈斜上下刻小尖喙。"
         ,"pos":{
-            "x": 34.07681696101927,
-            "y": -7.850301930531856,
-            "z": 31.20216569500978
-        }
+            "x": -15.751030254889292,
+            "y": -14.374671110735733,
+            "z": 38.292268387473456
+        },
+        "normal":{
+            "x": -0.177433615869908,
+            "y": -0.02791987875296653,
+            "z": 0.9837366478533517
+        },
+        "action":[
+            {
+                "view":{"yaw":-6.979408544368389,"pitch":-0.09552778921362132,"radius":470.73571244000294,"target":{"x":0,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":-300.5179022566837,"y":-359.53455080689497,"z":44.899979573755246}},
+                "tex":{"show":[0], "dur":3500, "delay":0.3, "shine":6000}  
+            } 
+        ]     
       },
       {
         "id": 3,
@@ -90,9 +99,9 @@
             "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}},
+        "view":{"yaw":-6.799121972740081,"pitch":-0.30815519609515646,"radius":776.3368181949618,"target":{"x":0,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":-364.96403271184903,"y":-643.4724929189371,"z":235.46392710661635}},
         "bright":3,
-        "texNum":1,
+        "texNum":2,
         "structureOpacity":0.6
     }
   },
@@ -126,14 +135,14 @@
         },
         "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 }, "备注":"轮廓线"
+                "view":{"position":{"x":-19.223484367951656,"y":394.1640911638143,"z":20.256669370853146},"target":{"x":-4.813451550776005,"y":-3.6378675309149457,"z":-1.6918808507048055}}
+                ,"tex":{"show":[0], "dur":1000, "delay":0.3 }, "备注":"轮廓线"
             }, 
             { 
                 "tex":{"hide":[0], "dur":1200},
-                "structure":{"state":1, "dur":4000, "dir":[0,1,0]}  , "备注":"通透高亮分层轮廓"
+                "structure":{"state":1, "dur":3000, "dir":[0,1,0]}  , "备注":"通透高亮分层轮廓"
             },{ 
-                "structure":{"state":0, "dur":7000, "delay":0.4}  
+                "structure":{"state":0, "dur":5000, "delay":0.3}  
             }
         ]
         
@@ -154,9 +163,11 @@
         },
         "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}  
-            }
+                "view":{"position":{"x":-19.223484367951656,"y":394.1640911638143,"z":20.256669370853146},"target":{"x":-4.813451550776005,"y":-3.6378675309149457,"z":-1.6918808507048055}}
+            },{
+                 "tex":{"show":[1], "dur":2000, "shine":6000}
+            } 
+              
         ]
       },
       {
@@ -174,8 +185,10 @@
         },
         "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}  
+                "view":{ "position":{"x":-19.223484367951656,"y":394.1640911638143,"z":20.256669370853146},"target":{"x":-4.813451550776005,"y":-3.6378675309149457,"z":-1.6918808507048055}}
+            },
+            {
+                "tex":{"show":[2], "dur":2000  }  
             }
         ]
       },
@@ -289,7 +302,7 @@
         "follow":"Group33026",
         "action":[
             {
-                "combineModel": {"dur":1000},
+                "combineModel": {"state":1, "dur":1000},
                 "view":{ "dur":1000, "position":{"x":-3.0859169996269227,"y":498.6574278516968,"z":15},"target":{"x":0,"y":5.684341886080802e-14,"z":15}}
             }
             
@@ -344,20 +357,109 @@
       {
         "id": 11,
         "description": "玉钺身长16.3厘米,上端宽10.3厘米,刃宽13厘米,钺身距顶端约1厘米处有一横向刻划线,刻划线以上有不同方向斜线或直线痕迹,圆孔两侧还有向端角延伸的细密线痕。"
+        ,"pos":{
+            "x": -6.917277235390102,
+            "y": 4.343167486241804,
+            "z": 0.13380066496033297
+        },"normal":{
+            "x": -0.05935574894831291,
+            "y": 0.003943099628443303,
+            "z": 0.9982291054823559
+        },"action":[ 
+            {
+                "view":{"yaw":0.18462127290443853,"pitch":-0.12950613270734818,"radius":316.2871410364983,"target":{"x":-7.105427357601002e-14,"y":1.1368683772161603e-13,"z":1.4210854715202004e-14},"endPos":{"x":57.57595194264978,"y":-308.3084667695939,"z":40.84672154679725}}  
+            },
+            {"structure":{"state":1, "object":"51", "dur":1000, "delay":0, "dir":[0,1,0], "rimPower":2, "opacity":0.7}}, 
+            {
+                "structure":{"state":0, "object":"51", "dur":2000, "delay":0.6} ,
+                "view":{"dur":2000, "delay":0.3, "yaw":0.08080703353990984,"pitch":-0.07124419498575221,"radius":198.03893653710304,"target":{"x":5,"y":0,"z":0},"endPos":{"x":15.944976736468071,"y":-196.89196855380945,"z":14.09719194115918}} ,
+                "combineModel": {"state":0, "dur":3000, "delay":0.4} 
+            },
+            {    
+                "view":{"delay":0.2, "dur": 2800, "yaw":0.09262127290443817,"pitch":-0.017506132707348552,"radius":1115.6932024729617,"target":{"x":-5.684341886080802e-14,"y":2.2737367544323206e-13,"z":-90},"endPos":{"x":103.17342700519947,"y":-1110.7408007833526,"z":-70.46952433932664}},
+                "planeTex":{"dur": 3400, "delay":0.4, "file":"linkLine.png", "side":2,"matrix":[68, 0, 0, 0, 0, 0, 270, 0, 0, -1, 0, 0, 22.6, 0, -94, 1]  }
+            }
+            
+        ]
+      
       },
       {
         "id": 12,
         "description": "玉钺瑁横截面呈扁椭圆形,顶端倾斜作台阶状,底端齐平,中段有长方形凸榫,凸榫中间开一直向槽口,并有横向卯销孔,两侧另有“∞”形桯钻卯孔;两面刻划简约神人兽面纹。"
+        ,"pos":{
+            "x": 8.294096395027799,
+            "y": 15.449899060629307,
+            "z": 0.5239779658633382
+        },"normal":{
+            "x": -0.07747343596944245,
+            "y": -0.03387669637551627,
+            "z": 0.9964187052448232
+        },
+        "follow":"52",
+        "action":[
+           { 
+               "view":{"yaw":-0.0633796301822252,"pitch":-0.1115111675872921,"radius":209.80174183057954,"target":{"x":25.999999999999964,"y":0,"z":35.9999999999999},"endPos":{"x":12.794276233096287,"y":-208.08005074635315,"z":59.34678171252909}},
+               "tex":{"show":[0], "dur":3500, "delay":0.2, "shine":4000 } 
+           } 
+        ]
       },
       {
         "id": 13,
         "description": "玉钺镦横断面呈扁椭圆形,底端为台阶状,承接钺柄一端有椭圆形榫头,上有横向凹槽,凹槽内有长方形掏膛卯孔,通体刻划简约神人兽面纹。"
+        ,"pos":{
+            "x": 11.481286271714124,
+            "y": -63.011149487051146,
+            "z": -0
+        },"normal":{
+            "x": 0.6780198649933236,
+            "y": 0.6239779415879152,
+            "z": -0.3885107348402914
+        },
+        "follow":"53",
+        "action":[ 
+             {  
+                "view": {"yaw":-0.11732065980326531,"pitch":-0.16380621684751956,"radius":138.75238941571814,"target":{"x":29.99999999999995,"y":-5.684341886080802e-14,"z":-45.00001407896029},"endPos":{"x":13.97620481353141,"y":-135.95397229041154,"z":-22.373017485262086}},
+                "structure":{"state":1, "dur":2000, "delay":0.4, "dir":[0,0,1], "rimPower":7, "opacity":0.1}  
+             },
+             { "structure":{"state":0, "dur":2000, "delay":0.05 }  ,
+                "tex":{"show":[1], "dur":3700, "delay":0.56 } 
+             }
+             
+        ]
       },
       {
         "id": 14,
         "description": "钺由斧演变而来,象征权力与威严。甲骨文和金文中的“王”字,是由钺演变而来的象形字。良渚文化豪华型玉钺,是王的权杖,柄嵌玉髹漆,上下端装配玉瑁和玉镦,玉瑁造型取自于神徽的冠帽,体现了王权神授。"
+        ,"pos":{
+            "x": 6.408146220647224,
+            "y": -4.39105392519378,
+            "z": -0.033528621626189185
+        },"normal":{
+            "x": 0.05123586396240527,
+            "y": -0.5335671442272555,
+            "z": 0.8442043525386475
+        }
       }
-    ]
+    ],
+    "model":{
+        "file":"lz051-052-053",
+        "transform": {
+            "rotation": [1.5707963267948966, 0, 0],
+            "position": [-3, 0, 0],
+            "scale":[3.5,3.5,3.5]
+        },
+        "texNum":2,
+        "splitParts":{ 
+            "52": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1.5, -3, 0, 1,
+                   1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -2, 0, 1 ], 
+            "53": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 51, 0, 1]
+        },
+        "combine": 1, 
+        "texParts":{"52":[0],"53":[1]}, 
+        "view":{"yaw":0.18462127290443853,"pitch":-0.1295061327073482,"radius":498.73850816797,"target":{"x":-7.105427357601002e-14,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":90.7888454906052,"y":-486.15730714919425,"z":64.40929878161344}}
+        ,"bright":3.7
+    }
+    
   },
   {
     "id": 5,
@@ -603,18 +705,51 @@
             { 
                 "structure":{"state":1, "dur":1200, "dir":[0,1,0], "rimPower":0.36 }  , "备注":"展示整体和中央孔"
             },{ 
-                "structure":{"state":0, "dur":2200, "delay":0.1}   , "备注":"缺少纹样"
+                "structure":{"state":0, "dur":2200, "delay":0.1} ,
+                "tex":{"show":[0],  "delay":0.4   }               
+            }
+        ]
+      },
+      
+      {
+        "id": 221,
+        "description": "测试 文字"
+        ,"pos":{
+            "x": -76.79665873233729,
+            "y": 68.5828422521318,
+            "z": 7.536379044276565
+        },
+        "normal":{
+            "x": -0.02001136216362797,
+            "y": 0.0033439391438464527,
+            "z": 0.9997941605427383
+        },
+        "action":[
+            {
+                "view":{"dur":5000, "easing":"linearTween", "yaw":0.6300555302541072,"pitch":-0.12180038518601677,"radius":514.8982623631078,"target":{"x":-1.9245945531984603,"y":0.02714036927829966,"z":0.979447942004434},"endPos":{"x":299.2005834852104,"y":-412.925777256436,"z":63.53930376260592}}, 
+                "text": {"text":"朱",  "delay":0.2, "dur":1400,  "matrix":[11.371008415820313, 16.452968352468915, 1.1102230246251565e-15, 0, -4.984655349660946, 3.4450049812715378, 19.060040706255347, 0, 15.679712326839418, -10.836594163835365, 6.059277867525896, 0, 0, -20, 0, 1]}  
+            },
+            {
+                
+                "view":{"dur":5000, "easing":"linearTween", "yaw":3.1000536351259633,"pitch":-0.16380118293337703,"radius":514.8982623531734,"target":{"x":-1.9245945531984603,"y":0.02714036927829966,"z":0.9794479420044269},"endPos":{"x":19.171412997214112,"y":507.5950436014331,"z":84.94374183092113}},
+                "text": {"text":"㫃",  "delay":0.2, "dur":1400,  "matrix":0}, 
+                "text": {"text":"戔",  "delay":0.2, "dur":1400,  "matrix":0} , 
+                "text": {"text":"石",  "delay":0.2, "dur":1400,  "matrix":0},  
+                "text": {"text":"网",  "delay":0.2, "dur":1400,  "matrix":0} , 
+                "text": {"text":"虎",  "delay":0.2, "dur":1400,  "matrix":0},  
+                "text": {"text":"石",  "delay":0.2, "dur":1400,  "matrix":0},  
+                "text": {"text":"封",  "delay":0.2, "dur":1400,  "matrix":0}  
             }
         ]
-      } 
+      }
     ],
     "model":{
         "file":"lz076",
         "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]
-        },
-        "texNum":0,
+        }, 
+        "texNum":1,
         "bright": 4
     } 
   },
@@ -636,16 +771,93 @@
       {
         "id": 23,
         "description": "圆牌外缘和两面以浅浮雕和阴线刻相结合的方式雕琢顺向的三幅龙首纹。"
+        ,"pos":{
+            "x": -7.61495032284546,
+            "y": -4.616300553486379,
+            "z": 0.32233903619686366
+        },
+        "normal":{
+            "x": -0.038526926655701635,
+            "y": -0.00726536123571819,
+            "z": -0.9992311496588671
+        }, 
+        "action":[
+            {"view":{"yaw":1.5660555017884197,"pitch":-0.15980038518837816,"radius":514.8982623532802,"target":{"x":-1.9245945531984034,"y":0.02714036927829966,"z":0.9794479420044553},"endPos":{"x":506.4076704682443,"y":-2.382791999110268,"z":82.91064553228063}}},
+            {"rotate" : {"axis":"z","angle": 6.283185307179586, "dur":16000, "dontWait":1}},
+            {"tex": {"show":[0], "dur":4000, "delay":0.05 } },
+            {"tex": {"show":[1], "dur":4500, "delay":0.2 } },
+            {"tex": {"show":[2], "dur":3000, "delay":0.25 } }
+        ] 
+        
       },
       {
         "id": 24,
-        "description": "三幅龙首纹之间另刻相连的双线凹弧边菱形纹。"
+        "description": "三幅龙首纹之间另刻相连的双线凹弧边菱形纹。",
+        "pos":{
+            "x": 5.06413613279774,
+            "y": -5.741043954168888,
+            "z": 0.3382511979145466
+        },
+        "normal":{
+            "x": 0.026859278754294703,
+            "y": -0.06240925669817543,
+            "z": -0.9976891619252864
+        },
+        "action":[
+            { 
+                "view":{"dur":3000,  "easing":"easeInSine",  "yaw":-0.8144780384871142,"pitch":0.10107410072488404,"radius":514.8982623532803,"target":{"x":-1.0794565241667442e-10,"y":3.296918293926865e-11,"z":-2.1032064978498966e-12},"endPos":{"x":-372.6088466453754,"y":-351.54461647796956,"z":-51.95431263567419}},
+                "tex": {"show":[4], "dur":2000, "delay":0.5} 
+            },
+            
+            {
+                "view":{"dur":3500,  "easing":"linearTween",  "yaw":-0.15200131226273328,"pitch":-0.41093683834291445,"radius":514.8982623532803,"target":{"x":-1.0794565241667442e-10,"y":3.296918293926865e-11,"z":-2.1032064978498966e-12},"endPos":{"x":-71.47342817104143,"y":-466.58892078867194,"z":205.68555767761782}},
+                "tex": {"show":[3], "dur":2500, "delay":0.5 } 
+            },
+            {
+                "view":{"dur":4000,"easing":"easeOutSine", "yaw":0.44802802242456546,"pitch":-0.05507840557352308,"radius":514.8982623532803,"target":{"x":-1.0794565241667442e-10,"y":3.296918293926865e-11,"z":-2.1032064978498966e-12},"endPos":{"x":222.7100402724058,"y":-463.3756517599435,"z":28.34543864997867}},
+                "tex": {"show":[5], "dur":3500, "delay":0.5 } 
+            } 
+            
+            
+        ]
       },
       {
         "id": 25,
-        "description": "龙首重圈凸眼,上方雕琢耳朵,阔鼻阔嘴。"
+        "description": "龙首重圈凸眼,上方雕琢耳朵,阔鼻阔嘴。",
+        "pos":{ 
+            "x": 3.7092474408104703,
+            "y": 3.6356195969290988,
+            "z": 0.4159916042983558
+        },
+        "normal":{
+            "x": 0.04490905112352026,
+            "y": 0.006448713884637411,
+            "z": -0.998970265431569
+        },
+        "action":[
+            {"view":{"yaw":-0.6913806139677159,"pitch":-0.13799675622318752,"radius":416.6704969852284,"target":{"x":-1.1368683772161603e-13,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":-263.14410294880935,"y":-317.9375128218564,"z":57.316856873310556}}},
+            {
+                "view":{"dur":7000, "dontWait":1,"yaw":-1.7793959002852373,"pitch":-0.26400002104409054,"radius":452.90271411437885,"target":{"x":-1.1368683772161603e-13,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":-427.7334935925872,"y":90.54212668667363,"z":118.18227534747079}},
+                "tex": {"show":[6], "dur":2000, "delay":0 } 
+            },
+            {"tex": {"show":[7], "dur":2000, "delay":0 } },
+            {"tex": {"show":[8], "dur":2500, "delay":0 } }, 
+            {"tex": {"show":[1],  "dur":1500, "delay":0 }} 
+         
+             
+        ]
       }
-    ]
+    ],
+    "model":{
+        "file":"lz032",
+        "view":  {"position":{"x":-29.441169903407676,"y":-647.976817688714, "z":0},"target":{"x":0,"y":0,"z":0}},  
+        "transform":{
+            "rotation" : [1.5707963267948966, 3.141592653589793, 0] ,
+            "position": [ -5, -15,  -5]
+        }, 
+        "texNum": 9,
+        "bright": 4
+    } 
   },
   {
     "id": 9,
@@ -693,20 +905,48 @@
             "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}   , "备注":"缺少纹样"
-            }
+          { "view": {"dur":2000, "yaw":10.934346526115373,"pitch":-1.5427963267941434,"radius":122.73212657494459,"target":{"x":0,"y":0,"z":0},"endPos":{"x":-3.429611937948045,"y":0.21025025872554687,"z":122.68401872449407}} ,
+            "structure":{"state":1, "dur":2000, "delay":0.7, "dir":[0,0,1], "rimPower":6, "opacity":0.07}   
+          },
+          { "structure":{"state":0, "dur":1800, "delay":0 }},  
+              
+          {
+            "view": {"dur":4000, "yaw":5.37370870643843,"pitch":-0.08940592180961196,"radius":330.16547770848854,"target":{"x":0,"y":0,"z":0},"endPos":{"x":-259.5200910298328,"y":-201.96417434667651,"z":29.479438644705695}},
+            "emissive":{"intensity": 0.12, "dur": 2500, "delay":0.25}
+           },
+          {"view": {"dontWait":1, "dur":6000,"yaw":8.790582984539286,"pitch":-0.12984711223612053,"radius":407.189210014463,"target":{"x":0,"y":0,"z":0},"endPos":{"x":239.24040962534033,"y":325.24955049333454,"z":52.72389456769767}}},
+           {"tex":{"show":[2], "dur":2000, "delay":0 } },  
+            {"tex":{"show":[1], "dur":2000, "delay":0 } } ,  
+            {"tex":{"show":[0], "dur":2000, "delay": 0 } },    
+             {"emissive":{"intensity": 0, "dur": 2000}} 
         ]
       }
     ],
     "model":{
         "file":"lz063",
-        "view":  {"position":{"x":-427.55457070867675,"y":-408.9037957630794,"z":112.7020641920662},"target":{"x":-0.5,"y":0.5000000000000568,"z":-0.7071067811865532}},
+        "view":  {"yaw":11.632346526115365,"pitch":-0.10880071012307324,"radius":650.4317196558476,"target":{"x":0,"y":0,"z":0},"endPos":{"x":-519.8673387948721,"y":-384.4620556855471,"z":70.62789623685545}},
         "transform":{
+            "rotation": [-1.5707963267948966, 0, 0] 
+        },
+        "texNum":3,
+        "materials":[{
+            "group":["group1_polySurface55","group1_polySurface11"],
+            "emissive":"#000",  "备注":"这段不高亮" 
         },
-        "texNum":0
+        {
+            "group":["group1_polySurface44"],
+            "emissive":"#5f5", "tex":[2]
+        },
+        {
+            "group":["group1_polySurface33"],
+            "emissive":"#ff6", "tex":[1]
+        },
+        {
+            "group":["polySurface22_group1"],
+            "emissive":"#5ff", "tex":[0]
+        } 
+        ],
+        "备注":"之所以materials要手动分组,是因为这个模型五个mesh竟然都是同一个材质"
     } 
   },
   {
@@ -728,17 +968,52 @@
       {
         "type": "animate",
         "id": 28,
-        "description": "释读内容表达的是“朱去往石地,在石的境内用网捕捉老虎”。"
+        "description": "释读内容表达的是“朱去往石地,在石的境内用网捕捉老虎”。",
+        "pos":{
+            "x": 1.5817869258649075,
+            "y": 2.741111726584801,
+            "z": 2.0949568716428693
+        },"normal":{
+            "x": 0.6384366990965952,
+            "y": -0.2496460359645506,
+            "z": 0.7280627980976848
+        }
       },
       {
         "id": 29,
         "description": "器物上发现了多个刻画符号的排列组合,突破了符号孤立存在的局面,说明良渚先民试图表达或传递某种信息。"
+        ,"pos":{
+            "x": 0.14558213598152742,
+            "y": 4.445009607375333,
+            "z": 2.736505570220036
+        },"normal":{
+            "x": 0.10267135709347071,
+            "y": 0.43574605482718953,
+            "z": 0.894194591873114
+        }
       },
       {
         "id": 30,
-        "description": "李学勤先生曾将符号辨识为8个字,并释读为“朱㫃戔石,网虎石封”。"
+        "description": "李学勤先生曾将符号辨识为8个字,并释读为“朱㫃戔石,网虎石封”。",
+        "pos":{
+            "x": 2.6255797334496096,
+            "y": 3.2471444389739346,
+            "z": 0.07514042388589814
+        },"normal":{
+            "x": 0.9935080977671001,
+            "y": -0.11371383983951541,
+            "z": 0.0032897264553873318
+        }
       }
-    ]
+    ],
+    "model":{
+        "file":"lz050",
+        "transform":{
+            "rotation": [ 1.5707963267948966, 0, 0], 
+            "position":[1.5,3,-50]
+        },
+        "view":{"yaw":6.74059569720568,"pitch":-0.21079632679489763,"radius":761.5916648606657,"target":{"x":-1.1368683772161603e-13,"y":1.1368683772161603e-13,"z":0},"endPos":{"x":328.8938350248258,"y":-668.1742862819426,"z":159.3544239094714}}        
+    }
   },
   {
     "id": 11,
@@ -755,5 +1030,6 @@
       }
     ],
     "hotspots": []
+    
   }
 ]

BIN
public/static/models/1/0.png


BIN
public/static/models/1/1.png


BIN
public/static/models/10/lz050.glb


BIN
public/static/models/11/lz349.glb


BIN
public/static/models/4/0.png


BIN
public/static/models/4/1.png


BIN
public/static/models/4/linkLine.png


BIN
public/static/models/4/lz051-052-053.glb


BIN
public/static/models/7/0.png


BIN
public/static/models/8/0.png


BIN
public/static/models/8/1.png


BIN
public/static/models/8/2.png


BIN
public/static/models/8/3.png


BIN
public/static/models/8/4.png


BIN
public/static/models/8/5.png


BIN
public/static/models/8/6.png


BIN
public/static/models/8/7.png


BIN
public/static/models/8/8.png


BIN
public/static/models/8/lz032.glb


BIN
public/static/models/9/0.png


BIN
public/static/models/9/1.png


BIN
public/static/models/9/2.png


BIN
public/static/models/9/lz063.glb


+ 59 - 23
src/modelViewer/ModelManager.js

@@ -557,7 +557,7 @@ export default class ModelManager extends THREE.EventDispatcher{
         
         const mapNames = ['map','aoMap', 'normalMap','metalnessMap','roughnessMap']
         let texs = []  
-        let materials = [], splitParts = [], allTexIndex = []
+        let materials = [], splitParts = [], allTexIndex = [], mat
          
         if(data.texNum){
             let i = -1
@@ -569,19 +569,50 @@ export default class ModelManager extends THREE.EventDispatcher{
         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.materials){//lz063 这个模型五个mesh竟然都是同一个材质,只好手动分开
+                      
+                    let a = data.materials.findIndex(ee=>ee.group.includes(e.name))
+                    
+                    if(a>-1 ){
+                        let name = "copy_"+a
+                        let mat = materials.find(e=>e.name == name)
+                        if(!mat){
+                            mat = materials.length ? materials[0].clone() : e.material
+                            materials.push(mat)
+                            mat.emissive.set(data.materials[a].emissive)
+                            mat.emissiveIntensity = 0
+                            mat.name = name 
+                            mat.emiss_texs = data.materials[a].tex || [] 
+                        }
+                        e.material = mat
+                    }
+                    
+                }else{ 
+                    materials.push(e.material)
+                    
+                    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()
+                        if(matrix.length == 32){ 
+                            e.initialMatrix = new THREE.Matrix4().fromArray(matrix)  
+                            matrix = matrix.slice(16,32) 
+                        }else{
+                            e.initialMatrix = e.matrix.clone()
+                        }
+                        
                         e.combineMatrix = new THREE.Matrix4().fromArray(matrix)
                         //e.combileMatrix.decompose(e.position, e.quaternion, e.scale) 
                         splitParts.push(e)
@@ -601,16 +632,19 @@ export default class ModelManager extends THREE.EventDispatcher{
             let i = -1
             while(++i<data.texNum){ 
                 let url = Common.joinUrl(model.props.prefixUrl, i+'.png')
-                let tex = texLoader.load(url)
-                texs.push(tex)
+                let tex = texLoader.load(url )
+                texs.push(tex) 
                 tex.premultiplyAlpha = true  //因透明部分不一定是黑色导致颜色不对,因emissive不读a通道所以预乘下 emissivemap_fragment
                 //tex.needsUpdate = true
             }
             
         }
-         
-        
+  
         model.materials_.forEach(material=>{
+            
+            
+            material.initialOpacity = material.opacity
+             
             mapNames.forEach(m=>{
                 material[m] && (material[m].anisotropy = settings.aniso)
             })
@@ -620,14 +654,18 @@ export default class ModelManager extends THREE.EventDispatcher{
             
             let emissiveText =  ''  
             if(material.emiss_texs.length > 0){ 
-                material.defines.emissiveCount = material.emiss_texs.length
+                material.defines.emissiveCount = texs.length//material.emiss_texs.length
                 material.defines.USE_UV = 1
-                material.emiss_texs.forEach(i=>{
+                /* material.emiss_texs.forEach(i=>{
                     emissiveText += 'if(emiIntensitys['+i+'] > 0.0){ gl_FragColor += texture2D(emiMaps['+i+'], vUv) * emiIntensitys['+i+'];} \n'
-                })
-            }
-            let firstCompile = true 
+                })  *///很奇怪这种写法会导致多个材质的时候另一个材质显示不出图,要从0遍历到最后才行
+             
+                for(let i=0;i<data.texNum; i++){
+                    emissiveText += 'if(emiIntensitys['+i+'] > 0.0){ gl_FragColor += texture2D(emiMaps['+i+'], vUv) * emiIntensitys['+i+'];} \n'
+                }    
             
+            }
+             
             material.onBeforeCompile = function ( shader ) {
                 // console.log(shader.vertexShader)
                // console.log(shader.fragmentShader)
@@ -641,18 +679,16 @@ export default class ModelManager extends THREE.EventDispatcher{
                     }  
                     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_.emiMaps = {value: texs_ } 
                         material.uniforms_.emiIntensitys = {value: new Array(data.texNum).fill(0) }//不同材质的数组长度都是总数,但只使用各自的部分
                     }
                 } 
                 
                 
                 Object.assign(shader.uniforms, material.uniforms_)
+                 
                 
-                
-                
-                
-                material.uniforms_ = shader.uniforms
+                material.shaderUni = shader.uniforms
                 
                 //原本的vNormal是视图空间下的
                 shader.vertexShader = shader.vertexShader.replace(
@@ -742,7 +778,7 @@ export default class ModelManager extends THREE.EventDispatcher{
                 );  
                 
                 //console.log(shader.vertexShader)
-                //console.log(shader.fragmentShader)
+                console.log(shader.fragmentShader)
                 //console.log(shader.uniforms)
                 
                 

+ 4 - 4
src/modelViewer/OrbitControls.js

@@ -280,17 +280,17 @@ export class OrbitControls extends THREE.EventDispatcher{
         { // accelerate while input is given
 			let ih = this.viewer.inputHandler;
 
-			let moveForward = this.keys.FORWARD.some(e => ih.pressedKeys[e]);
+			/* let moveForward = this.keys.FORWARD.some(e => ih.pressedKeys[e]);
 			let moveBackward = this.keys.BACKWARD.some(e => ih.pressedKeys[e]);
 			let moveLeft = this.keys.LEFT.some(e => ih.pressedKeys[e]);
 			let moveRight = this.keys.RIGHT.some(e => ih.pressedKeys[e]);
 			let moveUp = this.keys.UP.some(e => ih.pressedKeys[e]);
 			let moveDown = this.keys.DOWN.some(e => ih.pressedKeys[e]);
-            
+             */
              
             let moveSpeed = this.moveSpeed/* this.currentViewport.getMoveSpeed() */ / 100;
             let px = 0 , py = 0, pz = 0
-            if(moveForward){
+            /* if(moveForward){
                 py = 1 * moveSpeed 
             }else if(moveBackward){
                 py = -1 * moveSpeed 
@@ -305,7 +305,7 @@ export class OrbitControls extends THREE.EventDispatcher{
                 pz = 1 * moveSpeed 
             }else if(moveDown){
                 pz = -1 * moveSpeed 
-            }
+            } */
             
             if(px!=0 || py!=0 || pz!=0){
                 //console.log(px,py,px)

+ 1 - 0
src/modelViewer/View.js

@@ -562,6 +562,7 @@ export class View extends THREE.EventDispatcher{//base
         if(json.yaw != void 0){//角度更准
             json.yaw != void 0 && (this.yaw = json.yaw)
             json.pitch != void 0 && (this.pitch = json.pitch)
+            json.radius != void 0 && (this.radius = json.radius)
         }else{
             this.lookAt(json.target) 
         }

+ 264 - 110
src/modelViewer/Viewer.js

@@ -15,6 +15,9 @@ import {TransformationTool} from './utils/TransformationTool.js'
 import {LineMaterial} from "./objects/fatline/LineMaterial.js"; 
 import CursorDeal from './utils/CursorDeal.js'
 import Sprite from './objects/Sprite.js'
+import TextSprite from './objects/TextSprite.js'
+
+
 
 let texLoader = new THREE.TextureLoader()
 
@@ -22,7 +25,7 @@ const raycaster = new THREE.Raycaster()
 const planeGeo = new THREE.PlaneGeometry(1,1)
   
 
-
+let index = 0
 
 export class Viewer extends ViewerBase {
     constructor( args = {}) {
@@ -34,6 +37,7 @@ export class Viewer extends ViewerBase {
             })
         ) //分屏局部刷新要preserveDrawingBuffer
         
+        this.index = index++
         
         if(this.renderer.capabilities.isWebGL2){
             settings.isWebgl2 = true  //是否启用webgl2
@@ -82,7 +86,8 @@ export class Viewer extends ViewerBase {
             this.setAllTilesets((model) => model.visiChangeCallback())
         })
         this.scene.add(this.objs)
-  
+        this.textLabels = new THREE.Object3D , this.textLabels.name = 'textLabels'
+        this.scene.add(this.textLabels)
         this.renderer.setAnimationLoop(this.loop.bind(this))
   
   
@@ -174,7 +179,9 @@ export class Viewer extends ViewerBase {
         this.stats?.begin()
 
         this.dispatchEvent({ type: 'loopStart', delta })
-  
+          if(window.log1){      
+            console.log('transitions.update', this.index)
+          }
         transitions.update(delta) //写在开头,因为这时候最为固定,计时准确
         this.updateScreenSize() //判断是否改变canvas大小
         this.controls.update(delta)
@@ -601,7 +608,11 @@ export class Viewer extends ViewerBase {
             }, -duration, null, 0, easing.easeInOutQuad, 'highlight'  )
         }) 
     } */
-    
+    dispose(){ 
+        this.closeTag(0 )
+        transitions.funcs = []
+        super.dispose() 
+    }
     closeTag(duration, defaultTarget, split){
           
         this.highlightTex('all', 0, duration )  //hide emissive tex
@@ -618,75 +629,15 @@ export class Viewer extends ViewerBase {
             }) 
         }
         
-        if(split){
-            this.changeCombineState(true)
-        }
+         
+        this.changeCombineState(!split)
+         
         
         this.texPlaneState && this.showPlane(0)
         this.structureState && this.showStructure(0)
-        
+        this.showText(0,null,1000)
     }
     
-    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')
-         
-        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 
-            
-            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)=>{
-                    if(mat.emiss_texs.includes(index)){
-                          mat.uniforms_.emiIntensitys.value[index] = currents[i]
-                    } 
-                }) 
-            })
-            
-              
-            viewer.dispatchEvent('content_changed') 
-        },  duration, ()=>{
-          
-            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){
          
@@ -698,13 +649,14 @@ export class Viewer extends ViewerBase {
         
         
         if(hotspot !== this.lastOpenTag){
-            let defaultTarget = true   //如果等下没指定view,需要恢复target
-            let split = this.combineState && !hotspot.action?.find(e=>e.combineModel)
-            this.closeTag(0, defaultTarget, split, )
-        }
+            let defaultTarget = true    
+            let split = this.modelData.model.combine ? false : (this.combineState && !hotspot.action?.find(e=>e.combineModel))
+            this.closeTag(0, defaultTarget, split  )
+        } 
         this.lastOpenTag = hotspot
         
         if(hotspot.action){
+            console.log(hotspot.action)
             let actionIndex = 0
             let deal = ()=>{//执行任务
                 if(actionIndex >= hotspot.action.length){
@@ -723,66 +675,188 @@ export class Viewer extends ViewerBase {
                 
                 let action = hotspot.action[actionIndex]
                 if(action.view){ 
-                    
+                    let done = ()=>{
+                        action.view.dontWait || callback()
+                    }
                     taskCount++
                     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])
+                    let duration = action.view.dur || 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,
-                        position: action.view.position,
-                        endRadius: action.view.radius,
-                        target: action.view.target,
-                        duration,
-                        callback,
-                        cancelFun: callback
-                    }) 
+                    setTimeout(()=>{
+                        this.mainViewport.view.setView({
+                            endYaw:  action.view.yaw,
+                            endPitch: action.view.pitch,
+                            position: action.view.position,
+                            endRadius: action.view.radius,
+                            target: action.view.target,
+                            duration,
+                            callback: done,
+                            cancelFun: done,
+                            Easing: action.view.easing
+                        }) 
+                    }, (action.view.delay || 0) * duration)
+                     
+                    
+                    if(action.view.dontWait){
+                        setTimeout(callback, 1) //挂起,直接执行后面的
+                    } 
                 } 
                 
-                if(action.tex){
+                if(action.tex){ 
                     taskCount++
                     let t = action.tex
-                    t.show && this.highlightTex(t.show, 0.9, t.dur || 2000, callback,  t.delay, t.shine)  
+                    
+                    console.log('tex', t, action)
+                    
+                    t.show && this.highlightTex(t.show, 0.8, 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)
+                    this.changeCombineState(action.combineModel.state, 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)
+                    this.showStructure(s.state, s.object, 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)
+                    this.showPlane(t.state, t.file, t.matrix, t.side, t.dur, t.delay, callback)
+                    
+                }
+                if(action.text){
+                    taskCount++
+                    let t = action.text
+                    this.showText(t.text, t.matrix, t.dur, t.delay, callback)
                     
                 }
+                if(action.rotate){ 
+                    let m = action.rotate
+                    taskCount++ 
+                    this.rotateModel(m.axis, m.angle,  m.dur, m.dontWait ? null : callback)
+                    if(m.dontWait){
+                        setTimeout(callback, 1)//挂起,直接执行后面的
+                    } 
+                }
+                if(action.emissive){ 
+                    let e = action.emissive
+                    this.highlight(e.intensity, e.dur, e.delay, callback)
+                }
             }
             
             
             deal() 
-        } 
+        }else if(hotspot.type == 'animate'){ //播放video
+           
+            this.mainViewport.view.setView({  
+                target: this.defaultView.target,
+                position: this.defaultView.position, 
+                endYaw:  this.defaultView.yaw,
+                endPitch: this.defaultView.pitch,
+                endRadius: this.defaultView.radius,
+                duration: 500 
+            })   
+        }
+    }
+    highlightTex(indexes='all', toPercent=0.8, duration=2000, done, delay=0, loopDur){
+        console.log('highlightTex', indexes, toPercent) 
+        let model = viewer.objs.children[0]
+        if(!model)return console.error('模型未加载完')
+        //const duration = 2000, minAlpha = 0, maxAlpha = 0.8
+        transitions.cancelById('highlight')
+         
+        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 
+            
+            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)=>{
+                    if(mat.emiss_texs.includes(index)){
+                          mat.uniforms_.emiIntensitys.value[index] = currents[i]
+                    } 
+                }) 
+            })
+            
+              
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+          
+            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'  )  
+     
+    }
+    
+    highlight(intensity, duration=1000, delay=0, done){
+        
+        transitions.cancelById('emissive')
+        let model = viewer.objs.children[0]
+        
+        let start = model.materials_[0].emissiveIntensity
+
+ 
+        transitions.start((percent)=>{
+            let cur = math.linearClamp(percent,[0,1],[start,intensity])
+            model.materials_.forEach(e=>e.emissiveIntensity = cur)
+                     
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+            done && done()
+        }, delay, easing.easeInOutSine, null,'emissive'  ) 
     }
     
     
-    changeCombineState(split, duration=1000, done){ //默认是拆分的
+        
+    changeCombineState(combine, 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 endMatrixs =  model.splitParts.map(e=> combine ? e.combineMatrix.clone() : e.initialMatrix.clone() )   
         let matrix = new THREE.Matrix4
-        this.combineState = !split
+        this.combineState = combine
         
         transitions.start((percent)=>{
              
@@ -801,7 +875,7 @@ export class Viewer extends ViewerBase {
     }
 
     
-    showStructure0(state, fresnelDir = [0,1,0]){
+    /* showStructure0(state, fresnelDir = [0,1,0]){
         let meterials = viewer.objs.children[0].materials_
         if(state){
             meterials.forEach(mat=>{
@@ -822,24 +896,27 @@ export class Viewer extends ViewerBase {
         } 
         
         viewer.dispatchEvent('content_changed')
-    }
+    } */
+    
     
     
-    showStructure(state, fresnelDir = [0,1,0], rimPower=1, opacity=1, duration=500, done, delay=0  ){
+    showStructure(state, objectName, 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 meterials = objectName ? this.getMatsByName(objectName) : 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
+        let inited
         
         transitions.start((percent)=>{ 
+            let init = !inited && percent >0 && state 
+        
             meterials.forEach((mat,i)=>{ 
-                if(!init && percent >0 && state){ 
+                if(init){ 
                     Common.addOrRemoveDefine(mat, "Fresnel", "add")  //state ? "add" : "remove" 
                     mat.uniforms_.fresnelDir.value.set(...fresnelDir );  
                     mat.uniforms_.rimPower.value = rimPower 
@@ -847,7 +924,6 @@ export class Viewer extends ViewerBase {
                     //mat.opacity = model.structureOpacity || 1
                     mat.depthWrite = false //减轻环璧条纹, 透明度越低越不明显 用structureOpacity调节透明度
                     mat.side = 2
-                    init = true   
                 }
                  
                 //mat.uniforms_.fresnelRatio.value = state ? percent : 1-percent
@@ -856,7 +932,7 @@ export class Viewer extends ViewerBase {
                 mat.opacity = model.structureOpacity * math.linearClamp(percent,state?[0,0.1]:[0.7,1], [startOpacitys[i], opacity * mat.initialOpacity] )   //这得大部分时间很小则过曝
                 //console.log('opa',mat.opacity)
             })
-                  
+            init && (inited = true)   
             viewer.dispatchEvent('content_changed') 
         },  duration, ()=>{
             if(!state){
@@ -875,14 +951,14 @@ export class Viewer extends ViewerBase {
         
     }
      
-    showPlane(state=1, file, matrix, duration=1000, delay=0, done){
+    showPlane(state=1, file, matrix, side=0, 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)}))
+            let plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({transparent:true, side, map: texLoader.load(url)}))
             model.texPlane = plane
             plane.opacity = 0
             matrix = new THREE.Matrix4().fromArray(matrix)
@@ -900,19 +976,97 @@ export class Viewer extends ViewerBase {
         }, delay, easing.easeInSine, null,'texPlane'  ) 
         
         
-        
+         
     }
     
-    /* addPlane(){
-        
+    addPlane(){ 
+        let model = viewer.objs.children[0]
+        let url = Common.joinUrl(model.props.prefixUrl,  "linkLine.png")
         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
+        plane.scale.set( 68, 270, 1)
+         plane.position.set(22.6,   0,  -94 )
+        plane.rotation.x = 1.5707963267948966  
         this.scene.add(plane)  
-    } */
+    } 
+    showText(text, matrix, duration = 600, delay=0, done){
+        transitions.cancelById('text') //因多个label一起隐藏,不好控制停哪个,自己控制下一个亮完后再亮下一个
+        let state = !!text, changeLabels = []
+        if(state){
+            let label = new TextSprite({
+                backgroundColor:{r:0,g:0,b:0,a:0},
+                text,
+                fontsize: 100,  fontWeight: 300,
+                textColor: {r:255,g:255,b:255,a:1},
+            })  
+            if(matrix){
+                new THREE.Matrix4().fromArray(matrix).decompose(label.position, label.quaternion, label.scale)
+            }else{
+                label.scale.set(20,20,20)
+            }
+            this.textLabels.add(label)
+            changeLabels.push(label)
+        }else{ 
+            changeLabels = this.textLabels.children
+        }
+         
+        transitions.start((percent)=>{
+            changeLabels.forEach(label=>{ 
+                label.sprite.material.opacity = state ? percent : 1-percent 
+            })   
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+            if(!state){
+                changeLabels.slice().forEach(label=>label.dispose())
+            }
+            done && done()
+        }, delay, easing.easeInSine, null,'text'  ) 
+         
+    }
+    
+    rotateModel(axis, angle=Math.PI * 2, duration = 3000, done){ //转一圈回到原来状态
+    
+        let model = viewer.objs.children[0]
+        let from = model.rotation[axis], to = from + angle
+        
+        viewer.tags.visible = false
+        transitions.start((percent)=>{
+             
+            model.rotation[axis] = from * (1-percent) + to * percent
+                     
+            viewer.dispatchEvent('content_changed') 
+        },  duration, ()=>{
+            viewer.tags.visible = true 
+            done && done()
+        }, 0, easing.easeInOutSine, null,'rotateModel'  ) 
+        
+    }
+    
+    
+    
+    
+    
+    
+    
+    
+    getMatsByName(objectName){//获取模型中某个节点中所有材质
+        let model = viewer.objs.children[0]
+        let found, mats = []
+        model.traverse((e, lastResult={})=>{//lastResult用于标记当前分支找到没,找到了的话获取其子孙所有materials
+            if(e.name == objectName || lastResult.found){  
+                lastResult.found = found = 1
+                e.material && mats.includes(e.material) || mats.push(e.material)
+                return lastResult
+            }else if(found){//在别的分支找到了
+                return {stopContinue: 1}
+            } 
+             
+        })
+        return mats
+        
+    }
+    
 }
 
  

+ 41 - 10
src/modelViewer/app.js

@@ -8,7 +8,7 @@ import Tag from './objects/Tag.js'
 Object.assign(settings, {
     resourcePrefix: '/static/',    //'../../public/static/',
     pauseIntersect:true,
-    addTag: 0, //加热点时开启。开启的时候热点intersect会不太正常没关系,因为和model的深度冲突了
+    addTag: 1, //加热点时开启。开启的时候热点intersect会不太正常没关系,因为和model的深度冲突了
     moveMesh: 0, //可以拖动在splitParts中的mesh , 和上一个不能同时打开
     showLights: 0,
     fov:  15 , //八猴是这个值
@@ -18,38 +18,52 @@ let modelData
 let texLoader = new THREE.TextureLoader()
 const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {  
     
-     
     
     init(dom, id){ 
-        
+        if(window.viewer){
+            this.dispose() 
+        }
         window.viewer = new Viewer( {renderArea: dom })
+       
         const bound = 1000
         /* viewer.controls.targetBound = { min: new THREE.Vector3(-bound,-bound,-bound),
                                         max: new THREE.Vector3(bound,bound,bound)} */
         viewer.controls.minRadius = 100
-        viewer.controls.maxRadius = 1000
+        viewer.controls.maxRadius = 2000
         //viewer.background = '#999'
         viewer.backgroundOpacity = 0
         viewer.sdk = sdk
-        this.load(id)  
+        
         if(settings.moveMesh){
             viewer.getTranTool()
         }
+        
+        
+        
+        this.load(id)  
     },
     
-    
+
     
     async load(id){
         //const store = useStore();  
         let dataAll = store.getters.data     //await fetch("../../public/static/data/data.json") 
-            modelData = viewer.modelData = dataAll.find(e=>e.id == id)
+            modelData = dataAll.find(e=>e.id == id)
         if(!modelData)return console.error('无此id',id)
         console.log('modelData', modelData)
-         
+    
+        modelData = Common.CloneObject(modelData)
+        viewer.modelData = modelData
         viewer.addLights(modelData.model.bright)
-        
+        let curViewer = viewer
         let done = (model)=>{
             //document.querySelector('.progressContainer').style.display = 'none'
+            
+            if(window.viewer != curViewer){
+                debugger
+                curViewer.modelManager.removeModel(model)
+                return
+            }
             sdk.dispatchEvent({type:'modelProgress', value: 100})
              
             {
@@ -82,11 +96,19 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
             
             this.loadTags(modelData, model  )
             
+            if(modelData.model.combine){
+                viewer.changeCombineState(1,0) //修改位置
+            }
         }
         
         
         let onProgress_ = (v)=>{
             //因解析还要时间,所以加载完时设置为一个最大值
+            if(window.viewer != curViewer){
+                debugger 
+                return
+            }
+            
             let loadedValue = 90
             let clamped = Math.min(100, Math.max(0, (loadedValue * v / 100) ));
             
@@ -133,9 +155,18 @@ const sdk = Object.assign( {}, THREE.EventDispatcher.prototype, {
         
         viewer.setTagsDisplay(state, dur)
         
-    }
+    },
     
     
+    dispose(){
+        viewer.dispose()
+        
+        viewer.tags?.children.slice().forEach(e=>e.dispose())
+         
+        viewer.objs.children.slice().forEach(e=> viewer.modelManager.removeModel(e))   
+        
+        window.viewer = null
+    }
 })
 
 

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

@@ -112,7 +112,12 @@ export default class Tag extends THREE.Object3D {
         })
     }
 
-
+    dispose(){
+        this.parent.remove(this);
+        this.titleLabel?.dispose()
+        viewer.dispatchEvent('content_changed')
+        
+    } 
     /* getPos2d(){
         //暂且1个方向  不指定方向
         let renderer = viewer.renderer

+ 348 - 0
src/modelViewer/objects/TextSprite.js

@@ -0,0 +1,348 @@
+
+
+import * as THREE from 'three';  
+import Sprite from './Sprite.js' 
+import {Common} from '../utils/Common.js';
+
+ 
+ 
+export default class TextSprite extends THREE.Object3D{ 
+    //注:为了分两层控制scale,不直接extend Sprite
+	constructor( options={}){ 
+        super()
+		let map = new THREE.Texture();
+		map.minFilter = THREE.LinearFilter //缩小效果都不好,sizeInfo尽量放大些,用框选工具测出像素宽度大于map.image.width即可
+        map.magFilter = THREE.NearestFilter //试过 放大后可以很清晰
+        
+        this.sprite = new Sprite( Object.assign({
+                root:this
+            }
+            ,options,
+            { 
+                map,
+            })
+        )
+        this.add(this.sprite)
+        
+        this.fontWeight = options.fontWeight == void 0 ? 'Bold' : options.fontWeight
+		this.rectBorderThick = options.rectBorderThick || 0
+		this.textBorderThick = options.textBorderThick || 0 
+		this.fontface = 'Arial';
+		this.fontsize = options.fontsize ||  16; 
+        this.lineSpace = options.lineSpace 
+        this.textBorderColor = options.textBorderColor ? Common.CloneObject(options.textBorderColor):{ r: 0, g: 0, b: 0, a: 1.0 };
+		this.backgroundColor = options.backgroundColor ? Common.CloneObject(options.backgroundColor):{ r: 255, g: 255, b: 255, a: 1.0 };
+		this.textColor = options.textColor ? Common.CloneObject(options.textColor):{r: 0, g: 0, b: 0, a: 1.0};
+        this.borderColor = options.borderColor  ? Common.CloneObject(options.borderColor):{ r: 0, g: 0, b: 0, a: 0.0 };
+		this.borderRadius = options.borderRadius || 6;
+        this.margin = options.margin
+        this.textAlign = options.textAlign || 'center'
+        this.name = options.name
+        this.transform2Dpercent = options.transform2Dpercent
+        this.maxLineWidth = options.maxLineWidth
+        this.setText(options.text)
+          
+	}
+
+	setText(text){  
+        if(text == void 0)text = ''
+        if (this.text !== text) {
+            if (!(text instanceof Array)) {
+                this.text = (text + '').split('\n')   //如果是input手动输入的\n这里会是\\n且不会被拆分, 绘制的依然是\n。
+                //this.text = [text + '']
+            } else this.text = text
+            this.updateTexture() 
+        }
+	}
+/* setText(text){  
+        if(text == void 0)text = ''
+        if (this.text !== text) {
+            if (!(text instanceof Array)) {
+                this.text = text.split('\n')  //如果是input手动输入的\n这里会是\\n且不会被拆分, 绘制的依然是\n。
+                if(this.maxRowWordsCount){//每行显示最大字数
+                    this.text.forEach(str=>{
+                        if(str.length > this.maxRowWordsCount){
+                            
+                        }
+                    })
+                }
+                
+                //this.text = [text + '']
+            } else this.text = text
+            this.updateTexture() 
+        }
+	} */
+	setTextColor(color){
+		this.textColor = Common.CloneObject(color); 
+		this.updateTexture();
+	}
+
+	setBorderColor(color){
+		this.borderColor =  Common.CloneObject(color);
+
+		this.updateTexture();
+	}
+
+	setBackgroundColor(color){
+		this.backgroundColor =  Common.CloneObject(color);
+
+		this.updateTexture();
+	}
+    setPos(pos){
+        this.position.copy(pos)
+        this.sprite.waitUpdate() 
+    }
+    
+    updatePose(){
+        this.sprite.waitUpdate()
+    }  
+    
+    setUniforms(name,value){
+        this.sprite.setUniforms(name,value)
+    }
+	 
+    
+    
+    updateTexture(){
+        //canvas原点在左上角
+		let canvas = document.createElement('canvas');
+		let context = canvas.getContext('2d');
+         
+         
+        const r = window.devicePixelRatio //不乘会模糊
+		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface;  //context["font-weight"] = 100; //语法与 CSS font 属性相同。
+	 
+        
+        let textMaxWidth = 0, infos = [] 
+        context.textBaseline = 'alphabetic'  //  "middle"  //设置文字基线。当起点y设置为0时,只有该线以下的部分被绘制出来。middle时文字显示一半(但是对该字体所有字的一半,有的字是不一定显示一半的,尤其汉字),alphabetic时是英文字母的那条基线。
+                     
+        let textHeightAll = 0   
+
+        let texts = []
+        if(this.maxLineWidth){
+            this.text.forEach((words)=>{
+                if(!words){texts.push("");return;}
+                texts = texts.concat( breakLinesForCanvas( words,  context, this.maxLineWidth ) )
+            }) 
+        }else{
+            texts = this.text
+        }
+
+        
+        for (let text of texts) {
+            let metrics = context.measureText(text)
+            let textWidth = metrics.width
+            infos.push(metrics)
+            textMaxWidth = Math.max(textMaxWidth, textWidth)
+            textHeightAll += this.fontsize //metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent //文字真实高度
+        }
+         
+		let margin = (this.margin ? new THREE.Vector2().copy(this.margin) : new THREE.Vector2(this.fontsize,  this.fontsize * 0.8 )).multiplyScalar(r); 
+        
+        const lineSpace = (this.lineSpace || this.fontsize * 0.5) * r
+        let rectBorderThick = this.rectBorderThick * r,   
+            textBorderThick = this.textBorderThick * r   
+            
+        let spriteWidth = 2 * (margin.x + rectBorderThick + textBorderThick ) + textMaxWidth //还要考虑this.textshadowColor,太麻烦了不写了
+		let spriteHeight = 2 * (margin.y + rectBorderThick + textBorderThick * texts.length) + lineSpace * (texts.length - 1)+ textHeightAll; 
+        
+        //canvas宽高只会向下取整数,所以为了防止拉伸模糊这里必须先取整
+        spriteWidth = Math.floor(spriteWidth)
+        spriteHeight = Math.floor(spriteHeight)
+         
+        
+        context.canvas.width = spriteWidth;
+		context.canvas.height = spriteHeight;
+		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; //为何要再写一遍??
+        
+        if(spriteWidth>4000){
+            console.error('spriteWidth',spriteWidth,'spriteHeight',spriteHeight,this.fontsize,r,texts,margin)
+        }
+ 
+        
+        let expand = 0//Math.max(1, Math.pow(this.fontsize / 16, 1.1)) * r  // 针对英文大部分在baseLine之上所以降低一点,或者可以识别当不包含jgqp时才加这个值 . 但即使都是汉字也会不同,如"哈哈"和"粉色",前者居中后者不
+         
+       
+        context.strokeStyle = 'rgba(' + this.borderColor.r + ',' + this.borderColor.g + ',' + this.borderColor.b + ',' + this.borderColor.a + ')';
+      
+        context.lineWidth = rectBorderThick 
+		context.fillStyle = 'rgba(' + this.backgroundColor.r + ',' + this.backgroundColor.g + ',' + this.backgroundColor.b + ',' + this.backgroundColor.a + ')';
+        this.roundRect(context, rectBorderThick / 2 , rectBorderThick / 2, spriteWidth - rectBorderThick, spriteHeight - rectBorderThick, this.borderRadius * r);
+        
+        
+        context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' + this.textColor.b + ',' + this.textColor.a + ')'
+  
+        let y = margin.y + rectBorderThick 
+        for (let i = 0; i < texts.length; i++) { 
+            //文字y向距离从textBaseline向上算
+            let actualBoundingBoxAscent = infos[i].fontBoundingBoxAscent == void 0 ? this.fontsize * r * 0.8 : infos[i].fontBoundingBoxAscent //有的流览器没有。只能大概给一个
+            y += actualBoundingBoxAscent + textBorderThick
+        
+            let textLeftSpace = this.textAlign == 'center' ? (textMaxWidth - infos[i].width) / 2 : this.textAlign == 'left' ? 0  :  textMaxWidth - infos[i].width
+            let x = rectBorderThick + textBorderThick + 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 * r
+                context.strokeText(texts[i], x, y)
+            }
+               
+              
+            if (this.textshadowColor) {
+                context.shadowOffsetX = 0
+                context.shadowOffsetY = 0
+                context.shadowColor = this.textshadowColor //'red'
+                context.shadowBlur = (this.textShadowBlur || this.fontsize/3) * r
+            }
+            context.fillText(texts[i], x, y)
+            
+             
+            let actualBoundingBoxDescent = infos[i].fontBoundingBoxDescent == void 0 ? this.fontsize * r * 0.2 : infos[i].fontBoundingBoxDescent  
+            y += actualBoundingBoxDescent + textBorderThick + lineSpace
+        } 
+        
+		let texture = new THREE.Texture(canvas);
+		texture.minFilter = THREE.LinearFilter;
+		texture.magFilter = THREE.LinearFilter;
+		texture.needsUpdate = true;
+		//this.material.needsUpdate = true; 
+        
+        if(this.sprite.material.map){
+            this.sprite.material.map.dispose()
+        }
+		this.sprite.material.map = texture;
+		 
+
+        let oldScale = this.sprite.scale.clone()  
+		this.sprite.scale.set(spriteWidth * 0.01 / r, spriteHeight * 0.01 / r, 1.0);
+        
+        if(!oldScale.equals(this.sprite.scale)){
+            this.updateTransform2D()
+            this.sprite.waitUpdate() //重新计算各个viewport的matrix 
+            
+        }
+    }
+    
+	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();
+	}
+    
+    updateTransform2D(){
+        if(this.transform2Dpercent){
+            ['x','y'].forEach((axis)=>{
+                let percent = this.transform2Dpercent[axis]
+                this.sprite.position.y = this.sprite.scale.y * percent
+            }) 
+        } 
+    }
+    
+    dispose(){
+        this.sprite.material.map.dispose()
+        this.parent && this.parent.remove(this)
+        this.sprite.dispose()
+        this.removeAllListeners()
+        this.dispatchEvent('dispose')
+    }
+
+}
+
+
+function findBreakPoint(text, width, context) {
+    var min = 0;
+    var max = text.length - 1;
+
+    while (min <= max) {
+        var middle = Math.floor((min + max) / 2);
+        var middleWidth = context.measureText(text.substr(0, middle)).width;
+        var oneCharWiderThanMiddleWidth = context.measureText(text.substr(0, middle + 1)).width;
+        if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
+            return middle;
+        }
+        if (middleWidth < width) {
+            min = middle + 1;
+        } else {
+            max = middle - 1;
+        }
+    }
+
+    return -1;
+}
+
+function breakLinesForCanvas(text, context, width, font) {  
+    var result = [];
+    var breakPoint = 0;
+
+    if (font) {
+        context.font = font;
+    }
+
+    while ((breakPoint = findBreakPoint(text, width, context)) !== -1) {
+        result.push(text.substr(0, breakPoint));
+        text = text.substr(breakPoint);
+    }
+
+    if (text) {
+        result.push(text);
+    }
+
+    return result;
+} //'使用很寻常的二分查找,如果某一个位置之前的文字宽度小于等于设定的宽度,并且它之后一个字之前的文字宽度大于设定的宽度,那么这个位置就是文本的换行点。上面只是找到一个换行点,对于输入的一段文本,需要循环查找,直到不存在这样的换行点为止, 完整的代码如下',
+
+
+
+//待改进:breakLinesForCanvas后的lineSpace稍小于预设的[]换行的lineSpace, 用于测量标签
+
+/* 
+function wrapText(text, maxWidth) {  
+  // 创建一个 canvas 元素来测量文本宽度  
+  const canvas = document.createElement('canvas');  
+  const ctx = canvas.getContext('2d');  
+
+  // 设置字体样式  
+  ctx.font = '16px Arial';  
+
+  let currentLine = '';  
+  const lines = [];  
+
+  // 拆分文本为单词数组  
+  const words = text.split(' ');  
+
+  for (let i = 0; i < words.length; i++) {  
+    const word = words[i];  
+    const wordWidth = ctx.measureText(word).width;  
+    const currentLineWidth = ctx.measureText(currentLine).width;  
+
+    if (currentLineWidth + wordWidth < maxWidth) {  
+      // 如果当前行加上这个单词不会超出最大宽度,就将它添加到当前行  
+      currentLine += (currentLine ? ' ' : '') + word;  
+    } else {  
+      // 如果当前行加上这个单词会超出最大宽度,就将当前行添加到结果数组,并开始新的一行  
+      lines.push(currentLine.trim());  
+      currentLine = word;  
+    }  
+  }  
+
+  // 添加最后一行  
+  if (currentLine) {  
+    lines.push(currentLine.trim());  
+  }  
+
+  return lines;  
+}  
+
+ */

+ 8 - 0
src/modelViewer/utils/Common.js

@@ -590,6 +590,14 @@ THREE.Object3D.prototype.traverse = function ( callback, lastResult ) {
     } 
 }   
 
+THREE.Object3D.prototype.dealMaterial = function(fun){
+    if(!this.material)return
+    if(this.material instanceof Array){//obj
+        this.material.forEach(fun) 
+    }else{
+        fun(this.material)
+    }
+}
 
 
 function ascSort( a, b ) {