chenlei před 3 týdny
rodič
revize
afb31d6692
35 změnil soubory, kde provedl 3281 přidání a 126 odebrání
  1. 72 63
      app.json
  2. 3 0
      miniprogram_npm/threejs-miniprogram/index.js
  3. 21 0
      node_modules/.modules.yaml
  4. 16 0
      node_modules/.pnpm/lock.yaml
  5. 21 0
      node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/LICENSE
  6. 39 0
      node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/README.md
  7. 3 0
      node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/dist/index.js
  8. 43 0
      node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/package.json
  9. 21 0
      node_modules/threejs-miniprogram/LICENSE
  10. 39 0
      node_modules/threejs-miniprogram/README.md
  11. 3 0
      node_modules/threejs-miniprogram/dist/index.js
  12. 43 0
      node_modules/threejs-miniprogram/package.json
  13. 18 0
      package.json
  14. binární
      pages/cover/imgs/bg_home@2x-min.jpg
  15. binární
      pages/cover/imgs/icon_ar@2x-min.png
  16. binární
      pages/cover/imgs/icon_ar@2x.png
  17. binární
      pages/cover/imgs/icon_home@2x.png
  18. binární
      pages/cover/imgs/titile_zuantou@2x-min.png
  19. 66 0
      pages/cover/index.js
  20. 3 0
      pages/cover/index.json
  21. 15 0
      pages/cover/index.wxml
  22. 51 0
      pages/cover/index.wxss
  23. 16 0
      pnpm-lock.yaml
  24. 40 40
      project.config.json
  25. 35 23
      project.private.config.json
  26. 102 0
      subPackages/pages/ar/behavior.js
  27. 3 0
      subPackages/pages/ar/constants.js
  28. 249 0
      subPackages/pages/ar/index.js
  29. 5 0
      subPackages/pages/ar/index.json
  30. 11 0
      subPackages/pages/ar/index.wxml
  31. 42 0
      subPackages/pages/ar/index.wxss
  32. 123 0
      subPackages/pages/ar/utils.js
  33. 458 0
      subPackages/pages/ar/yuvBehavior.js
  34. 1647 0
      subPackages/utils/gltf-loader.js
  35. 73 0
      subPackages/utils/index.js

+ 72 - 63
app.json

@@ -1,64 +1,73 @@
-{
-  "pages": [
-    "pages/index/index",
-    "pages/collection/index",
-    "pages/exhibition/index",
-    "pages/user/index",
-    "pages/webview/index",
-    "pages/index/visit-preview/visit-preview",
-    "pages/index/start-preview/start-preview",
-    "pages/index/active-preview/active-preview",
-    "pages/index/active-page/active-page",
-    "pages/index/active-people/active-people",
-    "pages/index/visit-people/visit-people",
-    "pages/user/userList/index",
-    "pages/user/my-preview/index",
-    "pages/index/activity/activity",
-    "pages/index/news/news",
-    "pages/user/feedback/index",
-    "pages/user/map/index",
-    "pages/exhibition/activeDetails/index"
-  ],
-  "window": {
-    "navigationBarTextStyle": "black",
-    "navigationBarTitleText": "克拉玛依市博物馆",
-    "navigationBarBackgroundColor": "#ffffff",
-    "enablePullDownRefresh": false
-  },
-  "tabBar": {
-    "color": "#412A12",
-    "selectedColor": "#B1967B",
-    "borderStyle": "black",
-    "backgroundColor": "#ffffff",
-    "list": [
-      {
-        "pagePath": "pages/index/index",
-        "iconPath": "imgs/icon_home_normal.png",
-        "selectedIconPath": "imgs/icon_home_active.png",
-        "text": "首页"
-      },
-      {
-        "pagePath": "pages/exhibition/index",
-        "iconPath": "imgs/icon_exhibition_normal.png",
-        "selectedIconPath": "imgs/icon_exhibition_active.png",
-        "text": "展览"
-      },
-      {
-        "pagePath": "pages/collection/index",
-        "iconPath": "imgs/icon_culture_normal.png",
-        "selectedIconPath": "imgs/icon_culture_active.png",
-        "text": "典藏"
-      },
-      {
-        "pagePath": "pages/user/index",
-        "iconPath": "imgs/icon_user_normal.png",
-        "selectedIconPath": "imgs/icon_user_active.png",
-        "text": "我的"
-      }
-    ]
-  },
-  "style": "v2",
-  "componentFramework": "glass-easel",
-  "sitemapLocation": "sitemap.json",
-  "lazyCodeLoading": "requiredComponents"
+{
+  "pages": [
+    "pages/cover/index",
+    "pages/index/index",
+    "pages/collection/index",
+    "pages/exhibition/index",
+    "pages/user/index",
+    "pages/webview/index",
+    "pages/index/visit-preview/visit-preview",
+    "pages/index/start-preview/start-preview",
+    "pages/index/active-preview/active-preview",
+    "pages/index/active-page/active-page",
+    "pages/index/active-people/active-people",
+    "pages/index/visit-people/visit-people",
+    "pages/user/userList/index",
+    "pages/user/my-preview/index",
+    "pages/index/activity/activity",
+    "pages/index/news/news",
+    "pages/user/feedback/index",
+    "pages/user/map/index",
+    "pages/exhibition/activeDetails/index"
+  ],
+  "subPackages": [
+    {
+      "root": "subPackages",
+      "pages": [
+        "pages/ar/index"
+      ]
+    }
+  ],
+  "window": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "克拉玛依市博物馆",
+    "navigationBarBackgroundColor": "#ffffff",
+    "enablePullDownRefresh": false
+  },
+  "tabBar": {
+    "color": "#412A12",
+    "selectedColor": "#B1967B",
+    "borderStyle": "black",
+    "backgroundColor": "#ffffff",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "iconPath": "imgs/icon_home_normal.png",
+        "selectedIconPath": "imgs/icon_home_active.png",
+        "text": "首页"
+      },
+      {
+        "pagePath": "pages/exhibition/index",
+        "iconPath": "imgs/icon_exhibition_normal.png",
+        "selectedIconPath": "imgs/icon_exhibition_active.png",
+        "text": "展览"
+      },
+      {
+        "pagePath": "pages/collection/index",
+        "iconPath": "imgs/icon_culture_normal.png",
+        "selectedIconPath": "imgs/icon_culture_active.png",
+        "text": "典藏"
+      },
+      {
+        "pagePath": "pages/user/index",
+        "iconPath": "imgs/icon_user_normal.png",
+        "selectedIconPath": "imgs/icon_user_active.png",
+        "text": "我的"
+      }
+    ]
+  },
+  "style": "v2",
+  "componentFramework": "glass-easel",
+  "sitemapLocation": "sitemap.json",
+  "lazyCodeLoading": "requiredComponents"
 }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 3 - 0
miniprogram_npm/threejs-miniprogram/index.js


+ 21 - 0
node_modules/.modules.yaml

@@ -0,0 +1,21 @@
+hoistPattern:
+  - '*'
+hoistedDependencies: {}
+included:
+  dependencies: true
+  devDependencies: true
+  optionalDependencies: true
+injectedDeps: {}
+layoutVersion: 5
+nodeLinker: isolated
+packageManager: pnpm@8.6.10
+pendingBuilds: []
+prunedAt: Tue, 15 Jul 2025 06:34:23 GMT
+publicHoistPattern:
+  - '*eslint*'
+  - '*prettier*'
+registries:
+  default: https://registry.npmmirror.com/
+skipped: []
+storeDir: D:\.pnpm-store\v3
+virtualStoreDir: D:\web\2025-notme\karamay_wx\node_modules\.pnpm

+ 16 - 0
node_modules/.pnpm/lock.yaml

@@ -0,0 +1,16 @@
+lockfileVersion: '6.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+dependencies:
+  threejs-miniprogram:
+    specifier: ^0.0.8
+    version: 0.0.8
+
+packages:
+
+  /threejs-miniprogram@0.0.8:
+    resolution: {integrity: sha512-S5IgBMuX57kQsbeSv/5YDc87s+WKvPLYT+Go5FUbSdcjf6/Lq/LoY1uoVxfa00fjLHN+cMuumZ1Op/JmFKUmbw==}
+    dev: false

+ 21 - 0
node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 wechat-miniprogram
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 39 - 0
node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/README.md

@@ -0,0 +1,39 @@
+# threejs-miniprogram
+Three.js 小程序 WebGL 的适配版本。
+
+## 使用
+
+可参考 example 目录下的示例项目或参照以下流程:
+
+1. 通过 npm 安装
+
+   ```
+   npm install --save threejs-miniprogram
+   ```
+安装完成之后在微信开发者工具中点击构建 npm。
+
+2. 导入小程序适配版本的 Three.js
+
+```javascript
+import {createScopedThreejs} from 'threejs-miniprogram'
+
+Page({
+  onReady() {
+    wx.createSelectorQuery()
+      .select('#webgl')
+      .node()
+      .exec((res) => {
+        const canvas = res[0].node
+        // 创建一个与 canvas 绑定的 three.js
+        const THREE = createScopedThreejs(canvas)
+        // 传递并使用 THREE 变量
+      })
+  }
+})
+```
+
+## 说明
+
+- 本项目当前使用的 Three.js 版本号为 0.108.0,如要更新 threejs 版本可发 PR 修改或 fork 后自行修改。
+- 该适配版本的 THREE 不在全局环境中,如使用 Three.js 的其他配套类库,需要自行传入 THREE 到类库中。
+- 如在使用过程中发现有适配问题,可通过 issue 反馈或发 PR 修复。

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 3 - 0
node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/dist/index.js


+ 43 - 0
node_modules/.pnpm/threejs-miniprogram@0.0.8/node_modules/threejs-miniprogram/package.json

@@ -0,0 +1,43 @@
+{
+  "name": "threejs-miniprogram",
+  "version": "0.0.8",
+  "description": "ThreeJS adapter for WeChat MiniProgram",
+  "main": "dist/index.js",
+  "scripts": {
+    "dev": "webpack --config build/webpack.config.js --watch",
+    "build": "webpack --hide-modules --config build/webpack.config.js",
+    "lint": "eslint \"src/**/*.js\"",
+    "prepublishOnly": "npm run build"
+  },
+  "miniprogram": "dist/",
+  "author": "wechat-miniprogram",
+  "license": "MIT",
+  "homepage": "https://github.com/wechat-miniprogram/threejs-miniprogram",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/wechat-miniprogram/threejs-miniprogram"
+  },
+  "publishConfig": {
+    "registry": "https://registry.npmjs.org/"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.5.5",
+    "@babel/plugin-proposal-class-properties": "^7.5.5",
+    "@babel/preset-env": "^7.5.5",
+    "abab": "^2.0.3",
+    "babel-loader": "^8.0.6",
+    "eslint": "^4.18.2",
+    "eslint-friendly-formatter": "^2.0.6",
+    "eslint-loader": "^1.5.0",
+    "eslint-plugin-jest": "^20.0.3",
+    "eventemitter2": "^4.1.2",
+    "jest": "^20.0.4",
+    "path": "^0.12.7",
+    "string-replace-loader": "^2.2.0",
+    "string-replace-webpack-plugin": "^0.1.3",
+    "three": "0.108.0",
+    "webpack": "^4.39.1",
+    "webpack-cli": "^3.3.6",
+    "babel-eslint": "^10.1.0"
+  }
+}

+ 21 - 0
node_modules/threejs-miniprogram/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 wechat-miniprogram
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 39 - 0
node_modules/threejs-miniprogram/README.md

@@ -0,0 +1,39 @@
+# threejs-miniprogram
+Three.js 小程序 WebGL 的适配版本。
+
+## 使用
+
+可参考 example 目录下的示例项目或参照以下流程:
+
+1. 通过 npm 安装
+
+   ```
+   npm install --save threejs-miniprogram
+   ```
+安装完成之后在微信开发者工具中点击构建 npm。
+
+2. 导入小程序适配版本的 Three.js
+
+```javascript
+import {createScopedThreejs} from 'threejs-miniprogram'
+
+Page({
+  onReady() {
+    wx.createSelectorQuery()
+      .select('#webgl')
+      .node()
+      .exec((res) => {
+        const canvas = res[0].node
+        // 创建一个与 canvas 绑定的 three.js
+        const THREE = createScopedThreejs(canvas)
+        // 传递并使用 THREE 变量
+      })
+  }
+})
+```
+
+## 说明
+
+- 本项目当前使用的 Three.js 版本号为 0.108.0,如要更新 threejs 版本可发 PR 修改或 fork 后自行修改。
+- 该适配版本的 THREE 不在全局环境中,如使用 Three.js 的其他配套类库,需要自行传入 THREE 到类库中。
+- 如在使用过程中发现有适配问题,可通过 issue 反馈或发 PR 修复。

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 3 - 0
node_modules/threejs-miniprogram/dist/index.js


+ 43 - 0
node_modules/threejs-miniprogram/package.json

@@ -0,0 +1,43 @@
+{
+  "name": "threejs-miniprogram",
+  "version": "0.0.8",
+  "description": "ThreeJS adapter for WeChat MiniProgram",
+  "main": "dist/index.js",
+  "scripts": {
+    "dev": "webpack --config build/webpack.config.js --watch",
+    "build": "webpack --hide-modules --config build/webpack.config.js",
+    "lint": "eslint \"src/**/*.js\"",
+    "prepublishOnly": "npm run build"
+  },
+  "miniprogram": "dist/",
+  "author": "wechat-miniprogram",
+  "license": "MIT",
+  "homepage": "https://github.com/wechat-miniprogram/threejs-miniprogram",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/wechat-miniprogram/threejs-miniprogram"
+  },
+  "publishConfig": {
+    "registry": "https://registry.npmjs.org/"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.5.5",
+    "@babel/plugin-proposal-class-properties": "^7.5.5",
+    "@babel/preset-env": "^7.5.5",
+    "abab": "^2.0.3",
+    "babel-loader": "^8.0.6",
+    "eslint": "^4.18.2",
+    "eslint-friendly-formatter": "^2.0.6",
+    "eslint-loader": "^1.5.0",
+    "eslint-plugin-jest": "^20.0.3",
+    "eventemitter2": "^4.1.2",
+    "jest": "^20.0.4",
+    "path": "^0.12.7",
+    "string-replace-loader": "^2.2.0",
+    "string-replace-webpack-plugin": "^0.1.3",
+    "three": "0.108.0",
+    "webpack": "^4.39.1",
+    "webpack-cli": "^3.3.6",
+    "babel-eslint": "^10.1.0"
+  }
+}

+ 18 - 0
package.json

@@ -0,0 +1,18 @@
+{
+  "name": "karamay_wx",
+  "version": "1.0.0",
+  "description": "",
+  "main": "app.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://192.168.0.115:3000/wangfumin/karamay_wx.git"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "threejs-miniprogram": "^0.0.8"
+  }
+}

binární
pages/cover/imgs/bg_home@2x-min.jpg


binární
pages/cover/imgs/icon_ar@2x-min.png


binární
pages/cover/imgs/icon_ar@2x.png


binární
pages/cover/imgs/icon_home@2x.png


binární
pages/cover/imgs/titile_zuantou@2x-min.png


+ 66 - 0
pages/cover/index.js

@@ -0,0 +1,66 @@
+// pages/cover/index.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 3 - 0
pages/cover/index.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 15 - 0
pages/cover/index.wxml

@@ -0,0 +1,15 @@
+<view class="ar-cover">
+  <image mode="aspectFit" class="img" src="./imgs/icon_ar@2x-min.png" />
+  <image mode="aspectFit" class="logo" src="./imgs/titile_zuantou@2x-min.png" />
+
+  <navigator class="ar-cover__btn ar" url="/subPackages/pages/ar/index">
+    <image class="ar-cover__btn__icon" src="./imgs/icon_ar@2x.png" />
+    <view class="ar-cover__btn__label">开启AR相机</view>
+  </navigator>
+  <navigator class="ar-cover__btn home" open-type="switchTab" url="/pages/index/index">
+    <image class="ar-cover__btn__icon" src="./imgs/icon_home@2x.png" />
+    <view class="ar-cover__btn__label">小程序首页</view>
+  </navigator>
+
+  <image class="ar-cover__bg" src="./imgs/bg_home@2x-min.jpg" />
+</view>

+ 51 - 0
pages/cover/index.wxss

@@ -0,0 +1,51 @@
+.ar-cover {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  height: 100vh;
+  overflow: hidden;
+}
+.ar-cover__bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+  z-index: -1;
+}
+.img {
+  width: 666rpx;
+  height: 666rpx;
+}
+.logo {
+  width: 574rpx;
+  height: 166rpx;
+}
+.ar-cover__btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 20rpx;
+  width: 552rpx;
+  height: 114rpx;
+  font-size: 32rpx;
+  border-radius: 10rpx;
+}
+.ar-cover__btn__icon {
+  width: 80rpx;
+  height: 80rpx;
+}
+.ar-cover__btn__label {
+  width: 200rpx;
+}
+.ar-cover__btn.ar {
+  margin: 150rpx 0 30rpx;
+  color: #5C4C3D;
+  background: #EFDBAC;
+}
+.ar-cover__btn.home {
+  color: #EFDBAC;
+  border: 2rpx solid #EFDBAC;
+}

+ 16 - 0
pnpm-lock.yaml

@@ -0,0 +1,16 @@
+lockfileVersion: '6.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+dependencies:
+  threejs-miniprogram:
+    specifier: ^0.0.8
+    version: 0.0.8
+
+packages:
+
+  /threejs-miniprogram@0.0.8:
+    resolution: {integrity: sha512-S5IgBMuX57kQsbeSv/5YDc87s+WKvPLYT+Go5FUbSdcjf6/Lq/LoY1uoVxfa00fjLHN+cMuumZ1Op/JmFKUmbw==}
+    dev: false

+ 40 - 40
project.config.json

@@ -1,41 +1,41 @@
-{
-  "compileType": "miniprogram",
-  "libVersion": "3.8.7",
-  "packOptions": {
-    "ignore": [],
-    "include": []
-  },
-  "setting": {
-    "coverView": true,
-    "es6": true,
-    "postcss": true,
-    "minified": true,
-    "enhance": true,
-    "showShadowRootInWxmlPanel": true,
-    "packNpmRelationList": [],
-    "babelSetting": {
-      "ignore": [],
-      "disablePlugins": [],
-      "outputPath": ""
-    },
-    "compileWorklet": false,
-    "uglifyFileName": false,
-    "uploadWithSourceMap": true,
-    "packNpmManually": false,
-    "minifyWXSS": true,
-    "minifyWXML": true,
-    "localPlugins": false,
-    "condition": false,
-    "swc": false,
-    "disableSWC": true,
-    "disableUseStrict": false,
-    "useCompilerPlugins": false
-  },
-  "condition": {},
-  "editorSetting": {
-    "tabIndent": "auto",
-    "tabSize": 2
-  },
-  "appid": "wx6b8ed0132dae7d3e",
-  "simulatorPluginLibVersion": {}
+{
+  "compileType": "miniprogram",
+  "libVersion": "3.8.7",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "compileWorklet": false,
+    "uglifyFileName": false,
+    "uploadWithSourceMap": true,
+    "packNpmManually": false,
+    "minifyWXSS": true,
+    "minifyWXML": true,
+    "localPlugins": false,
+    "condition": false,
+    "swc": false,
+    "disableSWC": true,
+    "disableUseStrict": false,
+    "useCompilerPlugins": false
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "auto",
+    "tabSize": 2
+  },
+  "appid": "wx6b8ed0132dae7d3e",
+  "simulatorPluginLibVersion": {}
 }

+ 35 - 23
project.private.config.json

@@ -1,24 +1,36 @@
-{
-  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "projectname": "karamay",
-  "setting": {
-    "compileHotReLoad": true,
-    "urlCheck": true,
-    "coverView": true,
-    "lazyloadPlaceholderEnable": false,
-    "skylineRenderEnable": false,
-    "preloadBackgroundData": false,
-    "autoAudits": false,
-    "useApiHook": true,
-    "useApiHostProcess": true,
-    "showShadowRootInWxmlPanel": true,
-    "useStaticServer": false,
-    "useLanDebug": false,
-    "showES6CompileOption": false,
-    "bigPackageSizeSupport": false,
-    "checkInvalidKey": true,
-    "ignoreDevUnusedFiles": true
-  },
-  "libVersion": "3.7.12",
-  "condition": {}
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "karamay_wx",
+  "setting": {
+    "compileHotReLoad": true,
+    "urlCheck": true,
+    "coverView": true,
+    "lazyloadPlaceholderEnable": false,
+    "skylineRenderEnable": false,
+    "preloadBackgroundData": false,
+    "autoAudits": false,
+    "useApiHook": true,
+    "useApiHostProcess": true,
+    "showShadowRootInWxmlPanel": true,
+    "useStaticServer": false,
+    "useLanDebug": false,
+    "showES6CompileOption": false,
+    "bigPackageSizeSupport": true,
+    "checkInvalidKey": true,
+    "ignoreDevUnusedFiles": true
+  },
+  "libVersion": "3.8.10",
+  "condition": {
+    "miniprogram": {
+      "list": [
+        {
+          "name": "subPackages/ar/index",
+          "pathName": "subPackages/pages/ar/index",
+          "query": "",
+          "launchMode": "default",
+          "scene": null
+        }
+      ]
+    }
+  }
 }

+ 102 - 0
subPackages/pages/ar/behavior.js

@@ -0,0 +1,102 @@
+module.exports = Behavior({
+  // 全局变量
+  session: undefined, // 全局的VKsession对象
+  canvas: undefined, // canvas
+  // XRFrame相关变量
+  xrScene: undefined, // xr-frame 的场景
+  xrCamera: undefined, // xr-frame 的相机
+  xrFrameReady: undefined, // xr-frame初始化完毕
+  // WebGL相关
+  camera: undefined, // 主要相机
+  // ThreeJs 相关变量
+  gl: undefined, // 全局gl对象
+  THREE: undefined, // THREE 对象
+  // 全局 data
+  data: {
+    domWidth: 0,
+    domHeight: 0,
+    width: 0, // canvas大小
+    height: 0, // canvas大小
+    widthScale: 1, // canvas宽度缩放值
+    heightScale: 1, // canvas高度缩放值
+    cameraPosition: 0, // 相机朝向,默认后置摄像头
+  },
+  methods: {
+    onReady() {
+      // 获取canvas
+      wx.createSelectorQuery()
+        .select('#canvas')
+        .node()
+        .exec(res => {
+          this.canvas = res[0].node
+
+          // 运算画布大小
+          this.calcCanvasSize()
+
+          // 页面自定义初始化
+          if (this.init) this.init()
+        })
+    },
+    calcCanvasSize() {
+      const info = wx.getWindowInfo()
+      const pixelRatio = info.pixelRatio
+      const width = info.windowWidth * this.data.widthScale * pixelRatio
+      const height = info.windowHeight * this.data.heightScale * pixelRatio
+      // 存在 webgl Canvas的情况下,写入大小
+      if (this.canvas) {
+        this.canvas.width = width
+        this.canvas.height = height
+      }
+      console.log(`canvas size: width = ${width} , height = ${height}`)
+      this.setData({
+        width,
+        height,
+        domWidth: info.windowWidth * this.data.widthScale,
+        domHeight: info.windowHeight * this.data.heightScale,
+      })
+    },
+    // 前后摄像头
+    switchCamera() {
+      if (this.session.config) {
+        const config = this.session.config
+        let cameraPosNext
+        if (this.data.cameraPosition === 0) {
+          cameraPosNext = 1
+        } else {
+          cameraPosNext = 0
+        }
+        config.cameraPosition = cameraPosNext
+        this.session.config = config
+        this.setData({
+          cameraPosition: cameraPosNext
+        })
+      }
+    },
+    // 限帧逻辑
+    initLoop() {
+      // 限制调用帧率,暂时去掉
+      const fps = 30
+      const fpsInterval = 1000 / fps
+      let last = Date.now()
+
+      const session = this.session
+
+      // 逐帧渲染
+      const onFrame = timestamp => {
+        try {
+          const now = Date.now()
+          const mill = now - last
+          // 经过了足够的时间
+          if (mill > fpsInterval) {
+            last = now - (mill % fpsInterval) // 校正当前时间
+            this.loop()
+          }
+        } catch (e) {
+          console.error(e)
+        }
+        session.requestAnimationFrame(onFrame)
+      }
+      session.requestAnimationFrame(onFrame)
+    },
+  },
+})

+ 3 - 0
subPackages/pages/ar/constants.js

@@ -0,0 +1,3 @@
+export const MARKER_MAP = {
+  10001: 2
+}

+ 249 - 0
subPackages/pages/ar/index.js

@@ -0,0 +1,249 @@
+import getBehavior from './behavior'
+import yuvBehavior from './yuvBehavior'
+import { getCachedImage, hash } from '../../utils/index'
+import { MARKER_MAP } from './constants'
+
+const NEAR = 0.01
+const FAR = 1000
+
+Component({
+  behaviors: [getBehavior, yuvBehavior],
+  data: {
+    widthScale: 1, // canvas宽度缩放值
+    heightScale: 1, // canvas高度缩放值
+    markerImgList: [], // 使用的 marker 列表
+    chooseImgList: [], // 使用的 图片 列表
+    hintBoxList: [], // 显示提示盒子列表
+  },
+  markerIndex: 0, // 使用的 marker 索引
+  hintInfo: undefined, // 提示框信息
+  methods: {
+    // 对应案例的初始化逻辑,由统一的 behavior 触发
+    init() {
+      // 初始化 Three.js,用于模型相关的渲染
+      this.initTHREE()
+
+      // 初始化 GL,基于 Three.js 的 Context,用于相机YUV渲染
+      this.initYUV()
+
+      // 初始化VK
+      // start完毕后,进行更新渲染循环
+      this.initVK()
+
+      this.markerIndex = 0
+
+      // 添加 识别包围盒子
+      // this.add3DBox()
+    },
+    initVK() {
+      // VKSession 配置
+      const session = this.session = wx.createVKSession({
+        track: {
+          plane: {
+            mode: 1
+          },
+          marker: true,
+        },
+        version: 'v1',
+        gl: this.gl
+      })
+
+      session.start(err => {
+        if (err) return console.error('VK error: ', err)
+
+        console.log('@@@@@@@@ VKSession.version', session.version)
+
+        try {
+          const promises = [
+            'https://houseoss.4dkankan.com/project/kelamayi/ylzt-1.png',
+          ].map(url => {
+            const cacheKey = 'image_marker_' + hash(url)
+            return getCachedImage(url, cacheKey)
+          })
+          Promise.all(promises).then(paths => {
+            for (const path of paths) {
+              session.addMarker(path)
+            }
+          })
+        } catch(err) {
+          console.log(err)
+        }
+
+        //  VKSession EVENT resize
+        session.on('resize', () => {
+          this.calcCanvasSize()
+        })
+
+        // VKSession EVENT addAnchors
+        // session.on('addAnchors', anchors => {
+        //   this.left.visible = true
+        //   this.right.visible = true
+        //   this.top.visible = true
+        //   this.bottom.visible = true
+        // })
+
+        // VKSession EVENT updateAnchors
+        session.on('updateAnchors', anchors => {
+          // marker 模式下,目前仅有一个识别目标,可以直接取
+          const anchor = anchors[0]
+          const markerId = anchor.id
+          const size = anchor.size
+          this.hintInfo = {
+            markerId,
+            size
+          }
+
+          if (MARKER_MAP[markerId]) {
+            wx.navigateTo({
+              url: '/pages/webview/index?url=' + encodeURIComponent(`https://sit-kelamayi.4dage.com/zuan/#/info/${MARKER_MAP[markerId]}?x=h`),
+            })
+          }
+        })
+
+        // VKSession removeAnchors
+        // 识别目标丢失时,会触发一次
+        session.on('removeAnchors', anchors => {
+          // this.left.visible = false
+          // this.right.visible = false
+          // this.top.visible = false
+          // this.bottom.visible = false
+
+          if (this.data.hintBoxList && this.data.hintBoxList.length > 0) {
+            // 清理信息
+            this.hintInfo = undefined
+            // 存在列表的情况,去除remove
+            this.setData({
+              hintBoxList: []
+            })
+          }
+        })
+
+        console.log('ready to initloop')
+        // start 初始化完毕后,进行更新渲染循环
+        this.initLoop()
+      })
+    },
+    loop() {
+      // console.log('loop')
+
+      // 获取 VKFrame
+      const frame = this.session.getVKFrame(this.canvas.width, this.canvas.height)
+
+      // 成功获取 VKFrame 才进行
+      if (!frame) { return }
+
+      // 更新相机 YUV 数据
+      this.renderYUV(frame)
+
+      // 获取 VKCamera
+      const VKCamera = frame.camera
+
+      // 相机
+      if (VKCamera) {
+        // 接管 ThreeJs 相机矩阵更新,Marker模式下,主要由视图和投影矩阵改变渲染效果
+        this.camera.matrixAutoUpdate = false
+
+        // 视图矩阵
+        this.camera.matrixWorldInverse.fromArray(VKCamera.viewMatrix)
+        this.camera.matrixWorld.getInverse(this.camera.matrixWorldInverse)
+
+        // 投影矩阵
+        const projectionMatrix = VKCamera.getProjectionMatrix(NEAR, FAR)
+        this.camera.projectionMatrix.fromArray(projectionMatrix)
+        this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix)
+      }
+
+      // 绘制而为提示框的逻辑
+      if (this.hintInfo) {
+        // 存在提示信息,则更新
+        const THREE = this.THREE
+
+        // 原点偏移矩阵,VK情况下,marker 点对应就是 0 0 0,世界矩阵可以认为是一个单位矩阵
+        // marker 右侧点可以理解是 0.5 0 0
+        const center = new THREE.Vector3()
+        const right = new THREE.Vector3(0.5, 0, 0)
+
+        // 获取设备空间坐标
+        const devicePos = center.clone().project(this.camera)
+
+        // 转换坐标系,从 (-1, 1) 转到 (0, 100),同时移到左上角 0 0,右下角 1 1
+        const screenPos = new THREE.Vector3(0, 0, 0)
+        screenPos.x = devicePos.x * 50 + 50
+        screenPos.y = 50 - devicePos.y * 50
+
+        // 获取右侧点信息
+        const deviceRightPos = right.clone().project(this.camera)
+        const screenRightPos = new THREE.Vector3(0, 0, 0)
+        screenRightPos.x = deviceRightPos.x * 50 + 50
+
+        const markerHalfWidth = screenRightPos.x - screenPos.x
+
+        this.setData({
+          hintBoxList: [
+            {
+              markerId: this.hintInfo.markerId,
+              left: screenPos.x - markerHalfWidth,
+              top: screenPos.y - markerHalfWidth,
+              width: markerHalfWidth * this.data.domWidth * 2 / 100,
+              height: markerHalfWidth * this.data.domWidth * 2 / 100,
+            }
+          ]
+        })
+      }
+
+      this.renderer.autoClearColor = false
+      this.renderer.state.setCullFace(this.THREE.CullFaceBack)
+      this.renderer.render(this.scene, this.camera)
+      this.renderer.state.setCullFace(this.THREE.CullFaceNone)
+    },
+    add3DBox() {
+      // 添加marker需要的 三维包围框
+
+      const THREE = this.THREE
+      const scene = this.scene
+
+      const material = new THREE.MeshPhysicalMaterial({
+        metalness: 0.0,
+        roughness: 0.1,
+        color: 0x64f573,
+      })
+      const geometry = new THREE.BoxGeometry(1, 1, 1)
+
+      const borderSize = 0.1
+
+      const left = new THREE.Mesh(geometry, material)
+      left.position.set(-0.5, 0, 0)
+      left.rotation.set(-Math.PI / 2, 0, 0)
+      left.scale.set(borderSize, 1.1, borderSize)
+      scene.add(left)
+      left.visible = false
+      this.left = left
+
+      const right = new THREE.Mesh(geometry, material)
+      right.position.set(0.5, 0, 0)
+      right.rotation.set(-Math.PI / 2, 0, 0)
+      right.scale.set(borderSize, 1.1, borderSize)
+      scene.add(right)
+      right.visible = false
+      this.right = right
+
+      const top = new THREE.Mesh(geometry, material)
+      top.position.set(0, 0, 0.5)
+      top.rotation.set(0, 0, 0)
+      top.scale.set(1.1, borderSize, borderSize)
+      scene.add(top)
+      top.visible = false
+      this.top = top
+
+      const bottom = new THREE.Mesh(geometry, material)
+      bottom.position.set(0, 0, -0.5)
+      bottom.rotation.set(0, 0, 0)
+      bottom.scale.set(1.1, borderSize, borderSize)
+      scene.add(bottom)
+      bottom.visible = false
+      this.bottom = bottom
+
+      console.log('add3DBox is finish')
+    },
+  },
+})

+ 5 - 0
subPackages/pages/ar/index.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {},
+  "disableScroll": true,
+  "renderer": "webview"
+}

+ 11 - 0
subPackages/pages/ar/index.wxml

@@ -0,0 +1,11 @@
+<view class="page wrap-fix">
+  <view class="canvas-wrap" style="width: {{widthScale * 100}}%; height: {{heightScale * 100}}%">
+    <canvas type="webgl" id="canvas"></canvas>
+    
+    <!-- <view class="hint-box"  wx:for="{{hintBoxList}}"  wx:for-item="hintBox" wx:key="hintBoxId"
+      style="left: {{hintBox.left}}%; top: {{hintBox.top}}%; width: {{hintBox.width}}px;  height: {{hintBox.height}}px;"
+    >
+      <p class="hint-id">识别id: {{hintBox.markerId}}</p>
+    </view> -->
+  </view>
+</view>

+ 42 - 0
subPackages/pages/ar/index.wxss

@@ -0,0 +1,42 @@
+.canvas-wrap {
+  position: relative;
+  width: 100%;
+  background-color: #000;
+}
+
+.canvas-wrap #canvas {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.hint-box {
+  position: absolute;
+  left: 0%;
+  top: 0%;
+  width: 0;
+  height: 0;
+}
+.hint-id {
+  position: absolute;
+  left: -6rpx;
+  right: -6rpx;
+  bottom: 100%;
+  color: #fff;
+  font-size: 20rpx;
+  line-height: 40rpx;
+  text-align: center;
+  text-overflow: ellipsis;
+  background-color: red;
+  border-radius: 8rpx;
+}
+
+.wrap-fix {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+}

+ 123 - 0
subPackages/pages/ar/utils.js

@@ -0,0 +1,123 @@
+/**
+ * VisionKit 视觉能力状态码
+ * @param {number} state 状态码
+ * @param {boolean} user 用户可操作的项
+ * @param {string} msg 状态信息
+ */
+export const VK_STATE = [
+  {
+    "state": 0,
+    "user": false,
+    "msg": "成功"
+  },
+  {
+    "state": 104,
+    "user": true,
+    "msg": "用户取消授权"
+  },
+  {
+    "state": 112,
+    "user": false,
+    "msg": "接口未在隐私协议中声明"
+  },
+  {
+    "state": 1025,
+    "user": false,
+    "msg": "小程序隐私接口被封禁,解决方案参考链接"
+  },
+  {
+    "state": 1026,
+    "user": false,
+    "msg": "小游戏隐私接口被封禁,解决方案参考链接"
+  },
+  {
+    "state": 2000001,
+    "user": false,
+    "msg": "参数错误"
+  },
+  {
+    "state": 2003000,
+    "user": false,
+    "msg": "会话不可用"
+  },
+  {
+    "state": 2000000,
+    "user": false,
+    "msg": "系统错误"
+  },
+  {
+    "state": 2000002,
+    "user": false,
+    "msg": "设备不支持"
+  },
+  {
+    "state": 2000003,
+    "user": false,
+    "msg": "系统不支持"
+  },
+  {
+    "state": 2000004,
+    "user": false,
+    "msg": "设备不支持"
+  },
+  {
+    "state": 2003001,
+    "user": true,
+    "msg": "未开启系统相机权限"
+  },
+  {
+    "state": 2003002,
+    "user": true,
+    "msg": "未开启小程序相机权限"
+  }
+]
+/**
+ * 检测当前设备VisionKit 支持版本
+ * 如果不是用户可操作的状态,直接取上一次的值
+ * v1 / v2 是硬件问题,不需要重复检测
+ */
+export const checkVKVsion = async () => {
+  return new Promise(reslove => {
+    const v1 = wx.getStorageSync('vk_version1_err')
+    const v2 = wx.getStorageSync('vk_version2_err')
+    if (v1 && !VK_STATE.find(el => el.state === v1)?.user) {
+      reslove(null)
+    } else if (v2 && !VK_STATE.find(el => el.state === v2)?.user) {
+      reslove('v1')
+    } else {
+      const session_v2 = wx.createVKSession({
+        track: {
+          plane: {
+            mode: 1
+          },
+        },
+        version: 'v2'
+      })
+      const session_v1 = wx.createVKSession({
+        track: {
+          plane: {
+            mode: 3
+          },
+        },
+        version: 'v1',
+      })
+      session_v2.start((err2) => {
+        if (err2) {
+          wx.setStorageSync('vk_version2_err', err2)
+          session_v1.start(err1 => {
+            if (err1) {
+              wx.setStorageSync('vk_version1_err', err1)
+              reslove(null)
+            } else {
+              reslove('v1')
+            }
+            session_v1.destroy()
+          })
+        } else {
+          reslove('v2')
+        }
+        session_v2.destroy()
+      })
+    }
+  })
+}

+ 458 - 0
subPackages/pages/ar/yuvBehavior.js

@@ -0,0 +1,458 @@
+import { createScopedThreejs } from 'threejs-miniprogram'
+import { registerGLTFLoader } from '../../utils/gltf-loader'
+
+const threeBehavior = Behavior({
+  methods: {
+    // 针对 threejs 的初始化逻辑
+    initTHREE() {
+      const THREE = this.THREE = createScopedThreejs(this.canvas)
+      registerGLTFLoader(THREE)
+
+      // glTF loader
+      this.loader = new this.THREE.GLTFLoader()
+
+      // 相机
+      this.camera = new THREE.PerspectiveCamera(50, 0.7, 0.1, 1000)
+
+      // 场景
+      const scene = this.scene = new THREE.Scene()
+      const sceneCull = this.sceneCull = new THREE.Scene()
+
+      // 光源
+      const ambientLight = new THREE.AmbientLight(0x555555) // 氛围光
+      scene.add(ambientLight)
+      const dirLight = new THREE.DirectionalLight(0xffffff, 1) // 平行光
+      dirLight.position.set(1, 1, 1)
+      scene.add(dirLight)
+
+      const ambientLightCull = new THREE.AmbientLight(0x555555) // 氛围光
+      sceneCull.add(ambientLightCull)
+      const dirLightCull = new THREE.DirectionalLight(0xffffff, 1) // 平行光
+      dirLightCull.position.set(1, 1, 1)
+      sceneCull.add(dirLightCull)
+
+      // 渲染层
+      const renderer = this.renderer = new THREE.WebGLRenderer({
+        antialias: true,
+        alpha: true
+      })
+      renderer.gammaOutput = true
+      renderer.gammaFactor = 2.2
+    },
+    initYUVShader() {
+      const gl = this.gl = this.renderer.getContext()
+      const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM)
+      const vs = `
+                attribute vec2 a_position;
+                attribute vec2 a_texCoord;
+                uniform mat3 displayTransform;
+                varying vec2 v_texCoord;
+                void main() {
+                vec3 p = displayTransform * vec3(a_position, 0);
+                gl_Position = vec4(p, 1);
+                v_texCoord = a_texCoord;
+                }
+            `
+      const fs = `
+                precision highp float;
+
+                uniform sampler2D y_texture;
+                uniform sampler2D uv_texture;
+                varying vec2 v_texCoord;
+                void main() {
+                vec4 y_color = texture2D(y_texture, v_texCoord);
+                vec4 uv_color = texture2D(uv_texture, v_texCoord);
+
+                float Y, U, V;
+                float R ,G, B;
+                Y = y_color.r;
+                U = uv_color.r - 0.5;
+                V = uv_color.a - 0.5;
+                
+                R = Y + 1.402 * V;
+                G = Y - 0.344 * U - 0.714 * V;
+                B = Y + 1.772 * U;
+                
+                gl_FragColor = vec4(R, G, B, 1.0);
+                }
+            `
+      const vertShader = gl.createShader(gl.VERTEX_SHADER)
+      gl.shaderSource(vertShader, vs)
+      gl.compileShader(vertShader)
+
+      const fragShader = gl.createShader(gl.FRAGMENT_SHADER)
+      gl.shaderSource(fragShader, fs)
+      gl.compileShader(fragShader)
+
+      const program = this._program = gl.createProgram()
+      this._program.gl = gl
+      gl.attachShader(program, vertShader)
+      gl.attachShader(program, fragShader)
+      gl.deleteShader(vertShader)
+      gl.deleteShader(fragShader)
+      gl.linkProgram(program)
+      gl.useProgram(program)
+
+      const uniformYTexture = gl.getUniformLocation(program, 'y_texture')
+      gl.uniform1i(uniformYTexture, 5)
+      const uniformUVTexture = gl.getUniformLocation(program, 'uv_texture')
+      gl.uniform1i(uniformUVTexture, 6)
+
+      this._dt = gl.getUniformLocation(program, 'displayTransform')
+      gl.useProgram(currentProgram)
+    },
+    initVAO(program) {
+      const gl = this.renderer.getContext()
+      const ext = gl.getExtension('OES_vertex_array_object')
+      this.ext = ext
+
+      const currentVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING)
+      const vao = ext.createVertexArrayOES()
+
+      ext.bindVertexArrayOES(vao)
+
+      const posAttr = gl.getAttribLocation(program, 'a_position')
+      const pos = gl.createBuffer()
+      gl.bindBuffer(gl.ARRAY_BUFFER, pos)
+      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW)
+      gl.vertexAttribPointer(posAttr, 2, gl.FLOAT, false, 0, 0)
+      gl.enableVertexAttribArray(posAttr)
+      vao.posBuffer = pos
+
+      const texcoordAttr = gl.getAttribLocation(program, 'a_texCoord')
+      const texcoord = gl.createBuffer()
+      gl.bindBuffer(gl.ARRAY_BUFFER, texcoord)
+      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, 0, 1, 1, 0, 0, 0]), gl.STATIC_DRAW)
+      gl.vertexAttribPointer(texcoordAttr, 2, gl.FLOAT, false, 0, 0)
+      gl.enableVertexAttribArray(texcoordAttr)
+      vao.texcoordBuffer = texcoord
+
+      ext.bindVertexArrayOES(currentVAO)
+      return vao
+    },
+    initYUV() {
+      this.initYUVShader()
+      this._vao = this.initVAO(this._program)
+    },
+    renderYUV(frame) {
+      const gl = this.renderer.getContext()
+      gl.disable(gl.DEPTH_TEST)
+      const {
+        yTexture,
+        uvTexture
+      } = frame.getCameraTexture(gl, 'yuv')
+      const displayTransform = frame.getDisplayTransform()
+      if (yTexture && uvTexture) {
+        const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM)
+        const currentActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE)
+        const currentVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING)
+
+        gl.useProgram(this._program)
+        this.ext.bindVertexArrayOES(this._vao)
+
+        gl.uniformMatrix3fv(this._dt, false, displayTransform)
+        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1)
+
+        gl.activeTexture(gl.TEXTURE0 + 5)
+        const bindingTexture5 = gl.getParameter(gl.TEXTURE_BINDING_2D)
+        gl.bindTexture(gl.TEXTURE_2D, yTexture)
+
+        gl.activeTexture(gl.TEXTURE0 + 6)
+        const bindingTexture6 = gl.getParameter(gl.TEXTURE_BINDING_2D)
+        gl.bindTexture(gl.TEXTURE_2D, uvTexture)
+
+        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
+
+        gl.bindTexture(gl.TEXTURE_2D, bindingTexture6)
+        gl.activeTexture(gl.TEXTURE0 + 5)
+        gl.bindTexture(gl.TEXTURE_2D, bindingTexture5)
+
+        gl.useProgram(currentProgram)
+        gl.activeTexture(currentActiveTexture)
+        this.ext.bindVertexArrayOES(currentVAO)
+      }
+    },
+    initDepthShaderHint() {
+      const gl = this.gl = this.renderer.getContext()
+      const ext = gl.getExtension('OES_texture_float')
+      if (!ext) console.warn('OES_texture_float not support')
+      const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM)
+      const vs = `
+              precision highp float;
+              attribute vec2 a_position;
+              attribute vec2 a_texCoord;
+              uniform mat3 displayTransform;
+              varying vec2 v_texCoord;
+              void main() {
+                vec3 p = displayTransform * vec3(a_position, 0);
+                gl_Position = vec4(p, 1);
+                v_texCoord = a_texCoord;
+              }
+            `
+      const fs = `
+              precision highp float;
+              uniform sampler2D depth_texture;
+              varying vec2 v_texCoord;
+              void main() {
+                vec4 depth_color = texture2D(depth_texture, v_texCoord);
+                gl_FragColor = vec4(depth_color.rgb, 1.0);
+              }
+            `
+
+      const vertShader = gl.createShader(gl.VERTEX_SHADER)
+      gl.shaderSource(vertShader, vs)
+      gl.compileShader(vertShader)
+
+      const fragShader = gl.createShader(gl.FRAGMENT_SHADER)
+      gl.shaderSource(fragShader, fs)
+      gl.compileShader(fragShader)
+
+      const program = this._depthProgram = gl.createProgram()
+      this._depthProgram.gl = gl
+      gl.attachShader(program, vertShader)
+      gl.attachShader(program, fragShader)
+      gl.deleteShader(vertShader)
+      gl.deleteShader(fragShader)
+      gl.linkProgram(program)
+      gl.useProgram(program)
+
+      const uniformTexture = gl.getUniformLocation(program, 'depth_texture')
+      gl.uniform1i(uniformTexture, 5)
+
+      this._depthDt = gl.getUniformLocation(program, 'displayTransform')
+      gl.useProgram(currentProgram)
+    },
+    initDepthVAOHint() {
+      const gl = this.renderer.getContext()
+      const ext = gl.getExtension('OES_vertex_array_object')
+      this.ext = ext
+
+      const currentVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING)
+      const vao = ext.createVertexArrayOES()
+
+      ext.bindVertexArrayOES(vao)
+
+      const posAttr = gl.getAttribLocation(this._depthProgram, 'a_position')
+      const pos = gl.createBuffer()
+      gl.bindBuffer(gl.ARRAY_BUFFER, pos)
+      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.3, 0.3, 1, 0.3, 0.3, 1, 1, 1]), gl.STATIC_DRAW)
+      gl.vertexAttribPointer(posAttr, 2, gl.FLOAT, false, 0, 0)
+      gl.enableVertexAttribArray(posAttr)
+      vao.posBuffer = pos
+
+      const texcoordAttr = gl.getAttribLocation(this._depthProgram, 'a_texCoord')
+      const texcoord = gl.createBuffer()
+      gl.bindBuffer(gl.ARRAY_BUFFER, texcoord)
+      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW)
+      gl.vertexAttribPointer(texcoordAttr, 2, gl.FLOAT, false, 0, 0)
+      gl.enableVertexAttribArray(texcoordAttr)
+      vao.texcoordBuffer = texcoord
+
+      ext.bindVertexArrayOES(currentVAO)
+      this._depthVao = vao
+    },
+    initDepthShader() {
+      const gl = this.gl = this.renderer.getContext()
+      const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM)
+
+      const dvs = `#version 300 es
+                precision highp float;
+                in vec2 a_position;
+                in vec2 a_texCoord;
+                uniform mat3 displayTransform;
+                uniform sampler2D depth_texture;     
+                out vec2 v_texCoord;
+        
+                void main() {
+                vec3 p = displayTransform * vec3(a_position, 1);
+                v_texCoord = a_texCoord;
+                vec4 depth_color = texture(depth_texture, v_texCoord);
+                gl_Position = vec4(p.x, p.y, p.z, 1);
+                }
+            `
+
+      const dfs = `#version 300 es
+                precision highp float;
+                uniform sampler2D depth_texture;      
+                out vec4 FragColor;
+                in vec2 v_texCoord;
+        
+                void main() {
+                vec4 depth_color = texture(depth_texture, v_texCoord);
+                gl_FragDepth = depth_color.r;
+            //   FragColor = vec4(depth_color.rgb, 1.0);
+                }
+            `
+      const vertShader = gl.createShader(gl.VERTEX_SHADER)
+      gl.shaderSource(vertShader, dvs)
+      gl.compileShader(vertShader)
+
+      const fragShader = gl.createShader(gl.FRAGMENT_SHADER)
+      gl.shaderSource(fragShader, dfs)
+      gl.compileShader(fragShader)
+
+      const program = this._depthOutputProgram = gl.createProgram()
+      this._depthOutputProgram.gl = gl
+      gl.attachShader(program, vertShader)
+      gl.attachShader(program, fragShader)
+      gl.deleteShader(vertShader)
+      gl.deleteShader(fragShader)
+      gl.linkProgram(program)
+      gl.useProgram(program)
+
+      const uniformDepthTexture = gl.getUniformLocation(this._depthOutputProgram, 'depth_texture')
+      gl.uniform1i(uniformDepthTexture, 5)
+      gl.getUniformLocation(this._depthOutputProgram, 'displayTransform')
+
+      gl.useProgram(currentProgram)
+    },
+    initDepthGL() {
+      // 初始化提示
+      this.initDepthShaderHint()
+      this.initDepthVAOHint()
+      // 初始化深度纹理相关
+      this.initDepthShader()
+      this._vaoDepth = this.initVAO(this._depthOutputProgram)
+    },
+    renderDepthGLHint(frame) {
+      const gl = this.renderer.getContext()
+      const displayTransform = frame.getDisplayTransform()
+
+      // DepthBuffer
+      const depthBufferRes = frame.getDepthBuffer()
+      const depthBuffer = new Float32Array(depthBufferRes.DepthAddress)
+
+      // console.log('depthBuffer', depthBuffer[0], depthBuffer[16], depthBuffer[16 * 16], depthBuffer[56 * 56]);
+
+      const texture = gl.createTexture()
+      gl.bindTexture(gl.TEXTURE_2D, texture)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
+
+      const width = depthBufferRes.width
+      const height = depthBufferRes.height
+
+      // 先直接采用 uint8 写入深度纹理,使用浮点写入的方法会存在锯齿
+      const data = new Uint8Array(width * height * 4)
+      for (let i = 0; i < depthBuffer.length; i++) {
+        const num = parseInt(depthBuffer[i] * 255)
+        data[i] = num
+      }
+      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data)
+
+      const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM)
+      const currentActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE)
+      const currentVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING)
+
+      gl.useProgram(this._depthProgram)
+      this.ext.bindVertexArrayOES(this._depthVao)
+
+      gl.uniformMatrix3fv(this._depthDt, false, displayTransform)
+
+      gl.activeTexture(gl.TEXTURE0 + 5)
+      const bindingTexture5 = gl.getParameter(gl.TEXTURE_BINDING_2D)
+      gl.bindTexture(gl.TEXTURE_2D, texture)
+
+      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
+
+      gl.activeTexture(gl.TEXTURE0 + 5)
+      gl.bindTexture(gl.TEXTURE_2D, bindingTexture5)
+
+      gl.useProgram(currentProgram)
+      gl.activeTexture(currentActiveTexture)
+      this.ext.bindVertexArrayOES(currentVAO)
+    },
+    renderDepthGL(frame) {
+      const gl = this.renderer.getContext()
+      const displayTransform = frame.getDisplayTransform()
+
+      // DepthBuffer
+      const depthBufferRes = frame.getDepthBuffer()
+      const depthBuffer = new Float32Array(depthBufferRes.DepthAddress)
+
+      const texture = gl.createTexture()
+      gl.bindTexture(gl.TEXTURE_2D, texture)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
+
+      const width = depthBufferRes.width
+      const height = depthBufferRes.height
+
+      // 先直接采用 uint8 写入深度纹理,使用浮点写入的方法会存在锯齿
+      const data = new Uint8Array(width * height * 4)
+      for (let i = 0; i < depthBuffer.length; i++) {
+        const num = parseInt(depthBuffer[i] * 255)
+        data[i] = num
+      }
+      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data)
+
+      // console.log('gl depth texture end')
+
+      // 绘制左下角提示
+      const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM)
+      const currentActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE)
+      const currentVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING)
+      const bindingTexture = gl.getParameter(gl.TEXTURE_BINDING_2D)
+
+      gl.useProgram(this._depthProgram)
+      this.ext.bindVertexArrayOES(this._depthVao)
+
+      gl.uniformMatrix3fv(this._depthDt, false, displayTransform)
+
+      gl.activeTexture(gl.TEXTURE0 + 5)
+      const bindingTexture5 = gl.getParameter(gl.TEXTURE_BINDING_2D)
+      gl.bindTexture(gl.TEXTURE_2D, texture)
+
+      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
+
+      gl.activeTexture(gl.TEXTURE0 + 5)
+      gl.bindTexture(gl.TEXTURE_2D, bindingTexture5)
+
+      gl.useProgram(currentProgram)
+      gl.activeTexture(currentActiveTexture)
+      this.ext.bindVertexArrayOES(currentVAO)
+
+      // console.log('gl hint end')
+
+      // 写入深度遮挡纹理到深度值
+
+      gl.enable(gl.DEPTH_TEST)
+      gl.depthMask(true)
+      gl.depthFunc(gl.ALWAYS)
+
+      this.ext.bindVertexArrayOES(this._vaoDepth)
+      gl.useProgram(this._depthOutputProgram)
+
+      gl.uniformMatrix3fv(this._depthDt, false, displayTransform)
+
+      gl.activeTexture(gl.TEXTURE0 + 5)
+      const bindingTexture5Depth = gl.getParameter(gl.TEXTURE_BINDING_2D)
+
+      gl.bindTexture(gl.TEXTURE_2D, texture)
+
+      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
+
+      gl.activeTexture(gl.TEXTURE0 + 5)
+
+      gl.bindTexture(gl.TEXTURE_2D, bindingTexture5Depth)
+
+      gl.useProgram(currentProgram)
+      gl.activeTexture(currentActiveTexture)
+      gl.bindTexture(gl.TEXTURE_2D, bindingTexture)
+
+      this.ext.bindVertexArrayOES(currentVAO)
+
+      gl.depthMask(false)
+
+      gl.depthFunc(gl.LESS)
+
+      // console.log('gl depth draw end')
+    },
+  },
+})
+
+export default threeBehavior

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1647 - 0
subPackages/utils/gltf-loader.js


+ 73 - 0
subPackages/utils/index.js

@@ -0,0 +1,73 @@
+const downloadAndCache = (url, cacheKey) => {
+  return new Promise((resolve, reject) => {
+    wx.downloadFile({
+      url,
+      success(res) {
+        if (res.statusCode === 200) {
+          wx.setStorageSync(cacheKey, res.tempFilePath)
+          resolve(res.tempFilePath)
+        } else {
+          reject(new Error('download failed:' + res.statusCode))
+        }
+      },
+      fail: reject
+    })
+  })
+}
+
+export const clearImageCache = (caheKey => {
+  if (cacheKey) {
+    const cachedPath = wx.getStorageSync(cacheKey)
+    if (cachedPath) {
+      try {
+        wx.removeSavedFile({ filePath: cachedPath })
+      } catch (e) {
+        console.warn('删除文件失败', e)
+      }
+    }
+    wx.removeStorageSync(cacheKey)
+  } else {
+    // 清空所有图片缓存
+    const info = wx.getStorageInfoSync()
+    info.keys.forEach(key => {
+      if (key.startsWith('image_cache_')) {
+        const cachedPath = wx.getStorageSync(key)
+        try {
+          wx.removeSavedFile({ filePath: cachedPath })
+        } catch (e) {
+          console.warn('删除文件失败', e)
+        }
+        wx.removeStorageSync(key)
+      }
+    })
+  }
+})
+
+export const getCachedImage = (url, cacheKey) => {
+  return new Promise((res, rej) => {
+    const cachedPath = wx.getStorageSync(cacheKey)
+
+    if (cachedPath) {
+      wx.getFileInfo({
+        filePath: cachedPath,
+        success() {
+          res(cachedPath)
+        },
+        fail() {
+          downloadAndCache(url, cacheKey).then(res).catch(rej)
+        }
+      })
+    } else {
+      downloadAndCache(url, cacheKey).then(res).catch(rej)
+    }
+  })
+}
+
+export const hash = (str) => {
+  let hash = 0
+  for (let i = 0; i < str.length; i++) {
+    hash = ((hash << 5) - hash) + str.charCodeAt(i)
+    hash |= 0
+  }
+  return hash.toString()
+}