import { Camera, Engine, Transform } from "oasis-engine"; import { ARBackground } from "./ARBackground"; import { ARPlaneMode, ARStartState, ARVersion } from "./AREnum"; export default class ARControl { /** 当前VK版本 */ private static _curVersion: ARVersion = ARVersion.v2; /** 当前 VKSession */ private static _curARSession: WechatMiniprogram.VKSession | null; private static _allReadyCB: Function | null; private static _bStart: boolean = false; private static _bRunning: boolean = false; /** 相机部分 */ private static _camera: Camera; private static _cameraTransform: Transform; private static _viewWidth: number = 300; private static _viewHeight: number = 600; private static _nearClipPlane = 0; private static _farClipPlane = 0; /** 原始 engine 中与 raf 和 caf 相关的函数 */ private static _oriAnimate: () => void; private static _oriResume: () => void; private static _oriPause: () => void; /** 背景部分 */ private static _background: ARBackground; /** * 最终呈现的view宽 */ public static get viewWidth(): number { return this._viewWidth; } public static set viewWidth(val: number) { this._viewWidth = val; } /** * 最终呈现的view高 */ public static get viewHeight(): number { return this._viewHeight; } public static set viewHeight(val: number) { this._viewHeight = val; } /** * 初始化 ExpAR . * @param successFun - 成功回调 * @param failFun - 失败回调 */ static init( camera: Camera, background: ARBackground, planeMode: ARPlaneMode, successFun?: Function, failFun?: Function ): void { if ( !wx.isVKSupport(this._curVersion) && this._curVersion === ARVersion.v2 ) { console.error("AR.init notSupport " + this._curVersion); // 降级到v1 this._curVersion = ARVersion.v1; if ( planeMode === ARPlaneMode.vertical || planeMode === ARPlaneMode.both ) { console.error("AR.init notSupport Plane" + planeMode); // 降级到横向平面 planeMode = ARPlaneMode.horizontal; } if (!wx.isVKSupport(this._curVersion)) { console.error("AR.init notSupport " + this._curVersion); failFun && failFun(); return; } } // 销毁旧的 ARSession this._curARSession && this._curARSession.destroy(); this._camera = camera; this._cameraTransform = camera.entity.transform; if (this._background !== background) { this._background && this._background.destroy(); this._background = background; } this._curARSession = wx.createVKSession({ track: { plane: { mode: 1 }, }, version: this._curVersion, }); if (this._curARSession) { successFun && successFun(); } else { failFun && failFun(); } } /** * 启动 AR * @param allReadyCB - 启动成功且空间定位完毕的回调(所有准备工作都 OK ) * @param success - 启动成功的回调 * @param fail - 启动失败的回调 * @returns */ static start( allReadyCB?: Function, success?: Function, fail?: Function ): void { const { _curARSession: curARSession } = this; if (!curARSession) { console.error("VKSession not exit!"); fail && fail(); return; } curARSession.start((code: ARStartState | null) => { if (!code) { if (curARSession) { this._bStart = true; this._bRunning = true; this._overrideFunction(this._camera.engine); if (this._curVersion === ARVersion.v1) { const calPosition = () => { // @ts-ignore const hitTestRes = curARSession.hitTest(0.5, 0.5, true); if (hitTestRes.length) { allReadyCB && setTimeout(allReadyCB); } else { setTimeout(calPosition, 1000); } }; calPosition(); } else { allReadyCB && (this._allReadyCB = allReadyCB); } success && success(); } else { this._bStart = false; this._bRunning = false; fail && fail(); } } else { this._bStart = false; this._bRunning = false; fail && fail(); } }); } /** * 关闭 AR * @param success * @param fail */ static stop(success?: Function, fail?: Function): void { this._bStart = false; this._bRunning = false; const { _curARSession: curARSession } = this; if (!curARSession) { console.error("VKSession not exit!"); fail && fail(); return; } curARSession.stop(); this._reductionFunction(this._camera.engine); success && success(); } /** * HitTest. * @param x * @param y * @returns */ static hitTest(x: number, y: number): WechatMiniprogram.HitTestRes[] | null { if (this._curARSession) { // @ts-ignore return this._curARSession.hitTest(x, y); } else { return null; } } /** * 后台回调 */ static onHide() { if (this._bRunning && this._curARSession) { this._curARSession.stop(); this._bRunning = false; } } /** * 前台回调 */ static onShow() { if (!this._bRunning && this._bStart && this._curARSession) { this.start(); } } /** * 更新相机与背景 * @returns */ private static _onUpdate() { const arSession = this._curARSession; if (!arSession) { return; } // 更新 VKFrame const vkFrame = arSession.getVKFrame(this._viewWidth, this._viewHeight); if (!vkFrame) { return; } // 更新相机 this._updateCamera(vkFrame.camera); // 更新背景 this._background.onARUpdate(this._viewWidth, this.viewHeight, vkFrame); if (this._curVersion === ARVersion.v2 && this._allReadyCB) { this._allReadyCB(); this._allReadyCB = null; } } /** * 更新相机 * @param camera * @param transform */ private static _updateCamera(vkCamera: WechatMiniprogram.VKCamera) { const { _camera: camera, _cameraTransform: cameraTransform } = this; cameraTransform.worldMatrix = cameraTransform.worldMatrix .setValueByArray(vkCamera.viewMatrix) .invert(); if ( camera.nearClipPlane !== this._nearClipPlane || camera.farClipPlane !== this._farClipPlane ) { this._nearClipPlane = camera.nearClipPlane; this._farClipPlane = camera.farClipPlane; camera.projectionMatrix = camera.projectionMatrix.setValueByArray( vkCamera.getProjectionMatrix(camera.nearClipPlane, camera.farClipPlane) ); } } /** * 重写 engine 中与 raf 和 caf 相关的函数 * @param engine - 当事引擎 * @returns */ private static _overrideFunction(engine: Engine) { const curARSession = this._curARSession; if (!curARSession) { return; } // 涉及 requestAnimationFrame 和 cancelAnimationFrame 的都要重写 const raf = curARSession.requestAnimationFrame; const caf = curARSession.cancelAnimationFrame; // @ts-ignore this._oriAnimate = engine._animate; this._oriPause = engine.pause; this._oriResume = engine.resume; // @ts-ignore engine._animate = function () { // @ts-ignore if (this._vSyncCount) { // @ts-ignore this._requestId = raf(this._animate); // @ts-ignore if (this._vSyncCounter++ % this._vSyncCount === 0) { // 更新 VK ARControl._onUpdate(); // @ts-ignore this.update(); // @ts-ignore this._vSyncCounter = 1; } } else { // @ts-ignore this._timeoutId = window.setTimeout( // @ts-ignore this._animate, // @ts-ignore this._targetFrameInterval ); // @ts-ignore this.update(); } }.bind(engine); engine.resume = function () { // @ts-ignore if (!this._isPaused) return; // @ts-ignore this._isPaused = false; // @ts-ignore this.time.reset(); // @ts-ignore raf(this._animate); }.bind(engine); engine.pause = function () { // @ts-ignore this._isPaused = true; // @ts-ignore caf(this._requestId); // @ts-ignore clearTimeout(this._timeoutId); }; } /** * 恢复 engine 中与 raf 和 caf 相关的函数 * @param engine - 当事引擎 */ private static _reductionFunction(engine: Engine) { // @ts-ignore engine._animate = this._oriAnimate; engine.pause = this._oriPause; engine.resume = this._oriResume; } }