Android 即時刊登位置開發人員指南

瞭解如何使用 Instant Placement API 您的網站或應用程式

必要條件

請務必先瞭解基本 AR 概念,以及如何設定 ARCore 工作階段,再繼續操作。

使用即時刊登位置設定新的工作階段

在新的 ARCore 工作階段中,啟用即時刊登位置模式

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
)

然後將下列內容加入活動。

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