AR Session Integration
Integrate Hyperbasis with ARKit sessions.
Overview
Hyperbasis works with ARKit's ARSession and ARWorldMap to provide spatial persistence. This guide covers the key integration points.
Capturing World Maps
Check Mapping Status
Only capture world maps when the environment is well-mapped:
func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
switch camera.trackingState {
case .normal:
// Good mapping, can capture world map
canCaptureWorldMap = true
case .limited(let reason):
canCaptureWorldMap = false
switch reason {
case .initializing:
print("AR session initializing")
case .relocalizing:
print("Relocalizing to previous world map")
case .excessiveMotion:
print("Move device more slowly")
case .insufficientFeatures:
print("Point at more textured surfaces")
@unknown default:
break
}
case .notAvailable:
canCaptureWorldMap = false
}
}Capture World Map
func captureWorldMap() async throws -> ARWorldMap {
return try await arSession.currentWorldMap
}Tip
Best practice is to capture the world map after the user places their first anchor, or after significant environment scanning.
Relocalization
Load and Apply World Map
func relocalize(with worldMap: ARWorldMap) {
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
config.environmentTexturing = .automatic
config.initialWorldMap = worldMap
arSession.run(config, options: [.resetTracking, .removeExistingAnchors])
}Monitor Relocalization
class ARSessionManager: NSObject, ARSessionDelegate {
@Published var isRelocalizing = false
@Published var isRelocalized = false
func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
switch camera.trackingState {
case .normal:
if isRelocalizing {
isRelocalized = true
isRelocalizing = false
}
case .limited(let reason):
if case .relocalizing = reason {
isRelocalizing = true
}
case .notAvailable:
break
}
}
}Note
Relocalization may take a few seconds. Prompt the user to look around the environment to help the AR session find matching features.
Complete Integration Example
@MainActor
class ARSessionManager: NSObject, ObservableObject {
let session = ARSession()
@Published var trackingState: ARCamera.TrackingState = .notAvailable
@Published var isRelocalizing = false
@Published var isRelocalized = false
@Published var canCaptureWorldMap = false
override init() {
super.init()
session.delegate = self
}
func start() {
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
config.environmentTexturing = .automatic
session.run(config)
}
func relocalize(with worldMap: ARWorldMap) {
isRelocalizing = true
isRelocalized = false
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
config.environmentTexturing = .automatic
config.initialWorldMap = worldMap
session.run(config, options: [.resetTracking, .removeExistingAnchors])
}
func getCurrentWorldMap() async throws -> ARWorldMap {
return try await session.currentWorldMap
}
}
extension ARSessionManager: ARSessionDelegate {
nonisolated func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
Task { @MainActor in
trackingState = camera.trackingState
switch camera.trackingState {
case .normal:
if isRelocalizing {
isRelocalized = true
isRelocalizing = false
}
canCaptureWorldMap = true
case .limited(let reason):
canCaptureWorldMap = false
if case .relocalizing = reason {
isRelocalizing = true
}
case .notAvailable:
canCaptureWorldMap = false
}
}
}
}Placing Anchors
Get Transform from Hit Test
func placeAnchor(at screenPoint: CGPoint, in arView: ARView) -> simd_float4x4? {
let results = arView.raycast(
from: screenPoint,
allowing: .estimatedPlane,
alignment: .any
)
return results.first?.worldTransform
}Save Anchor with Hyperbasis
func placeAndSave(at screenPoint: CGPoint) async throws {
guard let transform = placeAnchor(at: screenPoint, in: arView),
let space = currentSpace else { return }
let anchor = HBAnchor(
spaceId: space.id,
transform: transform,
metadata: [
"type": "marker",
"createdAt": .string(ISO8601DateFormatter().string(from: Date()))
]
)
try await storage.save(anchor)
}Recreating Anchors After Relocalization
func recreateAnchors(from savedAnchors: [HBAnchor]) {
for anchor in savedAnchors {
guard let transform = anchor.validSimdTransform else { continue }
// Create your entity
let entity = createEntity(for: anchor)
entity.transform.matrix = transform
// Add to scene
let anchorEntity = AnchorEntity(world: anchor.position)
anchorEntity.addChild(entity)
arView.scene.addAnchor(anchorEntity)
}
}
func createEntity(for anchor: HBAnchor) -> Entity {
let type = anchor.stringMetadata(forKey: "type") ?? "default"
switch type {
case "sticky_note":
return createStickyNote(anchor: anchor)
case "marker":
return createMarker()
default:
return createDefaultEntity()
}
}App Lifecycle
Save on Background
@main
struct MyApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { oldPhase, newPhase in
if newPhase == .background {
NotificationCenter.default.post(name: .saveWorldMap, object: nil)
}
}
}
}
// In your manager
init() {
NotificationCenter.default.addObserver(
forName: .saveWorldMap,
object: nil,
queue: .main
) { [weak self] _ in
Task {
await self?.saveCurrentWorldMap()
}
}
}