SCNPhysicsbody velocity does not apply to child nodes - ios

I am using Scenekit and I attached a camera to a node and applied a velocity to the physics body of the parent node. But only the parent node moves and the camera stays in place. Any ideas as to why this is happening? I need the camera to still be attached to the parent as it moves.
code:
class GameViewController: UIViewController, UIGestureRecognizerDelegate, SCNSceneRendererDelegate{
var playerObj : Player?
var panGesture : UIPanGestureRecognizer?
override func viewDidLoad() {
//create view and add scene
let sceneView = self.view as SCNView
let scene = SCNScene()
sceneView.delegate = self
sceneView.scene = scene
//create camera
// let cameraNode = SCNNode()
// cameraNode.camera = SCNCamera()
//create player
playerObj = Player()
scene.rootNode.addChildNode(playerObj!.node)
//create floor
let floorGeometry = SCNFloor()
let floorNode = SCNNode(geometry: floorGeometry)
floorNode.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Static, shape: SCNPhysicsShape(geometry: floorGeometry, options: nil))
floorNode.position = SCNVector3Make(0, 0, 0)
scene.rootNode.addChildNode(floorNode)
playerObj?.node.camera = SCNCamera()
//other setup
sceneView.showsStatistics = true
sceneView.allowsCameraControl = true
scene.physicsWorld.gravity = SCNVector3Make(0, -50, 0)
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap:")
sceneView.gestureRecognizers?.append(tapGesture)
panGesture = UIPanGestureRecognizer(target: self, action: "handlePan:")
sceneView.gestureRecognizers?.append(panGesture!)
}
func handlePan(gestureRecognize: UIPanGestureRecognizer){
//take coordinates and transform into direction
if let gesture = panGesture?{
playerObj?.node.physicsBody?.velocity = SCNVector3Make(Float(gesture.translationInView(self.view).x), 0, Float(gesture.translationInView(self.view).y))
print(gesture.translationInView(self.view))
if(gesture.state == UIGestureRecognizerState.Ended){
playerObj?.node.physicsBody?.velocity = SCNVector3Make(0, 0, 0)
gestureRecognize.setTranslation(CGPointZero, inView: self.view)
}
}
}

your camera does probably not need a physics body on its own.
If it just has to follow its parent, you could implement -renderer:didSimulatePhysicsAtTime: and continuously update the camera's position according to the position of the presentationNode of its parent.

Related

How can I change a SCNView back to a UIView?

I am trying to use code from an example of a Game with Xcode as a Swift Playground. The code works perfectly in the Xcode version, but in the playground, I get the error:
Could not cast value of type 'UIView' (0x114debe38) to 'SCNView' (0x12521d3d0).
The type of class is set to be of type UIViewController, so I am not sure why this does not work in only the playground. II have the same files in both the app and the playground.
I have already looked at this question, but the method seems to be built in already.
I also tried to cast it back to a UIView if it was a SCNView and I also tried making a new view, adding a subview as a SCNView to it, and then setting the final view as the new view. None of my attempts worked.
class GameScene: UIViewController {
let finalView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// 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)
cameraNode.rotation = SCNVector4(0, 0, 0, 30)
// 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 = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the shark node
let shark = scene.rootNode.childNode(withName: "ship", recursively: true)!
// animate the 3d object
//shark.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
#objc
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
finalView.addSubview(scnView)
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// 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 = UIColor.black
SCNTransaction.commit()
}
material.emission.contents = UIColor.red
SCNTransaction.commit()
}
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .pad {
return .allButUpsideDown
} else {
return .all
}
}
}
PlaygroundSupport.PlaygroundPage.current.liveView = GameScene()
What this should show up is a 3D Model and a camera that can pan around it by touch on a mobile device.
In your example the UIViewController view was probably set to an SCNView in interface builder. If you're not using a nib you can do this by overriding loadView.
override func loadView() {
let scnView = SCNView(frame: UIScreen.main.bounds, options: nil)
self.view = scnView
}

How to change the position of an object Swift SceneKit

I've two objects in one SceneKit. One is the Earth and the other is the Moon. Both of them are positioned at x:0, y:0, z:0 and are overlapping. How should I change the coordinates of the Moon so it's around the Earth?
Here the code:
import UIKit
import SceneKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x:0, y:0, z:10)
scene.rootNode.addChildNode(cameraNode)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .directional
lightNode.position = SCNVector3(x:0, y:0, z:2)
scene.rootNode.addChildNode(lightNode)
let stars = SCNParticleSystem(named: "StarsParticles.scnp", inDirectory: nil)!
scene.rootNode.addParticleSystem(stars)
let moonNode = MoonNode()
scene.rootNode.addChildNode(moonNode)
let sceneview = self.view as! SCNView
sceneview.scene = scene
let earthNode = EarthNode()
scene.rootNode.addChildNode(earthNode)
let sceneView = self.view as! SCNView
sceneView.scene = scene
sceneView.showsStatistics = false
sceneView.backgroundColor = UIColor.black
sceneView.allowsCameraControl = true
}
override var prefersStatusBarHidden: Bool {
return true
}
}
You could make your view controller a SCNSceneRendererDelegate, and implement the delegate method renderer:willRenderScene which gives you a time. Based on that time, you can set something like this:
moonNode.position = SCNVector3(r * cos(Float(time), r * sin(Float(time)), 0)
where r is the radius of your Earth node - if you don't know this at compile time, just include something like this above that line:
let r = length(float3(earthNode.boundingBox.max) - float3(earthNode.boundingBox.min))
this won't quite be exact for the code I included which causes a rotation in the xy plane but it will at least give you the right order of magnitude for r. If you get an error that it can't find float3 or length, try and import simd.
Let me know if you have any issues with this, as I typed this up from memory.
More info in documentation: https://developer.apple.com/documentation/scenekit/scnscenerendererdelegate/1523483-renderer
You can easily do it using simdPivot instance property.
var simdPivot: simd_float4x4 { get set }
Here's a code for testing:
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let action = SCNAction.repeatForever(SCNAction.rotate(by: .pi,
around: SCNVector3(0,1,0),
duration: 1))
let earthNode = SCNNode(geometry: SCNSphere(radius: 3.57))
earthNode.geometry?.materials.first?.diffuse.contents = UIColor.blue
scene.rootNode.addChildNode(earthNode)
let moonNode = SCNNode(geometry: SCNSphere(radius: 0.15))
moonNode.geometry?.materials.first?.diffuse.contents = UIColor.yellow
moonNode.simdPivot.columns.3.x = 5
moonNode.runAction(action)
scene.rootNode.addChildNode(moonNode)
let sceneView = self.view as! SCNView
sceneView.scene = scene
sceneView.backgroundColor = UIColor.black
sceneView.allowsCameraControl = true
}
}

SCNAudioSource won't play with a SCNNode

I am trying to play a sound every time i tap the screen. Here is my code
Global:
var shapeNode: SCNNode!
var SoundAction: SCNAction!
ViewDidLoad:
let audioSource = SCNAudioSource(named: "launch.mp3")!
audioSource.isPositional = true
audioSource.volume = 1.0
SoundAction = SCNAction.playAudio(audioSource, waitForCompletion: false)
#objc func sceneTapped(recognizer: UITapGestureRecognizer)
shapeNode = SCNNode(geometry: myshape)
self.myscene?.rootNode.addChildNode(shapeNode)
shapeNode.runAction(SoundAction)
Sound won't play when I touch the screen... Someone please help
I generated a default game project, then put in your code. At first, I received the same results. However, when I took the let scene = SCNScene(named: "art.scanassets/ship.scn")! and made scene a class variable, the sound plays. OR if you add the SoundNode during we did load and NOT when tapped, the sound will also play.
class GameViewController: UIViewController {
var SoundAction = SCNAction()
var SoundNode = SCNNode()
var scene = SCNScene()
override func viewDidLoad() {
super.viewDidLoad()
// Change this
scene = SCNScene(named: "art.scnassets/ship.scn")!
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
let audioSource = SCNAudioSource(named: "MenuWinner.caf")!
audioSource.volume = 1.0
SoundAction = SCNAction.playAudio(audioSource, waitForCompletion: false)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = UIColor.black
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
#objc
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
scene.rootNode.addChildNode(SoundNode)
SoundNode.runAction(SoundAction)
}

Why is SCNNode "jiggling" when dropped onto SCNPlane?

I have a SCNPlane that is added to the scene when a sufficient area is detected for a horizontal surface. The plane appears to be placed in a correct spot, according to the floor/table it's being placed on. The problem is when I drop a SCNNode(this has been consistent whether it was a box, pyramid, 3D-model, etc.) onto the plane, it will eventually find a spot to land and 99% start jiggling all crazy. Very few times has it just landed and not moved at all. I also think this may be cause by the node being dropped and landing slightly below the plane surface. It is not "on top" neither "below" the plane. Maybe the node is freaking out because it's kind of teetering between both levels?
Here is a video of what's going on, you can see at the beginning that the box is below and above the plane and the orange box does stop when it collides with the dark blue box, but does go back to its jiggling ways when the green box collides with it at the end:
The code is here on github
I will also show some of the relevant parts embedded in code:
I just create a Plane class to add to the scene when I need to
class Plane: SCNNode {
var anchor :ARPlaneAnchor
var planeGeometry :SCNPlane!
init(anchor :ARPlaneAnchor) {
self.anchor = anchor
super.init()
setup()
}
func update(anchor: ARPlaneAnchor) {
self.planeGeometry.width = CGFloat(anchor.extent.x)
self.planeGeometry.height = CGFloat(anchor.extent.z)
self.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
let planeNode = self.childNodes.first!
planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: self.planeGeometry, options: nil))
}
private func setup() {
//plane dimensions
self.planeGeometry = SCNPlane(width: CGFloat(self.anchor.extent.x), height: CGFloat(self.anchor.extent.z))
//plane material
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "tronGrid.png")
self.planeGeometry.materials = [material]
//plane geometry and physics
let planeNode = SCNNode(geometry: self.planeGeometry)
planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: self.planeGeometry, options: nil))
planeNode.physicsBody?.categoryBitMask = BodyType.plane.rawValue
planeNode.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
planeNode.transform = SCNMatrix4MakeRotation(Float(-Double.pi / 2.0), 1, 0, 0)
//add plane node
self.addChildNode(planeNode)
}
This is the ViewController
enum BodyType: Int {
case box = 1
case pyramid = 2
case plane = 3
}
class ViewController: UIViewController, ARSCNViewDelegate, SCNPhysicsContactDelegate {
//outlets
#IBOutlet var sceneView: ARSCNView!
//globals
var planes = [Plane]()
var boxes = [SCNNode]()
//life cycle
override func viewDidLoad() {
super.viewDidLoad()
//set sceneView's frame
self.sceneView = ARSCNView(frame: self.view.frame)
//add debugging option for sceneView (show x, y , z coords)
self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
//give lighting to the scene
self.sceneView.autoenablesDefaultLighting = true
//add subview to scene
self.view.addSubview(self.sceneView)
// Set the view's delegate
sceneView.delegate = self
//subscribe to physics contact delegate
self.sceneView.scene.physicsWorld.contactDelegate = self
//show statistics such as fps and timing information
sceneView.showsStatistics = true
//create new scene
let scene = SCNScene()
//set scene to view
sceneView.scene = scene
//setup recognizer to add scooter to scene
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
sceneView.addGestureRecognizer(tapGestureRecognizer)
}
//MARK: helper funcs
#objc func tapped(recognizer: UIGestureRecognizer) {
let scnView = recognizer.view as! ARSCNView
let touchLocation = recognizer.location(in: scnView)
let touch = scnView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
//take action if user touches box
if !touch.isEmpty {
guard let hitResult = touch.first else { return }
addBox(hitResult: hitResult)
}
}
private func addBox(hitResult: ARHitTestResult) {
let boxGeometry = SCNBox(width: 0.1,
height: 0.1,
length: 0.1,
chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor(red: .random(),
green: .random(),
blue: .random(),
alpha: 1.0)
boxGeometry.materials = [material]
let boxNode = SCNNode(geometry: boxGeometry)
//adding physics body, a box already has a shape, so nil is fine
boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
//set bitMask on boxNode, enabling objects with diff categoryBitMasks to collide w/ each other
boxNode.physicsBody?.categoryBitMask = BodyType.plane.rawValue | BodyType.box.rawValue
boxNode.position = SCNVector3(hitResult.worldTransform.columns.3.x,
hitResult.worldTransform.columns.3.y + 0.3,
hitResult.worldTransform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(boxNode)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
//track objects in ARWorld and start session
sceneView.session.run(configuration)
}
//MARK: - ARSCNViewDelegate
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
//if no anchor found, don't render anything!
if !(anchor is ARPlaneAnchor) {
return
}
DispatchQueue.main.async {
//add plane to scene
let plane = Plane(anchor: anchor as! ARPlaneAnchor)
self.planes.append(plane)
node.addChildNode(plane)
//add initial scene object
let pyramidGeometry = SCNPyramid(width: CGFloat(plane.planeGeometry.width / 8), height: plane.planeGeometry.height / 8, length: plane.planeGeometry.height / 8)
pyramidGeometry.firstMaterial?.diffuse.contents = UIColor.white
let pyramidNode = SCNNode(geometry: pyramidGeometry)
pyramidNode.name = "pyramid"
pyramidNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
pyramidNode.physicsBody?.categoryBitMask = BodyType.pyramid.rawValue | BodyType.plane.rawValue
pyramidNode.physicsBody?.contactTestBitMask = BodyType.box.rawValue
pyramidNode.position = SCNVector3(-(plane.planeGeometry.width) / 3, 0, plane.planeGeometry.height / 3)
node.addChildNode(pyramidNode)
}
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
let plane = self.planes.filter {
plane in return plane.anchor.identifier == anchor.identifier
}.first
if plane == nil {
return
}
plane?.update(anchor: anchor as! ARPlaneAnchor)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//pause session
sceneView.session.pause()
}
}
I think i followed the same tutorial. I also had same result. Reason is because when the cube drops from higher place, it accelerates and doesnot exactly hit on the plane but passes through. If you scale down the cube to '1 mm' you can see box completely passes through plane and continue falling below plane. You can try droping cube from nearer to the plane, box drops slower and this 'jiggling' will not occur. Or you can try with box with small height instead of plane.
I had the same problem i found out one solution.I was initializing the ARSCNView programmatically.I just removed those code and just added a ARSCNView in the storyboard joined it in my UIViewcontroller class using IBOutlet it worked like a charm.
Hope it helps anyone who is going through this problem.
The same code is below.
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints,ARSCNDebugOptions.showWorldOrigin]
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
}
The "jiggling" is probably caused by an incorrect gravity vector. Try experimenting with setting the gravity of your scene.
For example, add this to your viewDidLoad function:
sceneView.scene.physicsWorld.gravity = SCNVector3Make(0.0, -1.0, 0.0)
I found that setting the gravity - either through code, or by loading an empty scene - resolves this issue.

SceneKit: Hit test not work with billboarded quads

I made billboarded quads using SceneKit.
The cameraNode is synchronized with UIDeviceMotion, and the billboard nodes are appearing as I expected.
The thing is, I want these nodes to be called when I tap it.
For this, I used UITapGestureRecognizer with hitTest.
Here is some of my code.
// ==== in viewDidLoad
// initialize tap gesture
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onNodeTapped))
sceneView.addGestureRecognizer(tapGesture)
// initialize scenekit.scene
let scene = SCNScene()
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(worldNode)
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.pointOfView = cameraNode
And this is the tap handler
func onNodeTapped(_ gestureRecognize: UIGestureRecognizer) {
let location = gestureRecognize.location(in: sceneView) // <---- updated
let hitResults = sceneView.hitTest(location, options: nil)
for result in hitResults {
// FOR_TEST: hit test visualization
if let material = result.node.geometry?.materials.first {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.emission.contents = UIColor.black
SCNTransaction.commit()
}
material.emission.contents = UIColor.red
SCNTransaction.commit()
}
// target tap event handling
if let target = (result.node as? TargetNode)?.target {
if onTargetTapped(target) {
return
}
}
}
}
This code works very rarly. What I mean is the visualization part respond only 1 out of 20 times, and the onTargetTapped is called only 1 out of 100...
The weired thing is the targeting is fine, which means this is not the coordinate problem.
I found something related to SCNHitTestOption.categoryBitMask, but it didn't help at all.
Also, when I open this Scenview, this error message appears on the console.
"[SceneKit] Error: error in _C3DUnProjectPoints"
Maybe this message is related to the hitTest malfunctioning?
Updated
This code is for building billboard SCNGeometry and SCNNode
override func initializeGeometry() -> SCNGeometry {
let geometry = SCNPlane(width: width, height: height)
let material = geometry.materials.first
material?.diffuse.contents = initializeTexture()
material?.writesToDepthBuffer = false
material?.readsFromDepthBuffer = false
return geometry
}
// ==== building node
node = SCNNode()
node.geometry = initializeGeometry()
node.categoryBitMask = MyConstraints.targetNodeHitTestCategoryBitMask
node.constraints = [SCNBillboardConstraint()]
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
}
Hit testing should work whether your node is billboard constrained or not. It does for me.
override func viewDidLoad() {
self.sceneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:))))
...
}
#objc func tap(_ recognizer: UITapGestureRecognizer) {
let loc = recognizer.location(in: self.sceneView)
if let hit = self.sceneView.hitTest(loc) {
...
}
}

Resources