Apress / Mastering-ARKit

source code
Other
9 stars 5 forks source link

Chapter 6 ARPlaneAnchor does not automatically have a node with plane geometry. #2

Closed erikuecke closed 2 years ago

erikuecke commented 2 years ago

Chapter 6 Section: Detecting horizontal planes. The guard statement always returns because of multile reasons. 1) the Node for the didAdd call does not have any child nodes. 2) the node for the didAdd call does not have any geometry and could not be cast to a SCNPLane.

A bit of code is missing for vizualing a plane on a plane anchor. Apple Documentation has an example here (https://developer.apple.com/documentation/arkit/content_anchors/tracking_and_visualizing_planes) that may be useful, but the book and source code definitely needs to be updated if that was the intention of this section of the chapter.

erikuecke commented 2 years ago
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor
        else {
            return
        }
        let planeGeometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z))
        planeGeometry.firstMaterial?.diffuse.contents = UIColor.blue
        let planeNode = SCNNode(geometry: planeGeometry)
        planeNode.simdPosition = planeAnchor.center
        planeNode.opacity = 0.5
        planeNode.eulerAngles.x = -.pi / 2
        node.addChildNode(planeNode)
    }

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        // 1
        guard let planeAnchor = anchor as? ARPlaneAnchor, let planeNode = node.childNodes.first, let plane = planeNode.geometry as? SCNPlane else {
            return
        }
        // 2
        let width = CGFloat(planeAnchor.extent.x)
        let height = CGFloat(planeAnchor.extent.z)
        plane.width = width
        plane.height = height

        let x = CGFloat(planeAnchor.center.x)
        let y = CGFloat(planeAnchor.center.y)
        let z = CGFloat(planeAnchor.center.z)
        planeNode.position = SCNVector3(x, y, z)
    }
erikuecke commented 2 years ago

Something like above would give the user the "Blue Ocean" to place their ships on.

jayvenn commented 2 years ago

@erikuecke There is missing instruction in the markdown file, as you've pointed out. I'll check in with the publisher for an update (maybe after WWDC22). For readers following the book, please check out the instruction below in the chapter:

. . .

Your setUpSceneView() method should now look like this:

func setUpSceneView() {
  let configuration = ARWorldTrackingConfiguration()
  configuration.planeDetection = .horizontal

  sceneView.session.run(configuration)

  sceneView.delegate = self
  sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
}

--- Missing Instruction [Begin] ---

Visualizing Horizontal Planes

Now that the app gets notified every time a new ARAnchor is being added onto sceneView, we may be interested in seeing what that newly added ARAnchor looks like.

Hence, update the renderer(_:didAdd:for:) method like this:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // 1
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    // 2
    let width = CGFloat(planeAnchor.extent.x)
    let height = CGFloat(planeAnchor.extent.z)
    let plane = SCNPlane(width: width, height: height)
    // 3
    plane.materials.first?.diffuse.contents = UIColor.transparentLightBlue
    // 4
    let planeNode = SCNNode(geometry: plane)
    // 5
    let x = CGFloat(planeAnchor.center.x)
    let y = CGFloat(planeAnchor.center.y)
    let z = CGFloat(planeAnchor.center.z)
    planeNode.position = SCNVector3(x,y,z)
    planeNode.eulerAngles.x = -.pi / 2
    // 6
    node.addChildNode(planeNode)
}

Let me walk you through the code line by line:

  1. We safely unwrap the anchor argument as an ARPlaneAnchor to make sure that we have information about a detected real world flat surface at hand.
  2. Here, we create an SCNPlane to visualize the ARPlaneAnchor. A SCNPlane is a rectangular “one-sided” plane geometry. We take the unwrapped ARPlaneAnchor extent’s x and z properties and use them to create an SCNPlane. An ARPlaneAnchor extent is the estimated size of the detected plane in the world. We extract the extent’s x and z for the height and width of our SCNPlane. Then we give the plane a transparent light blue color to simulate a body of water.
  3. We initialize a SCNNode with the SCNPlane geometry we just created.
  4. We initialize x, y, and z constants to represent the planeAnchor’s center x, y, and z position. This is for our planeNode’s position. We rotate the planeNode’s x euler angle by 90 degrees in the counter-clockerwise direction, else the planeNode will sit up perpendicular to the table. And if you rotate it clockwise, David Blaine will perform a magic illusion because SceneKit renders the SCNPlane surface using the material from one side by default.
  5. Finally, we add the planeNode as the child node onto the newly added SceneKit node.

Build and run the project. You should now be able to detect and visualize the detected horizontal plane.

6 5

Expanding Horizontal Planes

With ARKit receiving additional information about our environment, we may want to expand our previously detected horizontal plane(s) to make use of a larger surface or have a more accurate representation with the new information.

Hence, implement renderer(_:didUpdate:for:):

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

}

--- Missing Instruction [End] ---

This method gets called every time a SceneKit node's properties have been updated to match its corresponding anchor. This is where ARKit refines its estimation of the horizontal plane's position and extent.

The node argument gives us the updated position of the anchor. The anchor argument provides us with the anchor's updated width and height. With these two arguments, we can update the previously implemented SCNPlane to reflect the updated position with the updated width and height.

. . .