2

My Swift code below uses func touchesBegan to place a SCN object in the ARKit view. The problem is – it's only placing the object one time. I would like to create the code in a way that users can select any area to place the SCN object and it can place it as many times as they want too.

Here's GitHub link.

enter image description here

class ViewController: UIViewController, ARSCNViewDelegate { 

    @IBOutlet var sceneView: ARSCNView! 

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        // Handle the shooting
        guard let frame = sceneView.session.currentFrame else { return }
        let camMatrix = SCNMatrix4(frame.camera.transform)

        let direction = SCNVector3Make(camMatrix.m31 * 5.0, 
                                       camMatrix.m32 * 10.0, 
                                       camMatrix.m33 * 5.0)

        let position = SCNVector3Make(camMatrix.m41, camMatrix.m42, camMatrix.m43)
        let scene = SCNScene(named: "art.scnassets/dontCare.scn")!
        sceneView.scene = scene
    }    
}  

enter image description here

  • There may be several reasons why you can't see `Area` object in `Scene graph`: 1.Inverted normals of 3D geometry, 2.Damaged UV-coordinates of a texture, 3.Damaged Texture file, 4.Wrong hierarchy, 5.Zero-opacity object, etc... At first try to assign a new texture for this object via Xcode Inspector Material slots. But this is another question))), it's not about `touchesBegan` method... – Andy Jazz Feb 16 '20 at 05:16

2 Answers2

0

an 'object' is a SCNNode. These are displayed in a 3D scene, the SCNScene. each time you tap on the screen, you apply the scene to the sceneView instead of adding a node to the scene.

You also need to find where the user has tapped in the scene, ie the 3D position of the touch. This required a hit test.

try this

class ViewController: UIViewController, ARSCNViewDelegate {

@IBOutlet var sceneView: ARSCNView!

override func viewDidLoad() {
    super.viewDidLoad()
    sceneView.delegate = self
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let configuration = ARWorldTrackingConfiguration()
    sceneView.session.run(configuration)
}


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard
        let touchPosition = touches.first?.location(in: sceneView),
        let hitTest = sceneView.hitTest(touchPosition, types: .featurePoint).first
        else {return}

    let node = SCNScene(named: "art.scnassets/dontCare.scn")!.rootNode.childNodes.first!
    node.simdTransform = hitTest.worldTransform
    sceneView.scene.rootNode.addChildNode(node)
}

}
Rom4in
  • 532
  • 4
  • 13
  • Your code creates a scnnode shape and does place it over the ARSCNView exactly like I want it however. I want to how to do with a custom scn shape I create not one the comes from the library. Thanks for your help so far! –  Feb 08 '20 at 16:45
  • You can get your node from your scene, I edited the code assuming the node you are looking for is the scene you were trying to load. – Rom4in Feb 13 '20 at 09:02
  • I tried your code and when I touch nothing is happening. I added my code above via GitHub link so you can see exactly see what I am doing. I dont know if my ARKit object is to big but I think that is not what is going on. Thanks for your help so far. –  Feb 14 '20 at 00:46
  • from your code in GitHub, the "dontCare.scn" file has the camera as its first node, which is what the code above is adding multiple times. So to fix it, create an empty node and add your Sphere + Cylinder inside the empty node as children. You also need to change the scale as you mentioned, the object is 2x2x2 meters right now. – Rom4in Feb 14 '20 at 10:10
0

Tip: If you use RealityKit, read this post.

Solution 1

Adding 3D models using touchesBegan(:with:)

Use the following code to get a desired effect (place as many objects into a scene as you want):

enter image description here

At first create an extension for your convenience:

import ARKit

extension SCNVector3 {
    
    static func + (lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 {
        return SCNVector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z)
    }
}

Then use it in your ViewController for adding a pointOfView.position to desiredVector:

class ViewController: UIViewController {

    @IBOutlet var sceneView: ARSCNView!
    
    override func touchesBegan(_ touches: Set<UITouch>,
                              with event: UIEvent?) {
        
        sceneView.isMultipleTouchEnabled = true
        
        guard let pointOfView = sceneView.pointOfView   // Camera of SCNScene
        else { return }
        
        let cameraMatrix = pointOfView.transform
        
        let desiredVector = SCNVector3(cameraMatrix.m31 * -0.5,
                                       cameraMatrix.m32 * -0.5,
                                       cameraMatrix.m33 * -0.5)
        
        // What the extension SCNVector3 is for //
        let position = pointOfView.position + desiredVector 
        
        let sphereNode = SCNNode()
        sphereNode.geometry = SCNSphere(radius: 0.05)
        sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.green
        sphereNode.position = position
        sceneView.scene.rootNode.addChildNode(sphereNode)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = SCNScene(named: "art.scnassets/myScene.scn")!
        sceneView.scene = scene
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let config = ARWorldTrackingConfiguration()
        sceneView.session.run(config)
    }
}

And if you want to retrieve a 3D model from .scn file use the following code:

(Instead of the sphereNode):

var model = SCNNode()
let myScene = SCNScene(named: "art.scnassets/ship.scn")

// Model's name in a Scene graph hierarchy.
// Pay particular attention – it's not a name of .scn file.
let nodeName = "ship"
    
model = (myScene?.rootNode.childNode(withName: nodeName, recursively: true))!
model.position = position
sceneView.scene.rootNode.addChildNode(model)

enter image description here


Solution 2

Adding 3D models using Plane Detection + Hit-Testing

Use the following code if you want to add models using plane detection and Hit-testing :

At first create an extension for your convenience:

extension float4x4 {
    var simdThree: SIMD3<Float> {
        let translation = self.columns.3            
        return SIMD3<Float>(translation.x, translation.y, translation.z)
    }
}

Then use it in the ViewController:

class ViewController: UIViewController {

    @IBOutlet weak var sceneView: ARSCNView!

    override func viewDidLoad() {
        super.viewDidLoad()
        addGesture()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        sceneView.delegate = self             // for ARSCNViewDelegate

        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal]
        sceneView.session.run(config)
    }

    func addGesture() {
        let tapGesture = UITapGestureRecognizer(target: self, 
                                                action: #selector(addModel))
        sceneView.addGestureRecognizer(tapGesture)
    }

    // Don't forget to drag-and-drop TapGestureRecognizer object from library
    @objc func addModel(recognizer: UIGestureRecognizer) {
        
        let tap: CGPoint = recognizer.location(in: sceneView)

        let results: [ARHitTestResult] = sceneView.hitTest(tap, 
                                           types: .existingPlaneUsingExtent)
        
        guard let hitTestResult = results.first 
        else { return }

        let translation = hitTestResult.worldTransform.simdThree
        let x = translation.x
        let y = translation.y
        let z = translation.z
        
        guard let scene = SCNScene(named: "art.scnassets/myScene.scn"),
              let robotNode = scene.rootNode.childNode(withName: "robot", 
                                                    recursively: true)
        else { return }

        robotNode.position = SCNVector3(x, y, z)
        robotNode.scale = SCNVector3(0.02, 0.02, 0.02)
        sceneView.scene.rootNode.addChildNode(robotNode)
    }
}

And, you have to implement a logic inside two renderer() methods for ARPlaneAnchors:

extension ViewController: ARSCNViewDelegate {

    func renderer(_ renderer: SCNSceneRenderer,
                 didAdd node: SCNNode,
                  for anchor: ARAnchor) { // your logic here....  }

    func renderer(_ renderer: SCNSceneRenderer,
              didUpdate node: SCNNode,
                  for anchor: ARAnchor) { // your logic here....  }

}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • I am getting a error at Use of unresolved identifier 'configuration' in view will appear –  Feb 14 '20 at 14:29
  • Tried your code not getting anything to show up when touched on the screen. Updated my link above with your version. Thanks for you help so far. –  Feb 14 '20 at 23:51
  • Those methods are in the code don’t did not right anything inside of them –  Feb 15 '20 at 05:48
  • 1
    I tried option 1 and it works. It gives me 1 scn object and the ability to add all the green spheres I want to. I just need to know how to do what you are doing with the green spheres to the scn object. So every time I touch the screen instead of a green sphere the scn object appears. Thanks for your help so far. –  Feb 15 '20 at 14:30
  • I have edited my question above with your code. The good news is that the code works exactly how I want to with ship. The custom pre loaded scn file. When I tried to add my own scn file. It causes a runtime error. I have updated the link to my GitHub file as well. With exactly what I am running. Thanks. –  Feb 15 '20 at 19:58
  • I am doing solution 1. –  Feb 15 '20 at 19:58
  • It does not appear my custom made scn object has a node name. I took a comparison photo and added it to my question. Any idea why this is happening? –  Feb 16 '20 at 03:07