SceneKit rotate camera automatically - ios

I want my camera to keep rotating, automatically, around a object and only changing it's direction at certain moments. I saw some solutions but they were all with gesture recognizers... How can I keep it rotating at a constant velocity and change it's directions?
Here is my code:
class GameViewController: UIViewController {
var gameView:SCNView!
var gameScene:SCNScene!
var cameraNode:SCNNode!
var camera:SCNCamera!
var cameraOrbit:SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
initView()
initScene()
initCamera()
}
func initView() {
gameView = self.view as! SCNView
gameView.allowsCameraControl = false
gameView.autoenablesDefaultLighting = true
}
func initScene() {
gameScene = SCNScene()
gameView.scene = gameScene
gameView.isPlaying = true
}
func initCamera() {
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 1
camera.zFar = 100
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 5, z: 20)
gameScene.rootNode.addChildNode(cameraNode)
cameraOrbit.addChildNode(cameraNode)
cameraOrbit.eulerAngles.y = Float(M_PI)
cameraOrbit.eulerAngles.x = Float(2*M_PI)
gameScene.rootNode.addChildNode(cameraOrbit)
}

Related

SceneKit memory leak

The memory of app increased after segue to VC with scnView. I've used deinit and set geometry to nil but it didn't help. I saw some tips on stack about using deinit to solve this issue, but it doesn't work for me. Memory increased every time when I back to this ViewController with scnScene SceneKit: too much memory persisting
var ship1: SCNNode!
var ship2: SCNNode!
var ship3: SCNNode!
var ship4: SCNNode!
let cameraNode = SCNNode()
let lightNode = SCNNode()
let ambientLightNode = SCNNode()
class RecordVideoViewControllerWS: UIViewController {
#IBOutlet weak var scnView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
cameraNode.camera = SCNCamera()
scnScene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scnView.scene = scnScene
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scnScene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scnScene.rootNode.addChildNode(ambientLightNode)
ship1 = nodeFromResource(assetName: "shipFolder/test0", extensionName: "scn")
ship2 = nodeFromResource(assetName: "shipFolder/test1", extensionName: "scn")
ship3 = nodeFromResource(assetName: "shipFolder/test2", extensionName: "scn")
ship4 = nodeFromResource(assetName: "shipFolder/test3", extensionName: "scn")
scnScene.rootNode.addChildNode(ship1)
scnScene.rootNode.addChildNode(ship2)
scnScene.rootNode.addChildNode(ship3)
scnScene.rootNode.addChildNode(ship4)
}
override func viewWillDisappear(_ animated: Bool) {
ship1.removeFromParentNode()
ship1.geometry = nil
ship2.removeFromParentNode()
ship2.geometry = nil
ship3.removeFromParentNode()
ship3.geometry = nil
ship4.removeFromParentNode()
ship4.geometry = nil
cameraNode.removeFromParentNode()
cameraNode.geometry = nil
lightNode.removeFromParentNode()
lightNode.geometry = nil
ambientLightNode.removeFromParentNode()
ambientLightNode.geometry = nil
}
deinit {
scnScene.rootNode.cleanup()
}
}
extension SCNNode {
func cleanup() {
for child in childNodes {
child.cleanup()
}
geometry = nil
}
}

Move camera to tapped SCNNode

I'm using SceneKit and Swift to try and move the camera so it's 'focused' on the selected node. I understand I have the defaultCameraController enabled but I was trying to adjust the camera's position via dolly, rotate and translateInCameraSpaceBy but there was no animated transition - it just jumped to the new position.
Is there anyway for the camera to glide into position like how Google Maps slides/then zooms over to a searched location?
Any help would be greatly appreciated :)
Here's my code:
import UIKit
import SceneKit
class ViewController: UIViewController {
var gameView: SCNView!
var scene: SCNScene!
var cameraNode: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
// Scene
scene = SCNScene()
// Camera
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 10)
scene.rootNode.addChildNode(cameraNode)
// Light
/*
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(0, 10, 2)
scene.rootNode.addChildNode(lightNode)
*/
// Stars
//let stars = SCNParticleSystem(named: "starsParticles.scnp", inDirectory: nil)!
//scene.rootNode.addParticleSystem(stars)
// Earth
let earthNode = itemPlate()
earthNode.position = SCNVector3(0, 0, 0)
scene.rootNode.addChildNode(earthNode)
// Create orbiting moonOne
let moonNodeOne = itemPlate()
moonNodeOne.position = SCNVector3(3, 0, 0)
earthNode.addChildNode(moonNodeOne)
// Create orbiting moonOne
let moonNodeTwo = itemPlate()
moonNodeTwo.position = SCNVector3(5, 3, 2)
earthNode.addChildNode(moonNodeTwo)
// Create orbiting moonOne
let moonNodeThree = itemPlate()
moonNodeThree.position = SCNVector3(-4, -3, 5)
earthNode.addChildNode(moonNodeThree)
// Scene formation
gameView = self.view as! SCNView
gameView.scene = scene
gameView.showsStatistics = true
gameView.allowsCameraControl = true
gameView.autoenablesDefaultLighting = true
gameView.defaultCameraController.interactionMode = .fly
gameView.defaultCameraController.inertiaEnabled = true
gameView.defaultCameraController.maximumVerticalAngle = 89
gameView.defaultCameraController.minimumVerticalAngle = -89
scene.background.contents = UIImage(named: "orangeBg.jpg")
}
override var prefersStatusBarHidden: Bool {
return true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: gameView)
let hitList = gameView.hitTest(location, options: nil)
if let hitObject = hitList.first {
let node = hitObject.node
// Update camera position
//gameView.defaultCameraController.translateInCameraSpaceBy(x: node.position.x, y: node.position.y, z: node.position.z + 5)
let onScreenPoint:CGPoint = CGPoint(x: 1.0, y: 1.0)
let viewport:CGSize = CGSize(width: 50, height: 50)
gameView.defaultCameraController.dolly(by: 1.0, onScreenPoint: onScreenPoint, viewport: viewport)
//let newCameraPosition = SCNVector3Make(node.position.x, node.position.y, node.position.z + 10)
print("NODE_HIT_OBJECT_COORDS: \(node.position.x), \(node.position.y) \(node.position.y)")
//let moveToAction = SCNAction.move(by: newCameraPosition, duration: 1.0)
}
}
}
You can implement in your code a methodology like this (sorry, I used macOS project instead iOS, but it's almost the same):
func handleClick(_ gestureRecognizer: NSGestureRecognizer) {
let scnView = self.view as! SCNView
let p = gestureRecognizer.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
if hitResults.count > 0 {
let result = hitResults[0]
let nodePosition = result.node.position.z
var matrix = matrix_identity_float4x4
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5 // duration in seconds
matrix.columns.3.z = Float(nodePosition + 5.0)
scnView.pointOfView?.position.z = CGFloat(matrix.columns.3.z)
SCNTransaction.commit()
}
}
Or, as a second logical option, you can use SceneKit's constraints:
func handleClick(_ gestureRecognizer: NSGestureRecognizer) {
let scnView = self.view as! SCNView
let p = gestureRecognizer.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
if hitResults.count > 0 {
let result = hitResults[0]
let nodePosition = result.node
let constraint1 = SCNLookAtConstraint(target: nodePosition)
let constraint2 = SCNDistanceConstraint(target: nodePosition)
constraint2.minimumDistance = 5
constraint2.maximumDistance = 9
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
scnView.pointOfView?.constraints = [constraint2, constraint1]
SCNTransaction.commit()
}
}
P.S. These two approaches ain't out-of-the-box solutions but rather hints on how to implement what you want to.

How to detect touch on SKShapeNode in Video Sphere?

I use this simple code for playing 360 video. I need to add a point to the video - there is no problem with that. But how to track clicks on it? In this example, adding a point occurs in the viewDidLoad method.
I tried touchesBegan, but this method does not work. I really hope for your help
class ViewControllerTwo: UIViewController {
let motionManager = CMMotionManager()
let cameraNode = SCNNode()
var sphereNode: SCNNode!
#IBOutlet weak var sceneView: SCNView!
func createSphereNode(material: AnyObject?) -> SCNNode {
let sphere = SCNSphere(radius: 100.0)
sphere.segmentCount = 96
sphere.firstMaterial!.isDoubleSided = true
sphere.firstMaterial!.diffuse.contents = material
let sphereNode = SCNNode(geometry: sphere)
sphereNode.position = SCNVector3Make(0,0,0)
return sphereNode
}
func configureScene(node sphereNode: SCNNode) {
let scene = SCNScene()
sceneView.scene = scene
sceneView.showsStatistics = true
sceneView.allowsCameraControl = true
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 0)
scene.rootNode.addChildNode(sphereNode)
scene.rootNode.addChildNode(cameraNode)
}
func startCameraTracking() {
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(to: .main) { [weak self] (data, error) in
guard let data = data else { return }
let attitude: CMAttitude = data.attitude
self?.cameraNode.eulerAngles = SCNVector3Make(Float(attitude.roll + Double.pi/2.0), -Float(attitude.yaw), -Float(attitude.pitch))
}
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "google-help-vr", ofType: "mp4")!)
let player = AVPlayer(url: url )
let videoNode = SKVideoNode(avPlayer: player)
let size = CGSize(width: 1025, height: 512)
videoNode.size = size
videoNode.position = CGPoint(x: size.width / 2, y: size.height / 2)
let spriteScene = SKScene(size: size)
spriteScene.addChild(videoNode)
// How to detect when tapped?
let circ = SKShapeNode(rectOf: CGSize(width: 50, height: 50), cornerRadius: 25)
circ.fillColor = .red
circ.isUserInteractionEnabled = true
videoNode.addChild(circ)
sphereNode = createSphereNode(material:spriteScene)
configureScene(node: sphereNode)
guard motionManager.isDeviceMotionAvailable else {
return
}
startCameraTracking()
player.play()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
sceneView.play(self)
}
}
I did a self class for the object SKShapeNode, in order to track clicks through the touchesBegan method. But all without success
You can use a UITapGesture recognizer to get the 2D point then use SCNSceneRenderer .hitTest(_:options:) to get all of the possible intersections along that ray. Note that the method is on the SCNSceneRenderer protocol, which SCNView conforms to so you may have missed it in the SCNView documentation.
#IBAction func tap(_ recognizer: UITapGestureRecognizer) {
let location = recognizer.location(in: sceneView)
if let firstResult = sceneView.hitTest(location, options: nil).first,
//Do stuff with firstResult here
}

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

How to addChild to a already built overlaySKScene in SWIFT

Ok so I have all of the parts. Finally the code runs without a problem but right as I'm in the last step, trying to figure out how to add the child I'm having trouble. I created an overlay scene that is over the original Scene but I can't figure out the last step which is added the node/image to the actually screen because the traditional self.addChild(%^%) doesn't work on the "base" node in my code. Any help. Thanks
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{
//--------------True-False-statments-------------------------------------------------
var stickActive:Bool = false
var OnOffense = Bool()
var OnDefense = Bool()
var HasBall = Bool()
var OnCampusField = Bool()
var UserControlled = Bool()
//--------Offense-------------
var QuaterBack = SCNNode()
var RunningBack = SCNNode()
var WideReceiver1 = SCNNode()
var WideReceiver2 = SCNNode()
var Linemen1 = SCNNode()
var Linemen2 = SCNNode()
var Linemen3 = SCNNode()
//--------Defense-------------
var LineBacker1 = SCNNode()
var LineBacker2 = SCNNode()
var CornerBack1 = SCNNode()
var CornerBack2 = SCNNode()
var DefensiveLinemen1 = SCNNode()
var DefensiveLinemen2 = SCNNode()
var DefensiveLinemen3 = SCNNode()
//-----------------Controller-Buttons/Joystick---------------------------------------------------
let base = SKSpriteNode(imageNamed:"VirtualJoystickBase")
let ball = SKSpriteNode(imageNamed:"VirtualJoyStickHandle")
let ship = SKSpriteNode(imageNamed:"Ship")
let Button1 = SKSpriteNode(imageNamed:"BlackAButton")
let Button2 = SKSpriteNode(imageNamed:"BlackAButton")
let Button3 = SKSpriteNode(imageNamed:"BlackAButton")
let Button4 = SKSpriteNode(imageNamed:"BlackAButton")
//-------------------3D-Fields--------------------------------------------
let FieldScene = SCNScene(named: "art.scnassets/TesingCampusField.dae")!
//-------------------3D-Players--------------------------------------------
let GuyScene = SCNScene(named: "art.scnassets/Guy.dae")!
override func viewDidLoad() {
super.viewDidLoad()
let scnView = self.view as! SCNView
let skScene = scnView.overlaySKScene
scnView.overlaySKScene = skScene
scnView.backgroundColor = UIColor.whiteColor()
scnView.scene = FieldScene
scnView.delegate = self
scnView.allowsCameraControl = true
scnView.showsStatistics = false
let Guy1: SCNNode = GuyScene.rootNode.childNodeWithName("Bob_014", recursively: true)!
FieldScene.rootNode.addChildNode(Guy1)
//----Positioning-the-Base-of-the-Joystick-----------
base.size = CGSize(width: 100, height: 100)
base.anchorPoint = CGPointMake(-3.4, -5.2)
skScene?.self.addChild(base)
//----Positioning-the-Ball/Joystick-----------
ball.size = CGSize(width: 50, height: 50)
ball.position = base.position
//---Setting-Up-Ships-Position/PhysicsBody---------------------
ship.position = CGPointMake(0, 200)
ship.size = CGSize(width: 80, height: 80)
ship.physicsBody?.dynamic = true
ship.physicsBody?.allowsRotation = true
ship.physicsBody?.affectedByGravity = true
ship.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ship"), size: ship.size)
//----A-Button--Creation -------------------
Button1.size = CGSize(width: 40, height: 40)
Button1.anchorPoint = CGPointMake(-3.4, -5.2)
//----B-Button--Creation -------------------
Button2.size = CGSize(width: 40, height: 40)
Button2.anchorPoint = CGPointMake(-1.2, -5.2)
//----C-Button--Creation -------------------
Button3.size = CGSize(width: 40, height: 40)
Button3.anchorPoint = CGPointMake(-2.2, -6.4)
//----C-Button--Creation -------------------
Button4.size = CGSize(width: 40, height: 40)
Button4.anchorPoint = CGPointMake(-2.2, -3.4)
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 GotoMainMenu() {
let scene = MainMenuController()
let sKView = self.view! as! SKView
sKView.ignoresSiblingOrder = true
scene.size = sKView.bounds.size
scene.scaleMode = .AspectFill
let reveal = SKTransition.fadeWithDuration(0.45)
sKView.presentScene(scene, transition: reveal)
}
//====================================================================
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.
}
}

Resources