wangfumin 23 小時之前
父節點
當前提交
43482fa58c

+ 122 - 15
小程序入口_嵌套展示端/components/xr-ar-2dmarker/index.js

@@ -12,7 +12,10 @@ Component({
     adlScale: '0 0 0',
     adlPos: '-0.1 -0.5 -0.05',
     sunReady: false,
-    tigerReady: false
+    tigerReady: false,
+    item1Loaded: false,
+    item2Loaded: false,
+    assetsProgress: 0
   },
   lifetimes: {
     attached() {
@@ -23,23 +26,110 @@ Component({
     }
   },
   methods: {
+    emitLoadState(extra) {
+      const payload = Object.assign({
+        arReady: !!this.data.arReady,
+        assetsLoaded: !!this.data.loaded,
+        item1Loaded: !!this.data.item1Loaded,
+        item2Loaded: !!this.data.item2Loaded,
+        assetsProgress: typeof this.data.assetsProgress === 'number' ? this.data.assetsProgress : 0,
+      }, extra || {});
+      this.triggerEvent('loadState', payload);
+    },
+    pauseSunRotationSoft() {
+      if (this.sunRotationTimer) {
+        clearInterval(this.sunRotationTimer);
+        this.sunRotationTimer = null;
+      }
+      this.sunFrameLoading = false;
+    },
+    pauseTigerSoft() {
+      this.stopTigerAppearWatch();
+      if (this.tigerRotationTimer) {
+        clearInterval(this.tigerRotationTimer);
+        this.tigerRotationTimer = null;
+      }
+      this.tigerFrameLoading = false;
+      this.pauseTigerAudio();
+    },
+    pauseTigerAudio() {
+      if (!this.tigerAudioCtx) return;
+      try {
+        this.tigerAudioCtx.pause();
+      } catch (e) {
+      }
+    },
+    resumeTigerSoft() {
+      if (!this.tigerShouldRun) return;
+      if (this.tigerCompleted) return;
+      if (!this.tigerMeshes || !this.tigerMeshes.length) return;
+
+      const visibleMeshes = this.getVisibleTigerMeshes();
+      if (visibleMeshes && visibleMeshes.length) this.tigerTargetMeshes = visibleMeshes;
+
+      if (this.tigerAudioCtx) {
+        try {
+          this.tigerAudioCtx.volume = 1;
+        } catch (e) {
+        }
+        try {
+          this.tigerAudioCtx.play();
+        } catch (e) {
+        }
+      }
+
+      if (this.tigerRotationTimer) return;
+      const intervalMs = this.getTigerFrameIntervalMs();
+      this.tigerRotationTimer = setInterval(() => this.updateTigerFrame(), intervalMs);
+    },
+    tryStartEntryAnimation() {
+      if (!this._isTracking) return;
+      if (!this.data.loaded) return;
+      if (this.data.isStartPlay1) return;
+      if (!this.animator1 || !this.animator2) return;
+      this.setData({ isStartPlay1: true }, () => {
+        this.playitem1Action();
+      });
+    },
     handleReady({
       detail
     }) {
       const xrScene = this.scene = detail.value;
       // console.log('xr-scene', xrScene);
+      this.emitLoadState({ sceneReady: true });
     },
     handleAssetsProgress: function ({
       detail
     }) {
-      // console.log('assets progress', detail.value);
+      const v = detail && detail.value;
+      let progress = 0;
+      if (v && typeof v.progress === 'number') {
+        progress = Math.round(Math.max(0, Math.min(1, v.progress)) * 100);
+      } else if (v && typeof v.loaded === 'number' && typeof v.total === 'number' && v.total > 0) {
+        progress = Math.round(Math.max(0, Math.min(1, v.loaded / v.total)) * 100);
+      }
+
+      const now = Date.now();
+      if (!this._lastProgressEmitAt) this._lastProgressEmitAt = 0;
+      if (!this._lastProgressValue && this._lastProgressValue !== 0) this._lastProgressValue = -1;
+
+      const shouldEmit = (progress === 100) || (progress !== this._lastProgressValue && now - this._lastProgressEmitAt >= 120);
+      if (!shouldEmit) return;
+
+      this._lastProgressEmitAt = now;
+      this._lastProgressValue = progress;
+      this.setData({ assetsProgress: progress }, () => this.emitLoadState());
     },
     handleAssetsLoaded: function ({
       detail
     }) {
       console.log('assets loaded', detail.value);
       this.setData({
-        loaded: true
+        loaded: true,
+        assetsProgress: 100
+      }, () => {
+        this.emitLoadState();
+        if (this._pendingPlay || this._isTracking) this.play();
       });
     },
     handleARReady: function ({
@@ -48,6 +138,8 @@ Component({
       console.log('arReady');
       this.setData({
         arReady: true
+      }, () => {
+        this.emitLoadState();
       })
     },
     handleItem1Loaded({ detail }) {
@@ -65,11 +157,20 @@ Component({
       this.tigerMeshes = this.resolveTigerMeshesFromBG(gltf);
       this.setData({ tigerReady: !!(this.tigerMeshes && this.tigerMeshes.length) });
       if (this.tigerShouldRun && this.tigerStartRequested) this.scheduleTigerStart();
+
+      if (!this.data.item1Loaded) {
+        this.setData({ item1Loaded: true }, () => this.emitLoadState());
+      }
+      this.tryStartEntryAnimation();
     },
     handleItem2Loaded({ detail }) {
       const el = detail.value.target;
       const animator = el.getComponent("animator");
       this.animator2 = animator;
+      if (!this.data.item2Loaded) {
+        this.setData({ item2Loaded: true }, () => this.emitLoadState());
+      }
+      this.tryStartEntryAnimation();
     },
     handleARTrackerState1({
       detail
@@ -82,6 +183,8 @@ Component({
       const {
         state,
       } = tracker;
+      this._lastTrackerState = state;
+      this._isTracking = state == 2;
       if (state == 2) {
         this.play()
       } else {
@@ -89,28 +192,30 @@ Component({
       }
     },
     play() {
-      if (!this.data.loaded) return
+      if (!this.data.loaded) {
+        this._pendingPlay = true;
+        return;
+      }
+      this._pendingPlay = false;
       this.sunShouldRun = true;
       this.startSunRotation();
       this.tigerShouldRun = true;
-      this.tigerStartRequested = false;
+      if (!this.data.isStartPlay1) this.tigerStartRequested = false;
       this.prepareTigerAudio();
       this.prepareTigerFrames();
-
-      if (!this.data.isStartPlay1) {
-        this.setData({
-          isStartPlay1: true
-        }, () => {
-          this.playitem1Action()
-        })
+      this.tryStartEntryAnimation();
+      if (this.tigerHasStarted) {
+        this.resumeTigerSoft();
+      } else if (this.tigerStartRequested) {
+        this.scheduleTigerStart();
       }
     },
     pause() {
       this.sunShouldRun = false;
-      this.stopSunRotation();
+      this.pauseSunRotationSoft();
       this.tigerShouldRun = false;
-      this.tigerStartRequested = false;
-      this.stopTiger();
+      this._pendingPlay = false;
+      this.pauseTigerSoft();
     },
     resolveSunMeshesFromBG(gltf) {
       if (!gltf || !this.scene) return [];
@@ -360,6 +465,7 @@ Component({
       if (this.tigerCompleted) return;
       if (this.tigerRotationTimer) return;
 
+      this.tigerHasStarted = true;
       this.tigerFrameStart = 1;
       this.tigerFrameEnd = 460;
       this.tigerFrameCount = 460;
@@ -390,6 +496,7 @@ Component({
       }
       this.tigerFrameLoading = false;
       this.tigerTargetMeshes = null;
+      this.tigerHasStarted = false;
       if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.tigerTextureQueue) {
         for (const assetId of this.tigerTextureQueue) {
           if (assetId === this.tigerLastAssetId) continue;

+ 38 - 2
小程序入口_嵌套展示端/pages/ar/index.js

@@ -25,7 +25,43 @@ Page({
   behaviors: [sceneReadyBehavior],
   data: {
     xmlCode: '<div class="codeWrap">' + handleDecodedXML(xmlCode) + '</div>',
-    markerImg: 'https://ossxiaoan.4dage.com/hq-eduction-vr/hq-bag.jpg'
+    markerImg: 'https://ossxiaoan.4dage.com/hq-eduction-vr/hq-bag.jpg',
+    showLoading: true,
+    loadingText: '正在初始化相机…',
+    loadingProgress: 0,
+    _wxLoadingText: ''
+  },
+  onUnload() {
+    if (wx.hideLoading) wx.hideLoading();
+  },
+  handleLoadState(e) {
+    const detail = (e && e.detail) || {};
+    const arReady = !!detail.arReady;
+    const assetsLoaded = !!detail.assetsLoaded;
+    const item1Loaded = !!detail.item1Loaded;
+    const item2Loaded = !!detail.item2Loaded;
+    const p = typeof detail.assetsProgress === 'number' ? detail.assetsProgress : 0;
+
+    let loadingText = this.data.loadingText;
+    if (!arReady) loadingText = '';
+    else if (!assetsLoaded) loadingText = '';
+    else if (!item1Loaded || !item2Loaded) loadingText = '';
+    else loadingText = '';
+
+    const loadingProgress = Math.max(0, Math.min(100, Math.round(p)));
+    const showLoading = !(arReady && assetsLoaded && item1Loaded && item2Loaded);
+
+    this.setData({ showLoading, loadingText, loadingProgress });
+
+    if (showLoading) {
+      if (wx.showLoading && loadingText !== this.data._wxLoadingText) {
+        wx.showLoading({ title: loadingText, mask: true });
+        this.setData({ _wxLoadingText: loadingText });
+      }
+    } else {
+      if (wx.hideLoading) wx.hideLoading();
+      if (this.data._wxLoadingText) this.setData({ _wxLoadingText: '' });
+    }
   },
   handleChangeMarkerImg: function() {
     wx.chooseMedia({
@@ -42,4 +78,4 @@ Page({
       }
     });
   }
-});
+});

+ 9 - 1
小程序入口_嵌套展示端/pages/ar/index.wxml

@@ -7,5 +7,13 @@
     style="width:{{width}}px;height:{{height}}px;top:{{top}}px;left:{{left}}px;display:block;"
     markerImg="{{markerImg}}"
     bind:arTrackerState="handleARTrackerState"
+    bind:loadState="handleLoadState"
   />
-</view>
+  <view wx:if="{{showLoading}}" class="loading-mask">
+    <view class="loading-card">
+      <view class="loading-spinner"></view>
+      <view class="loading-text">{{loadingText}}</view>
+      <view wx:if="{{loadingProgress >= 0}}" class="loading-progress">{{loadingProgress}}%</view>
+    </view>
+  </view>
+</view>

+ 49 - 1
小程序入口_嵌套展示端/pages/ar/index.wxss

@@ -2,4 +2,52 @@
   position: absolute;
   inset: 0;
   overflow: hidden;
-}
+}
+
+.loading-mask {
+  position: fixed;
+  inset: 0;
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 0, 0, 0.55);
+}
+
+.loading-card {
+  width: 360rpx;
+  padding: 32rpx 28rpx;
+  border-radius: 16rpx;
+  background: rgba(0, 0, 0, 0.6);
+  color: #fff;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.loading-spinner {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  border: 6rpx solid rgba(255, 255, 255, 0.35);
+  border-top-color: #fff;
+  animation: arSpin 0.9s linear infinite;
+}
+
+.loading-text {
+  margin-top: 18rpx;
+  font-size: 28rpx;
+  line-height: 40rpx;
+  text-align: center;
+}
+
+.loading-progress {
+  margin-top: 6rpx;
+  font-size: 24rpx;
+  opacity: 0.8;
+}
+
+@keyframes arSpin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}