HyperbasisHyperbasisDocs
Home

HBSpace

Represents a mapped physical environment containing an ARWorldMap.

Overview

HBSpace is the container for ARWorldMap data. It represents a physical location that has been scanned and can be relocalized.

public struct HBSpace: Codable, Identifiable, Equatable {
    public let id: UUID
    public var name: String?
    public var worldMapData: Data
    public let createdAt: Date
    public var updatedAt: Date
}

Properties

PropertyTypeDescription
id*UUIDUnique identifier for the space
nameString?Human-readable name (e.g., 'Living Room')
worldMapData*DataSerialized ARWorldMap (typically 5-50MB)
createdAt*DateWhen the space was first created
updatedAt*DateWhen the space was last modified

Initialization

From ARWorldMap (typical usage)

let space = try HBSpace(
    id: UUID(),           // optional, defaults to new UUID
    name: "My Room",      // optional
    worldMap: arWorldMap  // ARWorldMap from ARSession
)

From pre-serialized data (loading from storage)

let space = HBSpace(
    id: existingId,
    name: "My Room",
    worldMapData: serializedData,
    createdAt: originalDate,
    updatedAt: modifiedDate
)

Methods

Serialization

// Serialize ARWorldMap to Data
public static func serialize(worldMap: ARWorldMap) throws -> Data

// Deserialize Data back to ARWorldMap
public static func deserialize(worldMapData: Data) throws -> ARWorldMap

// Convenience: get ARWorldMap from space
public func arWorldMap() throws -> ARWorldMap

Mutation

// Update with new world map (also updates updatedAt)
public mutating func update(worldMap: ARWorldMap) throws

// Update name
public mutating func update(name: String?)

Utilities

// Validate that worldMapData can be deserialized
public func validate() -> Bool

// Size in bytes
public var worldMapSize: Int

// Formatted size (e.g., "12.3 MB")
public var worldMapSizeFormatted: String

Serialization Details

ARWorldMap serialization uses NSKeyedArchiver with secure coding:

// Serialization
let data = try NSKeyedArchiver.archivedData(
    withRootObject: worldMap,
    requiringSecureCoding: true
)

// Deserialization
let worldMap = try NSKeyedUnarchiver.unarchivedObject(
    ofClass: ARWorldMap.self,
    from: data
)
Note

ARWorldMap data is typically 5-50MB depending on environment complexity. Hyperbasis compresses this data by ~40% when storing.

World Map Size Guide

World map size depends on environment complexity and how much the user has scanned:

EnvironmentTypical SizeAfter CompressionNotes
Small room (desk)5-10 MB3-6 MBLimited features, quick scan
Medium room10-25 MB6-15 MBNormal usage
Large room / office25-40 MB15-24 MBOpen floor plan
Outdoor / large area30-50+ MB18-30+ MBMany features, takes longer

Factors that increase size:

  • More textured surfaces (books, posters, patterns)
  • Longer scanning duration
  • Larger physical area covered
  • More feature points detected

Factors that decrease size:

  • Plain walls and surfaces
  • Quick, focused scans
  • Smaller area coverage

When to Capture World Maps

Save your world map at the right time for best relocalization results:

Checklist Before Saving

// Check these conditions before calling session.currentWorldMap
func isReadyToSave(frame: ARFrame) -> Bool {
    // 1. Tracking must be normal
    guard frame.camera.trackingState == .normal else {
        return false
    }

    // 2. Should have detected some planes
    let planeAnchors = frame.anchors.compactMap { $0 as? ARPlaneAnchor }
    guard planeAnchors.count >= 2 else {
        return false
    }

    // 3. World mapping status should be good
    guard frame.worldMappingStatus == .mapped ||
          frame.worldMappingStatus == .extending else {
        return false
    }

    return true
}

Best Practices

DoDon't
Wait for tracking state .normalSave immediately on app launch
Check worldMappingStatus is .mappedSave when tracking is .limited
Let user look around the space firstSave after scanning only one small area
Save after placing the first anchorSave before any AR content is placed
Save when going to backgroundWait too long (user might leave)

World Mapping Status

switch frame.worldMappingStatus {
case .notAvailable:
    // Too early, wait longer
    statusLabel.text = "Initializing..."

case .limited:
    // Some data, but not enough for reliable relocalization
    statusLabel.text = "Move around to scan more"

case .extending:
    // Good! Can save, but more scanning would help
    statusLabel.text = "Good - keep scanning for better results"

case .mapped:
    // Excellent! Best time to save
    statusLabel.text = "Ready to save"

@unknown default:
    break
}

Save on Background

Always save the world map when the app goes to background:

NotificationCenter.default.addObserver(
    forName: UIApplication.willResignActiveNotification,
    object: nil,
    queue: .main
) { [weak self] _ in
    Task {
        await self?.saveCurrentState()
    }
}

Relocalization Tips

For successful relocalization when the user returns:

  1. Similar lighting - Dramatically different lighting can prevent recognition
  2. Same physical space - User needs to be in the same room
  3. Minimal changes - Major furniture rearrangement may break relocalization
  4. Help the user - Show instructions to move the phone around
func showRelocalizationHelp() {
    instructionLabel.text = "Point your camera around the room to recognize the space"

    // Timeout and offer to start fresh
    DispatchQueue.main.asyncAfter(deadline: .now() + 20) { [weak self] in
        if self?.isRelocalized == false {
            self?.showStartFreshOption()
        }
    }
}

Error Handling

public enum HBSpaceError: LocalizedError {
    case serializationFailed(underlying: Error?)
    case deserializationFailed(underlying: Error?)
    case invalidWorldMapData
}

Usage Examples

Create and save a space

// Get world map from AR session
let worldMap = try await arSession.currentWorldMap

// Create space
let space = try HBSpace(name: "Living Room", worldMap: worldMap)

// Save to storage
try await storage.save(space)

Load and relocalize

// Load most recent space
let spaces = try await storage.loadAllSpaces()
let mostRecent = spaces.max(by: { $0.updatedAt < $1.updatedAt })

if let space = mostRecent {
    // Get world map for relocalization
    let worldMap = try space.arWorldMap()

    // Configure AR session
    let config = ARWorldTrackingConfiguration()
    config.initialWorldMap = worldMap
    arSession.run(config, options: [.resetTracking])
}

Update an existing space

// After environment has changed
var space = existingSpace
let newWorldMap = try await arSession.currentWorldMap
try space.update(worldMap: newWorldMap)
try await storage.save(space)

Next Steps