دليل مطوّري برامج المواضع الفورية لنظام التشغيل Android

تعرَّف على كيفية استخدام واجهة برمجة التطبيقات Instant Placement API في تطبيقاتك.

المتطلبات الأساسية

قبل المتابعة، تأكَّد من فهم مفاهيم الواقع المعزّز الأساسية وكيفية ضبط جلسة ARCore.

ضبط جلسة جديدة باستخدام ميزة "الوضع الفوري"

في جلسة ARCore جديدة، فعِّل وضع "الوضع الفوري".

جافا

// 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().

جافا

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.

جافا

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

بعد ذلك، أضِف ما يلي إلى نشاطك.

جافا

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

زيادة الكفاءة بعد وضع الكائن

أوقِف ميزة "وضع العناصر بشكل فوري" عندما يتم وضع العنصر بشكل صحيح لتوفير دورات وحدة المعالجة المركزية والطاقة.

جافا

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