How can I change a SCNView back to a UIView? - ios

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
}

Related

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)
}

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) {
...
}
}

Tap Gesture Recognizer for overlayScene not work in Swift Xcode [duplicate]

Hey ok what is the best way to recognize touch for a HUD on a scene kit 3D game on a Overlayskscene. because i have a button called "AButton" but when ever i touch the Button or the screen the game crashes after hours of search I'm guessing the problem is the touchbegin on the scene kit don't exactly get along. But then how do i make it so that when a user touches a button on the HUD it doesn't crash the system and it actually works. can you look at my code and rewrite it. do i use touches begin or do i us hit test or something else (I never have used hit test before)?
handleTap:]: unrecognized selector sent to instance 0x14d547710'
*** First throw call stack:
(0x1868042d8 0x1980300e4
Code:
import iAd
import UIKit
import GameKit
import SceneKit
import StoreKit
import SpriteKit
import QuartzCore
import Foundation
import AVFoundation
import AudioToolbox
//============================================================
class GameViewController: UIViewController, ADBannerViewDelegate, SKPhysicsContactDelegate, SKSceneDelegate, SCNSceneRendererDelegate, SCNPhysicsContactDelegate{
let FieldScene = SCNScene(named: "art.scnassets/TesingCampusField.dae")!
let GuyScene = SCNScene(named: "art.scnassets/Guy.dae")!
//-------------------HUD-SetUp-------------------------------------------------------
let overlayScene = SKScene(size: CGSizeMake(100, 100))
override func viewDidLoad() {
super.viewDidLoad()
let scnView = self.view as! SCNView
scnView.overlaySKScene = overlayScene
scnView.backgroundColor = UIColor.whiteColor()
scnView.scene = FieldScene
scnView.delegate = self
scnView.overlaySKScene!.delegate = self
scnView.overlaySKScene!.anchorPoint = CGPointMake(0, 0)
scnView.overlaySKScene!.physicsWorld.contactDelegate = self
scnView.overlaySKScene!.physicsWorld.gravity = CGVectorMake(0.0, 0.0)
scnView.allowsCameraControl = false
scnView.showsStatistics = false
let Guy1: SCNNode = GuyScene.rootNode.childNodeWithName("Bob_014", recursively: true)!
FieldScene.rootNode.addChildNode(Guy1)
ButtonA.size = CGSize(width: 6, height: 9)
ButtonA.anchorPoint = CGPointMake(-13.3, -0.5)
ButtonA.zPosition = 0
overlayScene.addChild(ButtonA)
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap:")
scnView.addGestureRecognizer(tapGesture)
//--------------------------
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
FieldScene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 5, z: 15)
//-----------------------------------------------
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
FieldScene.rootNode.addChildNode(lightNode)
//-----------------------------------------------
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor.darkGrayColor()
FieldScene.rootNode.addChildNode(ambientLightNode)
//----------------------------------------------
}
func AButtonPressed() {
let AButtonPressed = SKTexture(imageNamed: "GreenAButtonPressed")
let OrignalButtonA = SKTexture(imageNamed:"GreenAButton")
let AButtonPressedAnimation = SKAction.animateWithTextures([AButtonPressed, OrignalButtonA], timePerFrame: 0.2)
let RunAButtonPressedAnimation = SKAction.repeatAction(AButtonPressedAnimation, count: 1)
ButtonA.runAction(RunAButtonPressedAnimation)
print("finishedpressing")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location1 = touch.locationInNode(self.overlayScene)
if self.overlayScene.nodeAtPoint(location1) == self.ButtonA {
AButtonPressed()
print("AButtonPressed")
}
}
your gesture recognizer is configured to call the handleTap: method when the user taps the view, but you don't define that method.
func handleTap(gestureRecognizer: UITapGestureRecognizer) {
// do something
}
If you want to use touchesBegan directly, then you don't need the gesture recognizer.

How to add a SCNode to SceneKit from main art.scnassets folder

I want to add a .dae file that is sitting in my art.assets folder. to the scene. thats it lol and in swift preferable but I'll take Objective-C as well. Thanks here some code its just the basic scene kit file Xcode gives you.
Code:
import UIKit
import QuartzCore
import SceneKit
//============================================================
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//-------------------------
let scene = SCNScene(named: "art.scnassets/GenricFootball.dae")!
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = false
scnView.backgroundColor = UIColor.whiteColor()
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap:")
scnView.addGestureRecognizer(tapGesture)
//--------------------------
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
//-----------------------------------------------
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
//-----------------------------------------------
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor.darkGrayColor()
scene.rootNode.addChildNode(ambientLightNode)
//----------------------------------------------
//_ = scene.rootNode.childNodeWithName("Bob", recursively: true)!
// _ = scene.rootNode.childNodeWithName("CampusField1", recursively: true)!
//--------------------------------------------------------
// Bob.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 2, z: 0, duration: 1)))
}
func handleTap(gestureRecognize: UIGestureRecognizer) {
let scnView = self.view as! SCNView
let p = gestureRecognize.locationInView(scnView)
let hitResults = scnView.hitTest(p, options: nil)
if hitResults.count > 0 {
let result: AnyObject! = hitResults[0]
let material = result.node!.geometry!.firstMaterial!
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
SCNTransaction.setCompletionBlock {
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
material.emission.contents = UIColor.blackColor()
SCNTransaction.commit()
}
material.emission.contents = UIColor.yellowColor()
SCNTransaction.commit()
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
I got to objects on the scene by using this code
Code:
let scene = SCNScene(named: "art.scnassets/GenricFootball.dae")!
let characterscene = SCNScene(named: "art.scnassets/untitled.dae")!
let monkey: SCNNode = characterscene.rootNode.childNodeWithName("Cube_001", recursively: true)!
scene.rootNode.addChildNode(monkey)
monkey.position = SCNVector3(x: 5, y: 0, z: 5)

Resources