10

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.
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
aheze
  • 24,434
  • 8
  • 68
  • 125

1 Answers1

17

About SceneKit 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.

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

Deprecated SceneKit hit-testing method:

let sceneView = ARSCNView(frame: .zero)

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


SceneKit Raycasting equivalent

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

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


RealityKit Raycasting

If you need a raycasting method for hitting RealityKit's entity, use the following approach:

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)

Read this post to find out how to calculate an origin and direction of a ray.


Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • 1
    Thanks for the fast answer! But is there a way to get the cube node from `let results: [ARRaycastResult]`? – aheze Feb 12 '21 at 21:17
  • 1
    Got it. So basically, if I'm still using ARKit + SceneKit, just go with hit-testing even though it's deprecated? – aheze Feb 12 '21 at 21:30
  • Nope. There's a replacement for ARKit's deprecated method. SceneKit's methods are not deprecated. – Andy Jazz Feb 12 '21 at 21:31
  • I'm still kind of confused. The replacement for ARKit's `hittest` is raycasting, but that does not work with SceneKit right? Only RealityKit. – aheze Feb 13 '21 at 00:40
  • And [SceneKit's hit-testing](https://developer.apple.com/documentation/scenekit/scnscenerenderer/1522929-hittest) is a protocol function right? How can I do this with my ARKit scene? – aheze Feb 13 '21 at 00:42
  • 2
    There are 3 `hitTest()` methods for sceneView object (of ARSCNView type). One of them, that is deprecated, returns `[ARHitTestResult]` – it was used for hit-testing detected planes. The second `hitTest()` returns `UIView?, and the third one returns `[SCNHitTestResult]` that can be useful for pure SceneKit hit-testing against nodes. The third method isn't deprecated – you can use it for AR apps and VR apps. So your arsenal in ARSCNView for iOS 14 are 2 `hitTest` methods and 1 `raycastQuery()` method. Also, ARSCNView's session object allows you apply raycasting method for detected planes. – Andy Jazz Feb 13 '21 at 06:45
  • 1
    However, if you're using ARView (RealityKit's view that ARKit can use too), you can use only raycasting methods – `arView.raycast(...)` (returns [ARRaycastResult]), `arView.session.raycast(...)` (returns [ARRaycastResult]), `arView.session.trackedRaycast(...)` and 2 methods `arView.scene.raycast(...)`. – Andy Jazz Feb 13 '21 at 06:51
  • 1
    `hitTest` and `raycast` methods are not from protocol. You can use hit-testing for SceneKit nodes and you can use raycasting for RealityKit entities. For ARKit detected planes you must use `raycasting`. – Andy Jazz Feb 13 '21 at 07:04
  • 1
    Awesome, thank you so much! I wasn't seeing the third function, finally found it [here](https://developer.apple.com/documentation/scenekit/scnscenerenderer/1522929-hittest). – aheze Feb 13 '21 at 07:40
  • @AndyJazz, actually, by using hitTest on the same CGPoint. I can get result from hitTest, but raycast returned empty array. is it expected? raycast can only return result occasionally ... in ios16. I can see hitTest is deprecated. However, I couldn't get the wanted result by using raycast. – SKLTFZ Apr 07 '23 at 16:17
  • Hi SKLTFZ, post it as a question (with your code), please. – Andy Jazz Apr 09 '23 at 21:11