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

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

前提条件

确保您了解 AR 基础概念 以及如何在继续之前配置 ARCore 现场录像

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

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

JavaKotlin
// 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);
}
// 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() 方法检索当前姿势。

JavaKotlin
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
();
   
}
 
}
}
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 封装容器。

JavaKotlin
// 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;
 
}
}
// 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 中。

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

提高对象放置后的效率

正确放置对象时停用即时放置,以节省 CPU 周期 和电量。

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