0
1
00:00:00,950 --> 00:00:01,290
All right.
1

2
00:00:01,320 --> 00:00:07,790
So in the last lesson, we managed to get our 3D dice to get rendered and displayed in augmented reality.
2

3
00:00:07,800 --> 00:00:13,110
Now, the only problem is that it looks a bit weird because it's kind of just free floating in midair
3

4
00:00:13,170 --> 00:00:16,350
which is not very realistic for a solid object.
4

5
00:00:16,680 --> 00:00:20,210
So in this lesson, we're going to focus on plane detection.
5

6
00:00:20,370 --> 00:00:27,360
So we're going to be using a structure within ARKit that allows us to detect horizontal planes in
6

7
00:00:27,360 --> 00:00:29,200
the camera image that's captured.
7

8
00:00:29,280 --> 00:00:35,610
So at the moment, we can only detect horizontal planes because, as you can see, the options only include
8

9
00:00:35,610 --> 00:00:36,560
horizontal.
9

10
00:00:36,570 --> 00:00:42,420
Now, we think that in the near future, vertical plane detection is going to be added in here as an option.
10

11
00:00:42,420 --> 00:00:46,990
And I expect that the Apple engineers are probably just working on it as we speak.
11

12
00:00:47,040 --> 00:00:52,090
And that was certainly the indication that we got at WWDC.
12

13
00:00:52,200 --> 00:00:56,220
So talking to some of the engineers, they did seem to hint that this was something that was going to
13

14
00:00:56,220 --> 00:01:01,940
be enabled within a short period of time, and not something that we would have to wait for, say, iOS
14

15
00:01:02,040 --> 00:01:03,570
12 to appear.
15

16
00:01:03,570 --> 00:01:05,210
So that's really encouraging.
16

17
00:01:05,220 --> 00:01:11,190
But it does mean that, in the meantime, we won't be able to do any vertical plane detection.
17

18
00:01:11,280 --> 00:01:17,730
So all of you guys who have ideas of, you know, hanging paintings onto the wall and seeing how it looks
18

19
00:01:17,730 --> 00:01:22,890
in AR, you're going to have to hold your horses for now until they update the plane detection options
19

20
00:01:22,980 --> 00:01:29,820
to include vertical. But horizontal is good enough for our dice app because what we want to do is we
20

21
00:01:29,820 --> 00:01:36,390
want to be able to place our dice on the horizontal surface that we detect in augmented reality. So, say,
21

22
00:01:36,420 --> 00:01:38,590
if we're looking at a table or the floor,
22

23
00:01:38,670 --> 00:01:45,480
I want to be able to place my dice onto that horizontal surface and give the illusion that it is a part
23

24
00:01:45,540 --> 00:01:46,840
of the real world.
24

25
00:01:46,860 --> 00:01:50,880
So it's like when you play Pokémon GO  and all the Pokemon appear on the ground.
25

26
00:01:50,880 --> 00:01:53,370
This is what we're going to be doing in this lesson.
26

27
00:01:53,520 --> 00:01:53,780
All right.
27

28
00:01:53,790 --> 00:02:00,540
So the first thing we need to do is I'm going to comment out the part where we created our dice.
28

29
00:02:00,540 --> 00:02:05,640
I'm going to come back to that later, but for now I just want to focus on detecting the horizontal plane.
29

30
00:02:05,670 --> 00:02:10,950
So if you go into your viewWillAppear where you created your session configuration,
30

31
00:02:10,950 --> 00:02:17,820
I'm going to add another property to that configuration which is, as we saw earlier on, the plane detection
31

32
00:02:17,820 --> 00:02:18,510
property.
32

33
00:02:18,630 --> 00:02:21,560
And I'm going to set that to equal .horizontal.
33

34
00:02:21,600 --> 00:02:23,870
So as you can see, this is a enum.
34

35
00:02:23,910 --> 00:02:29,790
And if you don't remember what enums are, then have a look back at the intermediate Swift lessons where
35

36
00:02:29,790 --> 00:02:31,890
we went into that in more detail.
36

37
00:02:31,890 --> 00:02:38,700
But, again, having the .horizontal as an enum, rather than having something like configuration.horizontal
37

38
00:02:38,700 --> 00:02:45,640
planeDetection = true is another good indication, code wise that vertical is coming.
38

39
00:02:45,660 --> 00:02:46,110
All right, cool.
39

40
00:02:46,140 --> 00:02:52,140
So we've done that. We've said that let our configuration be our ARWWorldTrackingSessionConfiguration
40

41
00:02:52,380 --> 00:02:57,440
and enable horizontal plane detection on that configuration, and then run it.
41

42
00:02:57,480 --> 00:03:05,100
So the next step is we need to use one of the delegate methods from the ARSCNViewDelegate. And the
42

43
00:03:05,100 --> 00:03:08,790
one that we're going to be using is a method called didAdd.
43

44
00:03:08,820 --> 00:03:16,500
So if you just start typing didAdd with the ARSCNViewDelegate adopted, then you will see this delegate
44

45
00:03:16,500 --> 00:03:22,430
method show up. And as it says, it tells the delegate, which is this current view controller, that a SceneKit
45

46
00:03:22,440 --> 00:03:27,200
node corresponding to a new AR anchor has been added to the scene.
46

47
00:03:27,210 --> 00:03:27,440
Okay.
47

48
00:03:27,480 --> 00:03:28,660
So a lot of new words here.
48

49
00:03:28,680 --> 00:03:34,830
But it basically means that it's detected a horizontal surface and it's given that detected surface
49

50
00:03:34,920 --> 00:03:37,530
a width and a height which is an AR anchor
50

51
00:03:37,650 --> 00:03:41,750
so that we'll be able to use it to place things or to visualize it.
51

52
00:03:41,910 --> 00:03:49,320
And I'm going to hit enter to insert that piece of code. And inside this didAdd method is where we're going
52

53
00:03:49,320 --> 00:03:51,090
to setup our horizontal plane.
53

54
00:03:51,150 --> 00:03:57,330
So the most important thing that we get from this method is that when it detects a new horizontal plane,
54

55
00:03:57,390 --> 00:04:02,460
it's going to, firstly, call this method and trigger the code that's inside.
55

56
00:04:02,460 --> 00:04:09,900
But also we're going to receive this object called anchor and the anchor is of data type ARAnchor,
56

57
00:04:09,900 --> 00:04:15,680
so it's a real world position orientation that can be used for placing objects in an ARScene.
57

58
00:04:15,690 --> 00:04:22,710
So, this, you can think of as almost like a tile on the floor or on your horizontal plane, and you can
58

59
00:04:22,710 --> 00:04:29,730
use that tile to place objects. It has a dimension. It has a position. It has a rotation. It has a whole bunch
59

60
00:04:29,730 --> 00:04:36,420
of coordinates. And we're going to use those real world coordinates of that plane in order to place our
60

61
00:04:36,450 --> 00:04:37,980
object onto it.
61

62
00:04:37,980 --> 00:04:40,260
So this anchor is where we're going to be working with.
62

63
00:04:40,440 --> 00:04:45,520
Now, ARAnchor is a broad category of anchors.
63

64
00:04:45,600 --> 00:04:53,790
We want to specifically check to see if the anchor was of the type ARPlaneAnchor, i.e., that that anchor
64

65
00:04:53,790 --> 00:04:58,500
corresponds to a plane, rather than some other sort of 3D object in the scene.
65

66
00:04:58,800 --> 00:05:08,580
So to do that, we can say if anchor is ARPlaneAnchor, and this line of code basically checks to see
66

67
00:05:08,700 --> 00:05:16,780
if this new anchor that was added is of the type ARPlaneAnchor, i.e., it is from our plane detection.
67

68
00:05:16,770 --> 00:05:28,200
Now, if that was true, then we're going to print "plane detected," and if it's false or else, we're simply
68

69
00:05:28,200 --> 00:05:33,300
going to return which means that we're just going to exit from this method call.
69

70
00:05:33,390 --> 00:05:36,870
So let's give it a go and see what happens when we try to detect some planes.
70

71
00:05:39,750 --> 00:05:41,270
Now, with ARKit,
71

72
00:05:41,280 --> 00:05:46,230
you notice that there's usually a bit of delay before it detects a plane.
72

73
00:05:46,230 --> 00:05:52,710
So we can enable one of the debug options which will show us as it's trying to look for points in the
73

74
00:05:52,710 --> 00:05:55,230
view that are lining up to form a plane.
74

75
00:05:55,230 --> 00:06:01,470
So to do that, we're going to go right to the top of our viewDidLoad and we're gonna write
75

76
00:06:01,640 --> 00:06:09,180
self.sceneView.debugOptions, and this, again, is an array, but we're only gonna have one item in it ,and
76

77
00:06:09,180 --> 00:06:16,740
we're gonna use ARSCNDebugOptions.showFeaturedPoints.
77

78
00:06:16,740 --> 00:06:28,300
All right. So let's run the app again and you can see the difference.
78

79
00:06:28,410 --> 00:06:28,650
All right.
79

80
00:06:28,680 --> 00:06:33,600
So that made it a little bit easier to see what was actually happening behind the scenes in order to
80

81
00:06:33,600 --> 00:06:34,920
enable plane detection.
81

82
00:06:34,920 --> 00:06:37,160
So it was looking for all of these feature points.
82

83
00:06:37,560 --> 00:06:43,710
And you'll notice that feature points don't get generated very easily when you have a shiny metallic
83

84
00:06:43,710 --> 00:06:44,220
object.
84

85
00:06:44,220 --> 00:06:49,530
So if you go to a mirror and you try to use the app to detect feature points, you won't actually get
85

86
00:06:49,650 --> 00:06:50,730
all that far.
86

87
00:06:50,880 --> 00:06:57,060
But if you have a soft fabric that has maybe some creases in it that casts a little bit of shadow and
87

88
00:06:57,060 --> 00:07:02,760
gives a little bit texture to that plane, then your plane detection will work so much quicker and so
88

89
00:07:02,760 --> 00:07:03,570
much easier.
89

90
00:07:03,600 --> 00:07:08,490
But before the feature points actually appear on screen, your plane detection isn't taking place.
90

91
00:07:08,580 --> 00:07:12,630
So it's just a good way of being able to debug what's going on.
91

92
00:07:12,630 --> 00:07:12,930
All right.
92

93
00:07:12,960 --> 00:07:19,950
So, now instead of printing that the plane is detected, I'm going to downcast our anchor into the type
93

94
00:07:20,040 --> 00:07:21,180
ARPlaneAnchor.
94

95
00:07:21,210 --> 00:07:31,520
So I will say let planeAnchor = anchor as ARPlaneAnchor.
95

96
00:07:31,710 --> 00:07:40,530
So our code basically checks to see if this anchor that was added or detected is of type ARPlaneAnchor.
96

97
00:07:40,740 --> 00:07:47,430
And if it is, then we are going to change its type from ARAnchor to ARPlaneAnchor.
97

98
00:07:47,430 --> 00:07:54,000
Now, you'll notice that this is pretty wordy and there's certainly ways of making this more succinct
98

99
00:07:54,420 --> 00:07:57,200
and much, much shorter on the number of lines of code.
99

100
00:07:57,270 --> 00:08:01,950
But because there's going to be quite a lot of things going on in this block of code, some of which is
100

101
00:08:01,950 --> 00:08:05,080
quite abstract and quite difficult to understand,
101

102
00:08:05,190 --> 00:08:11,550
I'm going to try and keep my code as expressive as possible for now, and then later on, we can refactor
102

103
00:08:11,550 --> 00:08:16,510
it and make it shorter and simpler.
103

104
00:08:16,790 --> 00:08:23,360
So, now we have this constant called planeAnchor that is equal to the anchor that we got back from the
104

105
00:08:23,360 --> 00:08:28,990
delegate method which detected a new area that corresponds to a horizontal plane,
105

106
00:08:29,090 --> 00:08:33,190
and we have changed its data type to ARPlaneAnchor.
106

107
00:08:33,370 --> 00:08:33,710
All right.
107

108
00:08:33,710 --> 00:08:39,020
So the next thing that we need to do, we're going to convert the dimensions of our anchor into what's
108

109
00:08:39,020 --> 00:08:40,550
called a ScenePlane.
109

110
00:08:40,580 --> 00:08:46,910
So similar to how we created spheres and boxes using SceneBox or SceneSphere,
110

111
00:08:46,910 --> 00:08:52,190
there's something called SCNPlane that allows us to create a plane in SceneKit. And we're going to
111

112
00:08:52,190 --> 00:09:00,440
call ours plane and it's going to be created using ScenePlane and SCNPlane only requires two things:
112

113
00:09:00,620 --> 00:09:01,890
a width and height.
113

114
00:09:01,910 --> 00:09:09,620
So luckily, the anchor that we've got, which is of type ARPlaneAnchor has a property called extent. And
114

115
00:09:09,620 --> 00:09:15,860
the extent comprises of a width and a height. So you can think of an anchor as simply just like a tile
115

116
00:09:15,860 --> 00:09:20,090
on the ground, and it has a width, it has a height,
116

117
00:09:20,090 --> 00:09:25,130
and we're going to use that tile that's been detected and convert it into something that we can use with
117

118
00:09:25,130 --> 00:09:29,150
SceneKit and that we can render onto our sceneView.
118

119
00:09:29,150 --> 00:09:38,750
So the width is going to be planeAnchor.extent. x. And the height is going to be 
119

120
00:09:39,770 --> 00:09:42,590
planeAnchor.extent.z.
120

121
00:09:42,620 --> 00:09:48,980
So, a really important thing is that this is x and this is z, a really common error is that people will
121

122
00:09:48,980 --> 00:09:50,190
write y here.
122

123
00:09:50,210 --> 00:09:55,850
But if you actually read the documentation by option clicking on extent, you can see that this is the
123

124
00:09:55,910 --> 00:09:58,700
estimated width and length of the detected plane.
124

125
00:09:58,850 --> 00:10:05,060
And most importantly, although, what type of this property is vector_float3, i.e., a three-dimensional
125

126
00:10:05,060 --> 00:10:13,940
object, a planeAnchor is always two-dimensional, and is always position size in only the x and z directions.
126

127
00:10:14,000 --> 00:10:19,040
So make sure that you don't put y in here because, otherwise, your code is not going to work
127

128
00:10:19,040 --> 00:10:25,050
going forth. Now, the next thing you might notice is that Xcode is giving us this error here, and it's
128

129
00:10:25,070 --> 00:10:32,120
telling us that it can't convert the type 'Float,' which this extent.x is, into the expected type which
129

130
00:10:32,120 --> 00:10:36,080
is 'CGFloat' which this function requires of its parameters.
130

131
00:10:36,080 --> 00:10:42,860
So we can go ahead and cast it into a CGFloat by just clicking Fix. And we fixed the x,
131

132
00:10:42,860 --> 00:10:45,800
so now we need to fix the extent.z.
132

133
00:10:45,860 --> 00:10:52,580
So the next step is to create a planeNode. So you remember when we created spheres? We first created
133

134
00:10:52,580 --> 00:11:02,450
the geometry using SCNSphere and then we created a node to attach that geometry, too. let planeNode equals
134

135
00:11:02,540 --> 00:11:04,110
SCNNode.
135

136
00:11:05,240 --> 00:11:14,270
And then I want to set the planeNodes position property, planeNode.position =
136

137
00:11:14,270 --> 00:11:14,720
SCNVector3.
137

138
00:11:17,940 --> 00:11:24,490
So the x is going to be planeAnchor.center.x
138

139
00:11:24,510 --> 00:11:27,750
and y is going to be zero.
139

140
00:11:28,960 --> 00:11:35,150
The y position is going to be zero because, remember, it's a flat horizontal plane,
140

141
00:11:35,410 --> 00:11:37,230
so there is no y element.
141

142
00:11:37,240 --> 00:11:44,750
We don't want it to be above the horizontal plane that was detected. And z is going to be 
142

143
00:11:45,840 --> 00:11:48,860
planeAnchor.center.z.
143

144
00:11:49,260 --> 00:11:49,510
All right.
144

145
00:11:49,530 --> 00:11:55,180
So up till now, this is all pretty similar to what we did previously with the boxes and the spheres.
145

146
00:11:55,200 --> 00:11:59,580
Now, the next thing that we have to understand is a bit of a tricky concept.
146

147
00:11:59,910 --> 00:12:06,510
Now, the idea here is that we're creating this thing called a SCNPlane which is a rectangular one-sided
147

148
00:12:06,510 --> 00:12:10,970
plain geometry of a specified width and height.
148

149
00:12:11,070 --> 00:12:18,110
So we specified its width and height using the anchor that was detected.
149

150
00:12:18,450 --> 00:12:24,930
And then we might think that we're done but, actually, there's a slight problem because SCNPlanes are
150

151
00:12:24,930 --> 00:12:28,370
created as a vertical plane.
151

152
00:12:28,410 --> 00:12:37,260
So if you have a look at this graphic here and the 3D key, the red is the x, the green is the y, and the
152

153
00:12:37,260 --> 00:12:41,700
z component is non-existent because this is a 2D object.
153

154
00:12:41,700 --> 00:12:49,500
Now, as you might remember, in ARKit on our sceneView, everything is 3D, and also our plane is horizontal.
154

155
00:12:49,500 --> 00:12:51,890
So we need to convert this vertical plane
155

156
00:12:51,930 --> 00:12:59,760
that's got a component in the x and y into a flat plane that's got a component in the x and the
156

157
00:12:59,760 --> 00:13:00,780
z.
157

158
00:13:00,780 --> 00:13:07,410
So to do that, we're going to have to transform our planeNode and we're going to have to rotate it by
158

159
00:13:07,410 --> 00:13:14,840
90 degrees in order to make it horizontal. So planeNode.transform.
159

160
00:13:15,150 --> 00:13:22,110
And we're going to specify the amount that we want to transform it using something called a 
160

161
00:13:22,200 --> 00:13:25,510
SCNMatrix4MakeRotation.
161

162
00:13:25,740 --> 00:13:28,020
And this takes four parameters,
162

163
00:13:28,020 --> 00:13:34,050
first of which, is the angle that you want to rotate it by, and then the next three parameters, you specify
163

164
00:13:34,170 --> 00:13:37,410
along which axis you want to rotate your object.
164

165
00:13:37,410 --> 00:13:41,250
So the angle that we want to rotate our plane by is 90 degrees.
165

166
00:13:41,280 --> 00:13:44,060
We want to change it from vertical to horizontal.
166

167
00:13:44,070 --> 00:13:47,190
Now, this is in radians.
167

168
00:13:47,190 --> 00:13:50,320
So just a bit of recap on high school math,
168

169
00:13:50,370 --> 00:13:54,420
1 pi radian is equivalent to 180 degrees.
169

170
00:13:54,480 --> 00:13:59,400
So in order to rotate this by 90 degrees, we need half pi radians.
170

171
00:13:59,430 --> 00:14:08,880
So I'm going to grab pi by writing float.pi, and this is something that's available in UIKit because
171

172
00:14:08,970 --> 00:14:14,730
if you're working with spheres, if you're working with angles, and you tend to need to use pi, divide it by
172

173
00:14:14,730 --> 00:14:18,480
2, and that's specifying that we're rotating by 90 degrees.
173

174
00:14:18,480 --> 00:14:25,290
However, if you have a look at this function, you can see that the angle is the amount of rotation, in
174

175
00:14:25,290 --> 00:14:25,950
radians.
175

176
00:14:25,950 --> 00:14:30,680
We got all of that right. Measured counterclockwise around the rotation axis.
176

177
00:14:31,170 --> 00:14:38,730
So we actually want it to be rotated clockwise. So because when it's positive, it's counterclockwise.
177

178
00:14:38,790 --> 00:14:42,810
So to cancel that, we just need to add a minus in front of it.
178

179
00:14:42,840 --> 00:14:51,030
So two minuses cancel out and this is now rotating our vertical plane 90 degrees clockwise, and then
179

180
00:14:51,090 --> 00:14:58,320
we are going to specify the axis that we want to rotate around which is, of course, the x axis. Snd for
180

181
00:14:58,320 --> 00:15:00,300
the other two, we're going to leave it alone.
181

182
00:15:00,600 --> 00:15:04,690
So our rotation only has an x component.
182

183
00:15:04,710 --> 00:15:05,010
All right.
183

184
00:15:05,030 --> 00:15:09,880
So that is our plane node created and transformed.
184

185
00:15:09,900 --> 00:15:15,840
Now, for our human eyes to be able to see it and evaluate whether if our plane was created in the correct
185

186
00:15:15,840 --> 00:15:18,610
place, we're going to need to give it a material.
186

187
00:15:18,630 --> 00:15:26,400
So in this lesson, there is a download for a grid.png file. And this is the asset that Apple provided
187

188
00:15:26,460 --> 00:15:30,750
in their example project for us to be able to visualize these planes.
188

189
00:15:30,750 --> 00:15:32,780
So we're going to use the same image.
189

190
00:15:32,970 --> 00:15:39,990
And the important thing about this image that you might notice is that it's a .png file type.
190

191
00:15:40,560 --> 00:15:44,390
And you may or may not know, but PNG  file types have transparencies,
191

192
00:15:44,430 --> 00:15:48,850
so we'll be able to see through our grid and see the material underneath.
192

193
00:15:48,900 --> 00:15:51,450
And you're going to see really soon what that looks like.
193

194
00:15:51,810 --> 00:15:52,050
All right.
194

195
00:15:52,080 --> 00:15:58,560
So I'm going to drag my grid and, once again, into the art.scnassets folder, and then I'm going
195

196
00:15:58,560 --> 00:16:01,250
to create something called gridMaterial.
196

197
00:16:01,560 --> 00:16:05,880
And, again, it's going to be created using SCNMaterial.
197

198
00:16:05,940 --> 00:16:14,940
So we've done this before and we're going to set the gridMaterial's diffuse.contents to a UIImage
198

199
00:16:14,970 --> 00:16:17,720
which is the one that we dragged in just now.
199

200
00:16:18,720 --> 00:16:20,460
So we're going to have to specify the folder.
200

201
00:16:20,550 --> 00:16:25,770
So art.scnassets/grid.png.
201

202
00:16:26,100 --> 00:16:26,930
Okay. Cool.
202

203
00:16:26,940 --> 00:16:32,640
And we're going to assign this material to our planeGeometry that we created earlier on.
203

204
00:16:32,730 --> 00:16:38,430
So plane. materials = gridMaterial.
204

205
00:16:38,610 --> 00:16:45,500
And finally, we're going to set the geometry of our planeNode to our plane.
205

206
00:16:45,880 --> 00:16:46,360
All right.
206

207
00:16:46,390 --> 00:16:52,870
So the only last thing we need to do as we did before with our spheres and our boxes is to add our
207

208
00:16:52,870 --> 00:16:59,070
childNode into the rootNode. So you can see that as a part of this delegate method. We also have this parameter
208

209
00:16:59,080 --> 00:17:02,480
called node which gives us a blank node to work with.
209

210
00:17:02,530 --> 00:17:07,660
So you can either, as we did before, tap into the sceneView.SCNRootNode and childNode,
210

211
00:17:08,260 --> 00:17:14,560
or we can just simply use this node that got created when the didAdd delegate method got called.
211

212
00:17:14,740 --> 00:17:20,860
node.addChildNode planeNode onto the scene.
212

213
00:17:20,860 --> 00:17:32,880
So let's give it a run and see what it looks like.
213

214
00:17:33,070 --> 00:17:33,350
All right.
214

215
00:17:33,380 --> 00:17:34,360
So there you have it.
215

216
00:17:34,380 --> 00:17:39,660
Just with a few lines of code and the assistance of ARKit, we've been able to implement an immensely
216

217
00:17:39,660 --> 00:17:45,930
complicated computational task which is detecting a horizontal plane in the real world based on the
217

218
00:17:45,930 --> 00:17:51,120
image that's coming in through the camera. So you can pat yourself on the back and we can thank ARKit
218

219
00:17:51,120 --> 00:17:53,070
for making our lives easier.
219

220
00:17:53,100 --> 00:17:58,770
So in the next lesson, we're going to be looking at how we can detect touch in the scene because, ultimately,
220

221
00:17:58,770 --> 00:18:05,220
we want to be able to touch a plane that we've detected, say, a table or the floor and be able to place
221

222
00:18:05,310 --> 00:18:10,230
our dice at the position that we touched in the 3D world and scaled,
222

223
00:18:10,230 --> 00:18:15,570
so it looks like it's a realistic dice that's been placed in the 3D world.
223

224
00:18:15,570 --> 00:18:17,820
So we're going to do that in the next lesson.
224

225
00:18:17,850 --> 00:18:18,690
So I'll see you there.
