物体摆放(ArkTS)
本章节给出了关键开发步骤,完整代码可以参考示例代码。
约束与限制
物体摆放能力支持部分Phone、部分Tablet设备。请参考硬件要求判断设备是否支持运动跟踪及平面识别特性(ARENGINE_FEATURE_TYPE_SLAM)。
接口说明
AR物体摆放主要依赖ARViewContext,以下接口为AR物体摆放相关接口。详细接口和说明,请参考AR Engine API参考。
| 接口名 | 描述 |
|---|---|
| ARViewContext.init | 初始化ARViewContext,初始化AR会话和设置AR渲染场景等。 |
| ARViewContext.pause | 暂停相机跟踪与AR场景渲染。 |
| ARViewContext.destroy | 销毁ARViewContext,释放ARView使用资源,包括AR会话与呈现场景销毁,在退出ARView场景时使用。 |
| ARViewContext.resume | 用于恢复暂停的相机跟踪与AR场景渲染。 |
| ARViewContext.scene | 设置ARView的AR场景。 |
| ARViewContext.scene | 获得的AR呈现场景,用于管理空间节点。 |
| ARViewContext.session | 获取AR会话,用于获取相关AR环境跟踪、相机跟踪、命中检测等能力,如相机位姿、平面信息、创建锚点等。 |
| ARViewContext.config | 设置AR会话的配置文件,如北向坐标、性能模式等。 |
| ARViewContext.callback | 设置回调函数,以根据回调功能实现对应业务逻辑。 |
| ARFrame.getCamera | 获取当前帧的摄像机参数对象。 |
| ARFrame.getUpdatedTrackables | 获取更新后的指定类型的可追踪对象。 |
| ARFrame.hitTest | 根据相机投射光线,获取预览区域中的像素坐标(pixelX和pixelY)来确定射线方向,然后检测这个射线在平面或点云中是否有交点。 |
| ARAnchor.getPose | 获取锚点在世界坐标系中的位姿信息。 |
| ARHitResult.getHitPose | 获取交点位姿。 |
| ARHitResult.getTrackable | 获取被命中的可追踪对象。 |
| ARPose.getMatrix | 将位姿数据转换为一个4x4的矩阵。 |
开发步骤
AR Engine仅输出识别到的平面数据。为便于用户观察,可使用AGP(Ark Graphics Platform)渲染引擎或者XComponent绘制识别的平面。关于AGP的介绍可以查看ArkGraphics 3D简介和AGP引擎。
对于使用ArkTS的任何AR应用,首先需要创建一个AR会话ARViewContext,用于管理AR Engine的系统状态。AR会话ARViewContext的创建可以参考管理AR会话章节。
导入模块
AR物体摆放所需要导入的模块如下。
import { arEngine, ARView, arViewController } from '@kit.AREngine';
import { Node, Scene, Vec3 } from '@kit.ArkGraphics3D';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
定义变量
定义变量hitAnchorList存储放置物体处的锚点信息、hitPoseList存储放置物体处的位姿信息和statusBarHeight设备状态栏高度。
用户点击设备的坐标和显示预览流的坐标不一致,预览流的窗口略小于设备屏幕,因此需要减去设备状态栏高度以获取准确的点击坐标。
let frame: arEngine.ARFrame;
let hitAnchorList: arEngine.ARAnchor[] = [];
let hitPoseList: Vec3[] = [];
let statusBarHeight: number = 0;
显示预览流
在ARView中加入点击事件,进行碰撞检测,获取锚点位姿数据加入列表。
@Builder
export function ARWorldBuilder(): void {
ARWorld();
}
@Component
struct ARWorld {
@State arContext?: arViewController.ARViewContext = undefined;
@State context: Context = this.getUIContext().getHostContext() as Context;
build(): void {
NavDestination() {
RelativeContainer() {
if (this.arContext) {
ARView({ context: this.arContext })
.height('100%')
.width('100%')
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick((event: ClickEvent) => {
this.objectCollisionDetection(event);
})
}
}
}
.onAppear(() => {
this.initARView();
this.getAvoidArea();
})
.onWillDisappear(() => {
this.stopARView();
})
.onShown(() => {
this.resumeARView();
})
.onHidden(() => {
this.pauseARView();
})
.hideTitleBar(true)
.hideBackButton(true)
.hideToolBar(true)
}
// 获取用户点击坐标,获取碰撞检测结果
private objectCollisionDetection(event: ClickEvent): void {
let x: number = this.getUIContext().vp2px(event.windowX);
let y: number = this.getUIContext().vp2px(event.windowY) - statusBarHeight;
console.info(`Get onclick position, x: ${x} y: ${y}.`);
try {
let result: arEngine.ARHitResult[] = frame.hitTest(x, y);
console.info(`The hitResult size is: ${result.length}.`);
if (!result) {
return;
}
for (let i = 0; i < result.length; i++) {
let hitResult: arEngine.ARHitResult = result[i];
let trackable: arEngine.ARTrackable = hitResult.getTrackable();
if (trackable.type !== arEngine.ARTrackableType.PLANE) {
continue;
}
let hitPlane: arEngine.ARPlane = trackable as arEngine.ARPlane;
let hitPose: arEngine.ARPose = hitResult.getHitPose();
let inPolygon: boolean = hitPlane.isPoseInPolygon(hitPose);
let distance: number = hitResult.distance;
console.info(`The hitResult inPolygon is: ${inPolygon}, distance is: ${distance}.`);
if (!inPolygon || distance <= 0) {
continue;
}
let hitAnchor: arEngine.ARAnchor = hitResult.createAnchor();
let pos: Vec3 = hitAnchor.getPose().translation;
hitPoseList.push(pos);
hitAnchorList.push(hitAnchor);
}
console.info('Succeeded in getting hit result.');
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to get hitResults. Code is ${err.code}, message is ${err.message}.`);
}
}
private initARView(): void {
Scene.load().then((scene: Scene) => {
let viewContext: arViewController.ARViewContext = new arViewController.ARViewContext();
viewContext.scene = scene;
viewContext.callback = new ARViewCallbackImpl();
viewContext.config = {
type: arEngine.ARType.WORLD,
planeFindingMode: arEngine.ARPlaneFindingMode.HORIZONTAL_AND_VERTICAL,
powerMode: arEngine.ARPowerMode.NORMAL,
semanticMode: arEngine.ARSemanticMode.NONE,
poseMode: arEngine.ARPoseMode.GRAVITY,
depthMode: arEngine.ARDepthMode.AUTOMATIC,
meshMode: arEngine.ARMeshMode.DISABLED,
focusMode: arEngine.ARFocusMode.AUTO
}
viewContext.init().then(() => {
this.arContext = viewContext;
console.info('Succeeded in initializing ARView.');
}).catch((err: BusinessError) => {
console.error(`Failed to init ARView. Code is ${err.code}, message is ${err.message}.`);
})
})
}
// 获取屏幕上减去状态栏的真实高度(预览流高度)
private getAvoidArea(): void {
let avoidAreaType: window.AvoidAreaType = window.AvoidAreaType.TYPE_SYSTEM;
window.getLastWindow(this.context).then((data) => {
// 获取顶部状态栏高度
let avoidArea: window.AvoidArea = data.getWindowAvoidArea(avoidAreaType);
statusBarHeight = avoidArea.topRect.height;
console.info(`The statusBarHeight is ${statusBarHeight}.`);
}).catch((err: BusinessError) => {
console.error(`Failed to obtain the window. Code is ${err.code}, message is ${err.message}.`);
})
}
private stopARView(): void {
// ...
}
private resumeARView(): void {
// ...
}
private pauseARView(): void {
// ...
}
}
渲染识别的平面和放置的物体
调用ARViewCallback,使用其中的onFrameUpdate方法进行帧数据更新,获取平面信息。
class ARViewCallbackImpl extends arViewController.ARViewCallback {
onAnchorAdd(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
// ...
}
onAnchorUpdate(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
// ...
}
onFrameUpdate(ctx: arViewController.ARViewContext, sysBootTs: number): void {
if (!ctx.session) {
return;
}
let arSession: arEngine.ARSession = ctx.session;
try {
frame = arSession.getFrame();
let camera: arEngine.ARCamera = frame.getCamera();
let trackable: arEngine.ARTrackable[] = [];
if (camera.state === arEngine.ARTrackingState.TRACKING) {
trackable = arSession.getAllTrackables(arEngine.ARTrackableType.PLANE);
console.info(`Succeeded in getting tracking plane,length is: ${trackable.length}.`); // 输出识别到的平面数量
}
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to update data. Code is ${err.code}, message is ${err.message}.`);
}
}
}