Swift SCNNode subclass hittest always returns SCNNode *not* subclass
Just cast the object to your subclass:
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
for hit in hitResults {
if let hitnode = hit.node as? ExSCNNode {
…
}
SceneKit: Hit test not work with billboarded quads
You're retrieving the tap coordinates in annotationsDisplayView
, but then passing those coordinates to sceneView
for the hit test lookup. Unless those views are precisely on top of each other, you'll get a mismatch.
I think you want
let location = gestureRecognize.location(in: sceneView)
instead.
I was curious whether the hit test would use the rotated version of the billboard. I confirmed that it does. Here's a working (Mac) sample.
In the SCNView
subclass:
override func mouseDown(with theEvent: NSEvent) {
/* Called when a mouse click occurs */
// check what nodes are clicked
let p = self.convert(theEvent.locationInWindow, from: nil)
let hitResults = self.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
Swift.print("hit me")
}
for result in hitResults {
Swift.print(result.node.name)
// get its material
let material = result.node.geometry!.firstMaterial!
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.emission.contents = NSColor.black
SCNTransaction.commit()
}
material.emission.contents = NSColor.yellow
SCNTransaction.commit()
}
super.mouseDown(with: theEvent)
}
And in the view controller:
override func awakeFromNib(){
super.awakeFromNib()
// create a new scene
let scene = SCNScene()
let side = CGFloat(2.0)
let quad1 = SCNNode.init(geometry: SCNPlane(width: side, height: side))
quad1.name = "green"
quad1.geometry?.firstMaterial?.diffuse.contents = NSColor.green
let quad2 = SCNNode.init(geometry: SCNPlane(width: side, height: side))
quad2.name = "red"
quad2.geometry?.firstMaterial?.diffuse.contents = NSColor.red
let quad3 = SCNNode.init(geometry: SCNPlane(width: side, height: side))
quad3.name = "blue"
quad3.geometry?.firstMaterial?.diffuse.contents = NSColor.blue
scene.rootNode.addChildNode(quad1)
scene.rootNode.addChildNode(quad2)
scene.rootNode.addChildNode(quad3)
let rotation = CGFloat(M_PI_2) * 0.9
quad1.position.y = side/2
quad1.eulerAngles.y = rotation
quad2.position.x = side/2
quad2.eulerAngles.x = rotation
// comment out these constraints to verify that they matter
quad1.constraints = [SCNBillboardConstraint()]
quad2.constraints = [SCNBillboardConstraint()]
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// set the scene to the view
self.gameView!.scene = scene
// allows the user to manipulate the camera
self.gameView!.allowsCameraControl = true
// show statistics such as fps and timing information
self.gameView!.showsStatistics = true
// configure the view
self.gameView!.backgroundColor = NSColor.black
}
Scenekit hitTestWithSegment only returns first hit (all of a sudden)
Apple decided to surprise everyone in IOS 11 by changing the default behavior of the hittest functions. Since IOS 11 you need to specfically tell the hittest to search for all nodes and not just return the first hit result:
hitTestOptions[SCNHitTestOption.searchMode] = SCNHitTestSearchMode.all.rawValue as NSNumber
This will set it back to returning an array of hitresults if multiple nodes were hit.
SceneKit hit test error while moving camera
I've started a new project and figured it out step by step when does the hittest go wrong. I didn't find it anywhere in the offical Apple documentation, but my experiences are the followings:
If you want to change the camera's position or any other property, you can do it by adding a new camera to a new node with new position, parameters, etc. then you set the SCNView's pointOfView property, you can do it animated like this:
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(2.0)
sceneView.pointOfView = cameraNode
SCNTransaction.commit()
One important point here: the node that holding the new SCNCamera has to be added to the SCNScene's rootView, otherwise (if you add it to the rootView's childNode) the hittest will give you an error instead the SCNNode that you touched.
SKLabelNode Hit Test
I resolved my problem by setting the SCNHitTestOption.boundingBoxOnly option to true. This will conduct the hit test ignoring the node's geometry.
let hitTestResults = sceneView.hitTest(tapPoint, options: [.boundingBoxOnly: true])
Swift: can't detect hit test on SCNNode? See if vector3 is contained in a node?
When you're working with ARSCNView
, there are two kinds of hit testing you can do, and they use entirely separate code paths.
- Use the ARKit method
hitTest(_:types:)
if you want to hit test against the real world (or at least, against ARKit's estimate of where real-world features are). This returnsARHitTestResult
objects, which tell you about real-world features like detected planes. In other words, use this method if you want to find a real object that anyone can see and touch without a device — like the table you're pointing your device at. - Use the SceneKit method
hitTest(_:options:)
if you want to hit test against SceneKit content; that is, to search for virtual 3D objects you've placed in the AR scene. This returnsSCNHitTestResult
objects, which tell you about things like nodes and geometry. Use this method if you want to find SceneKit nodes, the model (geometry) in a node, or the specific point on the geometry at a tap location.
In both cases the 3D position found by a hit test is the same, because ARSCNView
makes sure that the virtual "world coordinates" space matches real-world space.
It looks like you're using the former but expecting the latter. When you do a SceneKit hit test, you get results if and only if there's a node under the hit test point — you don't need any kind of bounding box test because it's already being done for you.
Related Topics
In Swift, Dynamic Height for UItextview in UIcollectionview
Using Scenekit for Hittesting Not Returning a Hit with Scnnode
Is There a Simple Way to Test If You Match One of a Set of Enumerations
How to Parse Url # Fragments with Query Items in Swift
Swift: Forward Keystrokes to a Different Process
Do Swift Hashable Protocol Hash Functions Need to Return Unique Values
How to Put Gamecenter on Application with Swift
Firestore - Creating a Copy of a Collection
Implement an Equatable Void (None) Type
Passing Data Between View Controllers (Swift)
How to Force Sktextureatlas Created from a Dictionary to Not Modify Textures Size
How to Do Imageview.Startanimating() with Completion in Swift
Swift Janus Can Not Publish Video, But Get Remote Video Successful - Can Not Know Reason