SCNNode not moving due to animation - swift

I created SCNNode object and added simple CABasicAnimation that just spins my object. I used to set velocity to the physics body of my node, but it’s not moving while animation is playing. However other animations which were created in blender are working.
Example:
self.node = SCNNode()
let obj = SCNScene(named: "art.scnassets/player.scn")!
self.node = (obj.rootNode.childNode(withName: "Body", recursively: true))!
let spin = CABasicAnimation(keyPath: "rotation")
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: Float(CGFloat(2 * Double.pi))))
spin.duration = 1
spin.repeatCount = .infinity
self.node.addAnimation(SCNAnimation(caAnimation: spin), forKey: "spin")
scene.rootNode.addChildNode(self.node)
self.player.node.physicsBody?.velocity.x = 2.0 // not working together with animation

Related

How can I rotate 3d object using right and left arrows in Swift? (SceneKit)

How can I enable the user to move the object using the right and left arrows I put below, instead of manually moving it?
"allowscameracontrol" allows the user to rotate the object with their hand. But I just want it to be rotated using arrows.
** -> sceneView.allowsCameraControl = true**
import UIKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet weak var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
// 1: Load .obj file
let scene = SCNScene(named: "converse_obj.obj")
//Add camera node
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
//Place camera
cameraNode.position = SCNVector3(x: 0, y: 10, z: 35)
//*Set camera on scene
scene?.rootNode.addChildNode(cameraNode)
//Adding light to scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 35)
scene?.rootNode.addChildNode(lightNode)
// 6: Creating and adding ambien light to scen
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene?.rootNode.addChildNode(ambientLightNode)
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
// Set background color
sceneView.backgroundColor = UIColor.white
// Allow user translate image
sceneView.cameraControlConfiguration.allowsTranslation = false
// Set scene settings
sceneView.scene = scene
}
}
I haven't used SceneKit before.
I'm not sure if you would want to rotate your camera or rotate the node that contains the object. From the way it's named, it makes it sound like allowsCameraControl rotates the camera. In any case you would use rotationMatrixAroundZ to create a rotation matrix, and then multiply that by the matrix for whichever thing you want to rotate.
If rotating the camera is what you want to do, your code might look like this:
func rotateCameraZByDegrees(_ zRotation: Double) {
let radians = zRotation / 180.0 * Double.pi
let rotationZ = rotationMatrixAroundZ(radians: radians)
cameraNode.transform = SCNMatrix4(simd_mul(simd_float4x4(cameraNode.transform), rotationZ))
}
If you instead want to rotate your object's node, and your object's node is named sneakersNode, change the last line to:
sneakersNode.transform = SCNMatrix4(simd_mul(simd_float4x4(sneakersNode.transform), rotationZ))
This post [31881911] in stackO shows you some examples for arrow keys.
Turn allowsCameraControl = false
This post [68506605] see my answer - creating a camera control class makes things a lot easier.
For your rotation, just rotate the object directly - several ways to do this.
func resetRotation()
{
for (_, vNode) in displayNodes
{
vNode.node.removeAllActions()
}
vRotate0 = SCNAction.rotateTo(x: 0, y: 0, z: 0, duration: 2)
vRotate1 = SCNAction.rotateBy(x: 0, y: CGFloat(GLKMathDegreesToRadians(450)), z: 0, duration: 3)
vRotate2 = SCNAction.rotateBy(x: 0, y: CGFloat(GLKMathDegreesToRadians(-90)), z: 0, duration: 1)
vRotate3 = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(-45)), y: 0, z: 0, duration: 1)
vRotate4 = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(45)), y: 0, z: 0, duration: 1)
seq = SCNAction.sequence([vRotate0, vRotate1, vRotate2, vRotate3, vRotate4, vRotate3, vRotate4])
allSeq = SCNAction.repeatForever(seq)
for (_, vNode) in displayNodes
{
vNode.node.runAction(allSeq)
}
}
You could use some sequences - so your object could do a 360 and stop, come back 90 at a slower pace, whatever you want. The above repeats forever, but you can take that out.
Or just on Left key press, rotate Y += n degrees.

swift SceneKit Sphere deformation after changing position

I have two spheres on the sceneView
let earthSphere = SCNSphere(radius: 5.0)
earthSphere.firstMaterial?.diffuse.contents = UIColor.green
let objNode1 = SCNNode(geometry: earthSphere)
objNode1.position = SCNVector3(x: 0, y: 0, z: 0)
let moonSphere = SCNSphere(radius: 0.5)
moonSphere.firstMaterial?.diffuse.contents = UIColor.red
let objNode2 = SCNNode(geometry: moonSphere)
objNode2.position = SCNVector3(x: 3, y: 3, z: 0)
but moonSphere is deforming image. How to fix that ? It is possible to do that earthSphere and moonSphere look same ? without deformation
etc: cameraNode set to position SCNVector3(x: 0, y: 0, z: 7)

SCNVector4: Rotate on x-axis

I have a sphere and managed to rotate it. But unfortunately along the wrong axis.
My goal is a rotation like the earth along the x axis. Can you help me to apply this?
Here is my existing code:
let spin = CABasicAnimation(keyPath: "rotation")
// Use from-to to explicitly make a full rotation around z
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: Float(CGFloat(-2 * Double.pi))))
spin.duration = 30
spin.repeatCount = .infinity
sphereNode.addAnimation(spin, forKey: "spin around")
It will be something like this
self.imageView.layer.transform = CATransform3DConcat(self.imageView.layer.transform, CATransform3DMakeRotation(M_PI,1.0,0.0,0.0));
You're already have working solution:
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: CGFloat.pi * 2))
If you wanna change direction, just change vectors :)
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: CGFloat.pi * 2))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
You can rotate your Earth model about X-axis using SceneKit's Transaction:
let scene = SCNScene(named: "art.scnassets/model.scn")!
let modelEarth = scene.rootNode.childNode(withName: "model",
recursively: true)!
SCNTransaction.begin()
SCNTransaction.animationDuration = 500 // animation in seconds
SCNTransaction.animationTimingFunction = .init(name: .default)
modelEarth.rotation.x = 1
modelEarth.rotation.y = 0
modelEarth.rotation.z = 0
modelEarth.rotation.w = 100 * Float.pi // fourth component
SCNTransaction.commit()

Keep track of animation in scenekit (swift)

I am trying to rotate a geometry on the right as long as I am touching the right side of the screen and left when touching the left side. When not touching the screen, the animation should stop.
For this I came up with the following code which works until I press again the screen and the geometry goes back to its original state instead of starting the rotatation from the previous position.
I also need to keep track of the angle for future development and node.rotation.w always returns 0.
func respondToLPGesture(gestureRecognize: UILongPressGestureRecognizer)
{
let scnView = self.view as SCNView
let spin = CABasicAnimation(keyPath: "rotation")
spin.duration = 2
spin.repeatCount = .infinity
let p = gestureRecognize.locationInView(scnView)
switch gestureRecognize.state
{
case .Began:
//Determine rotation direction
if p.x < scnView.frame.width / 2 {
//spin.fromValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: angle))
//spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: angle + 2*Float(M_PI)))
spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(M_PI)))
scnView.scene.rootNode.childNodes[1].addAnimation(spin, forKey: "spin around")
}
else {
spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(-M_PI)))
scnView.scene.rootNode.childNodes[1].addAnimation(spin, forKey: "spin around")
}
case .Ended:
println(scnView.scene.rootNode.childNodes[1].rotation.w) // Always returns 0.
scnView.scene.rootNode.childNodes[1].pauseAnimationForKey("spin around")
default:
println("Inside default")
}
}
Watching WWDC 2014 "What's New in SceneKit" helped me to find the answer.
To track a node position/rotation in Scenekit use "presentationNode".
Example below:
let angle = scnView.scene.rootNode.childNodes[1].presentationNode().rotation.w
Using the following will return the value of the targeted end of the animation which is always 0 in my code example:
let angle = scnView.scene.rootNode.childNodes[1].rotation.w

Using SceneKit in Swift Playground

I've looked everywhere for this but I'm coming up blank. How do you replicate what Chris Lattner was demonstrating with Playgrounds and SceneKit at WWDC? I want to have a SceneKit scene, animating, in Playgrounds.
I tried cutting and pasting the setup code from the SceneKit project template, thinking it would magically start rendering, but it does not.
I tried watching the keynote and pausing and zooming on on Lattner's screen looking for hints at the source code, but he appeared to be importing all his code from elsewhere in his project, so it gave me no clues. There does not seem to be anything in the documentation, or I'm missing it.
Since Swift doesn't have source compatibility between versions, the code in this answer might not work in either future or previous versions of Swift. Currently is has been updated to work in Xcode 7.0 Playgrounds with Swift 2.0.
The XCPlayground framework is what you need, and it is documented here.
Here is a very simple scene to get you started with Scene Kit in Swift:
import SceneKit
import QuartzCore // for the basic animation
import XCPlayground // for the live preview
import PlaygroundSupport
// create a scene view with an empty scene
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
var scene = SCNScene()
sceneView.scene = scene
// start a live preview of that view
PlaygroundPage.current.liveView = sceneView
// default lighting
sceneView.autoenablesDefaultLighting = true
// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
// a geometry object
var torus = SCNTorus(ringRadius: 1, pipeRadius: 0.35)
var torusNode = SCNNode(geometry: torus)
scene.rootNode.addChildNode(torusNode)
// configure the geometry object
torus.firstMaterial?.diffuse.contents = NSColor.red // (or UIColor on iOS)
torus.firstMaterial?.specular.contents = NSColor.white // (or UIColor on iOS)
// set a rotation axis (no angle) to be able to
// use a nicer keypath below and avoid needing
// to wrap it in an NSValue
torusNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: 0.0)
// animate the rotation of the torus
var spin = CABasicAnimation(keyPath: "rotation.w") // only animate the angle
spin.toValue = 2.0*Double.pi
spin.duration = 3
spin.repeatCount = HUGE // for infinity
torusNode.addAnimation(spin, forKey: "spin around")
When I run it, it looks like this:
Note that to run Scene Kit in an iOS playground, you need to check the "Run in Full Simulator" checkbox.
You find the Playground Setting in the Utilities Pane (⌥⌘0 to hide or show)
To get the playground running with iOS as target, and using the latest Xcode 8.1, I got it working with the following modifications to David Rönnqvist's original code.
import UIKit
import SceneKit
import QuartzCore // for the basic animation
import PlaygroundSupport
// create a scene view with an empty scene
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
var scene = SCNScene()
sceneView.scene = scene
PlaygroundPage.current.liveView = sceneView
// default lighting
sceneView.autoenablesDefaultLighting = true
// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
// a geometry object
var torus = SCNTorus(ringRadius: 1, pipeRadius: 0.35)
var torusNode = SCNNode(geometry: torus)
scene.rootNode.addChildNode(torusNode)
// configure the geometry object
torus.firstMaterial?.diffuse.contents = UIColor.red
torus.firstMaterial?.specular.contents = UIColor.white
// set a rotation axis (no angle) to be able to
// use a nicer keypath below and avoid needing
// to wrap it in an NSValue
torusNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: 0.0)
// animate the rotation of the torus
var spin = CABasicAnimation(keyPath: "rotation.w") // only animate the angle
spin.toValue = 2.0*M_PI
spin.duration = 3
spin.repeatCount = HUGE // for infinity
torusNode.addAnimation(spin, forKey: "spin around")
The main things you have to do different are:
to assign to the playground's liveView and,
also open up Xcode's Assistant Editor (The two intersecting circles icon on the toolbar)
Expanding on Moshe's response.
If that keyboard combination doesn't work for you, try going to the the menu bar and select View > Assistant Editor > Show Assistant.
In Xcode 10.2 and higher, there's a PlaygroundSupport framework. It shares a playground data, manages live views, and controls the execution of a playground.
import PlaygroundSupport
You can use Playground Support from within playgrounds to:
Access a playground page and manage its execution
Access and share persistent data
Assess the progress of the learner, update hints, and show success text
About Swift Playgrounds for iPad read here.
Here's the code:
import PlaygroundSupport
import SceneKit
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 1000, height: 200))
var scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = .black
PlaygroundPage.current.liveView = sceneView
var lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .directional
lightNode.light?.intensity = 3000
lightNode.light?.shadowMode = .deferred
lightNode.rotation = SCNVector4(x: 0, y: 0, z: 0.5, w: 1.5 * Float.pi)
scene.rootNode.addChildNode(lightNode)
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 2.5, y: 0, z: 5)
scene.rootNode.addChildNode(cameraNode)
var box = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0.4)
var boxNode = SCNNode(geometry: box)
scene.rootNode.addChildNode(boxNode)
box.firstMaterial?.diffuse.contents = UIColor.blue
box.firstMaterial?.specular.contents = UIColor.purple
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: 0.0)
boxNode.scale = SCNVector3(x: 1.0, y: 1.0, z: 1.0)
Then apply a transform animation:
var spin = CABasicAnimation(keyPath: "rotation.w")
var scale = CABasicAnimation(keyPath: "scale.x")
spin.toValue = 3 * -CGFloat.pi
spin.duration = 2
spin.repeatCount = .greatestFiniteMagnitude
scale.toValue = 1.5
scale.duration = 2
scale.repeatCount = .infinity
boxNode.addAnimation(spin, forKey: "spin around")
boxNode.addAnimation(scale, forKey: "scale x")
If the playground is complaining with 'int is not convertible to CGFloat' then you can use this line of code:
spin.toValue = NSValue(SCNVector4: SCNVector4(x: 1, y: 1, z: 0, w: CGFloat(2.0*M_PI)))
Implicit typecasts seem not to be defined in swift.