3 Commits 88b28ac4dd ... d81c6f1b32

Tác giả SHA1 Thông báo Ngày
  DESKTOP-6LTVLN7\Liumouren d81c6f1b32 Merge branch 'master' of http://47.103.92.60:3003/skyversation/qp_onemap_business_component into lmr 6 ngày trước cách đây
  DESKTOP-6LTVLN7\Liumouren a3493c1c81 Merge branch 'lmr' of http://47.103.92.60:3003/skyversation/qp_onemap_business_component 6 ngày trước cách đây
  ximinghao f0b900f888 实现文旅和日常巡查功能 1 tuần trước cách đây

BIN
public/static/3dt/dm/NoLod_0.b3dm


BIN
public/static/3dt/dm/NoLod_1.b3dm


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
public/static/3dt/dm/scenetree.json


+ 1 - 0
public/static/3dt/dm/tileset.json

@@ -0,0 +1 @@
+{"asset":{"generatetool":"cesiumlab3@www.cesiumlab.com/model2tiles","version":"1.0"},"extras":{"scenetree":"scenetree.json"},"geometricError":1795.090538983699,"properties":null,"refine":"REPLACE","root":{"boundingVolume":{"box":[-2.561137080192566e-09,0.03873231098987162,11.893352137412876,897.5452694918495,0,0,0,881.5318217126187,0,0,0,19.77482459275052]},"children":[{"boundingVolume":{"box":[-0.29028675603694865,0.49269872534478054,12.520279979080865,897.0791948528567,0,0,0,880.8605976438794,0,0,0,19.22942092899382]},"content":{"uri":"NoLod_0.b3dm"},"geometricError":0.0,"refine":"REPLACE"},{"boundingVolume":{"box":[9.6032091904126,226.93783910102684,13.098070526120864,865.3977200685367,0,0,0,609.3464457665132,0,0,0,14.940079243825352]},"content":{"uri":"NoLod_1.b3dm"},"geometricError":0.0,"refine":"REPLACE"}],"geometricError":1795.090538983699,"transform":[-0.8546905861530742,-0.5191377485227928,0.0,0.0,0.26883308396819183,-0.4425975702362635,0.8554742332677863,0.0,-0.44410896737790106,0.731165773870496,0.5178453786748445,0.0,-2835133.282590905,4667666.210008461,3283726.179921346,1.0]}}

BIN
public/static/3dt/jz/NoLod_0.b3dm


BIN
public/static/3dt/jz/NoLod_1.b3dm


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
public/static/3dt/jz/scenetree.json


+ 1 - 0
public/static/3dt/jz/tileset.json

@@ -0,0 +1 @@
+{"asset":{"generatetool":"cesiumlab3@www.cesiumlab.com/model2tiles","version":"1.0"},"extras":{"scenetree":"scenetree.json"},"geometricError":1237.4341955077834,"properties":null,"refine":"REPLACE","root":{"boundingVolume":{"box":[-1.6297008187393658e-09,0.018240597331896424,25.981761089991778,618.7170977538917,0,0,0,357.23421095334925,0,0,0,26.36555195460096]},"children":[{"boundingVolume":{"box":[-1.5632680161015742,-4.59086416278933,26.012095389296068,617.1260951226595,0,0,0,352.44811117062454,0,0,0,26.34451143589285]},"content":{"uri":"NoLod_0.b3dm"},"geometricError":0.0,"refine":"REPLACE"},{"boundingVolume":{"box":[-0.0069375864732137416,21.025877213554395,26.086207012087975,618.6810306408333,0,0,0,336.16262676196084,0,0,0,26.112705691121654]},"content":{"uri":"NoLod_1.b3dm"},"geometricError":0.0,"refine":"REPLACE"}],"geometricError":1237.4341955077834,"transform":[-0.8547020027635365,-0.5191189521410285,0.0,0.0,0.26885557504706703,-0.44265653854310305,0.8554366537921725,0.0,-0.44407337933962054,0.731143421233508,0.517907454424872,0.0,-2834906.7418924444,4667524.581697549,3284120.5619260757,1.0]}}

BIN
public/static/3dt/xp/NoLod_0.b3dm


BIN
public/static/3dt/xp/NoLod_1.b3dm


BIN
public/static/3dt/xp/NoLod_2.b3dm


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
public/static/3dt/xp/scenetree.json


+ 1 - 0
public/static/3dt/xp/tileset.json

@@ -0,0 +1 @@
+{"asset":{"generatetool":"cesiumlab3@www.cesiumlab.com/model2tiles","version":"1.0"},"extras":{"scenetree":"scenetree.json"},"geometricError":1347.201030983124,"properties":null,"refine":"REPLACE","root":{"boundingVolume":{"box":[1.1641532182693481e-09,0.02159971510991454,6.93273348454386,673.600515491562,0,0,0,348.88075639726594,0,0,0,7.228751529939473]},"children":[{"boundingVolume":{"box":[2.2336584741369734,12.303074245379321,7.086508053532938,670.7603081226571,0,0,0,335.94647643840835,0,0,0,7.107564609882218]},"content":{"uri":"NoLod_0.b3dm"},"geometricError":0.0,"refine":"REPLACE"},{"boundingVolume":{"box":[2.3658691304153763,0.008234431730670622,6.349530736251367,671.2116485788574,0,0,0,348.8832576533323,0,0,0,6.6054002914554815]},"content":{"uri":"NoLod_1.b3dm"},"geometricError":0.0,"refine":"REPLACE"},{"boundingVolume":{"box":[0.0032016983968787827,26.106332474379087,6.462461248081784,673.5743160108759,0,0,0,322.7851596106839,0,0,0,6.483163860981218]},"content":{"uri":"NoLod_2.b3dm"},"geometricError":0.0,"refine":"REPLACE"}],"geometricError":1347.201030983124,"transform":[-0.8547054169491544,-0.5191133308226367,0.0,0.0,0.26885363521449795,-0.442659906306307,0.8554355207617003,0.0,-0.44406798248660295,0.731145373445746,0.5179093258661779,0.0,-2834872.3052461958,4667537.070977109,3284132.447709489,1.0]}}

BIN
public/static/image/picture_pltd.jpg


+ 489 - 64
src/views/CityManagement.vue

@@ -1,16 +1,116 @@
 <template>
   <div class="home">
+    <div style="position: fixed; z-index: 1; padding: 5px;" class="compact-control">
+      <button class="roam-btn">
+        <i class="fas fa-play"></i>
+        <span @click="startAnimation">开始模拟</span>
+      </button>
+      <button class="roam-btn">
+        <i class="fas fa-play"></i>
+        <span @click="stopAnimation">结束模拟</span>
+      </button>
+    </div>
+    <div v-if="endPanelVisible" style="position: fixed; top: 50%; left:50%;transform: translate(-50%, -50%);z-index: 1;" class="compact-control">
+      <div class="control-header">
+        <i class="fas fa-tasks"></i>
+        <span>任务完成提示</span>
+      </div>
+
+      <div class="completion-message">
+        <h3>事件处置报告。</h3>
+        <p>事件处置完成!</p>
+      </div>
+
+      <div class="completion-actions">
+        <button class="tile-btn" @click="() => { endPanelVisible = false; }">确定</button>
+      </div>
+    </div>
     <div id="skysceneryContainer"></div>
   </div>
 </template>
 
 <script>
+
 export default {
   mounted() {
     window.SkySceneryConfig = {};
+    this.staticInitData();
     this.getToken();
   },
+  data() {
+    return {
+      //init 数据
+      carModelPath: undefined,
+      humanAModelPath: undefined,
+      humanBModelPath: undefined,
+      carRouteNodes: [],
+      humanARouteNodes: undefined,
+      humanBLocation: undefined,
+      humanASpeakText: [],
+      humanBSpeakText: [],
+      tileProviderUrl: undefined,
+      //运行时各种引用的Holder
+      runningClockTickEventRemover: undefined,
+      //运行时数据
+      isRunning: false,
+      carStartTime: null,
+      carStopTime: null,
+      humanAStartTime: null,
+      humanAStopTime: null,
+      talkStartTime: null,
+      talkStopTime: null,
+      endPanelVisible: false
+    };
+  },
   methods: {
+    staticInitData() {
+      this.carModelPath = "http://121.43.55.7:65456/model/车辆/警车.gltf";
+      this.humanAModelPath = "http://121.43.55.7:65456/model/人物/巡逻人员.glb";
+      this.humanBModelPath = "http://121.43.55.7:65456/model/人物/行人.gltf";
+      this.carRouteNodes = [
+
+        [121.1055674641875, 31.14948997298628],
+        [121.1049455557126, 31.149625603510888],
+        [121.10453307504021, 31.148607047068204],
+        [121.1042215514566, 31.148643139863402]
+      ];
+      this.humanARouteNodes = [[121.10422192119506, 31.14864943768712], [121.10422766810703, 31.14874032312098]];
+      this.humanBLocation = [121.10422908795015, 31.148759090201995];
+      this.humanASpeakText = [
+        {
+          startTime: 2.5,
+          endTime: 5,
+          text: "您好,我们是市城管局的巡查人员。观察到您在此处设摊经营,该区域属于禁止摆摊区域。"
+        },
+        {
+          startTime: 10.5,
+          endTime: 16,
+          text: "理解您的困难,但人行道摆摊会影响市民通行,也存在食品安全隐患。我们可以引导您到正规市场经营。"
+        },
+        {
+          startTime: 25,
+          endTime: 29,
+          text: "这是本市便民摊点分布图,东区市场目前有免费入驻政策,我们现在就可以帮您联系管理方。"
+        }
+      ];
+      this.humanBSpeakText = [
+        {
+          startTime: 6,
+          endTime: 8.5,
+          text: "同志您好,我就是临时卖点自家水果,不知道这里不能摆摊,最近家里确实比较困难..."
+        },
+        {
+          startTime: 17.5,
+          endTime: 21.5,
+          text: "正规市场摊位费太贵了,我们小本生意实在承担不起。能不能通融一下?我保证不影响通行。"
+        },
+        {
+          startTime: 31,
+          endTime: 35,
+          text: "谢谢您!太好了,我这就收拾东西。以后一定按规定经营,给您添麻烦了。"
+        }];
+        this.tileProviderUrl = "https://szlszxdt.qpservice.org.cn/internal_map/?servertype=shmap_blue_web&proxyToken=C4BCA7C6-DF66-4A7D-2931-A258DFEFF8AB"
+    },
     getToken() {
       let that = this;
       let loginInfo = new FormData();
@@ -105,88 +205,276 @@ export default {
 
       // 添加地图服务
       viewer.imageryLayers.addImageryProvider(
-        // 加载 上海2000坐标系 瓦片服务
-        // new SkyScenery.CGCS2000ArcGisMapServerImageryProvider({
-        //   url:
-        //     "http://10.235.245.174:10092/proxy/?servertype=air_2023&proxyToken=" +
-        //     SkySceneryConfig.token
-        // })
         new SkyScenery.ArcGisMapServerImageryProvider({
-          url: "https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer"
+          url: this.tileProviderUrl,
         })
       );
+      // 添加点
+      this.initAnimal();
+    },
+    initAnimal() {
+      //init route points
+      const carRoutePoints = this.carRouteNodes.map(item => {
+        return SkyScenery.Cartesian3.fromDegrees(item[0], item[1], item[2]);
+      });
+      const humanARoutePoints = this.humanARouteNodes.map(item => {
+        return SkyScenery.Cartesian3.fromDegrees(item[0], item[1], item[2] || 0);
+      });
+      const humanBPosition = SkyScenery.Cartesian3.fromDegrees(this.humanBLocation[0], this.humanBLocation[1], this.humanBLocation[2] || 0);
+
+      //定义一些方向计算用的量
+
+      const fix90 = SkyScenery.Quaternion.fromAxisAngle(SkyScenery.Cartesian3.UNIT_Z, Math.PI / 2);
+      const fix270 = SkyScenery.Quaternion.conjugate(fix90, new SkyScenery.Quaternion());
+
+      //generate time line
+      const startTime = SkyScenery.JulianDate.fromIso8601('2000-01-01T12:00:00+08:00');
+      const carDuration = 10; //seconds
+      const humanAWalkDuration = 4; //seconds
+      const talkDuration = [...this.humanASpeakText, ...this.humanBSpeakText].reduce((total, text) => {
+        return total > text.endTime ? total : text.endTime;
+      }, 0) + 3; //seconds
+      const totalDuration = carDuration + 1 + humanAWalkDuration + talkDuration;
+
+      this.carStartTime = SkyScenery.JulianDate.clone(startTime);
+      this.carStopTime = SkyScenery.JulianDate.addSeconds(this.carStartTime, carDuration, new SkyScenery.JulianDate());
+      this.humanAStartTime = SkyScenery.JulianDate.addSeconds(this.carStopTime, 1, new SkyScenery.JulianDate());
+      this.humanAStopTime = SkyScenery.JulianDate.addSeconds(this.humanAStartTime, humanAWalkDuration, new SkyScenery.JulianDate());
+      this.talkStartTime = SkyScenery.JulianDate.clone(this.humanAStopTime);
+      this.talkStopTime = SkyScenery.JulianDate.addSeconds(this.talkStartTime, talkDuration, new SkyScenery.JulianDate());
+
+      const stopTime = SkyScenery.JulianDate.clone(this.talkStopTime);
+
+      //init clock
+      viewer.clock.startTime = startTime.clone();
+      viewer.clock.stopTime = stopTime.clone();
+
+      viewer.clock.multiplier = 1; //播放速度
+      viewer.clock.clockRange = SkyScenery.ClockRange.CLAMPED; //到达终点后停止
 
-      // 定位
-      viewer.camera.setView({
-        destination: SkyScenery.Cartesian3.fromDegrees(121, 31, 30000.0), // 设置位置
-        orientation: {
-          heading: SkyScenery.Math.toRadians(0.0), // 方向
-          pitch: SkyScenery.Math.toRadians(-90.0), // 倾斜角度
-          roll: 0
+      viewer.clock.currentTime = viewer.clock.startTime.clone();
+      viewer.clock.shouldAnimate = false; //是否自动播放
+      //part 1: car
+      //计算车的路线点距离和时间
+      let routePointdistaces = [0];
+      let totalDistance = 0;
+      for (let i = 1; i < carRoutePoints.length; i++) {
+        const distance = SkyScenery.Cartesian3.distance(carRoutePoints[i - 1], carRoutePoints[i]);
+        routePointdistaces.push(distance);
+        totalDistance += distance;
+      }
+      let unitDistanceTime = carDuration / totalDistance;
+      //生成车的路线采样点
+      const carPosition = new SkyScenery.SampledPositionProperty();
+      let carTimeAdder = 0;
+      carRoutePoints.forEach((point, index) => {
+        let timesecend = routePointdistaces[index] * unitDistanceTime;
+        carTimeAdder += timesecend;
+        const time = SkyScenery.JulianDate.addSeconds(this.carStartTime, carTimeAdder, new SkyScenery.JulianDate());
+        carPosition.addSample(time, point);
+      });
+      carPosition.forwardExtrapolationType = SkyScenery.ExtrapolationType.HOLD;
+      carPosition.backwardExtrapolationType = SkyScenery.ExtrapolationType.HOLD;
+      carPosition.setInterpolationOptions({
+        interpolationDegree: 1,
+        interpolationAlgorithm: SkyScenery.LinearApproximation
+      });
+      const carEntity = viewer.entities.add({
+        id: 'car',
+        name: 'car',
+        position: carPosition,
+        viewFrom: new SkyScenery.Cartesian3(24.0, -20.0, 12.0),
+        model: {
+          uri: this.carModelPath,
+          minimumPixelSize: 64,
+          maximumScale: 200
+        }
+      })
+      const carVelOrient = new SkyScenery.VelocityOrientationProperty(carPosition);
+      let carLastOrient = SkyScenery.Quaternion.IDENTITY.clone();
+      carEntity.orientation = new SkyScenery.CallbackProperty(function (time) {
+        let qVel;
+        qVel = carVelOrient.getValue(time);
+        if (qVel != null) {                    // 有速度,正常算
+          SkyScenery.Quaternion.clone(qVel, carLastOrient); // 留一份
+        } else {                               // 零速,用缓存
+          qVel = carLastOrient;
         }
+        return SkyScenery.Quaternion.multiply(qVel, fix270, new SkyScenery.Quaternion());
+      }, false);
+      //初始即跟踪车辆
+      viewer.trackedEntity = viewer.entities.getById('car');
+
+      //part 2: human A walk
+      //计算人A的路线点距离和时间
+      routePointdistaces = [0];
+      totalDistance = 0;
+      for (let i = 1; i < humanARoutePoints.length; i++) {
+        const distance = SkyScenery.Cartesian3.distance(humanARoutePoints[i - 1], humanARoutePoints[i]);
+        routePointdistaces.push(distance);
+        totalDistance += distance;
+      }
+      unitDistanceTime = humanAWalkDuration / totalDistance;
+      //生成人A的路线采样点
+      const humanAPosition = new SkyScenery.SampledPositionProperty();
+      let humanATimeAdder = 0;
+      humanARoutePoints.forEach((point, index) => {
+        let timesecend = routePointdistaces[index] * unitDistanceTime;
+        humanATimeAdder += timesecend;
+        const time = SkyScenery.JulianDate.addSeconds(this.humanAStartTime, humanATimeAdder, new SkyScenery.JulianDate());
+        humanAPosition.addSample(time, point);
       });
-      // 添加点
-      viewer.entities.add({
-        name: "点",
-        position: SkyScenery.Cartesian3.fromDegrees(121.1, 31), //经纬度转世界坐标
-        point: {
-          show: true,
-          color: SkyScenery.Color.GREEN,
-          pixelSize: 20,
-          outlineColor: SkyScenery.Color.YELLOW,
-          outlineWidth: 3
+
+      humanAPosition.forwardExtrapolationType = SkyScenery.ExtrapolationType.HOLD;
+      humanAPosition.backwardExtrapolationType = SkyScenery.ExtrapolationType.NONE;
+      humanAPosition.setInterpolationOptions({
+        interpolationDegree: 1,
+        interpolationAlgorithm: SkyScenery.LinearApproximation
+      });
+
+      const humanAEntity = viewer.entities.add({
+        id: 'humanA',
+        name: 'humanA',
+        position: humanAPosition,
+        model: {
+          uri: this.humanAModelPath,
+          minimumPixelSize: 32,
+          maximumScale: 20,
+          runAnimations: false
         },
         label: {
-          text: "这里是标签", //设置文字内容
           font: "normal 18px 楷体", //设置文字大小和字体
-          fillColor: SkyScenery.Color.fromCssColorString("#00ff00"), //设置文字填充的颜色
+          fillColor: SkyScenery.Color.fromCssColorString("#f0f0f0"), //设置文字填充的颜色
           horizontalOrigin: SkyScenery.HorizontalOrigin.CENTER,
           verticalOrigin: SkyScenery.VerticalOrigin.BOTTOM,
-          pixelOffset: new SkyScenery.Cartesian2(0, -20)
-        }
+          pixelOffset: new SkyScenery.Cartesian2(0, -120),
+          showBackground: true,
+          backgroundColor: SkyScenery.Color.fromCssColorString("#00000080"),
+        },
+        viewFrom: new SkyScenery.Cartesian3(24.0, -20.0, 12.0)
       });
-      // 添加图片点
-      viewer.entities.add({
-        position: SkyScenery.Cartesian3.fromDegrees(121.2, 31),
-        billboard: {
-          image: "/static/image/point.png", // 图片地址
-          width: 48, // 图片宽
-          height: 48, // 图片高
-          show: true,
-          horizontalOrigin: SkyScenery.HorizontalOrigin.CENTER,
-          verticalOrigin: SkyScenery.VerticalOrigin.BOTTOM
+      const humanAVelOrient = new SkyScenery.VelocityOrientationProperty(humanAPosition);
+      let humanALastOrient = SkyScenery.Quaternion.IDENTITY.clone();
+      humanAEntity.orientation = new SkyScenery.CallbackProperty(function (time) {
+        let qVel;
+        qVel = humanAVelOrient.getValue(time);
+        if (qVel != null) {                    // 有速度,正常算
+          SkyScenery.Quaternion.clone(qVel, humanALastOrient); // 留一份
+        } else {                               // 零速,用缓存
+          qVel = humanALastOrient;
+        }
+        return qVel;
+      }, false);
+      //part 3: human B stand
+      const position = SkyScenery.Cartesian3.fromDegrees(this.humanBLocation[0], this.humanBLocation[1], this.humanBLocation[2] || 0);
+
+      const humanBEntity = viewer.entities.add({
+        id: 'humanB',
+        name: 'humanB',
+        position: position,
+        orientation: SkyScenery.Transforms.headingPitchRollQuaternion(position, new SkyScenery.HeadingPitchRoll(SkyScenery.Math.toRadians(90), 0, 0)),
+        model: {
+          uri: this.humanBModelPath,
+          minimumPixelSize: 32,
+          maximumScale: 20,
+          runAnimations: false
         },
         label: {
-          text: "这是自定义图片", // 设置文字内容
-          font: "normal 18px 楷体", // 设置文字大小和字体
-          fillColor: SkyScenery.Color.fromCssColorString("#00ff00"), //设置文字填充的颜色
+          font: "normal 18px 楷体", //设置文字大小和字体
+          fillColor: SkyScenery.Color.fromCssColorString("#f0f0f0"), //设置文字填充的颜色
           horizontalOrigin: SkyScenery.HorizontalOrigin.CENTER,
           verticalOrigin: SkyScenery.VerticalOrigin.BOTTOM,
-          pixelOffset: new SkyScenery.Cartesian2(0, -50)
+          pixelOffset: new SkyScenery.Cartesian2(0, -120),
+          showBackground: true,
+          backgroundColor: SkyScenery.Color.fromCssColorString("#00000080"),
         }
       });
-      let positions = [
-        [121.04829640102727, 31.12735759260756],
-        [121.05219953077487, 31.126058264888133],
-        [121.0556573133586, 31.125143117515066],
-        [121.0585577885634, 31.12489903339664],
-        [121.05855842602281, 31.117437091290032],
-        [121.05755469646111, 31.116865171387122],
-        [121.05855867110577, 31.114567660254956],
-        [121.05716503781966, 31.111412856109656],
-        [121.05169919616742, 31.109414349328368],
-        [121.04846566530048, 31.10731617798831],
-        [121.04523257218045, 31.105074455848328],
-        [121.04372646220537, 31.108713847196935],
-        [121.04238895034425, 31.11239990163383],
-        [121.04216536839769, 31.11503097997496],
-        [121.03965751482633, 31.117141773623022],
-        [121.0392664980893, 31.119247569299414],
-        [121.0401584271992, 31.124267692010815],
-        [121.04534263782213, 31.124207732078528],
-        [121.04813004860797, 31.124919266167907],
-        [121.04829640102727, 31.12735759260756]
-      ];
+    },
+    startAnimation() {
+      if (this.isRunning) {
+        return;
+      }
+      this.isRunning = true;
+
+      const that = this;
+
+      viewer.clock.currentTime = viewer.clock.startTime.clone();
+      viewer.clock.shouldAnimate = true; //开始动画
+
+      this.runningClockTickEventRemover = viewer.clock.onTick.addEventListener(function (clock) {
+
+        //set stopper
+        if (SkyScenery.JulianDate.greaterThanOrEquals(clock.currentTime, clock.stopTime)) {
+          that.stopAnimation(true);
+          that.nomalyEnd();
+        }
+        const car = viewer.entities.getById('car');
+        //track entity
+        if (SkyScenery.JulianDate.greaterThan(clock.currentTime, clock.startTime) && SkyScenery.JulianDate.lessThan(clock.currentTime, that.carStopTime)) {//fast then car stop
+          viewer.trackedEntity = car;
+          car.model.runAnimations = true;
+        } else if (SkyScenery.JulianDate.lessThan(clock.currentTime, that.humanAStopTime)) {//fast then human A stop
+          viewer.trackedEntity = viewer.entities.getById('humanA');
+          
+          car.model.runAnimations = false;
+        } else if (SkyScenery.JulianDate.lessThan(clock.currentTime, that.talkStopTime)) {//fast then talk stop
+          viewer.trackedEntity = viewer.entities.getById('humanA');
+          
+          car.model.runAnimations = false;
+        } else {
+          viewer.trackedEntity = undefined;
+          
+          car.model.runAnimations = false;
+        }
+        //show talk
+        const talkerA = viewer.entities.getById('humanA');
+        const talkerB = viewer.entities.getById('humanB');
+        if (SkyScenery.JulianDate.greaterThan(clock.currentTime, that.talkStartTime) && SkyScenery.JulianDate.lessThan(clock.currentTime, that.talkStopTime)) {
+          const talkTimeSec = SkyScenery.JulianDate.secondsDifference(clock.currentTime, that.talkStartTime);
+          let talkTextA = "";
+          let talkTextB = "";
+          that.humanASpeakText.forEach(text => {
+            if (talkTimeSec >= text.startTime && talkTimeSec <= text.endTime) {
+              talkTextA = text.text;
+            }
+          });
+          that.humanBSpeakText.forEach(text => {
+            if (talkTimeSec >= text.startTime && talkTimeSec <= text.endTime) {
+              talkTextB = text.text;
+            }
+          });
+          if (talkTextA) {
+            talkerA.label.text = talkTextA;
+          } else {
+            talkerA.label.text = "";
+          }
+          if (talkTextB) {
+            talkerB.label.text = talkTextB;
+          } else {
+            talkerB.label.text = "";
+          }
+        } else {
+          talkerA.label.text = "";
+          talkerB.label.text = "";
+        }
+
+      });
+    },
+    stopAnimation(force) {
+      if (!(force || this.isRunning)) {
+        return;
+      }
+      this.isRunning = false;
+
+      viewer.clock.shouldAnimate = false; //停止动画
+
+      if (this.runningClockTickEventRemover) {
+        this.runningClockTickEventRemover();
+        this.runningClockTickEventRemover = undefined;
+      }
+    },
+    nomalyEnd() {
+      this.endPanelVisible = true;
     }
   }
 };
@@ -201,4 +489,141 @@ export default {
     height: 100%;
   }
 }
+
+.compact-control {
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 10px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
+  padding: 12px;
+  width: 360px;
+  display: inline-block;
+  backdrop-filter: blur(5px);
+  border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.control-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #eaeaea;
+}
+
+.control-header i {
+  font-size: 16px;
+  color: #3498db;
+  margin-right: 8px;
+}
+
+.control-header span {
+  font-weight: 600;
+  color: #2c3e50;
+  font-size: 14px;
+}
+
+.tiles-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+  margin-bottom: 10px;
+  max-width: 300px;
+}
+
+.tile-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  background: #3498db;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  padding: 6px 10px;
+  font-size: 13px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  min-width: 40px;
+  height: 28px;
+}
+
+.tile-btn:hover {
+  background: #2980b9;
+  transform: translateY(-1px);
+}
+
+.tile-btn:active {
+  transform: translateY(0);
+}
+
+.roam-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  background: #2ecc71;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  padding: 6px 10px;
+  font-size: 13px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  height: 28px;
+  width: 50%;
+}
+
+.roam-btn:hover {
+  background: #27ae60;
+  transform: translateY(-1px);
+}
+
+.roam-btn:active {
+  transform: translateY(0);
+}
+
+.roam-btn i {
+  margin-right: 5px;
+  font-size: 12px;
+}
+
+.completion-check {
+  display: flex;
+  justify-content: center;
+  margin: 15px 0;
+}
+
+.check-circle {
+  width: 60px;
+  height: 60px;
+  border-radius: 50%;
+  background: #2ecc71;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 28px;
+  box-shadow: 0 4px 10px rgba(46, 204, 113, 0.3);
+}
+
+.completion-message {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.completion-message h3 {
+  color: #2c3e50;
+  margin: 0 0 8px 0;
+  font-size: 18px;
+}
+
+.completion-message p {
+  color: #7f8c8d;
+  margin: 0;
+  font-size: 14px;
+  line-height: 1.5;
+}
+
+.completion-actions {
+  display: flex;
+  justify-content: center;
+  gap: 10px;
+}
 </style>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 47 - 0
src/views/Tourism.vue


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác