index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. import getBehavior from './behavior'
  2. import yuvBehavior from './yuvBehavior'
  3. import { getCachedImage, hash } from '../../utils/index'
  4. import { MARKER_LIST } from './constants'
  5. const NEAR = 0.01
  6. const FAR = 1000
  7. Component({
  8. behaviors: [getBehavior, yuvBehavior],
  9. data: {
  10. widthScale: 1, // canvas宽度缩放值
  11. heightScale: 1, // canvas高度缩放值
  12. markerImgList: [], // 使用的 marker 列表
  13. chooseImgList: [], // 使用的 图片 列表
  14. hintBoxList: [], // 显示提示盒子列表
  15. markerMap: {},
  16. forzen: false,
  17. showVideo: false,
  18. markerIndex: 1, // 使用的 marker 索引
  19. hasCameraPermission: true, // 是否有摄像头权限
  20. },
  21. hintInfo: undefined, // 提示框信息
  22. pageLifetimes: {
  23. show() {
  24. this.checkCameraPermission()
  25. .then(hasPermission => {
  26. if (hasPermission) {
  27. setTimeout(() => {
  28. this.setData({
  29. forzen: false
  30. })
  31. }, 1000)
  32. }
  33. })
  34. }
  35. },
  36. methods: {
  37. checkCameraPermission() {
  38. return new Promise((resolve) => {
  39. wx.getSetting({
  40. success: (res) => {
  41. // 检查是否有摄像头权限
  42. if (!res.authSetting['scope.camera']) {
  43. this.setData({ hasCameraPermission: false })
  44. // 询问用户是否授权
  45. wx.showModal({
  46. title: '权限申请',
  47. content: '需要获取摄像头权限以进行AR识别',
  48. confirmText: '去开启',
  49. cancelText: '取消',
  50. success: (modalRes) => {
  51. if (modalRes.confirm) {
  52. // 打开权限设置页
  53. wx.openSetting({
  54. success: (settingRes) => {
  55. const hasPermission = !!settingRes.authSetting['scope.camera']
  56. this.setData({ hasCameraPermission: hasPermission })
  57. resolve(hasPermission)
  58. // 如果获得权限,初始化相关功能
  59. if (hasPermission) {
  60. this.initIfNeeded()
  61. }
  62. }
  63. })
  64. } else {
  65. resolve(false)
  66. }
  67. }
  68. })
  69. } else {
  70. this.setData({ hasCameraPermission: true })
  71. resolve(true)
  72. }
  73. },
  74. fail: () => {
  75. resolve(false)
  76. }
  77. })
  78. })
  79. },
  80. initIfNeeded() {
  81. if (!this.session) {
  82. this.init()
  83. }
  84. },
  85. handleEnded() {
  86. wx.navigateTo({
  87. url: '/pages/webview/index?url=' + encodeURIComponent(`https://sit-kelamayi.4dage.com/zuan/#/info/${this.data.markerIndex}`),
  88. })
  89. this.setData({
  90. showVideo: false
  91. })
  92. },
  93. // 对应案例的初始化逻辑,由统一的 behavior 触发
  94. init() {
  95. // 初始化 Three.js,用于模型相关的渲染
  96. this.initTHREE()
  97. // 初始化 GL,基于 Three.js 的 Context,用于相机YUV渲染
  98. this.initYUV()
  99. // 初始化VK
  100. // start完毕后,进行更新渲染循环
  101. this.initVK()
  102. // 添加 识别包围盒子
  103. // this.add3DBox()
  104. },
  105. initVK() {
  106. // VKSession 配置
  107. const session = this.session = wx.createVKSession({
  108. track: {
  109. plane: {
  110. mode: 1
  111. },
  112. marker: true,
  113. },
  114. version: 'v1',
  115. gl: this.gl
  116. })
  117. session.start(async err => {
  118. if (err) return console.error('VK error: ', err)
  119. console.log('@@@@@@@@ VKSession.version', session.version)
  120. // VKSession EVENT resize
  121. session.on('resize', () => {
  122. this.calcCanvasSize()
  123. })
  124. // VKSession EVENT addAnchors
  125. // session.on('addAnchors', anchors => {
  126. // this.left.visible = true
  127. // this.right.visible = true
  128. // this.top.visible = true
  129. // this.bottom.visible = true
  130. // })
  131. // VKSession EVENT updateAnchors
  132. session.on('updateAnchors', anchors => {
  133. // marker 模式下,目前仅有一个识别目标,可以直接取
  134. const anchor = anchors[0]
  135. const markerId = anchor.id
  136. const size = anchor.size
  137. this.hintInfo = {
  138. markerId,
  139. size
  140. }
  141. const url = this.data.markerMap[markerId]
  142. if (url) {
  143. const match = url.match(/\/kelamayi\/(\d+)/);
  144. const id = match ? match[1] : null;
  145. console.log('识别到的图片:', url, id)
  146. if (id && !this.data.forzen) {
  147. this.setData({
  148. forzen: true,
  149. markerIndex: Number(id),
  150. showVideo: true
  151. })
  152. }
  153. }
  154. })
  155. // VKSession removeAnchors
  156. // 识别目标丢失时,会触发一次
  157. session.on('removeAnchors', anchors => {
  158. // this.left.visible = false
  159. // this.right.visible = false
  160. // this.top.visible = false
  161. // this.bottom.visible = false
  162. if (this.data.hintBoxList && this.data.hintBoxList.length > 0) {
  163. // 清理信息
  164. this.hintInfo = undefined
  165. // 存在列表的情况,去除remove
  166. this.setData({
  167. hintBoxList: []
  168. })
  169. }
  170. })
  171. console.log('ready to initloop')
  172. // start 初始化完毕后,进行更新渲染循环
  173. this.initLoop()
  174. const batchSize = 2;
  175. const delay = 1000;
  176. for (let i = 0; i < MARKER_LIST.length; i += batchSize) {
  177. const batch = MARKER_LIST.slice(i, i + batchSize);
  178. const promises = batch.map(async url => {
  179. const cacheKey = 'image_marker_' + hash(url);
  180. const sysPath = await getCachedImage(url, cacheKey);
  181. return [sysPath, url];
  182. });
  183. // 等待当前批次完成
  184. const results = await Promise.all(promises);
  185. // 处理当前批次结果
  186. for (const [path, url] of results) {
  187. console.log('marker添加成功:', path);
  188. const id = session.addMarker(path);
  189. console.log('markerId:', id);
  190. this.data.markerMap[id] = url;
  191. }
  192. // 如果不是最后一批,等待2秒
  193. if (i + batchSize < MARKER_LIST.length) {
  194. await new Promise(resolve => setTimeout(resolve, delay));
  195. }
  196. }
  197. })
  198. },
  199. loop() {
  200. // console.log('loop')
  201. // 获取 VKFrame
  202. const frame = this.session.getVKFrame(this.canvas.width, this.canvas.height)
  203. // 成功获取 VKFrame 才进行
  204. if (!frame) { return }
  205. // 更新相机 YUV 数据
  206. this.renderYUV(frame)
  207. // 获取 VKCamera
  208. const VKCamera = frame.camera
  209. // 相机
  210. if (VKCamera) {
  211. // 接管 ThreeJs 相机矩阵更新,Marker模式下,主要由视图和投影矩阵改变渲染效果
  212. this.camera.matrixAutoUpdate = false
  213. // 视图矩阵
  214. this.camera.matrixWorldInverse.fromArray(VKCamera.viewMatrix)
  215. this.camera.matrixWorld.getInverse(this.camera.matrixWorldInverse)
  216. // 投影矩阵
  217. const projectionMatrix = VKCamera.getProjectionMatrix(NEAR, FAR)
  218. this.camera.projectionMatrix.fromArray(projectionMatrix)
  219. this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix)
  220. }
  221. // 绘制而为提示框的逻辑
  222. // if (this.hintInfo) {
  223. // // 存在提示信息,则更新
  224. // const THREE = this.THREE
  225. // // 原点偏移矩阵,VK情况下,marker 点对应就是 0 0 0,世界矩阵可以认为是一个单位矩阵
  226. // // marker 右侧点可以理解是 0.5 0 0
  227. // const center = new THREE.Vector3()
  228. // const right = new THREE.Vector3(0.5, 0, 0)
  229. // // 获取设备空间坐标
  230. // const devicePos = center.clone().project(this.camera)
  231. // // 转换坐标系,从 (-1, 1) 转到 (0, 100),同时移到左上角 0 0,右下角 1 1
  232. // const screenPos = new THREE.Vector3(0, 0, 0)
  233. // screenPos.x = devicePos.x * 50 + 50
  234. // screenPos.y = 50 - devicePos.y * 50
  235. // // 获取右侧点信息
  236. // const deviceRightPos = right.clone().project(this.camera)
  237. // const screenRightPos = new THREE.Vector3(0, 0, 0)
  238. // screenRightPos.x = deviceRightPos.x * 50 + 50
  239. // const markerHalfWidth = screenRightPos.x - screenPos.x
  240. // this.setData({
  241. // hintBoxList: [
  242. // {
  243. // markerId: this.hintInfo.markerId,
  244. // left: screenPos.x - markerHalfWidth,
  245. // top: screenPos.y - markerHalfWidth,
  246. // width: markerHalfWidth * this.data.domWidth * 2 / 100,
  247. // height: markerHalfWidth * this.data.domWidth * 2 / 100,
  248. // }
  249. // ]
  250. // })
  251. // }
  252. this.renderer.autoClearColor = false
  253. this.renderer.state.setCullFace(this.THREE.CullFaceBack)
  254. this.renderer.render(this.scene, this.camera)
  255. this.renderer.state.setCullFace(this.THREE.CullFaceNone)
  256. },
  257. add3DBox() {
  258. // 添加marker需要的 三维包围框
  259. const THREE = this.THREE
  260. const scene = this.scene
  261. const material = new THREE.MeshPhysicalMaterial({
  262. metalness: 0.0,
  263. roughness: 0.1,
  264. color: 0x64f573,
  265. })
  266. const geometry = new THREE.BoxGeometry(1, 1, 1)
  267. const borderSize = 0.1
  268. const left = new THREE.Mesh(geometry, material)
  269. left.position.set(-0.5, 0, 0)
  270. left.rotation.set(-Math.PI / 2, 0, 0)
  271. left.scale.set(borderSize, 1.1, borderSize)
  272. scene.add(left)
  273. left.visible = false
  274. this.left = left
  275. const right = new THREE.Mesh(geometry, material)
  276. right.position.set(0.5, 0, 0)
  277. right.rotation.set(-Math.PI / 2, 0, 0)
  278. right.scale.set(borderSize, 1.1, borderSize)
  279. scene.add(right)
  280. right.visible = false
  281. this.right = right
  282. const top = new THREE.Mesh(geometry, material)
  283. top.position.set(0, 0, 0.5)
  284. top.rotation.set(0, 0, 0)
  285. top.scale.set(1.1, borderSize, borderSize)
  286. scene.add(top)
  287. top.visible = false
  288. this.top = top
  289. const bottom = new THREE.Mesh(geometry, material)
  290. bottom.position.set(0, 0, -0.5)
  291. bottom.rotation.set(0, 0, 0)
  292. bottom.scale.set(1.1, borderSize, borderSize)
  293. scene.add(bottom)
  294. bottom.visible = false
  295. this.bottom = bottom
  296. console.log('add3DBox is finish')
  297. },
  298. },
  299. })