index.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import getBehavior from './behavior'
  2. import yuvBehavior from './yuvBehavior'
  3. import { getCachedImage, hash } from '../../utils/index'
  4. import { MARKER_MAP } 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. },
  16. markerIndex: 0, // 使用的 marker 索引
  17. hintInfo: undefined, // 提示框信息
  18. methods: {
  19. // 对应案例的初始化逻辑,由统一的 behavior 触发
  20. init() {
  21. // 初始化 Three.js,用于模型相关的渲染
  22. this.initTHREE()
  23. // 初始化 GL,基于 Three.js 的 Context,用于相机YUV渲染
  24. this.initYUV()
  25. // 初始化VK
  26. // start完毕后,进行更新渲染循环
  27. this.initVK()
  28. this.markerIndex = 0
  29. // 添加 识别包围盒子
  30. // this.add3DBox()
  31. },
  32. initVK() {
  33. // VKSession 配置
  34. const session = this.session = wx.createVKSession({
  35. track: {
  36. plane: {
  37. mode: 1
  38. },
  39. marker: true,
  40. },
  41. version: 'v1',
  42. gl: this.gl
  43. })
  44. session.start(err => {
  45. if (err) return console.error('VK error: ', err)
  46. console.log('@@@@@@@@ VKSession.version', session.version)
  47. try {
  48. const promises = [
  49. 'https://houseoss.4dkankan.com/project/kelamayi/ylzt-1.png',
  50. ].map(url => {
  51. const cacheKey = 'image_marker_' + hash(url)
  52. return getCachedImage(url, cacheKey)
  53. })
  54. Promise.all(promises).then(paths => {
  55. for (const path of paths) {
  56. session.addMarker(path)
  57. }
  58. })
  59. } catch(err) {
  60. console.log(err)
  61. }
  62. // VKSession EVENT resize
  63. session.on('resize', () => {
  64. this.calcCanvasSize()
  65. })
  66. // VKSession EVENT addAnchors
  67. // session.on('addAnchors', anchors => {
  68. // this.left.visible = true
  69. // this.right.visible = true
  70. // this.top.visible = true
  71. // this.bottom.visible = true
  72. // })
  73. // VKSession EVENT updateAnchors
  74. session.on('updateAnchors', anchors => {
  75. // marker 模式下,目前仅有一个识别目标,可以直接取
  76. const anchor = anchors[0]
  77. const markerId = anchor.id
  78. const size = anchor.size
  79. this.hintInfo = {
  80. markerId,
  81. size
  82. }
  83. if (MARKER_MAP[markerId]) {
  84. wx.navigateTo({
  85. url: '/pages/webview/index?url=' + encodeURIComponent(`https://sit-kelamayi.4dage.com/zuan/#/info/${MARKER_MAP[markerId]}?x=h`),
  86. })
  87. }
  88. })
  89. // VKSession removeAnchors
  90. // 识别目标丢失时,会触发一次
  91. session.on('removeAnchors', anchors => {
  92. // this.left.visible = false
  93. // this.right.visible = false
  94. // this.top.visible = false
  95. // this.bottom.visible = false
  96. if (this.data.hintBoxList && this.data.hintBoxList.length > 0) {
  97. // 清理信息
  98. this.hintInfo = undefined
  99. // 存在列表的情况,去除remove
  100. this.setData({
  101. hintBoxList: []
  102. })
  103. }
  104. })
  105. console.log('ready to initloop')
  106. // start 初始化完毕后,进行更新渲染循环
  107. this.initLoop()
  108. })
  109. },
  110. loop() {
  111. // console.log('loop')
  112. // 获取 VKFrame
  113. const frame = this.session.getVKFrame(this.canvas.width, this.canvas.height)
  114. // 成功获取 VKFrame 才进行
  115. if (!frame) { return }
  116. // 更新相机 YUV 数据
  117. this.renderYUV(frame)
  118. // 获取 VKCamera
  119. const VKCamera = frame.camera
  120. // 相机
  121. if (VKCamera) {
  122. // 接管 ThreeJs 相机矩阵更新,Marker模式下,主要由视图和投影矩阵改变渲染效果
  123. this.camera.matrixAutoUpdate = false
  124. // 视图矩阵
  125. this.camera.matrixWorldInverse.fromArray(VKCamera.viewMatrix)
  126. this.camera.matrixWorld.getInverse(this.camera.matrixWorldInverse)
  127. // 投影矩阵
  128. const projectionMatrix = VKCamera.getProjectionMatrix(NEAR, FAR)
  129. this.camera.projectionMatrix.fromArray(projectionMatrix)
  130. this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix)
  131. }
  132. // 绘制而为提示框的逻辑
  133. if (this.hintInfo) {
  134. // 存在提示信息,则更新
  135. const THREE = this.THREE
  136. // 原点偏移矩阵,VK情况下,marker 点对应就是 0 0 0,世界矩阵可以认为是一个单位矩阵
  137. // marker 右侧点可以理解是 0.5 0 0
  138. const center = new THREE.Vector3()
  139. const right = new THREE.Vector3(0.5, 0, 0)
  140. // 获取设备空间坐标
  141. const devicePos = center.clone().project(this.camera)
  142. // 转换坐标系,从 (-1, 1) 转到 (0, 100),同时移到左上角 0 0,右下角 1 1
  143. const screenPos = new THREE.Vector3(0, 0, 0)
  144. screenPos.x = devicePos.x * 50 + 50
  145. screenPos.y = 50 - devicePos.y * 50
  146. // 获取右侧点信息
  147. const deviceRightPos = right.clone().project(this.camera)
  148. const screenRightPos = new THREE.Vector3(0, 0, 0)
  149. screenRightPos.x = deviceRightPos.x * 50 + 50
  150. const markerHalfWidth = screenRightPos.x - screenPos.x
  151. this.setData({
  152. hintBoxList: [
  153. {
  154. markerId: this.hintInfo.markerId,
  155. left: screenPos.x - markerHalfWidth,
  156. top: screenPos.y - markerHalfWidth,
  157. width: markerHalfWidth * this.data.domWidth * 2 / 100,
  158. height: markerHalfWidth * this.data.domWidth * 2 / 100,
  159. }
  160. ]
  161. })
  162. }
  163. this.renderer.autoClearColor = false
  164. this.renderer.state.setCullFace(this.THREE.CullFaceBack)
  165. this.renderer.render(this.scene, this.camera)
  166. this.renderer.state.setCullFace(this.THREE.CullFaceNone)
  167. },
  168. add3DBox() {
  169. // 添加marker需要的 三维包围框
  170. const THREE = this.THREE
  171. const scene = this.scene
  172. const material = new THREE.MeshPhysicalMaterial({
  173. metalness: 0.0,
  174. roughness: 0.1,
  175. color: 0x64f573,
  176. })
  177. const geometry = new THREE.BoxGeometry(1, 1, 1)
  178. const borderSize = 0.1
  179. const left = new THREE.Mesh(geometry, material)
  180. left.position.set(-0.5, 0, 0)
  181. left.rotation.set(-Math.PI / 2, 0, 0)
  182. left.scale.set(borderSize, 1.1, borderSize)
  183. scene.add(left)
  184. left.visible = false
  185. this.left = left
  186. const right = new THREE.Mesh(geometry, material)
  187. right.position.set(0.5, 0, 0)
  188. right.rotation.set(-Math.PI / 2, 0, 0)
  189. right.scale.set(borderSize, 1.1, borderSize)
  190. scene.add(right)
  191. right.visible = false
  192. this.right = right
  193. const top = new THREE.Mesh(geometry, material)
  194. top.position.set(0, 0, 0.5)
  195. top.rotation.set(0, 0, 0)
  196. top.scale.set(1.1, borderSize, borderSize)
  197. scene.add(top)
  198. top.visible = false
  199. this.top = top
  200. const bottom = new THREE.Mesh(geometry, material)
  201. bottom.position.set(0, 0, -0.5)
  202. bottom.rotation.set(0, 0, 0)
  203. bottom.scale.set(1.1, borderSize, borderSize)
  204. scene.add(bottom)
  205. bottom.visible = false
  206. this.bottom = bottom
  207. console.log('add3DBox is finish')
  208. },
  209. },
  210. })