swiftuirealitykithittestraycast

RealityKit method of hit testing from an Entity in a direction


I should point out that thus far I have no ARView instances; I'm working from the template code provided by Xcode 16.4 which kindly gives me a RealityView:

var body: some View {
    ZStack {
        RealityView { content in
        }
    }
}

I'm trying to work out, when a user interaction occurs via say GameController or even a tap on screen, how to decide, when looking directly forward from the current position, how far away the first Entity is.

In SceneKit we have:

func hitTestWithSegment(
    from pointA: SCNVector3,
    to pointB: SCNVector3,
    options: [String : Any]? = nil
) -> [SCNHitTestResult]

But I've been unable to identify how to achieve the same with RealityKit. Perhaps it is time to create an ARView in this game, and use some of the tools there, like:

func entity(at point: CGPoint) -> Entity?

or

func raycast(
    from point: CGPoint,
    allowing target: ARRaycastQuery.Target,
    alignment: ARRaycastQuery.TargetAlignment
) -> [ARRaycastResult]

Solution

  • The "equivalent" of the SCNHitTestResult object in RealityKit is the CollisionCastHit object. To get a collection of CollisionCastHit objects, you need to call the raycast(..) instance method on the RealityKit.Scene object. The ray(..) method returns an optional tuple containing a pair of SIMD3<Float> coordinates. For a SceneKit developer, this solution may not seem obvious.

    import SwiftUI
    import RealityKit
    
    struct ContentView : View {
        @State var rvc: RealityViewCameraContent? = nil
        let plane = ModelEntity(mesh: .generatePlane(width: 1, height: 1))
        
        init() {
            plane.name = "PLANE MODEL"
            plane.generateCollisionShapes(recursive: false)
            plane.position.z = -1.5
        }
        var body: some View {
            RealityView { content in
                // Default camera position is 2.0 meters along +Z axis
                self.rvc = content
                self.rvc?.add(plane)
            }
            .onTapGesture {
                if let scene = plane.scene, let rvc = self.rvc {
                    let ray = rvc.ray(
                        through: .init(x: $0.x, y: $0.y),
                        in: .local,
                        to: .scene
                    )
                    let castHits: [CollisionCastHit] = scene.raycast(
                        origin: ray?.origin ?? [],
                        direction: ray?.direction ?? []
                    )
                    if let distance = castHits.first?.distance {
                        print(plane.name, "is", distance, "away")  // 3.5 m. away
                    } else {
                        print("nothing")
                    }
                }
            }
            .background(.black)
        }
    }
    

    By the way, the implementation of the entity(..) method will look like this:

    let entity = self.rvc?.entity(
        at: .init(x: $0.x, y: $0.y),
        in: .local
    )
    print(entity?.name ?? "nil", "–", entity?.position ?? [])