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.

5 (1) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

  1. First, you make sure that there’s a scanned room and that it’s possible to access it.
  2. 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.
  3. 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:

  1. 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.
  2. You create an AnchorEntity to add a model to the scene. You place it at the table’s position by using table.transform. This property contains the table’s position and orientation in the scene.
  3. Before the scene can show the block, you need to add its anchor to the scene.
  4. 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:

  1. Remove all anchors in the scene. This removes all blocks currently placed on tables.
  2. 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:

The Custom Capture Session screen shows a table in front of the window. A black box floats mid-air underneath the table. The place block and delete buttons are shown in the lower left corner.

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:

  1. transform is the position of the table and dimensions 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 the y value of dimensions.
  2. 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.
  3. You multiply this new translation matrix with the table’s position.
  4. 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.

The black block appears on the top of a table

This time, the block sits on top of the table. Great work! Now nobody will trip over your digital blocks! :]