📜 ⬆️ ⬇️

When size matters: create a ruler application with ARKit



That was the day of the long-awaited official release of iOS 11, and therefore it is no longer possible to postpone acquaintance with ARKit — Apple’s SDK for creating applications with augmented reality. Many have heard about the essence of the tool: with the help of ARKit, you can apply the created virtual reality to the real world around us. At the same time, the iPhone or iPad acts as a viewing window through which we can observe what is happening and change something in it. There are already many different demo applications on the Internet - with their help, you can arrange furniture, park a car in the parking lot, draw in the surrounding space, create doors to other worlds and much more. In short, the range of opportunities is wide, you only need to understand the technical implementation.

ARKit support devices exclusively with iOS 11 and A9 or A10 processor. Accordingly, to write and run the application, we need, first, Xcode 9, and secondly, a device with one of these processors and the latest version of iOS installed. The start project can be downloaded from here .

ARKit uses data from the camera and other sensors of the device to recognize key points and horizontal surfaces in the surrounding space in real time. In parentheses, we note that the process is quite resource-intensive - the device will heat up. First, add the line to the viewDidLoad () method:
')
sceneView.debugOptions = ARSCNDebugOptions.showFeaturePoints 

This will allow us to see the key points that ARKit finds. Now you can run the application, and after a while the following picture will appear in front of us:

/

It should be noted that the device needs to be moved slightly in space - in the process of movement, more variable information will flow into the system than in a stationary state. The abundance of data helps ARKit identify key points, and as a result they get more.

In order to “test” the capabilities of ARKit, we take as an example a simple line-up application and follow the entire process of its creation. First, we need to implement a line drawing between two points, then calculate its length, adjust the output of the result to the screen - and our primitive ruler will be ready. Add the variables that we need to draw the line in space:

 private var points: (start: SCNVector3?, end: SCNVector3?) private var line = SCNNode() private var isDrawing = false private var canPlacePoint = false 

The tuple points will contain the points of the beginning and end of the line. line is SCNNode, an object that is added to the SceneKit scene, isDrawing is a variable that indicates whether we have finished selecting points or not. The variable SanPlacePoint speaks for itself: it indicates whether it is possible to position the point in focus. In our case, the focus will be the center of the screen.

In order to determine whether we can put a point in focus, we need to use the hitTest method of the ARSCNVeiw object. Based on the ARKit data, this method identifies all recognized objects and surfaces that intersect the beam directed from the camera and returns the received intersection data in the order in which they were removed from the device.

We will use it in ARSCNViewDelegate, in the method
 func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) 

to receive data in real time. As a result, we will have something like this:

 func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { DispatchQueue.main.async { self.measure() } }   private func measure() { let hitResults = sceneView.hitTest(view.center, types: [.featurePoint]) if let hit = hitResults.first { canPlacePoint = true focus.image = UIImage(named: "focus") } else { canPlacePoint = false focus.image = UIImage(named: "focus_off") } } 

This code checks the availability of hitTest results in real time, and then, depending on them, sets the canPlacePoint value and the image of our focus (green or red). Also in the hitTest method there is a list of options that determines which objects to take into account in the implementation - in our case these are key points. If desired, you can add horizontal surfaces.

Tap on the screen will indicate the start or end point. Directly in the function of implementing tapa on the screen, we will change the isDrawing variable and reset the start and end values ​​whenever we start a new line:

 @objc private func tapped() { if canPlacePoint { isDrawing = !isDrawing if isDrawing { points.start = nil points.end = nil } } } 

Let's go back to the updateAtTime delegate function. Having hitTest result, we get the coordinates of the point and then, depending on the value of the variables, we set the start or end point of the line:

 if isDrawing { let hitTransform = SCNMatrix4(hit.worldTransform) let hitPoint = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)               if points.start == nil { points.start = hitPoint } else { points.end = hitPoint } } 

Now we have the coordinates of the beginning and end of the line - it remains to draw it. To do this, we add a function that will return the geometry of the line (according to it, SceneKit will understand how and where to draw SCNNode):

 func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry { let indices: [Int32] = [0, 1]       let source = SCNGeometrySource(vertices: [vector1, vector2]) let element = SCNGeometryElement(indices: indices, primitiveType: .line)       return SCNGeometry(sources: [source], elements: [element]) } 

And finally, add the code that will draw our line in space:

 if points.start == nil { points.start = hitPoint } else { points.end = hitPoint line.geometry = lineFrom(vector: points.start!, toVector: points.end!) if line.parent == nil { line.geometry?.firstMaterial?.diffuse.contents = UIColor.white line.geometry?.firstMaterial?.isDoubleSided = true sceneView.scene.rootNode.addChildNode(line) } } 

Now, having started the application, we can draw a line between two points: the first tap marks the beginning and starts drawing the line to the point in focus, the second tap stops drawing mode and fixes the line. It remains only to calculate its length and display the resulting value on the screen.



 func distance(from startPoint: SCNVector3, to endPoint: SCNVector3) -> Float { let vector = SCNVector3Make(startPoint.x - endPoint.x, startPoint.y - endPoint.y, startPoint.z - endPoint.z) let distance = sqrtf(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z) return distance } 

This function calculates the distance between two points in space. Things to do: add a text field and display the result in it. In SceneKit 0.01 equals one centimeter. As a result, we obtain the following:


The length of the drawn line, according to the application, is 9 cm, which is quite well correlated with the testimony of a real ruler. But, in fact, the accuracy is not too high. Maximum accuracy is obtained in cases where objects are located at a small distance from the camera and the measurement is made from the position of the device perpendicular to the surface (that is, you need to move the phone parallel to the surface, and not rotate it). Measurement on horizontal surfaces will be more accurate. Also, if you point the camera at distant objects, hitTest may return non-valid results - the distance to the found objects is not determined correctly. Although here you need to make a reservation that all this was tested on the iPhone 7, which does not have a dual camera. And if you look at the demo of different lines on the Internet, for the most part you will notice the same limitations and inaccuracies in the measurements.

That's what happened as a result .

To sum up: ARKit is an excellent SDK for creating games and entertainment applications, with it you can come up with a lot of interesting things. The significant merit of Apple is that they let the augmented reality to the masses: there are quite a few devices that support ARKit and now there is no need to purchase special helmets and other accessories. In addition, ARKit supports work with both native SpriteKit SceneKit and Metal, as well as with Unity and Unreal Engine, which simplifies development.

Source: https://habr.com/ru/post/337808/


All Articles