In Which Cases Arscnview.Raycastquery Returns Nil

SceneKit- Cannot query using bitmask

SceneKit

In SceneKit you can use bitmasks in context of [SCNHitTestResult]. hitTest(_:options:) instance method is not deprecated yet and it works in iOS 15.4.

let sceneView = ARSCNView(frame: .zero)

enum HitTestType: Int {
case object_A = 0b00000001
case object_B = 0b00000010
}

let point: CGPoint = gesture.location(in: self.sceneView)

let bitMask = HitTestType.object_A.rawValue | HitTestType.object_B.rawValue

let results = sceneView.hitTest(point, options: [.categoryBitMask: bitMask])

P.S.

Only hitTest(_:types:) is deprecated at the moment.


RealityKit

In RealityKit you can use bitmasks in CollisionCastHit's context:

let arView = ARView(frame: .zero)

let point: CGPoint = gesture.location(in: self.arView)

let (origin, direction) = arView.ray(through: point)!

let raycasts: [CollisionCastHit] = arView.scene.raycast(origin: origin,
direction: direction,
length: 50,
query: .any,
mask: .default,
relativeTo: nil)

...or this way:

let raycasts: [CollisionCastHit]  = arView.hitTest(point, 
query: .any,
mask: .default)

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

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.

ARKit SCNNode always in the center when camera move

It's not clear exactly what you're trying to do, but I assume its one of the following:

A) Place the green dot centered in front of the camera at a fixed distance, eg. always exactly 1 meter in front of the camera.

B) Place the green dot centered in front of the camera at the depth of the nearest detected plane, i.e. using the results of a raycast from the mid point of the ARSCNView

I would have assumed A, but your example code is using (now deprecated) sceneView.hitTest() function which in this case would give you the depth of whatever is behind the pixel at sceneView.center

Anyway here's both:

Fixed Depth Solution

This is pretty straightforward, though there are few options. The simplest is to make the green dot a child node of the scene's camera node, and give it position with a negative z value, since z increases as a position moves toward the camera.

cameraNode.addChildNode(textNode)
textNode.position = SCNVector3(x: 0, y: 0, z: -1)

As the camera moves, so too will its child nodes. More details in this very thorough answer

Scene Depth Solution

To determine the estimated depth behind a pixel, you should use ARSession.raycast instead of SceneView.hitTest, because the latter is definitely deprecated.

Note that, if the raycast() (or still hitTest()) methods return an empty result set (not uncommon given the complexity of scene estimation going on in ARKit), you won't have a position to update the node and this it might not be directly centered in every frame. To handle this is a bit more complex, as you'd need decide exactly what you want to do in that case.

The SCNAction is unnecessary and potentially causing problems. These delegate methods run 60fps, so simply updating the position directly will produce smooth results.

Adapting and simplifying the code you posted:

func createCenterShipNode() -> SCNNode {
let scene = SCNScene(named: "art.scnassets/ship.scn")!
let node = scene.rootNode.childNode(withName: "ship", recursively: false)
node!.opacity = 0.7
node!.name = "CenterShip"
sceneView.scene.rootNode.addChildNode(node!)
return node!
}

func session(_ session: ARSession, didUpdate frame: ARFrame) {
// Check the docs for what the different raycast query parameters mean, but these
// give you the depth of anything ARKit has detected
guard let query = sceneView.raycastQuery(from: sceneView.center, allowing: .estimatedPlane, alignment: .any) else {
return
}

let results = session.raycast(query)
if let hit = results.first {
let node = sceneView.scene.rootNode.childNode(withName: "CenterShip", recursively: false) ?? createCenterShipNode()
let pos = hit.worldTransform.columns.3
node.simdPosition = simd_float3(pos.x, pos.y, pos.z)
}
}

See also: ARRaycastQuery

One last note - you generally don't want to do scene manipulation within this delegate method. It runs on a different thread than the Scenekit rendering thread, and SceneKit is very thread sensitive. This will likely work fine, but beyond adding or moving a node will certainly cause crashes from time to time. You'd ideally want to store the new position, and then update the actual scene contents from within the renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) delegate method.



Related Topics



Leave a reply



Submit