Augmented Reality’s RoomPlan for iOS: Getting Started
Learn how to scan a room and share the 3D model with Apple’s RoomPlan in a SwiftUI app. By David Piper.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Augmented Reality’s RoomPlan for iOS: Getting Started
25 mins
- Getting Started
- Your First Custom AR View
- Scanning a Room
- Using the Scanning Experience API
- Using RoomCaptureView to Capture a Room
- Working with the Scanning Result
- Taking a Look at a Scan Result
- How Can We Access Room Data?
- Exporting your Room Data
- Advanced Scanning With the Data API
- Setting up Delegate Callbacks
- Other Capture Session Delegate Methods
- Trying to Place an Object on the Table
- Placing a Block on the Table
- Understanding Matrix Operations
- Actually Placing a Block on the Table!
- Where to Go From Here?
Trying to Place an Object on the Table
Whenever a user taps the button to place a block, it sends the action placeBlock
via ARViewModel
to CustomCaptureView
. This calls placeBlockOnTables
, which doesn’t do anything at the moment. You’ll change this now.
Replace the empty body of placeBlockOnTables()/code> with the following:
// 1
guard let capturedRoom else { return }
// 2
let tables = capturedRoom.objects.filter { $0.category == .table }
// 3
for table in tables {
placeBlock(onTable: table)
}
Here’s what’s happening:
- First, you make sure that there’s a scanned room and that it’s possible to access it.
- Unlike surfaces, where each type of surface has its own list, a room stores all objects in one list. Here you find all tables in the list of objects by looking at each object
category
. - For each table recognized in the scanned room, you call
placeBlock(onTable:)
.
Placing a Block on the Table
The compiler warns that placeBlock(onTable:)
is missing. Change this by adding this method below placeBlockOnTables
:
private func placeBlock(onTable table: CapturedRoom.Object) {
// 1
let block = MeshResource.generateBox(size: 0.1)
let material = SimpleMaterial(color: .black, isMetallic: false)
let entity = ModelEntity(mesh: block, materials: [material])
// 2
let anchor = AnchorEntity()
anchor.transform = Transform(matrix: table.transform)
anchor.addChild(entity)
// 3
scene.addAnchor(anchor)
// 4
DispatchQueue.main.async {
self.viewModel.canDeleteBlocks = true
}
}
Taking a look at each step:
- You create a box and define its material. In this example, you set its size to 0.1 meters and give it a simple black coloring.
- You create an
AnchorEntity
to add a model to the scene. You place it at the table’s position by usingtable.transform
. This property contains the table’s position and orientation in the scene. - Before the scene can show the block, you need to add its anchor to the scene.
- You change the view model’s property
canDeleteBlocks
. This shows a button to remove all blocks.
Finally, add this code as the implementation of removeAllBlocks
:
// 1
scene.anchors.removeAll()
// 2
DispatchQueue.main.async {
self.viewModel.canDeleteBlocks = false
}
This is what the code does:
- Remove all anchors in the scene. This removes all blocks currently placed on tables.
- Since there are no blocks left, you change the view model’s property
canDeleteBlocks
. This hides the delete button again.
Build and run. Tap Custom Capture Session and start scanning your room. You need a table in the room you’re scanning for the place block button to appear. Continue scanning until the button appears. Now point your phone at a table and tap the button. You’ll see a screen similar to this:
A block appears, but it’s not where it’s supposed to be. Instead of laying on the table, it floats mid-air underneath the table. That’s not how a block would behave in real life, is it?
Something went wrong, but don’t worry, you’ll fix that next.
Understanding Matrix Operations
So, what went wrong? The faulty line is this one:
anchor.transform = Transform(matrix: table.transform)
An AnchorEntity
places an object in the AR scene. In the code above, you set its transform
property. This property contains information about scale, rotation and translation of an entity. In the line above you use the table’s transform
property for this, which places the block in the middle of the table.
The table’s bounding box includes the legs and the top of the table. So when you place the block in the middle of the table, it will be in the middle of this bounding box. Hence the block appears underneath the top of the table, between the legs.
You can probably already think of the solution for this: You need to move the block up a little bit. Half the height of the table, to be precise.
But how, you may wonder?
You can think of a Transform
as a 4×4 matrix, so 16 values in 4 rows and 4 columns. The easiest way to change a matrix is to define another matrix that does the operation and multiply the two. You can do different operations like scaling, translating or rotating. The type of operation depends on which values you set in this new matrix.
You need to create a translate matrix to move the block up by half the table height. In this matrix, the last row defines the movement, and each column corresponds to a coordinate:
1 0 0 tx 0 1 0 ty 0 0 1 tz 0 0 0 1
tx is the movement in x, ty in y and tz in z direction. So, if you want to move an object by 5 in the y-direction, you need to multiply it with a matrix like this:
1 0 0 0 0 1 0 5 0 0 1 0 0 0 0 1
To learn more about matrices and how to apply changes, check out Apple’s documentation Working with Matrices.
Now it’s time to apply your new knowledge!
Actually Placing a Block on the Table!
Ok, time to place the block on the table. Open CustomCaptureView.swift to the following code:
let anchor = AnchorEntity()
anchor.transform = Transform(matrix: table.transform)
anchor.addChild(entity)
Replace it with this code:
// 1
let tableMatrix = table.transform
let tableHeight = table.dimensions.y
// 2
let translation = simd_float4x4(
SIMD4(1, 0, 0, 0),
SIMD4(0, 1, 0, 0),
SIMD4(0, 0, 1, 0),
SIMD4(0, (tableHeight / 2), 0, 1)
)
// 3
let boxMatrix = translation * tableMatrix
// 4
let anchor = AnchorEntity()
anchor.transform = Transform(matrix: boxMatrix)
anchor.addChild(entity)
This might look complicated at first, so inspect it step-by-step:
-
transform
is the position of the table anddimensions
is a bounding box around it. To place a block on the table, you need both its position and the top of its bounding box. You get these properties via they
value ofdimensions
. - Before, you placed the block at the center of the table. This time you use the matrix defined above to do a matrix multiplication. This moves the position of the box up in the scene. It’s important to note that each line in this matrix represents a column, not a row. So although it looks like
(tableHeight / 2)
is in row 4 column 2, it’s actually in row 2, column 4. This is the place you define the y-translation at. - You multiply this new translation matrix with the table’s position.
- Finally, you create an
AnchorEntity
. But this time, with the matrix that’s the result of the translation.
Build and run. Tap Custom Capture Session, scan your room, and once the place block button appears, point your device at a table and tap the button.
This time, the block sits on top of the table. Great work! Now nobody will trip over your digital blocks! :]