2023 Spatial Computing
WWDC23 · 23 min · Spatial Computing
Explore enhancements to RoomPlan
Join us for an exciting update to RoomPlan as we explore MultiRoom support and enhancements to room representations. Learn how you can scan areas with more detail, capture multiple rooms, and merge individual scans into one larger structure. We’ll also share workflows and best practices when working with RoomPlan results that you want to combine into your existing 3D model library.
Watch at developer.apple.com ↗Code shown on screen · 10 snippets
RoomPlan with custom ARSession
// RoomCaptureSession
public class RoomCaptureSession {
// Init: ARSession is an optional input for RoomCaptureSession
public init(arSession: ARSession? = nil) {
...
}
// Stop: pauseARSession is used for whether to continue ARSession experience
public func stop(pauseARSession: Bool = true) {
...
}
} MultiRoom support with Continuous ARSession
// Continuous ARSession
// start 1st scan
roomCaptureSession.run(configuration: captureSessionConfig)
// stop 1st scan with continuing ARSession
roomCaptureSession.stop(pauseARSession: false)
// start 2nd scan
roomCaptureSession.run(configuration: captureSessionConfig)
// stop 2nd scan (pauseARSession = true by default)
roomCaptureSession.stop() MultiRoom capture with loading ARWorldMap
// Capture with loading ARWorldMap
// load ARWorldMap
let arWorldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data)
// run ARKit relocalization
let arWorldTrackingConfig = ARWorldTrackingConfiguration()
arWorldTrackingConfig.initialWorldMap = arWorldMap
roomCaptureSession.init()
roomCaptureSession.arSession.run(arWorldTrackingConfig, options: [])
// Wait for relocalization to complete
// start 2nd scan
roomCaptureSession.run(configuration: captureSessionConfig)
// stop 2nd scan
roomCaptureSession.stop() StructureBuilder
// StructureBuilder
// create structureBuilder instance
let structureBuilder = StructureBuilder(option: [.beautifyObjects])
// load multiple capturedRoom results to capturedRoomArray
var capturedRoomArray: [CapturedRoom] = []
// run structureBuilder API to get capturedStructure
let capturedStructure = try await structureBuilder.capturedStructure(from: capturedRoomArray)
// export capturedStructure to usdz
try capturedStructure.export(to: destinationURL) CapturedStructure
// CapturedStructure
public struct CapturedStructure: Codable, Sendable {
public var rooms: [CapturedRoom]
public var walls: [Surface]
public var doors: [Surface]
public var windows: [Surface]
public var openings: [Surface]
public var objects: [Object]
public var floors: [Surface]
public var sections: [Section]
public func export(to url: URL, metadataURL: URL? = nil, modelProvider: ModelProvider? = nil, exportOptions: USDExportOptions = .mesh) throws
} Parse attributes and categories to create folder hierarchy
// Parse attributes and categories to create folder hierarchy
for category in CapturedRoom.Object.Category.allCases {
let url = generateFolderURL(category: category, attributes: [])
FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
for attributes in category.supportedCombinations {
let url = generateFolderURL(category: category, attributes: attributes)
FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
}
} Create a Catalog index
// Create a Catalog index
struct RoomPlanCatalog: Codable {
let categoryAttributes: [RoomPlanCatalogCategoryAttribute]
}
struct RoomPlanCatalogCategoryAttribute: Codable {
enum CodingKeys: String, CodingKey {
case folderRelativePath
case category
case attributes
case modelFilename
}
let category: CapturedRoom.Object.Category
let attributes: [any CapturedRoomAttribute]
let folderRelativePath: String
private(set) var modelFilename: String? = nil
func encode(to encoder: Encoder) throws {
…
}
} Create a Catalog bundle
// Create a Catalog bundle
let catalog = RoomPlanCatalog(categoryAttributes: categoryAttributes)
let plistEncoder = PropertyListEncoder()
let data = try plistEncoder.encode(catalog)
let catalogURL = inputURL.appending(path: "catalog.plist")
try data.write(to: catalogURL)
let fileWrapper = try FileWrapper(url: inputURL)
try fileWrapper.write(to: outputURL, options: [.atomic, .withNameUpdating],
originalContentsURL: nil) Instantiate a Model Provider from a Catalog
// Instantiate a Model Provider from a Catalog
for categoryAttribute in catalog.categoryAttributes {
guard let modelFilename = categoryAttribute.modelFilename else {
continue
}
let folderRelativePath = categoryAttribute.folderRelativePath
let modelURL = url.appending(path: folderRelativePath).appending(path: modelFilename)
if categoryAttribute.attributes.isEmpty {
try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.category)
} else {
try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.attributes)
}
} Exporting a captured room to usdz with models
// Exporting a captured room to usdz with models
try capturedRoom.export(to: outputURL, modelProvider: modelProvider,
exportOptions: .model) Related sessions
-
11 min -
16 min