import getBehavior from './behavior' import yuvBehavior from './yuvBehavior' import { getCachedImage, hash } from '../../utils/index' import { MARKER_LIST } from './constants' const NEAR = 0.01 const FAR = 1000 Component({ behaviors: [getBehavior, yuvBehavior], data: { widthScale: 1, // canvas宽度缩放值 heightScale: 1, // canvas高度缩放值 markerImgList: [], // 使用的 marker 列表 chooseImgList: [], // 使用的 图片 列表 hintBoxList: [], // 显示提示盒子列表 markerMap: {}, forzen: false, showVideo: false, markerIndex: 1, // 使用的 marker 索引 hasCameraPermission: true, // 是否有摄像头权限 }, hintInfo: undefined, // 提示框信息 pageLifetimes: { show() { this.checkCameraPermission() .then(hasPermission => { if (hasPermission) { setTimeout(() => { this.setData({ forzen: false }) }, 1000) } }) } }, methods: { checkCameraPermission() { return new Promise((resolve) => { wx.getSetting({ success: (res) => { // 检查是否有摄像头权限 if (!res.authSetting['scope.camera']) { this.setData({ hasCameraPermission: false }) // 询问用户是否授权 wx.showModal({ title: '权限申请', content: '需要获取摄像头权限以进行AR识别', confirmText: '去开启', cancelText: '取消', success: (modalRes) => { if (modalRes.confirm) { // 打开权限设置页 wx.openSetting({ success: (settingRes) => { const hasPermission = !!settingRes.authSetting['scope.camera'] this.setData({ hasCameraPermission: hasPermission }) resolve(hasPermission) // 如果获得权限,初始化相关功能 if (hasPermission) { this.initIfNeeded() } } }) } else { resolve(false) } } }) } else { this.setData({ hasCameraPermission: true }) resolve(true) } }, fail: () => { resolve(false) } }) }) }, initIfNeeded() { if (!this.session) { this.init() } }, handleEnded() { wx.navigateTo({ url: '/pages/webview/index?url=' + encodeURIComponent(`https://sit-kelamayi.4dage.com/zuan/#/info/${this.data.markerIndex}`), }) this.setData({ showVideo: false }) }, // 对应案例的初始化逻辑,由统一的 behavior 触发 init() { // 初始化 Three.js,用于模型相关的渲染 this.initTHREE() // 初始化 GL,基于 Three.js 的 Context,用于相机YUV渲染 this.initYUV() // 初始化VK // start完毕后,进行更新渲染循环 this.initVK() // 添加 识别包围盒子 // this.add3DBox() }, initVK() { // VKSession 配置 const session = this.session = wx.createVKSession({ track: { plane: { mode: 1 }, marker: true, }, version: 'v1', gl: this.gl }) session.start(async err => { if (err) return console.error('VK error: ', err) console.log('@@@@@@@@ VKSession.version', session.version) // 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 } const url = this.data.markerMap[markerId] if (url) { const match = url.match(/\/kelamayi\/(\d+)/); const id = match ? match[1] : null; console.log('识别到的图片:', url, id) if (id && !this.data.forzen) { this.setData({ forzen: true, markerIndex: Number(id), showVideo: true }) } } }) // 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() const batchSize = 2; const delay = 1000; for (let i = 0; i < MARKER_LIST.length; i += batchSize) { const batch = MARKER_LIST.slice(i, i + batchSize); const promises = batch.map(async url => { const cacheKey = 'image_marker_' + hash(url); const sysPath = await getCachedImage(url, cacheKey); return [sysPath, url]; }); // 等待当前批次完成 const results = await Promise.all(promises); // 处理当前批次结果 for (const [path, url] of results) { console.log('marker添加成功:', path); const id = session.addMarker(path); console.log('markerId:', id); this.data.markerMap[id] = url; } // 如果不是最后一批,等待2秒 if (i + batchSize < MARKER_LIST.length) { await new Promise(resolve => setTimeout(resolve, delay)); } } }) }, 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') }, }, })