chenlei 1 тиждень тому
коміт
80ad767fcc

+ 19 - 0
app.js

@@ -0,0 +1,19 @@
+// app.js
+App({
+  onLaunch() {
+    // 展示本地存储能力
+    const logs = wx.getStorageSync('logs') || []
+    logs.unshift(Date.now())
+    wx.setStorageSync('logs', logs)
+
+    // 登录
+    wx.login({
+      success: res => {
+        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+      }
+    })
+  },
+  globalData: {
+    userInfo: null
+  }
+})

+ 14 - 0
app.json

@@ -0,0 +1,14 @@
+{
+  "pages": [
+    "pages/ar/index"
+  ],
+  "window": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "Weixin",
+    "navigationBarBackgroundColor": "#ffffff"
+  },
+  "style": "v2",
+  "componentFramework": "glass-easel",
+  "sitemapLocation": "sitemap.json",
+  "lazyCodeLoading": "requiredComponents"
+}

+ 10 - 0
app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

+ 48 - 0
components/common/share-behavior.js

@@ -0,0 +1,48 @@
+export default Behavior({
+  created: function () {
+    this.checkInitShare();
+  },
+  methods: {
+    checkInitShare() {
+      wx.xrScene = undefined;
+
+      if (!this.scene) {
+        setTimeout(() => {
+          this.checkInitShare()
+        }, 100);
+        return;
+      }
+
+      if (this.scene.ar) {
+        if (this.scene.ar.ready) {
+          this.initARTrackerState(this.scene);    
+        } else { 
+          this.scene.event.add('ar-ready', () => this.initARTrackerState(this.scene));
+        }
+      }
+
+      if (!this.scene.share.supported) {
+        console.warn('Not support xr-frame share system now!');
+        return;
+      }
+
+      this.sharing = false;
+      wx.xrScene = this.scene;
+    },
+    initARTrackerState(scene) {
+      const xrFrameSystem = wx.getXrFrameSystem();
+      scene.dfs(() => {}, undefined, true, el => {
+        const comp = el.getComponent(xrFrameSystem.ARTracker);
+        if (comp) {
+          if (typeof comp.state === 'number') {
+            this.triggerEvent('arTrackerState', {state: comp.state, error: comp.errorMessage});
+            el.event.add('ar-tracker-state', tracker => {
+              this.triggerEvent('arTrackerState', {state: tracker.state, error: tracker.errorMessage});
+            });
+          }
+          return true;
+        }
+      });
+    }
+  }
+})

+ 182 - 0
components/xr-ar-2dmarker/index.js

@@ -0,0 +1,182 @@
+Component({
+  behaviors: [require('../common/share-behavior').default],
+  properties: {
+    markerImg: {
+      type: String
+    },
+  },
+  data: {
+    loaded: false,
+    arReady: false,
+    isStartPlay1: false,
+    adlScale: '0 0 0',
+    adlPos: '-0.1 -0.5 -0.05'
+  },
+  lifetimes: {
+    attached() {
+    }
+  },
+  methods: {
+    handleReady({
+      detail
+    }) {
+      const xrScene = this.scene = detail.value;
+      // console.log('xr-scene', xrScene);
+    },
+    handleAssetsProgress: function ({
+      detail
+    }) {
+      // console.log('assets progress', detail.value);
+    },
+    handleAssetsLoaded: function ({
+      detail
+    }) {
+      console.log('assets loaded', detail.value);
+      this.setData({
+        loaded: true
+      });
+    },
+    handleARReady: function ({
+      detail
+    }) {
+      console.log('arReady');
+      this.setData({
+        arReady: true
+      })
+    },
+    handleItem1Loaded({ detail }) {
+      const el = detail.value.target;
+      const animator = el.getComponent("animator");
+      this.animator1 = animator
+      console.log('animator1', animator)
+    },
+    handleItem2Loaded({ detail }) {
+      const el = detail.value.target;
+      const animator = el.getComponent("animator");
+      this.animator2 = animator;
+    },
+    handleARTrackerState1({
+      detail
+    }) {
+      // 事件的值即为`ARTracker`实例
+      const tracker = detail.value;
+      // 获取当前状态和错误信息
+      console.log('tracker', tracker)
+
+      const {
+        state,
+      } = tracker;
+      if (state == 2) {
+        this.play()
+      } else {
+        this.pause()
+      }
+    },
+    play() {
+      if (!this.data.loaded) return
+
+      if (!this.data.isStartPlay1) {
+        this.setData({
+          isStartPlay1: true
+        }, () => {
+          this.playitem1Action()
+        })
+      }
+    },
+    pause() {
+
+    },
+    playitem1Action() {
+      if (!this.animator1 || !this.animator2) return;
+
+      console.log('start animator1');
+      this.animator1.play('All Animations', {
+        loop: 0
+      });
+
+      setTimeout(() => {
+        console.log('start animator2');
+        this.animator2.pause();
+
+          const duration = 1500;
+          const startTime = Date.now();
+          const startScale = 0;
+          const endScale = 0.38;
+          const startPX = -0.1
+          const endPX = 0
+          const startPY = -0.5
+          const endPY = 0
+          const startPZ = -0.05
+          const endPZ = 0
+
+          const step = () => {
+            const now = Date.now();
+            let t = (now - startTime) / duration;
+            if (t > 1) t = 1;
+
+            const s = startScale + (endScale - startScale) * t;
+            const scaleStr = `${s} ${s} ${s}`;
+            const p = `${startPX + (endPX - startPX) * t} ${startPY + (endPY - startPY) * t} ${startPZ + (endPZ - startPZ) * t}`;
+
+            if (t >= 0.5 && !this.isStartPlay2) {
+              this.isStartPlay2 = true;
+              this.animator2.play('All Animations', {
+                loop: 0
+              });
+            }
+
+            this.setData({
+              adlScale: scaleStr,
+              adlPos: p
+            });
+
+            if (t < 1) {
+              setTimeout(step, 16); // 60fps
+            } else {
+              const audioUrl = 'https://houseoss.4dkankan.com/project/hq-eduction-vr/public/%E5%AE%89%E5%BE%B7%E7%83%88.mp3';
+              if (!this.audioCtx) {
+                const audioCtx = wx.createInnerAudioContext();
+                audioCtx.src = audioUrl;
+                audioCtx.play();
+                audioCtx.onEnded(() => {
+                  const hideDuration = 1000;
+                  const hideStartTime = Date.now();
+                  const startScaleHide = 0.38;
+                  const endScaleHide = 0;
+
+                  const hideStep = () => {
+                    const now = Date.now();
+                    let t = (now - hideStartTime) / hideDuration;
+                    if (t > 1) t = 1;
+
+                    const s = startScaleHide + (endScaleHide - startScaleHide) * t;
+                    const scaleStr = `${s} ${s} ${s}`;
+                    this.setData({
+                      adlScale: scaleStr
+                    });
+
+                    if (t < 1) {
+                      setTimeout(hideStep, 16);
+                    } else {
+                      this.animator2 = null;
+                      if (this.audioCtx) {
+                        this.audioCtx.destroy();
+                        this.audioCtx = null;
+                      }
+                    }
+                  };
+
+                  hideStep();
+                });
+              }
+            }
+          };
+
+          this.setData({
+            adlScale: '0 0 0'
+          });
+          step();
+      }, 1500);
+    }
+  }
+})

+ 5 - 0
components/xr-ar-2dmarker/index.json

@@ -0,0 +1,5 @@
+{
+  "component": true,
+  "usingComponents": {},
+  "renderer": "xr-frame"
+}

+ 22 - 0
components/xr-ar-2dmarker/index.wxml

@@ -0,0 +1,22 @@
+<xr-scene ar-system="modes:Marker" id="xr-scene" bind:ready="handleReady" bind:ar-ready="handleARReady">
+  <xr-assets bind:progress="handleAssetsProgress" bind:loaded="handleAssetsLoaded">
+    <xr-asset-load type="gltf" asset-id="bg" src="https://houseoss.4dkankan.com/project/hq-eduction-vr/public/BG/HQ.gltf" options="preserveRaw: true" />
+    <xr-asset-load type="gltf" asset-id="adl" src="https://houseoss.4dkankan.com/project/hq-eduction-vr/public/adl-gltf/adl.gltf" options="preserveRaw: true" />
+  </xr-assets>
+
+  <xr-env env-data="xr-frame-team-workspace-day" />
+  
+  <xr-node wx:if="{{arReady}}">
+    <xr-ar-tracker mode="Marker" src="{{markerImg}}" bind:ar-tracker-state="handleARTrackerState1">
+      <xr-gltf vis model="bg" position="0 0 0" scale="0.5 0.5 0.5" rotation="-90 0 0" bind:gltf-loaded="handleItem1Loaded" />
+
+      <xr-gltf vis model="adl" position="{{adlPos}}" scale="{{adlScale}}" rotation="-90 0 0" bind:gltf-loaded="handleItem2Loaded" />
+    </xr-ar-tracker>
+
+    <xr-camera id="camera" position="1 1 1" node-id="camera" clear-color="0.925 0.925 0.925 1" background="ar" is-ar-camera></xr-camera>
+  </xr-node>
+  <xr-node node-id="lights">
+    <xr-light type="ambient" color="1 1 1" intensity="1" />
+    <xr-light type="directional" rotation="180 0 0" color="1 1 1" intensity="2" />
+  </xr-node>
+</xr-scene>

+ 1 - 0
components/xr-ar-2dmarker/index.wxss

@@ -0,0 +1 @@
+/* components/xr-ar-2dmarker/index.wxss */

+ 45 - 0
pages/ar/index.js

@@ -0,0 +1,45 @@
+var sceneReadyBehavior = require('../behavior-scene/scene-ready');
+var handleDecodedXML = require('../behavior-scene/util').handleDecodedXML;
+var xmlCode = `&lt;xr-scene ar-system=&quot;modes:Marker&quot; id=&quot;xr-scene&quot; bind:ready=&quot;handleReady&quot; bind:arReady=&quot;handleARReady&quot; bind:log=&quot;handleLog&quot;&gt;
+&lt;xr-assets bind:progress=&quot;handleAssetsProgress&quot; bind:loaded=&quot;handleAssetsLoaded&quot;&gt;
+  &lt;xr-asset-material asset-id=&quot;ar-anchor&quot; effect=&quot;standrand&quot; uniforms=&quot;u_baseColorFactor:0 1 0 1&quot;&gt;&lt;/xr-asset-material&gt;
+&lt;/xr-assets&gt;
+&lt;xr-node&gt;
+  &lt;xr-node node-id=&quot;id2&quot; scale=&quot;2 2 2&quot; position=&quot;0 0 0&quot;&gt;&lt;/xr-node&gt;
+  &lt;xr-ar-tracker mode=&quot;Marker&quot; src=&quot;{{markerImg}}&quot;&gt;
+    &lt;xr-mesh geometry=&quot;cylinder&quot; material=&quot;ar-anchor&quot; /&gt;
+  &lt;/xr-ar-tracker&gt;
+  &lt;xr-camera
+    id=&quot;camera&quot; node-id=&quot;camera&quot; position=&quot;0.8 2.2 -5&quot; clear-color=&quot;0.925 0.925 0.925 1&quot;
+    target=&quot;id2&quot; background=&quot;ar&quot; is-ar-camera
+    camera-orbit-control
+  &gt;&lt;/xr-camera&gt;
+&lt;/xr-node&gt;
+&lt;xr-node node-id=&quot;lights&quot;&gt;
+  &lt;xr-light type=&quot;ambient&quot; color=&quot;1 1 1&quot; intensity=&quot;1&quot; /&gt;
+  &lt;xr-light type=&quot;directional&quot; rotation=&quot;180 0 0&quot; color=&quot;1 1 1&quot; intensity=&quot;3&quot; /&gt;
+&lt;/xr-node&gt;
+&lt;/xr-scene&gt;
+`;
+Page({
+  behaviors: [sceneReadyBehavior],
+  data: {
+    xmlCode: '<div class="codeWrap">' + handleDecodedXML(xmlCode) + '</div>',
+    markerImg: 'https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/marker/2dmarker-test.jpg'
+  },
+  handleChangeMarkerImg: function() {
+    wx.chooseMedia({
+      count: 1,
+      sizeType: ['compressed'],
+      mediaType: ['image'],
+      sourceType: ['album'],
+      success: res => {
+        const fp = res.tempFiles[0].tempFilePath;
+        this.setData({markerImg: fp});
+      },
+      fail: err => {
+        console.error('[xr-demo]chooseImage failed', err);
+      }
+    });
+  }
+});

+ 8 - 0
pages/ar/index.json

@@ -0,0 +1,8 @@
+{
+  "usingComponents": {
+    "xr-ar-2dmarker": "../../components/xr-ar-2dmarker/index"
+  },
+  "disableScroll": true,
+  "renderer": "webview",
+  "navigationStyle": "custom"
+}

+ 11 - 0
pages/ar/index.wxml

@@ -0,0 +1,11 @@
+<view class="ar-page">
+  <xr-ar-2dmarker
+    disable-scroll
+    id="main-frame"
+    width="{{renderWidth}}"
+    height="{{renderHeight}}"
+    style="width:{{width}}px;height:{{height}}px;top:{{top}}px;left:{{left}}px;display:block;"
+    markerImg="{{markerImg}}"
+    bind:arTrackerState="handleARTrackerState"
+  />
+</view>

+ 5 - 0
pages/ar/index.wxss

@@ -0,0 +1,5 @@
+.ar-page {
+  position: absolute;
+  inset: 0;
+  overflow: hidden;
+}

+ 88 - 0
pages/behavior-scene/scene-ready.js

@@ -0,0 +1,88 @@
+module.exports = Behavior({
+  behaviors: [],
+  properties: {
+  },
+  data: {
+    left: 0,
+    top: 0,
+    width: 0,
+    height: 0,
+    renderWidth: 0,
+    renderHeight: 0,
+    windowHeight: 1000,
+    heightScale: 1,
+    dpiScale: 1,
+    showBackBtn: false,
+    activeValues: [1],
+    arTrackerShow: false,
+    arTrackerState: 'Init',
+    arTrackerError: ''
+  },
+  attached: function(){},
+  ready() {
+    const info = wx.getSystemInfoSync();
+    const width = info.windowWidth;
+    const windowHeight = info.windowHeight;
+    const height = windowHeight * this.data.heightScale;
+    const dpi = info.pixelRatio;
+    this.setData({
+      width,
+      height,
+      renderWidth: width * dpi * this.data.dpiScale,
+      renderHeight: height * dpi * this.data.dpiScale,
+      windowHeight
+    });
+  },
+  methods: {
+    onLoad(options) {
+      wx.reportEvent("xr_frame", {
+        "xr_page_path": options.path
+      });
+    },
+    onShareAppMessage() {
+      try {
+        if (wx.xrScene) {
+          const buffer = wx.xrScene.share.captureToArrayBuffer({quality: 0.5});
+          const fp = `${wx.env.USER_DATA_PATH}/xr-frame-share.jpg`;
+          wx.getFileSystemManager().writeFileSync(fp, buffer, 'binary');
+          return {
+            title: this.getTitle(),
+            imageUrl: fp
+          };
+        }
+      } catch (e) {
+        return {
+          title: this.getTitle()
+        };
+      }
+    },
+    onShareTimeline() {
+      try {
+        if (wx.xrScene) {
+          const buffer = wx.xrScene.share.captureToArrayBuffer({quality: 0.5});
+          const fp = `${wx.env.USER_DATA_PATH}/xr-frame-share.jpg`;
+          wx.getFileSystemManager().writeFileSync(fp, buffer, 'binary');
+          return {
+            title: this.getTitle(),
+            imageUrl: fp
+          };
+        }
+      } catch (e) {
+        return {
+          title: this.getTitle()
+        }
+      }
+    },
+    getTitle() {
+      return wx.xrTitle ? `XR - ${wx.xrTitle}` : 'XR-FRAME官方示例';
+    },
+    handleARTrackerState({detail}) {
+      const {state, error} = detail;
+      this.setData({
+        arTrackerShow: true,
+        arTrackerState: wx.getXrFrameSystem().EARTrackerState[state],
+        arTrackerError: error
+      });
+    }
+  }
+})

+ 75 - 0
pages/behavior-scene/util.js

@@ -0,0 +1,75 @@
+var handleDecodedXML = function(decodedXml) {
+  let rerurnXml = '';
+
+  const blockArr = decodedXml.split('&lt;');
+
+  for (let i = 0; i < blockArr.length; i++) {
+    let blockStr = blockArr[i];
+    let handleBlockStr = '';
+    let returnBlockStr = '';
+
+    const sliceBlockStr = blockStr.split(' ');
+
+    for(let j = 0; j < sliceBlockStr.length; j++) {
+      const subBlockStr = sliceBlockStr[j];
+      
+      const eIndex = subBlockStr.indexOf('=');
+      if (eIndex !== -1) {
+        handleBlockStr += ' <span class="attr-name">' + subBlockStr.slice(0, eIndex) +'</span>' + subBlockStr.slice(eIndex);
+      } else {
+        handleBlockStr += subBlockStr;
+      }
+    }
+    // console.log(sliceBlockStr);
+
+    const blockEndIndexB = handleBlockStr.indexOf(' ');
+    const blockEndIndexR = handleBlockStr.indexOf('&gt;');
+    // Handle XMLTag
+    if (blockEndIndexB === -1 && blockEndIndexR === -1) {
+      continue;
+    }
+    const endBlockFlag = handleBlockStr[0] === '/';
+
+    if (blockEndIndexR !== -1) {
+      handleBlockStr += '<br>'
+    }
+    if (blockEndIndexR < blockEndIndexB) {
+      returnBlockStr = '&lt;' + (endBlockFlag ? '/' : '') + '<span class="block-name">' + handleBlockStr.slice(endBlockFlag ? 1 : 0, blockEndIndexR) + '</span>' + handleBlockStr.slice(blockEndIndexR);
+    } else if (blockEndIndexB !== -1) {
+      returnBlockStr = '&lt;' + (endBlockFlag ? '/' : '') +'<span class="block-name">' + handleBlockStr.slice(endBlockFlag ? 1 : 0, blockEndIndexB) + '</span>' + handleBlockStr.slice(blockEndIndexB);
+    } else if (blockEndIndexR !== -1) {
+      returnBlockStr = '&lt;' + (endBlockFlag ? '/' : '') + '<span class="block-name">' + handleBlockStr.slice(endBlockFlag ? 1 : 0, blockEndIndexR) + '</span>' + handleBlockStr.slice(blockEndIndexR);
+    }
+    rerurnXml += returnBlockStr;
+  }
+  return rerurnXml;
+}
+
+var escapeMarkup = function(dangerousInput) {
+  const dangerousString = String(dangerousInput);
+  const matchHtmlRegExp = /["'&<>]/;
+  const match = matchHtmlRegExp.exec(dangerousString);
+  if (!match) {
+    return dangerousInput;
+  }
+
+  const encodedSymbolMap = {
+    '"': '&quot;',
+    '\'': '&#39;',
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;'
+  };
+  const dangerousCharacters = dangerousString.split('');
+  const safeCharacters = dangerousCharacters.map(function (character) {
+    return encodedSymbolMap[character] || character;
+  });
+  const safeString = safeCharacters.join('');
+  return safeString;
+}
+
+
+module.exports = {
+  handleDecodedXML,
+  escapeMarkup
+}

+ 28 - 0
project.config.json

@@ -0,0 +1,28 @@
+{
+  "compileType": "miniprogram",
+  "libVersion": "trial",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    }
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "auto",
+    "tabSize": 2
+  },
+  "appid": "wxf6a70b1b4fc774cc"
+}

+ 7 - 0
project.private.config.json

@@ -0,0 +1,7 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "hq-eduction-vr",
+  "setting": {
+    "compileHotReLoad": true
+  }
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 19 - 0
utils/util.js

@@ -0,0 +1,19 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+module.exports = {
+  formatTime
+}