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
How to Switch to Swift 4.0 in Xcode 9.3
How to 'Addtarget' to Uilabel in Swift
Codable Enum with Multiple Keys and Associated Values
Mandatory Init Override in Swift Uinavigationcontroller Subclass
Counting Coloured Pixels on the Gpu - Theory
Integrate Existing Aws Cognito User Pool into iOS Project with Amplify
How to Set Priority on Constraints in Swift
Mfmailcomposeviewcontroller Error [Mc] Filtering Mail Sheet Accounts for Bundle Id
Background Upload with Share Extension
Set Uitextfield Placeholder Color Programmatically
Is There an Kotlin Equivalent 'With' Function in Swift
Why Can't I Use .Reduce() in a One-Liner Swift Closure with a Variadic, Anonymous Argument
Command Failed Due to Signal: Segmentation Fault: 11 While Emitting Ir Sil Function
Could Not Find a Storyboard Named 'Maintabcontroller' in Bundle Nsbundle