עבודה עם ARCore ל-Jetpack XR

ARCore ל-Jetpack XR מאפשר לאפליקציות לפעול עם מושגים בסיסיים של מציאות מוגברת (AR), באמצעות פרימיטיבים ברמה נמוכה של הבנת סצנות ומעקב אחר תנועה. כדאי להשתמש ב-ARCore for Jetpack XR כשאתם יוצרים חוויות AR, ואתם צריכים להשתמש בנתונים שטחיים או לתקוע תוכן למיקום קבוע במרחב.

הסבר על מחזור החיים של סשן

כדי לגשת לכל האובייקטים שאחריהם ARCore for Jetpack XR עוקב, צריך להשתמש בסשן. בדומה למחזור החיים של פעילות, גם לאובייקטים של סשנים יש מחזור חיים שצריך לתחזק בהתאם לשימוש של האפליקציה בתכונות של אובייקט הסשן. אם האפליקציה מכילה פעילות אחת שתומכת ב-XR, כדאי לטפל במחזור החיים של הסשן באמצעות רכיב שמתעדכן במחזור החיים.

יצירת סשן

כדי להשתמש בסשן, צריך ליצור אותו. כדי ליצור סשן, המשתמש צריך להעניק לאפליקציה את ההרשאה android.permission.SCENE_UNDERSTANDING.

כדי ליצור סשן:

when (val result = Session.create(owner)) {
  is SessionCreateSuccess -> {
    session = result.session
  }
  is SessionCreatePermissionsNotGranted -> {
   // Request android.permission.SCENE_UNDERSTANDING.
  }
}

במאמר SessionCreateResult מפורטות הסיבות האפשריות לכך שייתכן שלא תהיה אפשרות ליצור סשן.

המשך סשן

צריך להמשיך את הסשן כשהאפליקציה מוכנה לטפל בשינויי המצב מ-ARCore ל-Jetpack XR. במקרים רבים, הפעולה הזו מתבצעת בקריאה החוזרת (callback) של onResume() בפעילות, אבל יכול להיות שתרצו לדחות את העיבוד עד שתהיה אינטראקציה של משתמש.

קטע הקוד הבא מציג דוגמה להמשך סשן.

when (val result = session.resume()) {
  is SessionResumeSuccess -> {
    // Session has been created successfully.
    // Attach any successful handlers here.
  }
  is SessionResumePermissionsNotGranted -> {
    // Request android.permission.SCENE_UNDERSTANDING.
}

במאמר SessionResumeResult מפורטות הסיבות לכך שאי אפשר להמשיך סשן.

השהיה של סשן

כשהפעילות עוברת לרקע, משהים את הסשן באמצעות Session.pause(). השהיה של סשן מפסיקה את המעקב באופן זמני עד שהסשן ממשיך, תוך שמירה על המצב של מערכת התפיסה.

השמדת סשן

כדי למחוק סשן באופן סופי, משתמשים ב-Session.destroy(). הפעולה הזו משחררת משאבים שנמצאים בשימוש בסשן ומוחקת את כל מצבי הסשן.

אחזור המצב של המטוסים הנראים

ARCore ל-Jetpack XR מספק את המצב של המטוסים באמצעות StateFlow שמפיץ את המצב של המטוסים. כשנרשמים למטוסים בסשן, האפליקציה מקבלת התראות כשמטוסים מתווספים, מתעדכנים או מוסרים.

Plane.subscribe(session).collect { planes ->
 // Planes have changed; update plane rendering
}

למטוס יש את המאפיינים הבאים:

  • label: תיאור סמנטי של Plane נתון. יכול להיות Wall,‏ Floor,‏ Ceiling או Table.
  • centerPose: תנוחת מרכז המטוס שזוהה.
  • extents: המימדים של המטוס שזוהה, במטרים.
  • vertices: רשימה של קודקודים של פוליגון מעוגל שמתקרב למישן.

ביצוע בדיקת כניסה (hit-test) למישורים

בדיקת התאמה היא שיטה לחישוב הצטלבות של קרן עם אובייקטים שנמצאים במעקב במהלך הסשן. שימוש נפוץ בבדיקה של אירוע היטה הוא להצביע על טבלה ולהציב אובייקט במיקום הזה. ביצוע בדיקת היטים מניב רשימה של אובייקטים שהוגדרו כהיטים. במילים אחרות, בדיקת ההיט לא נעצרת באובייקט הראשון שנמצא. עם זאת, לרוב אתם רוצים לקבל רק את ההיט הראשון של האובייקט מסוג נתון.

כדי לבצע בדיקת התאמה, משתמשים ב-Interaction.hitTest() עם Ray:

val results = Interaction.hitTest(session, ray)
// When interested in the first Table hit:
val tableHit = results.firstOrNull {
  val trackable = it.trackable
  trackable is Plane && trackable.state.value.label == Plane.Label.Table
}

עיגון תוכן במיקום קבוע במרחב המשותף

כדי לתת לאובייקטים וירטואליים מיקום בעולם האמיתי, משתמשים ב-Anchor. אובייקט עוגן עוזר לאפליקציה לעקוב אחרי מיקום קבוע במרחב הפיזי.

עוגן נוצר באמצעות Pose, שאפשר לפרש ביחס ל-Trackable קיים או לא.

יצירת עוגן ביחס לפריט שניתן למעקב

כשיצירת הצמדה יחסית ל-Trackable, כמו Plane, כך שהצמדה עוקבת אחרי ה-Trackable המצורף כשהוא זז במרחב.

val anchor = trackable.createAnchor(pose)

יצירת עוגן ללא פריט שניתן למעקב

כדי ליצור עוגן שלא מחובר ל-Trackable:

when (val result = Anchor.create(session, pose)) {
  is AnchorCreateSuccess -> // ...
  else -> // handle failure
}

צירוף ישות לעוגן

כדי ליצור עיבוד גרפי של מודל במיקום הזה, יוצרים GltfModel ומגדירים את התנוחה שלו לתנוחה של הצמד. מוודאים שהמודל מוסתר כשהערך של TrackingState של הציר הוא Stopped.

// renderSession is androidx.xr.core.Session
anchor.state.collect { state ->
  if (state.trackingState == TrackingState.Tracking) {
    gltfEntity.setPose(
      renderSession.perceptionSpace.transformPoseTo(state.pose, renderSession.activitySpace)
    )
  } else if (state.trackingState == TrackingState.Stopped) {
    entity.setHidden(true)
  }
}

הסבר על TrackingState

לכל Trackable יש TrackingState שצריך לבדוק לפני שמשתמשים בו. אם ל-Trackable יש TrackableState של Tracking, המערכת מעדכנת את Pose שלו באופן פעיל. Trackable שהערך שלו הוא Paused עשוי להפוך ל-Tracking בעתיד, אבל Trackable שהערך שלו הוא Stopped לעולם לא יהפוך ל-Tracking.

שמירה של ציר לאורך סשנים

עוגן שלא נשמר נעלם אחרי שהסשן נהרס. שמירת עוגן מאפשרת לאפליקציה לזכור את המיקום של העוגן בנתוני האפליקציה הפרטיים שלה. אפשר לאחזר את העוגן הזה בסשן מאוחר יותר, והוא מקובע באותו מיקום בעולם.

כדי לשמור את הצמד, משתמשים ב-anchor.persist() כפי שמוצג כאן:

val uuid = anchor.persist()

האפליקציה יכולה לאחזר את הצמד באמצעות UUID בסשן עתידי:

when (val result = Anchor.load(session, uuid)) {
  is AnchorCreateSuccess -> // Loading was successful. The anchor is stored in result.anchor.
  else -> // handle failure
}

כשלא צריכים יותר את העוגן, צריך להתקשר למספר unpersist(). הפעולה הזו מסירה את הצ'אנק מהאחסון של האפליקציה, ואי אפשר לאחזר את ה-UUID הזה בקריאות ל-Anchor.load().

Anchor.unpersist(session, uuid)

האפליקציה יכולה גם לבקש רשימה של כל עוגנים שנשמרו ועדיין נמצאים באחסון של האפליקציה:

val uuids = Anchor.getPersistedAnchorUuids(session)