适用于 Android 的即时展示位置开发者指南

了解如何在您的应用中使用 Instant Placement API

前提条件

确保您了解基本 AR 概念以及如何配置 ARCore 会话,然后再继续。

使用即时展示位置配置新会话

在新的 ARCore 会话中,启用 Instant Placement 模式

Java

// Create the ARCore session.
public void createSession() {
  session = new Session(applicationContext);
  Config config = new Config(session);
  // Set the Instant Placement mode.
  config.setInstantPlacementMode(InstantPlacementMode.LOCAL_Y_UP);
  session.configure(config);
}

Kotlin

// Create the ARCore session.
fun createSession() {
  session = Session(applicationContext);
  val config = Config(session)
  // Set the Instant Placement mode.
  config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
  session.configure(config)
}

放置对象

使用 Frame.hitTestInstantPlacement() 可在给定屏幕点按位置的情况下创建可跟踪的即时放置点。使用 getPose() 方法检索当前姿势。

Java

private placementIsDone = false;

public void onDrawFrame(GL10 gl) {
  Frame frame = session.update();

  // Place an object on tap.
  if (!placementIsDone && didUserTap()) {
    // Use estimated distance from the user's device to the real world, based
    // on expected user interaction and behavior.
    float approximateDistanceMeters = 2.0f;
    // Performs a ray cast given a screen tap position.
    List<HitResult> results =
      frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters);
    if (!results.isEmpty()) {
      InstantPlacementPoint point = (InstantPlacementPoint) results.get(0).getTrackable();
      // Create an Anchor from the point's pose.
      Anchor anchor = point.createAnchor(point.getPose());
      placementIsDone = true;
      disableInstantPlacement();
    }
  }
}

Kotlin

var placementIsDone = false;

fun onDrawFrame(gl: GL10) {
  val frame = session.update();

  // Place an object on tap.
  if (!placementIsDone && didUserTap()) {
    // Use estimated distance from the user's device to the real world, based
    // on expected user interaction and behavior.
    val approximateDistanceMeters = 2.0f;
    // Performs a ray cast given a screen tap position.
    val results = frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters)
    if (results.isNotEmpty()) {
      val point = results[0].trackable as InstantPlacementPoint
      // Create an Anchor from the point's pose.
      val anchor = point.createAnchor(point.pose)
      placementIsDone = true
      disableInstantPlacement()
    }
  }
}

即时放置支持通过大致距离跟踪屏幕空间,一旦即时放置位置点锚定在现实世界中,它就会自动切换到完整跟踪。使用 getPose() 检索当前姿势。使用 getTrackingMethod() 获取当前的跟踪方法。

尽管 ARCore 可以针对任何方向的表面执行即时放置命中测试,但命中结果始终会返回针对重力方向 +Y 向上的姿势。在水平表面,点击测试可以更快地返回准确位置。

平滑跟踪方法转换

当实际深度可用时,ARCore 会将 InstantPlacementPoint跟踪方法SCREENSPACE_WITH_APPROXIMATE_DISTANCE 更改为 FULL_TRACKING。该点的姿态也会改变,以反映实际深度。这可能会导致对象看起来突然放大或缩小。为避免这种突然更改,请添加 InstantPlacementPoint 封装容器。

Java

// Wrapper class to track state to reduce sudden visual changes in object size
class WrappedInstantPlacement {
  public InstantPlacementPoint point;
  public InstantPlacementPoint.TrackingMethod previousTrackingMethod;
  public float previousDistanceToCamera;
  public float scaleFactor = 1.0f;

  public WrappedInstantPlacement(
      InstantPlacementPoint point,
      InstantPlacementPoint.TrackingMethod previousTrackingMethod,
      float previousDistanceToCamera) {
    this.point = point;
    this.previousTrackingMethod = previousTrackingMethod;
    this.previousDistanceToCamera = previousDistanceToCamera;
  }
}

Kotlin

// Wrapper class to track state to reduce sudden visual changes in object size
class WrappedInstantPlacement(
  var point: InstantPlacementPoint,
  var previousTrackingMethod: InstantPlacementPoint.TrackingMethod,
  var previousDistanceToCamera: Float,
  var scaleFactor: Float = 1.0f
)

然后,将以下代码添加到您的 activity 中。

Java

List<WrappedInstantPlacement> wrappedPoints = new ArrayList<>();

public void onDrawFrame(GL10 gl) {
  Frame frame = session.update();
  Camera camera = frame.getCamera();

  // Place an object on tap.
  if (didUserTap()) {
    // Instant Placement should only be applied if no results are available through hitTest.
    List<HitResult> results = frame.hitTest(tapX, tapY);
    if (results.isEmpty()) {
      // Use the estimated distance from the user's device to the closest
      // available surface, based on expected user interaction and behavior.
      float approximateDistanceMeters = 2.0f;

      // Returns a single result if the hit test was successful.
      results =
          frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters);
      if (!results.isEmpty()) {
        // Instant placement was successful.
        InstantPlacementPoint point = (InstantPlacementPoint) results.get(0).getTrackable();
        wrappedPoints.add(new WrappedInstantPlacement(point, point.getTrackingMethod(),
          distance(camera.getPose(), point.getPose())));
      }
    } else {
      // results contain valid hit tests which can be used directly, so instant placement is not required.
    }
  }

  for (WrappedInstantPlacement wrappedPoint : wrappedPoints) {
    InstantPlacementPoint point = wrappedPoint.point;
    if (point.getTrackingState() == TrackingState.STOPPED) {
      wrappedPoints.remove(wrappedPoint);
      continue;
    }
    if (point.getTrackingState() == TrackingState.PAUSED) {
      continue;
    }

    if (point.getTrackingMethod() == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE) {
       // Continue to use the estimated depth and pose. Record the distance to
       // the camera for use in the next frame if the transition to full
       // tracking happens.
       wrappedPoint.previousDistanceToCamera = distance(point.getPose(), camera.getPose());
    }
    else if (point.getTrackingMethod() == TrackingMethod.FULL_TRACKING) {
      if (wrappedPoint.previousTrackingMethod == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE) {
        // Change from the estimated pose to the accurate pose. Adjust the
        // object scale to counteract the apparent change due to pose jump.
        wrappedPoint.scaleFactor = distance(point.getPose(), camera.getPose()) /
            wrappedPoint.previousDistanceToCamera;
        // Apply the scale factor to the model.
        // ...
        wrappedPoint.previousTrackingMethod = TrackingMethod.FULL_TRACKING;
      }
    }
  }
}

float distance(Pose p, Pose q) {
  return Math.sqrt(Math.pow(p.tx() - q.tx(), 2) + Math.pow(p.ty() - q.ty(), 2) + Math.pow(p.tz() - q.tz(), 2));
}

Kotlin

var wrappedPoints = mutableListOf<WrappedInstantPlacement>()

fun onDrawFrame(gl: GL10?) {
  val frame = session.update()
  val camera = frame.camera

  // Place an object on tap.
  if (didUserTap()) {
    // Instant Placement should only be applied if no results are available through hitTest.
    var results = frame.hitTest(tapX, tapY);
    if (results.isEmpty()) {
      // Use the estimated distance from the user's device to the closest
      // available surface, based on expected user interaction and behavior.
      val approximateDistanceMeters = 2.0f;

      // Returns a single result if the hit test was successful.
      results = frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters);
      if (results.isNotEmpty()) {
        // Instant placement was successful.
        val point = results[0].trackable as InstantPlacementPoint
        val wrapped = WrappedInstantPlacement(point, point.trackingMethod, point.pose.distance(camera.pose))
        wrappedPoints.add(wrapped)
      }
    } else {
      // Results contain valid hit tests which can be used directly, so Instant Placement 
      // is not required.
    }
  }

  loop@ for (wrappedPoint in wrappedPoints) {
    val point = wrappedPoint.point
    when {
      point.trackingState == TrackingState.STOPPED -> {
        wrappedPoints.remove(wrappedPoint)
        continue@loop
      }
      point.trackingState == TrackingState.PAUSED -> continue@loop
      point.trackingMethod == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE -> {
        // Continue to use the estimated depth and pose. Record the distance to
        // the camera for use in the next frame if the transition to full
        // tracking happens.
        wrappedPoint.previousDistanceToCamera = point.pose.distance(camera.pose)
      }
      wrappedPoint.previousTrackingMethod == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE &&
      point.trackingMethod == TrackingMethod.FULL_TRACKING -> {
        // Change from the estimated pose to the accurate pose. Adjust the
        // object scale to counteract the apparent change due to pose jump.
        wrappedPoint.scaleFactor =
          point.pose.distance(camera.pose) / wrappedPoint.previousDistanceToCamera
        // Apply the scale factor to the model.
        // ...
        wrappedPoint.previousTrackingMethod = TrackingMethod.FULL_TRACKING
      }
    }
  }
}

fun Pose.distance(other: Pose) = sqrt(
  (tx() - other.tx()).pow(2.0f) + (ty() - other.ty()).pow(2.0f) + (tz() - other.tz()).pow(2.0f)
)

放置对象后提高效率

在正确放置对象后停用 Instant Placement,以节省 CPU 周期和功耗。

Java

void disableInstantPlacement() {
  Config config = new Config(session);
  config.setInstantPlacementMode(Config.InstantPlacementMode.DISABLED);
  session.configure(config);
}

Kotlin

fun disableInstantPlacement() {
  val config = Config(session)
  // Set the Instant Placement mode.
  config.instantPlacementMode = Config.InstantPlacementMode.DISABLED
  session.configure(config)
}