Search code examples
iosswiftxcodeaugmented-reality

Unable to simplify 3 blocks of code to 1 in iOS for augmented reality


I created a fun little AR app that allows me to point my phone at 3 index cards, that have 2D drawings (xmas tree, ginger bread man, gift), and have 3D versions of the images pop up.

However, I have limited my app to just 3 images for now, as each picture/3D combo has its own block of code (shown below) but I would like to somehow consolidate to one block (even if I need to rename the image files to a numbered format, but would appreciate any advice).

I have tried two methods:

  1. one in which I have 3 blocks of code, one for each picture (this method works - a translucent plane appears just over the index card and a 3d Object appears)
  2. one in which I have consolidated to one block of code (this method results in only the translucent plane appearing when pointing the camera at the index card)

3 blocks of code method

Screenshots of the app, along with the code are as follows:

Before After

    // MARK: - ARSCNViewDelegate
    
    //the anchor will be the image detected and the node will be a 3d object 
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        
        //the 3d object
        let node = SCNNode()
        
        //if this detects a plane or point or anything other then an image, will not run this code
        if let imageAnchor = anchor as? ARImageAnchor {
            
            
            //this plane is created from the image detected (card).  want the width and height in the physical world to be the same as the card, so effectively saying "look at the anchor image found, and get its size properties”
            let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)
            
            //once dimension is given to the plane, use it to create a planenode which is a 3d object that will be rendered on top of the card
            let planeNode = SCNNode(geometry: plane)
            
            //makes the plane translucent
            plane.firstMaterial?.diffuse.contents = UIColor(white: 1.0, alpha: 0.5)
            
            //by default the plane created is vertical so need to flip it to be flat with the card detected
            planeNode.eulerAngles.x = -.pi / 2
            
            
            // tap into the node created above and add a child node which will be the plane node
            node.addChildNode(planeNode)
            
             // ------------3 BLOCKS OF CODE BELOW THAT NEED TO SIMPLIFY TO 1----------------
             // This is the first block of code for the ChristmasTree.png image/ChristmasTree.scn 3D object – note that the 2D image is not detected if “.png” is included at the end of the image anchor, however the “.scn” appears to be required for the 3D object).
            if imageAnchor.referenceImage.name == "ChristmasTree" {
                
                //create the 3d model on top of the card
                if let cardScene = SCNScene(named: "art.scnassets/ChristmasTree.scn") {
                    
                    //create a node that will represent the 3d object.
                    if let cardNode = cardScene.rootNode.childNodes.first {
                        
                        //Since the 3d image is rotated the other way, need to bring it forward so same as above only with positive
                        cardNode.eulerAngles.x = .pi / 2
                        
                        planeNode.addChildNode(cardNode)
                    }
                }
            }
            
           // This is the second block of code for the Gift.png image/Gift.scn 3D object
            if imageAnchor.referenceImage.name == "Gift" {
                
                //create the 3d model on top of the card
                if let cardScene = SCNScene(named: "art.scnassets/Gift.scn") {
                    
                    //create a node that will represent the 3d object
                    if let cardNode = cardScene.rootNode.childNodes.first {
                        
                        //Since the 3d image is rotated the other way, need to bring it forward so same as above only with positive
                        cardNode.eulerAngles.x = .pi / 2
                        
                        planeNode.addChildNode(cardNode)
                    }
                }
            }
            
            // This is the third block of code for the GingerbreadMan.png image/GingerbreadMan.scn 3D object
            if imageAnchor.referenceImage.name == "GingerbreadMan" {
                
                //create the 3d model on top of the card
                if let cardScene = SCNScene(named: "art.scnassets/GingerbreadMan.scn") {
                    
                    //create a node that will represent the 3d object
                    if let cardNode = cardScene.rootNode.childNodes.first {
                        
                        //Since the 3d image is rotated the other way, need to bring it forward so same as above only with positive
                        cardNode.eulerAngles.x = .pi / 2
                        
                        planeNode.addChildNode(cardNode)
                    }
                }
            }
        }
        
        //this method is expecting an output of SCNNode and it needs to send that back onto the scene so it can render the 3d object
        return node
    }
    
}

One block of code method

I have revised the code as follows, however it has resulted in only the translucent plane appearing when pointing the camera at the index card:

// MARK: - ARSCNViewDelegate
    
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        
        let node = SCNNode()
        
        if let imageAnchor = anchor as? ARImageAnchor {
            
            let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)
            
            let planeNode = SCNNode(geometry: plane)
         
            plane.firstMaterial?.diffuse.contents = UIColor(white: 1.0, alpha: 0.5)
            
            planeNode.eulerAngles.x = -.pi / 2
            
            node.addChildNode(planeNode)
            
            //------------single block of code------------

            let name = imageAnchor.referenceImage.name
            if ["ChristmasTree", "Gift", "GingerbreadMan"].contains(name) {
                    
                if let cardScene = SCNScene(named: "art.scnassets/\(name).scn") {
                        
                    if let cardNode = cardScene.rootNode.childNodes.first {
                            
                        cardNode.eulerAngles.x = .pi / 2
                            
                        planeNode.addChildNode(cardNode)
                    }
                }
            }
            //------------single block of code------------
        }

        return node
    }
    
}

Solution

  • Your issue there is that name is an optional String and when you do string interpolation it results in a string like "Optional("whatever"). What you need is to unwrap your optional String:

    if let name = imageAnchor.referenceImage.name {
    //  this condition is not needed considering that you are already safely unwrapping SCNScene fallible initializer
    //  if ["ChristmasTree", "Gift", "GingerbreadMan"].contains(name) {        
            if let cardScene = SCNScene(named: "art.scnassets/\(name).scn"),
               let cardNode = cardScene.rootNode.childNodes.first {
                cardNode.eulerAngles.x = .pi / 2                    
                planeNode.addChildNode(cardNode)
                
            }
    //  }
    }