Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ARKit – Tap node with raycastQuery instead of hitTest, which is deprecated

In iOS 14, hitTest(_:types:) was deprecated. It seems that you are supposed to use raycastQuery(from:allowing:alignment:) now. From the documentation:

Raycasting is the preferred method for finding positions on surfaces in the real-world environment, but the hit-testing functions remain present for compatibility. With tracked raycasting, ARKit continues to refine the results to increase the position accuracy of virtual content you place with a raycast.

However, how can I hit test SCNNodes with raycasting? I only see options to hit test a plane.

raycastQuery method documentation Only choices for allowing: are planes
Screenshot of documentation for the "raycastQuery(from:allowing:alignment:)" method Screenshot of documentation of the possible options for the "allowing:" argument label, showing 3 different types of planes

This is my current code, which uses hit-testing to detect taps on the cube node and turn it blue.

class ViewController: UIViewController {

    @IBOutlet weak var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /// Run the configuration
        let worldTrackingConfiguration = ARWorldTrackingConfiguration()
        sceneView.session.run(worldTrackingConfiguration)
        
        /// Make the red cube
        let cube = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
        cube.materials.first?.diffuse.contents = UIColor.red
        
        let cubeNode = SCNNode(geometry: cube)
        cubeNode.position = SCNVector3(0, 0, -0.2) /// 20 cm in front of the camera
        cubeNode.name = "ColorCube"
        
        /// Add the node to the ARKit scene
        sceneView.scene.rootNode.addChildNode(cubeNode)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        guard let location = touches.first?.location(in: sceneView) else { return }
        
        let results = sceneView.hitTest(location, options: [SCNHitTestOption.searchMode : 1])
        for result in results.filter( { $0.node.name == "ColorCube" }) {  /// See if the beam hit the cube
            let cubeNode = result.node
            cubeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue /// change to blue
        }
    }
}

How can I replace let results = sceneView.hitTest(location, options: [SCNHitTestOption.searchMode : 1]) with the equivalent raycastQuery code?

Gif of the code's result. Tapping the red cube turns it blue.
like image 369
aheze Avatar asked Feb 12 '21 19:02

aheze


1 Answers

About Hit-Testing

Official documentation says that only ARKit's hitTest(_:types:) instance method is deprecated in iOS 14. However, in iOS 15 you can still use it. ARKit's hit-testing method is supposed to be replaced with a raycasting methods.

Deprecated hit-testing:

let results: [ARHitTestResult] = sceneView.hitTest(sceneView.center, 
                                           types: .existingPlaneUsingGeometry)


Raycasting equivalent

let raycastQuery: ARRaycastQuery? = sceneView.raycastQuery(
                                                      from: sceneView.center, 
                                                  allowing: .estimatedPlane, 
                                                 alignment: .any)

let results: [ARRaycastResult] = sceneView.session.raycast(raycastQuery!)

If you prefer raycasting method for hitting a node (entity), use RealityKit module instead of SceneKit:

let arView = ARView(frame: .zero)

let query: CollisionCastQueryType = .nearest
let mask: CollisionGroup = .default
    
let raycasts: [CollisionCastHit] = arView.scene.raycast(from: [0, 0, 0], 
                                                          to: [5, 6, 7],  
                                                       query: query, 
                                                        mask: mask, 
                                                  relativeTo: nil)
    
guard let raycast: CollisionCastHit = raycasts.first else { return }
    
print(raycast.entity.name)

P.S.

There is no need to look for a replacement for the SceneKit's hitTest(_:options:) instance method returning [SCNHitTestResult], because it works fine and it's not a time to make it deprecated.

like image 134
Andy Jazz Avatar answered Nov 03 '22 23:11

Andy Jazz