识别平面语义（ArkTS）
=============

#### 约束与限制

从5.1.0(18)开始，识别平面语义能力支持部分Phone、部分Tablet设备。请参考[硬件要求](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-preparations#硬件要求)判断设备是否支持平面及物体语义特性（[ARENGINE\_FEATURE\_TYPE\_SEMANTIC](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arfeaturetype)）。

#### 接口说明

获取平面语义信息可以通过[ARPlane](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arplane)平面对象获取，以下接口为平面相关接口。详细接口和说明，请参考[AR Engine API参考](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine)。

| 接口名 | 描述 |
| :-- | :-- |
| [ARTrackable.getPose](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#artrackablegetpose) | 获取追踪目标的位姿信息。 |
| [ARTrackable.getAnchors](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#artrackablegetanchors) | 获取绑定到输入可跟踪对象的锚点对象。 |
| [ARPose.getMatrix](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arposegetmatrix) | 将位姿数据转换为一个4x4的矩阵。 |
| [ARPlane.getPolygonXZ](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arplanegetpolygonxz) | 获取检测到的平面2D顶点数组。 |
| [ARPlane.getSubsumedBy](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arplanegetsubsumedby) | 获取平面的父平面（当平面与另一个平面合并时会生成父平面）。 |
| [ARPlane.isPoseInExtents](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arplaneisposeinextents) | 检查给定位姿是否在平面的边界矩形内。 |
| [ARPlane.isPoseInPolygon](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arplaneisposeinpolygon) | 检查给定位姿是否在平面的边界多边形内。 |

#### 开发步骤

AR Engine仅输出识别到的平面数据。为便于用户观察，可使用AGP（Ark Graphics Platform）渲染引擎或者[XComponent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-xcomponent)绘制识别的平面。关于AGP的介绍可以查看[ArkGraphics 3D简介](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkgraphics3d-overview)和[AGP引擎](https://gitcode.com/openharmony/graphic_graphic_3d)。

对于使用ArkTS的任何AR应用，首先需要创建一个AR会话[ARViewContext](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arviewcontroller#arviewcontext)，用于管理AR Engine的系统状态。AR会话[ARViewContext](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arviewcontroller#arviewcontext)的创建可以参考[管理AR会话](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-arsession)章节。

识别平面语义之前需要先检测识别环境中的平面，如何检测识别环境中的平面请参考[检测环境中的平面](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-get-plane)。

#### \[h2\]导入模块

识别平面语义能力所需要导入的模块如下：

import { arEngine, ARView, arViewController } from '@kit.AREngine';
import { Node, Scene } from '@kit.ArkGraphics3D';
import { BusinessError } from '@kit.BasicServicesKit';

#### \[h2\]定义变量

定义变量planeLabel接收平面类型标签信息。

let planeLabel: arEngine.ARSemanticPlaneLabel;

#### \[h2\]显示平面语义信息

首先初始化AR会话和AR场景，可以参考[初始化AR会话和AR场景](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-arsession#初始化ar会话和ar场景)章节。

更改semanticMode为[ARSemanticMode](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arengine#arsemanticmode).PLANE，启用平面语义识别能力。

在设备界面上显示识别到平面的信息，使用重复调用函数方法在设备界面上实时更新识别到的平面语义信息。

@Builder
export function ARTargetBuilder(): void {
  ARTarget();
}

@Component
struct ARTarget {
  @State arContext?: arViewController.ARViewContext = undefined;
  // 平面类型
  @State targetPlaneLabel: arEngine.ARSemanticPlaneLabel = planeLabel;
  private intervalId: number = -1;
  // 重复调用函数时间间隔为33ms，即设定为30fps
  private delayInterval: number = 33;

  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 }
            })

          // 在屏幕底部显示识别的平面信息
          Column() {
            Text(\`Label: ${convertSemanticLabel(this.targetPlaneLabel)}\`)
              .infoStyles()
          }
          .alignItems(HorizontalAlign.Center)
          .margin({ bottom: 10 })
          .alignRules({
            bottom: { anchor: '\_\_container\_\_', align: VerticalAlign.Bottom },
            middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
          })
        }
      }
    }
    .onAppear(() => {
      this.initARView();
      // 设定在30fps下更新识别平面语义信息
      this.intervalId = setInterval(() => {
        this.targetPlaneLabel = planeLabel;
      }, this.delayInterval);
    })
    .onWillDisappear(() => {
      // 退出setInterval函数
      clearInterval(this.intervalId);
      this.stopARView();
    })
    .onShown(() => {
      this.resumeARView();
    })
    .onHidden(() => {
      this.pauseARView();
    })
    .hideTitleBar(true)
    .hideBackButton(true)
    .hideToolBar(true)
  }

  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.PLANE, // 识别平面语义
        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 stopARView(): void {
    // ...
  }
  private resumeARView(): void {
    // ...
  }
  private pauseARView(): void {
    // ...
  }
}

// 界面显示文本样式
@Extend(Text)
function infoStyles() {
  .fontColor(Color.Yellow)
  .fontSize(24)
  .textShadow({
    radius: 10,
    color: Color.Black,
    offsetX: 0,
    offsetY: 0
  })
  .textAlign(TextAlign.Start)
}

#### \[h2\]获取语义信息

调用[ARViewCallback](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arviewcontroller#arviewcallback)，使用其中的[onFrameUpdate](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arengine-api-arviewcontroller#arviewcallbackonframeupdate)方法进行帧数据更新，在设备界面上显示识别到的平面类型。

增加获取语义信息的方法plane.label，获取每一帧识别到的平面语义信息。

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 {
      let frame: arEngine.ARFrame = 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}\`);
      }

      for (let i = 0; i < trackable.length; ++i) {
        let plane: arEngine.ARPlane = trackable\[i\] as arEngine.ARPlane;

        // 更新识别的平面语义信息
        planeLabel = plane.label;
        console.info(\`Succeeded in updating frame data for loop: ${plane.label}\`);
      }

    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(\`Failed to update data. Code is ${err.code}, message is ${err.message}.\`);
    }
  }
}

#### \[h2\]识别平面语义的自定义方法

自定义方法获取顶点数据getVertices、创建索引generateMeshIndex、创建mesh数据generateMeshInput，可参考[检测平面的自定义方法](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-get-plane#检测平面的自定义方法)。

arrayBufferFloat32ToNumber可以参考[数据类型转换说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-arraybuffer-info)。

平面语义标签转换convertSemanticLabel可参考如下。

function convertSemanticLabel(obj: number): string {
  let res: string = '';
  if (obj === 0) {
    res = 'UNKNOWN';
  } else if (obj === 1) {
    res = 'WALL';
  } else if (obj === 2) {
    res = 'FLOOR';
  } else if (obj === 3) {
    res = 'SEAT';
  } else if (obj === 4) {
    res = 'TABLE';
  } else if (obj === 5) {
    res = 'CEILING';
  } else if (obj === 6) {
    res = 'DOOR';
  } else if (obj === 7) {
    res = 'WINDOW';
  } else if (obj === 8) {
    res = 'BED';
  } else if (obj === 9) {
    res = 'PLANE SPACE';
  } else if (obj === 10) {
    res = 'CUBE VOLUME';
  } else if (obj === 11) {
    res = 'CUBE SPACE';
  }
  return res;
}